Compare commits
744 Commits
master
...
develop-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2461d060e0 | ||
|
|
aa3cd6d08d | ||
|
|
cba2f7013f | ||
|
|
b3052fe57b | ||
|
|
af570c7769 | ||
|
|
1d2187b957 | ||
|
|
c85bb53062 | ||
|
|
0272d2a303 | ||
|
|
5e275e36ad | ||
|
|
13601c97a9 | ||
|
|
c7a347f723 | ||
|
|
88cacb4823 | ||
|
|
64a218810a | ||
|
|
7f880b1563 | ||
|
|
86db60ba97 | ||
|
|
db007af3ea | ||
|
|
153c1ac4a2 | ||
|
|
287b362765 | ||
|
|
dcdd126bdf | ||
|
|
31a5d6c2d0 | ||
|
|
d6f61acace | ||
|
|
cb1126e216 | ||
|
|
cd0e6af55f | ||
|
|
27c7d2dd18 | ||
|
|
1a166bdb22 | ||
|
|
e9f9999f41 | ||
|
|
fae3b6a654 | ||
|
|
0f7534d12c | ||
|
|
de3d73de70 | ||
|
|
4f74ab2840 | ||
|
|
b289594c7f | ||
|
|
fb5e69f703 | ||
|
|
d203510527 | ||
|
|
6394a48766 | ||
|
|
8fef5d3580 | ||
|
|
70001196cb | ||
|
|
26b693d609 | ||
|
|
f07302588c | ||
|
|
cb58a553f5 | ||
|
|
2c27787f27 | ||
|
|
3d5f665b59 | ||
|
|
503a340035 | ||
|
|
72a85f1a4a | ||
|
|
46c1e3f7e5 | ||
|
|
249414e027 | ||
|
|
0619eee41c | ||
|
|
bc0cf883b4 | ||
|
|
ad32dcc2df | ||
|
|
759e4e2b95 | ||
|
|
d902acacf4 | ||
|
|
ad4cc5884f | ||
|
|
1fc2d08c18 | ||
|
|
94b743bfc0 | ||
|
|
9eff14f3ba | ||
|
|
428477bdec | ||
|
|
bc3a48f30f | ||
|
|
59de8e7f63 | ||
|
|
8db9a89f68 | ||
|
|
87a3f9fa96 | ||
|
|
8f13a0932e | ||
|
|
caa525e31d | ||
|
|
39fb43d6a3 | ||
|
|
38b5c4a5ed | ||
|
|
9bf017fe27 | ||
|
|
8a9878b8e5 | ||
|
|
ef0886ec85 | ||
|
|
8d014c0098 | ||
|
|
4576969739 | ||
|
|
47e0a90116 | ||
|
|
d287e3fe91 | ||
|
|
b08a0f225e | ||
|
|
408601f0d2 | ||
|
|
996b8c1cd3 | ||
|
|
2dc74d80ea | ||
|
|
afcbfd1b04 | ||
|
|
301f531a4a | ||
|
|
d971448c9a | ||
|
|
48f142accc | ||
|
|
a9c613164c | ||
|
|
62951067ae | ||
|
|
3f19c94e0f | ||
|
|
79c8c4beae | ||
|
|
83452a2be0 | ||
|
|
ddb699db7e | ||
|
|
14e6fd57a7 | ||
|
|
a85e14e94d | ||
|
|
7b45348710 | ||
|
|
6831de37a3 | ||
|
|
e0960bd930 | ||
|
|
8a826543d0 | ||
|
|
a2c8f0c655 | ||
|
|
824685fc07 | ||
|
|
aeec3c688e | ||
|
|
df8c80e4db | ||
|
|
d8f33b9cb7 | ||
|
|
922c391bf2 | ||
|
|
64ec8c82d3 | ||
|
|
7e520744a2 | ||
|
|
1be7494982 | ||
|
|
c873a2e588 | ||
|
|
957ac4f95f | ||
|
|
7773ecd55d | ||
|
|
b1a24235ea | ||
|
|
3255a5a670 | ||
|
|
db31a0e5de | ||
|
|
a7cc764c53 | ||
|
|
be2bf9fcaf | ||
|
|
18441ffaef | ||
|
|
cecce156d7 | ||
|
|
636deed29f | ||
|
|
61755fb024 | ||
|
|
06c340c064 | ||
|
|
7423176c52 | ||
|
|
4ce86a9e5a | ||
|
|
e1d05da20f | ||
|
|
5c0ea3a2a2 | ||
|
|
a36d54a372 | ||
|
|
e5866b415e | ||
|
|
119d952860 | ||
|
|
0803b69bbe | ||
|
|
10df63ae9e | ||
|
|
5616ef0007 | ||
|
|
3665bfb26a | ||
|
|
3bd391a173 | ||
|
|
50b68c312e | ||
|
|
267ee439b5 | ||
|
|
e68c9e938f | ||
|
|
e73ce3875b | ||
|
|
96e8d7e7cd | ||
|
|
a48bd364fd | ||
|
|
86e6155cde | ||
|
|
edb29f5875 | ||
|
|
7b91ddefe9 | ||
|
|
b4f51df463 | ||
|
|
e36f1bf364 | ||
|
|
89a6c19bdf | ||
|
|
da00b5149f | ||
|
|
628eb56613 | ||
|
|
60484feb8e | ||
|
|
327b40a414 | ||
|
|
e4b1b50117 | ||
|
|
ae20a8ada7 | ||
|
|
b16c6ec4d3 | ||
|
|
f51b286f2b | ||
|
|
a79e79db28 | ||
|
|
d1bac2b309 | ||
|
|
a421c78a0c | ||
|
|
dc6e6c546b | ||
|
|
6ddeda308e | ||
|
|
0144c61b7f | ||
|
|
8067f3bd99 | ||
|
|
d2e25cb951 | ||
|
|
b86f32b83a | ||
|
|
ed99703fda | ||
|
|
6fa7779d7d | ||
|
|
fdf6590dc7 | ||
|
|
b8544644b7 | ||
|
|
d4e379af2e | ||
|
|
b5014729e7 | ||
|
|
03643bcd34 | ||
|
|
bbd36ec96a | ||
|
|
8a88df815f | ||
|
|
045b32884f | ||
|
|
3b9b509429 | ||
|
|
fcc4faefaa | ||
|
|
4a6fa305f4 | ||
|
|
180f0f9b93 | ||
|
|
9f3070bbdc | ||
|
|
86cba3b585 | ||
|
|
8ea2598254 | ||
|
|
e61940a7aa | ||
|
|
731fc2c761 | ||
|
|
074e0742d5 | ||
|
|
0ec0c63ba6 | ||
|
|
7549c4e620 | ||
|
|
ab6e3c8d6b | ||
|
|
ec11ae0b50 | ||
|
|
e3a3ab004c | ||
|
|
23c81cd546 | ||
|
|
f77f7cf499 | ||
|
|
b03b0d366b | ||
|
|
c486301116 | ||
|
|
c3ee734c19 | ||
|
|
ddf0bd4727 | ||
|
|
da160be233 | ||
|
|
54bba18522 | ||
|
|
fff680d202 | ||
|
|
5892da5d1b | ||
|
|
36c75981d6 | ||
|
|
b3a8432f86 | ||
|
|
9cf630000b | ||
|
|
7e092b66ee | ||
|
|
320714c2d0 | ||
|
|
60efe15fd9 | ||
|
|
8b1d1c12e3 | ||
|
|
826e30c129 | ||
|
|
93b08eb4f3 | ||
|
|
7d8edefe66 | ||
|
|
340759f95d | ||
|
|
78603b1736 | ||
|
|
954711c5fe | ||
|
|
a6ddc77b4e | ||
|
|
f13e56327e | ||
|
|
1d6839a499 | ||
|
|
61e2a2e8bf | ||
|
|
b116e56676 | ||
|
|
4733116476 | ||
|
|
d45249e022 | ||
|
|
ade43e90e8 | ||
|
|
c3b06e4854 | ||
|
|
a16510e254 | ||
|
|
fba1546213 | ||
|
|
8f2c97d269 | ||
|
|
5f2f33f1f2 | ||
|
|
426c856dfb | ||
|
|
d5eb07c3b9 | ||
|
|
1c30ccda29 | ||
|
|
ad165825bd | ||
|
|
8f432c4638 | ||
|
|
54ed32ecd9 | ||
|
|
71b343cf86 | ||
|
|
e51a4108ad | ||
|
|
5fb22a2637 | ||
|
|
e9faa553c3 | ||
|
|
6f3606de20 | ||
|
|
a181a58420 | ||
|
|
4d06a9a738 | ||
|
|
f8eb2965ae | ||
|
|
a4162e546b | ||
|
|
1961882327 | ||
|
|
81abec75f5 | ||
|
|
82d355cdf0 | ||
|
|
d25e160452 | ||
|
|
8ca0071786 | ||
|
|
3d815c6332 | ||
|
|
dabd64046e | ||
|
|
f6d077bf08 | ||
|
|
2cd1ec27e0 | ||
|
|
048fbc41c5 | ||
|
|
6bf7ca4985 | ||
|
|
b1c709795f | ||
|
|
b4c894b8a1 | ||
|
|
4362a92193 | ||
|
|
ebd0896ee9 | ||
|
|
ed0dfcd473 | ||
|
|
a0a92818d6 | ||
|
|
81a7894037 | ||
|
|
d8e0a669af | ||
|
|
7ac10948df | ||
|
|
b43761faf1 | ||
|
|
f2a8a7e639 | ||
|
|
1e5014492c | ||
|
|
3192dc52d7 | ||
|
|
9392fe47f8 | ||
|
|
7d83e66e4a | ||
|
|
fd5343c9c2 | ||
|
|
d3e8a158b4 | ||
|
|
e5f1374325 | ||
|
|
5a55c584b6 | ||
|
|
76367eb8c9 | ||
|
|
fcb6914276 | ||
|
|
29bcb1bcc6 | ||
|
|
838b7d5d69 | ||
|
|
389038dc77 | ||
|
|
d5d05b9690 | ||
|
|
10787dd2b3 | ||
|
|
177533b760 | ||
|
|
5af2bb1e91 | ||
|
|
116e39034c | ||
|
|
e0846ec91c | ||
|
|
442f1c4d47 | ||
|
|
4303aeb1f8 | ||
|
|
d62d9fe729 | ||
|
|
b8919851e6 | ||
|
|
5dc87ef8d1 | ||
|
|
3d875890b0 | ||
|
|
b580bab10b | ||
|
|
40f80809c1 | ||
|
|
6bd90ef46e | ||
|
|
9db6d22ab5 | ||
|
|
f37dac0c79 | ||
|
|
70a3bd1166 | ||
|
|
10e7c1c233 | ||
|
|
1643145cf0 | ||
|
|
9f009c164a | ||
|
|
07b7151fd2 | ||
|
|
0775a67cfe | ||
|
|
3564dff4fd | ||
|
|
20bb3d831f | ||
|
|
aabfea6e19 | ||
|
|
1d4b9c9b24 | ||
|
|
8d3dfa165b | ||
|
|
4342326c62 | ||
|
|
e2c3fb6e42 | ||
|
|
f3e131ec00 | ||
|
|
f5d2c0e527 | ||
|
|
d81ca1c10d | ||
|
|
1a887e9dbe | ||
|
|
d50749a753 | ||
|
|
0ca89a3a7f | ||
|
|
cbfc5901f3 | ||
|
|
88c9d33758 | ||
|
|
c09e0a99e4 | ||
|
|
152bb35cde | ||
|
|
68997b28a0 | ||
|
|
d26092baa6 | ||
|
|
e1b6d46d9d | ||
|
|
23715f827c | ||
|
|
eee431ce26 | ||
|
|
f30b1b19f9 | ||
|
|
68c1e8ccde | ||
|
|
d98ba35a21 | ||
|
|
e0bad8edaf | ||
|
|
3d0dc8528d | ||
|
|
8a0104585c | ||
|
|
b31d027516 | ||
|
|
a541aee3f3 | ||
|
|
a12b25cda8 | ||
|
|
8a0cf10498 | ||
|
|
af85d0aafb | ||
|
|
e537922e46 | ||
|
|
3f2f8afaad | ||
|
|
b07343a530 | ||
|
|
ab22ae8224 | ||
|
|
c180387438 | ||
|
|
328df55a19 | ||
|
|
eb4740df0a | ||
|
|
06106049f8 | ||
|
|
9de52daa11 | ||
|
|
e264d98739 | ||
|
|
04815b2d09 | ||
|
|
bae28521db | ||
|
|
779d58cd7e | ||
|
|
408687e4f9 | ||
|
|
574316d519 | ||
|
|
1de92f153f | ||
|
|
4276ea533f | ||
|
|
e4cfba5750 | ||
|
|
1de46ed867 | ||
|
|
15943bc191 | ||
|
|
2e898a32b4 | ||
|
|
ce5bb368ad | ||
|
|
5acf27cdb8 | ||
|
|
3241c9d83b | ||
|
|
2cfc715aad | ||
|
|
b66e214dbf | ||
|
|
8fed8618d3 | ||
|
|
fc4514b6e2 | ||
|
|
ef5eab65f9 | ||
|
|
6416e67130 | ||
|
|
7914990d6c | ||
|
|
ad671621e8 | ||
|
|
be405fac23 | ||
|
|
dc200f95f5 | ||
|
|
f2f03b4cd3 | ||
|
|
f82ebabc24 | ||
|
|
b674c6ab90 | ||
|
|
1cc9829eed | ||
|
|
905783ad7a | ||
|
|
376be37256 | ||
|
|
dee2b05cfb | ||
|
|
91d434f93d | ||
|
|
9921ede8fa | ||
|
|
9cb3a084db | ||
|
|
11da26e078 | ||
|
|
17912f6054 | ||
|
|
bfa528756e | ||
|
|
42e72cf3c8 | ||
|
|
21b7c7c3af | ||
|
|
6a37e47086 | ||
|
|
1dbbe17ea0 | ||
|
|
00d0c0a507 | ||
|
|
8dcab201dc | ||
|
|
df2f2dcb59 | ||
|
|
4311c982cd | ||
|
|
df8855acd5 | ||
|
|
c5c79e6de3 | ||
|
|
004dac65ce | ||
|
|
e07298a9c4 | ||
|
|
b0042eaca2 | ||
|
|
4686804f3c | ||
|
|
6ac9816189 | ||
|
|
1164bec812 | ||
|
|
e7692a282a | ||
|
|
aa525dbd9c | ||
|
|
f148bee303 | ||
|
|
2539fce815 | ||
|
|
b05e614130 | ||
|
|
3d27808ce7 | ||
|
|
57aecae996 | ||
|
|
1f00136f42 | ||
|
|
6606975f5b | ||
|
|
d0a7a6e488 | ||
|
|
706e1351ae | ||
|
|
dd0f37d9dd | ||
|
|
00e698eb18 | ||
|
|
859977f75c | ||
|
|
1aa5dfdeaf | ||
|
|
759f7b0fa6 | ||
|
|
89b6f15785 | ||
|
|
96fc44473d | ||
|
|
46be7de2bd | ||
|
|
9cd480f188 | ||
|
|
b726e5011f | ||
|
|
d4b8ffb978 | ||
|
|
d2deddcf99 | ||
|
|
615e2a2202 | ||
|
|
0679147a22 | ||
|
|
03b7e9602b | ||
|
|
0a2a3c06dc | ||
|
|
8b354549cc | ||
|
|
32a831862b | ||
|
|
ffab2372de | ||
|
|
297b0bdd9f | ||
|
|
931e368357 | ||
|
|
10b9772130 | ||
|
|
61fbbf5e3d | ||
|
|
83a9aa46ab | ||
|
|
29af471374 | ||
|
|
e69d68c7b4 | ||
|
|
9104b915af | ||
|
|
3123478d0b | ||
|
|
8aa9a63da6 | ||
|
|
89b05f1fae | ||
|
|
fdf9c70338 | ||
|
|
12aebb0b88 | ||
|
|
4bcc851f22 | ||
|
|
eb8d6f0ebb | ||
|
|
7f458484b6 | ||
|
|
400ebabed3 | ||
|
|
c08e2a560a | ||
|
|
e0c45bd87c | ||
|
|
82a82fbcab | ||
|
|
073c55314f | ||
|
|
ecd3d52797 | ||
|
|
bf3983a7e2 | ||
|
|
37ffae42bd | ||
|
|
c814d178e6 | ||
|
|
c0092ba52c | ||
|
|
d0d825dd9a | ||
|
|
5c28bc736f | ||
|
|
11c4670213 | ||
|
|
f209719c9d | ||
|
|
0041ffaa5e | ||
|
|
74aa9829c3 | ||
|
|
c79c93e4d7 | ||
|
|
aa3e3e615c | ||
|
|
fff04ec01a | ||
|
|
e90e6cebdd | ||
|
|
0d13af3827 | ||
|
|
21d182fd55 | ||
|
|
1421d3ed1b | ||
|
|
9c81386a8e | ||
|
|
a49d2d820d | ||
|
|
93d2aa331c | ||
|
|
ef0cf88387 | ||
|
|
4b229b868e | ||
|
|
27c455ba16 | ||
|
|
4a844eebfe | ||
|
|
0853a16b59 | ||
|
|
e1084422e5 | ||
|
|
c5d209161e | ||
|
|
53c11db773 | ||
|
|
e9d9025ea7 | ||
|
|
05cbb72c1d | ||
|
|
fcbdda953c | ||
|
|
995f635807 | ||
|
|
7b17e756f9 | ||
|
|
a2a87db49c | ||
|
|
ccec3ceaf4 | ||
|
|
f0b058959a | ||
|
|
b6bad398ca | ||
|
|
c70a22964a | ||
|
|
d4bba2ce4f | ||
|
|
f3240affc9 | ||
|
|
f3411f0ba3 | ||
|
|
94e160f738 | ||
|
|
6138ebcf7c | ||
|
|
6f60d91e52 | ||
|
|
4833f7f12b | ||
|
|
427006d2cb | ||
|
|
c24def5d2e | ||
|
|
bf4f45f5e5 | ||
|
|
3830a02c34 | ||
|
|
8c2674e5dd | ||
|
|
537db89a82 | ||
|
|
dff7f4bdab | ||
|
|
cbee122165 | ||
|
|
8b439baf30 | ||
|
|
c8f3ad3b65 | ||
|
|
8ff38e52b7 | ||
|
|
d426b80475 | ||
|
|
4774b6b504 | ||
|
|
cf39d1d74c | ||
|
|
dc61f89739 | ||
|
|
252fdfb138 | ||
|
|
fa43186d52 | ||
|
|
7498276573 | ||
|
|
0787cc7913 | ||
|
|
a5d62c3271 | ||
|
|
d076326273 | ||
|
|
d6b0fef5d4 | ||
|
|
4df1b30098 | ||
|
|
cc913bc10c | ||
|
|
6b11b29bba | ||
|
|
2605410c6d | ||
|
|
939ec9516d | ||
|
|
39edf76c6b | ||
|
|
72f23d4814 | ||
|
|
b73775ad93 | ||
|
|
3e756abe42 | ||
|
|
560b9e42c9 | ||
|
|
2abbece8c3 | ||
|
|
a460bc31f3 | ||
|
|
1328fc233a | ||
|
|
364ae04ceb | ||
|
|
8e9ca385d9 | ||
|
|
38777041d1 | ||
|
|
f6c05dba56 | ||
|
|
16c56aa092 | ||
|
|
6870378422 | ||
|
|
2af29c5d93 | ||
|
|
986fe160c7 | ||
|
|
c50de60f43 | ||
|
|
1eaaca2cc3 | ||
|
|
52ea4e039c | ||
|
|
3f095ff522 | ||
|
|
9ee8ce9f33 | ||
|
|
fe1fc6ea93 | ||
|
|
a00a66f4e6 | ||
|
|
bc9028f0c7 | ||
|
|
aff05f8587 | ||
|
|
7d09073703 | ||
|
|
e59a7a59a4 | ||
|
|
5e744be252 | ||
|
|
85b1d1d52e | ||
|
|
0893164868 | ||
|
|
a0dd3a7b6d | ||
|
|
6db34bc4f9 | ||
|
|
3098cd86bf | ||
|
|
01452ced72 | ||
|
|
d113b1da1a | ||
|
|
6de154dc48 | ||
|
|
ed6f95ef94 | ||
|
|
4d65ebe24c | ||
|
|
1f7ef4a483 | ||
|
|
aa66b1c663 | ||
|
|
047c2cdddb | ||
|
|
10bbb5ebf4 | ||
|
|
fa109c40ed | ||
|
|
93a31994b7 | ||
|
|
44f8e61f2f | ||
|
|
865d642e5c | ||
|
|
4d3a896d6b | ||
|
|
871f44a170 | ||
|
|
f24aeb135d | ||
|
|
46ed01630d | ||
|
|
ff892dfac2 | ||
|
|
906d26112b | ||
|
|
ca22b49aaf | ||
|
|
1c70085450 | ||
|
|
9d45460a3b | ||
|
|
b96c064431 | ||
|
|
763f6bb27a | ||
|
|
dc6af1d0c3 | ||
|
|
57e7a574c4 | ||
|
|
d227d6ebb7 | ||
|
|
87da95ca05 | ||
|
|
f9ae197cfc | ||
|
|
6dde2245a6 | ||
|
|
2be14cdd43 | ||
|
|
9ee67c1fad | ||
|
|
6d3389c9c6 | ||
|
|
eb6ab3ed08 | ||
|
|
ceb558dcff | ||
|
|
9f9dcafc60 | ||
|
|
6ce4039e8a | ||
|
|
d5b0ad73b9 | ||
|
|
3687df3d36 | ||
|
|
55653aa494 | ||
|
|
eb3cdad008 | ||
|
|
27f38032ca | ||
|
|
e45aad7ed0 | ||
|
|
9664077f2e | ||
|
|
a19960959b | ||
|
|
f40f7ae7a8 | ||
|
|
9e5487b100 | ||
|
|
f56611d957 | ||
|
|
782850b94d | ||
|
|
d5c102054a | ||
|
|
0db3ad47bb | ||
|
|
1290ca5486 | ||
|
|
51e73e1c98 | ||
|
|
72bf4d89ba | ||
|
|
21c1827682 | ||
|
|
e8b69254fd | ||
|
|
93b1635de2 | ||
|
|
d4296d2b8b | ||
|
|
b4f7e3a69c | ||
|
|
a4121c50af | ||
|
|
1da12b8342 | ||
|
|
611e4676a4 | ||
|
|
77aada762f | ||
|
|
755c37bc9b | ||
|
|
708476b734 | ||
|
|
0bda31a574 | ||
|
|
b712accfc0 | ||
|
|
23368d1112 | ||
|
|
b0d5738a1f | ||
|
|
dc67a4e9ac | ||
|
|
0441662514 | ||
|
|
5a332d8c35 | ||
|
|
d82cef0546 | ||
|
|
1bf79de220 | ||
|
|
3d1ee74f0f | ||
|
|
08bc0d578e | ||
|
|
55f49a1127 | ||
|
|
767efbae7d | ||
|
|
8590f867bd | ||
|
|
76590d6638 | ||
|
|
e532ee60f3 | ||
|
|
f62b760174 | ||
|
|
14a62585d8 | ||
|
|
8b2620d4aa | ||
|
|
9986857ef1 | ||
|
|
dce51a0903 | ||
|
|
b1afacfbb1 | ||
|
|
a9c7672fbf | ||
|
|
2f8cb22105 | ||
|
|
df72e11a45 | ||
|
|
2743792ef3 | ||
|
|
b798f392ad | ||
|
|
2e72b1884d | ||
|
|
f170a00f02 | ||
|
|
c1f6ac11e1 | ||
|
|
1e59c9301f | ||
|
|
d905e97aec | ||
|
|
8096a208e1 | ||
|
|
715fc1c2c6 | ||
|
|
71dfb8af4b | ||
|
|
37d3264999 | ||
|
|
b970aa047e | ||
|
|
2caa51db7c | ||
|
|
f08794079e | ||
|
|
b2ba4b272d | ||
|
|
a77c2f6ea0 | ||
|
|
3c73567c62 | ||
|
|
b1696bd141 | ||
|
|
ea3258ed7e | ||
|
|
1eaeed35c7 | ||
|
|
959058b5fb | ||
|
|
2bf2e41ee6 | ||
|
|
3926ee1705 | ||
|
|
759ed6bba7 | ||
|
|
f38c968656 | ||
|
|
00ab55be72 | ||
|
|
1ad06ee70e | ||
|
|
0524f35e57 | ||
|
|
af6435682b | ||
|
|
77b2632f3f | ||
|
|
f253afd221 | ||
|
|
90024b3460 | ||
|
|
a58ad48b69 | ||
|
|
71677604e0 | ||
|
|
ba308022bb | ||
|
|
3dd8341605 | ||
|
|
e37a10c931 | ||
|
|
038e6b185b | ||
|
|
4df6923062 | ||
|
|
b92b620e4d | ||
|
|
ade176e977 | ||
|
|
3e8b01be93 | ||
|
|
6eff117d4a | ||
|
|
cfc1502f2a | ||
|
|
2cfa6c8c7f | ||
|
|
1bee7c97f7 | ||
|
|
74fb1d0fbc | ||
|
|
ebad012099 | ||
|
|
d7b91eca7b | ||
|
|
204777ba41 | ||
|
|
3d9cdcc10f | ||
|
|
fe0ad66618 | ||
|
|
398e0bfaec | ||
|
|
2a8617dab1 | ||
|
|
ebe55c265e | ||
|
|
c874d3e0ea | ||
|
|
dcb9e9e73a | ||
|
|
059bcbaedb | ||
|
|
fc394801a1 | ||
|
|
2343823734 | ||
|
|
9920c0b65a | ||
|
|
37e15489ee | ||
|
|
3298c41f3a | ||
|
|
8d8c18c3db | ||
|
|
bfe1d5a789 | ||
|
|
d5b550d074 | ||
|
|
d1f16ddc0a | ||
|
|
86d427fd13 | ||
|
|
e8824aec3a | ||
|
|
6dbb2774b4 | ||
|
|
bcf08b0fe3 | ||
|
|
fcaaf87052 | ||
|
|
1a0bf56b70 | ||
|
|
663d8c3696 | ||
|
|
40da3f1683 | ||
|
|
f7893ab2fe | ||
|
|
d4cf76b8e8 | ||
|
|
44932ac515 | ||
|
|
f2b9efc80f | ||
|
|
c48243eae5 | ||
|
|
45bbc72fc3 | ||
|
|
aa50b55ab1 | ||
|
|
85c5f542a8 | ||
|
|
863b7ff600 | ||
|
|
0ce537d4ce | ||
|
|
73dc16703a | ||
|
|
f6fb6a4433 | ||
|
|
2526308eb6 | ||
|
|
d97a9d7468 | ||
|
|
9939c75fc3 | ||
|
|
77107e1d57 | ||
|
|
a2f452dc90 | ||
|
|
e0cac06c2a | ||
|
|
5e80591160 | ||
|
|
c057f98830 | ||
|
|
11a005969c | ||
|
|
6a1f72f957 | ||
|
|
3622518b20 | ||
|
|
1eb47cf3c8 | ||
|
|
7892c0dbb7 | ||
|
|
fe79e582cd | ||
|
|
a5c7fde530 | ||
|
|
c04ec356e5 | ||
|
|
194e6f0a91 | ||
|
|
d0c8d2dc4f | ||
|
|
728cda2215 | ||
|
|
1f31f7ef40 | ||
|
|
3734afed92 | ||
|
|
5232b5a115 | ||
|
|
f5dede540e | ||
|
|
980c635037 | ||
|
|
ed2d6eeef1 | ||
|
|
bba7841c43 | ||
|
|
8ef97df223 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
|||||||
/etc/fstab
|
/etc/fstab
|
||||||
|
/etc/recipes2.db
|
||||||
|
/.vscode
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -1,2 +1,18 @@
|
|||||||
# opus-apps
|
# opus-apps
|
||||||
Applications for Opus OS
|
Applications for Opus OS
|
||||||
|
|
||||||
|
## Installing an application
|
||||||
|
To install an application, follow these steps
|
||||||
|
1. Start your OpusOS Computer
|
||||||
|
2. Go to the System tab on the main menu
|
||||||
|
3. Find the packages app and open it
|
||||||
|
4. Select the package you want
|
||||||
|
5. Select the package and press the `+` button
|
||||||
|
6. Your application should get installed!
|
||||||
|
|
||||||
|
## Updating your applications
|
||||||
|
To update your applications, follow these steps
|
||||||
|
1. Start your OpusOS Computer
|
||||||
|
2. Go to the System tab on the main menu
|
||||||
|
3. Find the packages app and open it
|
||||||
|
4. Press the `Update All` button
|
||||||
|
|||||||
135
apis/base64.lua
135
apis/base64.lua
@@ -1,135 +0,0 @@
|
|||||||
-- Base64 Encoder / Decoder
|
|
||||||
-- By KillaVanilla
|
|
||||||
-- see: http://www.computercraft.info/forums2/index.php?/topic/12450-killavanillas-various-apis/
|
|
||||||
|
|
||||||
Base64 = { }
|
|
||||||
|
|
||||||
local alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
|
||||||
|
|
||||||
local function sixBitToBase64(input)
|
|
||||||
return string.sub(alphabet, input+1, input+1)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function base64ToSixBit(input)
|
|
||||||
for i=1, 64 do
|
|
||||||
if input == string.sub(alphabet, i, i) then
|
|
||||||
return i-1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function octetToBase64(o1, o2, o3)
|
|
||||||
local i1 = sixBitToBase64(bit.brshift(bit.band(o1, 0xFC), 2))
|
|
||||||
local i2 = "A"
|
|
||||||
local i3 = "="
|
|
||||||
local i4 = "="
|
|
||||||
if o2 then
|
|
||||||
i2 = sixBitToBase64(bit.bor( bit.blshift(bit.band(o1, 3), 4), bit.brshift(bit.band(o2, 0xF0), 4) ))
|
|
||||||
if not o3 then
|
|
||||||
i3 = sixBitToBase64(bit.blshift(bit.band(o2, 0x0F), 2))
|
|
||||||
else
|
|
||||||
i3 = sixBitToBase64(bit.bor( bit.blshift(bit.band(o2, 0x0F), 2), bit.brshift(bit.band(o3, 0xC0), 6) ))
|
|
||||||
end
|
|
||||||
else
|
|
||||||
i2 = sixBitToBase64(bit.blshift(bit.band(o1, 3), 4))
|
|
||||||
end
|
|
||||||
if o3 then
|
|
||||||
i4 = sixBitToBase64(bit.band(o3, 0x3F))
|
|
||||||
end
|
|
||||||
|
|
||||||
return i1..i2..i3..i4
|
|
||||||
end
|
|
||||||
|
|
||||||
-- octet 1 needs characters 1/2
|
|
||||||
-- octet 2 needs characters 2/3
|
|
||||||
-- octet 3 needs characters 3/4
|
|
||||||
|
|
||||||
local function base64ToThreeOctet(s1)
|
|
||||||
local c1 = base64ToSixBit(string.sub(s1, 1, 1))
|
|
||||||
local c2 = base64ToSixBit(string.sub(s1, 2, 2))
|
|
||||||
local c3 = 0
|
|
||||||
local c4 = 0
|
|
||||||
local o1 = 0
|
|
||||||
local o2 = 0
|
|
||||||
local o3 = 0
|
|
||||||
if string.sub(s1, 3, 3) == "=" then
|
|
||||||
c3 = nil
|
|
||||||
c4 = nil
|
|
||||||
elseif string.sub(s1, 4, 4) == "=" then
|
|
||||||
c3 = base64ToSixBit(string.sub(s1, 3, 3))
|
|
||||||
c4 = nil
|
|
||||||
else
|
|
||||||
c3 = base64ToSixBit(string.sub(s1, 3, 3))
|
|
||||||
c4 = base64ToSixBit(string.sub(s1, 4, 4))
|
|
||||||
end
|
|
||||||
o1 = bit.bor( bit.blshift(c1, 2), bit.brshift(bit.band( c2, 0x30 ), 4) )
|
|
||||||
if c3 then
|
|
||||||
o2 = bit.bor( bit.blshift(bit.band(c2, 0x0F), 4), bit.brshift(bit.band( c3, 0x3C ), 2) )
|
|
||||||
else
|
|
||||||
o2 = nil
|
|
||||||
end
|
|
||||||
if c4 then
|
|
||||||
o3 = bit.bor( bit.blshift(bit.band(c3, 3), 6), c4 )
|
|
||||||
else
|
|
||||||
o3 = nil
|
|
||||||
end
|
|
||||||
return o1, o2, o3
|
|
||||||
end
|
|
||||||
|
|
||||||
local function splitIntoBlocks(bytes)
|
|
||||||
local blockNum = 1
|
|
||||||
local blocks = {}
|
|
||||||
for i=1, #bytes, 3 do
|
|
||||||
blocks[blockNum] = {bytes[i], bytes[i+1], bytes[i+2]}
|
|
||||||
--[[
|
|
||||||
if #blocks[blockNum] < 3 then
|
|
||||||
for j=#blocks[blockNum]+1, 3 do
|
|
||||||
table.insert(blocks[blockNum], 0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
]]
|
|
||||||
blockNum = blockNum+1
|
|
||||||
end
|
|
||||||
return blocks
|
|
||||||
end
|
|
||||||
|
|
||||||
function Base64.encode(bytes)
|
|
||||||
local blocks = splitIntoBlocks(bytes)
|
|
||||||
local output = ""
|
|
||||||
for i=1, #blocks do
|
|
||||||
output = output..octetToBase64( unpack(blocks[i]) )
|
|
||||||
end
|
|
||||||
return output
|
|
||||||
end
|
|
||||||
|
|
||||||
function Base64.decode(str)
|
|
||||||
local bytes = {}
|
|
||||||
local blocks = {}
|
|
||||||
local blockNum = 1
|
|
||||||
for i=1, #str, 4 do
|
|
||||||
blocks[blockNum] = string.sub(str, i, i+3)
|
|
||||||
blockNum = blockNum+1
|
|
||||||
end
|
|
||||||
for i=1, #blocks do
|
|
||||||
local o1, o2, o3 = base64ToThreeOctet(blocks[i])
|
|
||||||
table.insert(bytes, o1)
|
|
||||||
table.insert(bytes, o2)
|
|
||||||
table.insert(bytes, o3)
|
|
||||||
if (i % 1000) == 0 then
|
|
||||||
os.sleep(0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Remove padding:
|
|
||||||
--[[
|
|
||||||
for i=#bytes, 1, -1 do
|
|
||||||
if bytes[i] ~= 0 then
|
|
||||||
break
|
|
||||||
else
|
|
||||||
bytes[i] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
]]
|
|
||||||
return bytes
|
|
||||||
end
|
|
||||||
|
|
||||||
return Base64
|
|
||||||
599
apis/blocks.lua
599
apis/blocks.lua
@@ -1,599 +0,0 @@
|
|||||||
local class = require('class')
|
|
||||||
local Util = require('util')
|
|
||||||
local TableDB = require('tableDB')
|
|
||||||
local JSON = require('json')
|
|
||||||
|
|
||||||
-- see https://github.com/Khroki/MCEdit-Unified/blob/master/pymclevel/minecraft.yaml
|
|
||||||
-- see https://github.com/Khroki/MCEdit-Unified/blob/master/Items/minecraft/blocks.json
|
|
||||||
|
|
||||||
--[[-- nameDB --]]--
|
|
||||||
local nameDB = TableDB({
|
|
||||||
fileName = 'blocknames.db'
|
|
||||||
})
|
|
||||||
function nameDB:load(dir, blockDB)
|
|
||||||
self.fileName = fs.combine(dir, self.fileName)
|
|
||||||
if fs.exists(self.fileName) then
|
|
||||||
TableDB.load(self)
|
|
||||||
end
|
|
||||||
self.blockDB = blockDB
|
|
||||||
end
|
|
||||||
|
|
||||||
function nameDB:getName(id, dmg)
|
|
||||||
return self:lookupName(id, dmg) or id .. ':' .. dmg
|
|
||||||
end
|
|
||||||
|
|
||||||
function nameDB:lookupName(id, dmg)
|
|
||||||
-- is it in the name db ?
|
|
||||||
local name = self:get({ id, dmg })
|
|
||||||
if name then
|
|
||||||
return name
|
|
||||||
end
|
|
||||||
|
|
||||||
-- is it in the block db ?
|
|
||||||
for _,v in pairs(self.blockDB.data) do
|
|
||||||
if v.strId == id and v.dmg == dmg then
|
|
||||||
return v.name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[-- blockDB --]]--
|
|
||||||
local blockDB = TableDB()
|
|
||||||
|
|
||||||
function blockDB:load()
|
|
||||||
|
|
||||||
local blocks = JSON.decodeFromFile('usr/etc/blocks.json')
|
|
||||||
|
|
||||||
if not blocks then
|
|
||||||
error('Unable to read blocks.json')
|
|
||||||
end
|
|
||||||
|
|
||||||
for strId, block in pairs(blocks) do
|
|
||||||
strId = 'minecraft:' .. strId
|
|
||||||
if type(block.name) == 'string' then
|
|
||||||
self:add(block.id, 0, block.name, strId, block.place)
|
|
||||||
else
|
|
||||||
for nid,name in pairs(block.name) do
|
|
||||||
self:add(block.id, nid - 1, name, strId, block.place)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function blockDB:lookup(id, dmg)
|
|
||||||
if not id then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
return self.data[id .. ':' .. dmg]
|
|
||||||
end
|
|
||||||
|
|
||||||
function blockDB:add(id, dmg, name, strId, place)
|
|
||||||
local key = id .. ':' .. dmg
|
|
||||||
|
|
||||||
TableDB.add(self, key, {
|
|
||||||
id = id,
|
|
||||||
dmg = dmg,
|
|
||||||
name = name,
|
|
||||||
strId = strId,
|
|
||||||
place = place,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[-- placementDB --]]--
|
|
||||||
-- in memory table that expands the standardBlock and blockType tables for each item/dmg/placement combination
|
|
||||||
local placementDB = TableDB()
|
|
||||||
|
|
||||||
function placementDB:load(sbDB, btDB)
|
|
||||||
|
|
||||||
for k,blockType in pairs(sbDB.data) do
|
|
||||||
local bt = btDB.data[blockType]
|
|
||||||
if not bt then
|
|
||||||
error('missing block type: ' .. blockType)
|
|
||||||
end
|
|
||||||
local id, dmg = string.match(k, '(%d+):*(%d+)')
|
|
||||||
self:addSubsForBlockType(tonumber(id), tonumber(dmg), bt)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function placementDB:load2(sbDB, btDB)
|
|
||||||
|
|
||||||
for k,v in pairs(sbDB.data) do
|
|
||||||
if v.place then
|
|
||||||
local bt = btDB.data[v.place]
|
|
||||||
if not bt then
|
|
||||||
error('missing block type: ' .. v.place)
|
|
||||||
end
|
|
||||||
local id, dmg = string.match(k, '(%d+):*(%d+)')
|
|
||||||
self:addSubsForBlockType(tonumber(id), tonumber(dmg), bt)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- special case for quartz pillars
|
|
||||||
self:addSubsForBlockType(155, 2, btDB.data['quartz-pillar'])
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function placementDB:addSubsForBlockType(id, dmg, bt)
|
|
||||||
for _,sub in pairs(bt) do
|
|
||||||
local odmg = sub.odmg
|
|
||||||
if type(sub.odmg) == 'string' then
|
|
||||||
odmg = dmg + tonumber(string.match(odmg, '+(%d+)'))
|
|
||||||
end
|
|
||||||
|
|
||||||
local b = blockDB:lookup(id, dmg)
|
|
||||||
local strId = tostring(id)
|
|
||||||
if b then
|
|
||||||
strId = b.strId
|
|
||||||
end
|
|
||||||
|
|
||||||
self:add(
|
|
||||||
id,
|
|
||||||
odmg,
|
|
||||||
sub.sid or strId,
|
|
||||||
sub.sdmg or dmg,
|
|
||||||
sub.dir,
|
|
||||||
sub.extra)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function placementDB:add(id, dmg, sid, sdmg, direction, extra)
|
|
||||||
if direction and #direction == 0 then
|
|
||||||
direction = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local entry = {
|
|
||||||
oid = id, -- numeric ID
|
|
||||||
odmg = dmg, -- dmg with placement info
|
|
||||||
id = sid, -- string ID
|
|
||||||
dmg = sdmg, -- dmg without placement info
|
|
||||||
direction = direction,
|
|
||||||
}
|
|
||||||
if extra then
|
|
||||||
Util.merge(entry, extra)
|
|
||||||
end
|
|
||||||
|
|
||||||
self.data[id .. ':' .. dmg] = entry
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[-- BlockTypeDB --]]--
|
|
||||||
local blockTypeDB = TableDB()
|
|
||||||
|
|
||||||
function blockTypeDB:addTemp(blockType, subs)
|
|
||||||
local bt = self.data[blockType]
|
|
||||||
if not bt then
|
|
||||||
bt = { }
|
|
||||||
self.data[blockType] = bt
|
|
||||||
end
|
|
||||||
for _,sub in pairs(subs) do
|
|
||||||
table.insert(bt, {
|
|
||||||
odmg = sub[1],
|
|
||||||
sid = sub[2],
|
|
||||||
sdmg = sub[3],
|
|
||||||
dir = sub[4],
|
|
||||||
extra = sub[5]
|
|
||||||
})
|
|
||||||
end
|
|
||||||
self.dirty = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function blockTypeDB:load()
|
|
||||||
|
|
||||||
blockTypeDB:addTemp('stairs', {
|
|
||||||
{ 0, nil, 0, 'east-up' },
|
|
||||||
{ 1, nil, 0, 'west-up' },
|
|
||||||
{ 2, nil, 0, 'south-up' },
|
|
||||||
{ 3, nil, 0, 'north-up' },
|
|
||||||
{ 4, nil, 0, 'east-down' },
|
|
||||||
{ 5, nil, 0, 'west-down' },
|
|
||||||
{ 6, nil, 0, 'south-down' },
|
|
||||||
{ 7, nil, 0, 'north-down' },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('gate', {
|
|
||||||
{ 0, nil, 0, 'north' },
|
|
||||||
{ 1, nil, 0, 'east' },
|
|
||||||
{ 2, nil, 0, 'south' },
|
|
||||||
{ 3, nil, 0, 'west' },
|
|
||||||
{ 4, nil, 0, 'north' },
|
|
||||||
{ 5, nil, 0, 'east' },
|
|
||||||
{ 6, nil, 0, 'south' },
|
|
||||||
{ 7, nil, 0, 'west' },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('pumpkin', {
|
|
||||||
{ 0, nil, 0, 'north-block' },
|
|
||||||
{ 1, nil, 0, 'east-block' },
|
|
||||||
{ 2, nil, 0, 'south-block' },
|
|
||||||
{ 3, nil, 0, 'west-block' },
|
|
||||||
{ 4, nil, 0, 'north-block' },
|
|
||||||
{ 5, nil, 0, 'east-block' },
|
|
||||||
{ 6, nil, 0, 'south-block' },
|
|
||||||
{ 7, nil, 0, 'west-block' },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('anvil', {
|
|
||||||
{ 0, nil, 0, 'south' },
|
|
||||||
{ 1, nil, 0, 'east' },
|
|
||||||
{ 2, nil, 0, 'south'},
|
|
||||||
{ 3, nil, 0, 'east' },
|
|
||||||
{ 4, nil, 0, 'south' },
|
|
||||||
{ 5, nil, 0, 'east' },
|
|
||||||
{ 6, nil, 0, 'east' },
|
|
||||||
{ 7, nil, 0, 'south' },
|
|
||||||
{ 8, nil, 0, 'south' },
|
|
||||||
{ 9, nil, 0, 'east' },
|
|
||||||
{ 10, nil, 0, 'east' },
|
|
||||||
{ 11, nil, 0, 'south' },
|
|
||||||
{ 12, nil, 0 },
|
|
||||||
{ 13, nil, 0 },
|
|
||||||
{ 14, nil, 0 },
|
|
||||||
{ 15, nil, 0 },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('bed', {
|
|
||||||
{ 0, nil, 0, 'south' },
|
|
||||||
{ 1, nil, 0, 'west' },
|
|
||||||
{ 2, nil, 0, 'north' },
|
|
||||||
{ 3, nil, 0, 'east' },
|
|
||||||
{ 4, nil, 0, 'south' },
|
|
||||||
{ 5, nil, 0, 'west' },
|
|
||||||
{ 6, nil, 0, 'north' },
|
|
||||||
{ 7, nil, 0, 'east' },
|
|
||||||
{ 8, 'minecraft:air', 0 },
|
|
||||||
{ 9, 'minecraft:air', 0 },
|
|
||||||
{ 10, 'minecraft:air', 0 },
|
|
||||||
{ 11, 'minecraft:air', 0 },
|
|
||||||
{ 12, 'minecraft:air', 0 },
|
|
||||||
{ 13, 'minecraft:air', 0 },
|
|
||||||
{ 14, 'minecraft:air', 0 },
|
|
||||||
{ 15, 'minecraft:air', 0 },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('comparator', {
|
|
||||||
{ 0, nil, 0, 'south' },
|
|
||||||
{ 1, nil, 0, 'west' },
|
|
||||||
{ 2, nil, 0, 'north' },
|
|
||||||
{ 3, nil, 0, 'east' },
|
|
||||||
{ 4, nil, 0, 'south' },
|
|
||||||
{ 5, nil, 0, 'west' },
|
|
||||||
{ 6, nil, 0, 'north' },
|
|
||||||
{ 7, nil, 0, 'east' },
|
|
||||||
{ 8, nil, 0, 'south' },
|
|
||||||
{ 9, nil, 0, 'west' },
|
|
||||||
{ 10, nil, 0, 'north' },
|
|
||||||
{ 11, nil, 0, 'east' },
|
|
||||||
{ 12, nil, 0, 'south' },
|
|
||||||
{ 13, nil, 0, 'west' },
|
|
||||||
{ 14, nil, 0, 'north' },
|
|
||||||
{ 15, nil, 0, 'east' },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('quartz-pillar', {
|
|
||||||
{ 2, nil, 2 },
|
|
||||||
{ 3, nil, 2, 'north-south-block' },
|
|
||||||
{ 4, nil, 2, 'east-west-block' }, -- should be east-west-block
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('hay-bale', {
|
|
||||||
{ 0, nil, 0 },
|
|
||||||
{ 4, nil, 0, 'east-west-block' }, -- should be east-west-block
|
|
||||||
{ 8, nil, 0, 'north-south-block' },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('button', {
|
|
||||||
{ 1, nil, 0, 'west-block' },
|
|
||||||
{ 2, nil, 0, 'east-block' },
|
|
||||||
{ 3, nil, 0, 'north-block' },
|
|
||||||
{ 4, nil, 0, 'south-block' },
|
|
||||||
{ 5, nil, 0 }, -- block top
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('cauldron', {
|
|
||||||
{ 0, nil, 0 },
|
|
||||||
{ 1, nil, 0 },
|
|
||||||
{ 2, nil, 0 },
|
|
||||||
{ 3, nil, 0 },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('dispenser', {
|
|
||||||
{ 0, nil, 0, 'wrench-down' },
|
|
||||||
{ 1, nil, 0, 'wrench-up' },
|
|
||||||
{ 2, nil, 0, 'south' },
|
|
||||||
{ 3, nil, 0, 'north' },
|
|
||||||
{ 4, nil, 0, 'east' },
|
|
||||||
{ 5, nil, 0, 'west' },
|
|
||||||
{ 9, nil, 0 },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('end_rod', {
|
|
||||||
{ 0, nil, 0, 'wrench-down' },
|
|
||||||
{ 1, nil, 0, 'wrench-up' },
|
|
||||||
{ 2, nil, 0, 'south-block-flip' },
|
|
||||||
{ 3, nil, 0, 'north-block-flip' },
|
|
||||||
{ 4, nil, 0, 'east-block-flip' },
|
|
||||||
{ 5, nil, 0, 'west-block-flip' },
|
|
||||||
{ 9, nil, 0 },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('hopper', {
|
|
||||||
{ 0, nil, 0 },
|
|
||||||
{ 1, nil, 0 },
|
|
||||||
{ 2, nil, 0, 'south-block' },
|
|
||||||
{ 3, nil, 0, 'north-block' },
|
|
||||||
{ 4, nil, 0, 'east-block' },
|
|
||||||
{ 5, nil, 0, 'west-block' },
|
|
||||||
{ 8, nil, 0 },
|
|
||||||
{ 9, nil, 0 },
|
|
||||||
{ 10, nil, 0 },
|
|
||||||
{ 11, nil, 0, 'south-block' },
|
|
||||||
{ 12, nil, 0, 'north-block' },
|
|
||||||
{ 13, nil, 0, 'east-block' },
|
|
||||||
{ 14, nil, 0, 'west-block' },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('mobhead', {
|
|
||||||
{ 0, nil, 0 },
|
|
||||||
{ 1, nil, 0 },
|
|
||||||
{ 2, nil, 0, 'south-block' },
|
|
||||||
{ 3, nil, 0, 'north-block' },
|
|
||||||
{ 4, nil, 0, 'west-block' },
|
|
||||||
{ 5, nil, 0, 'east-block' },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('rail', {
|
|
||||||
{ 0, nil, 0, 'south' },
|
|
||||||
{ 1, nil, 0, 'east' },
|
|
||||||
{ 2, nil, 0, 'east' },
|
|
||||||
{ 3, nil, 0, 'east' },
|
|
||||||
{ 4, nil, 0, 'south' },
|
|
||||||
{ 5, nil, 0, 'south' },
|
|
||||||
{ 6, nil, 0, 'east' },
|
|
||||||
{ 7, nil, 0, 'south' },
|
|
||||||
{ 8, nil, 0, 'east' },
|
|
||||||
{ 9, nil, 0, 'south' },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('adp-rail', {
|
|
||||||
{ 0, nil, 0, 'south' },
|
|
||||||
{ 1, nil, 0, 'east' },
|
|
||||||
{ 2, nil, 0, 'east' },
|
|
||||||
{ 3, nil, 0, 'east' },
|
|
||||||
{ 4, nil, 0, 'south' },
|
|
||||||
{ 5, nil, 0, 'south' },
|
|
||||||
{ 8, nil, 0, 'south' },
|
|
||||||
{ 9, nil, 0, 'east' },
|
|
||||||
{ 10, nil, 0, 'east' },
|
|
||||||
{ 11, nil, 0, 'east' },
|
|
||||||
{ 12, nil, 0, 'south' },
|
|
||||||
{ 13, nil, 0, 'south' },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('signpost', {
|
|
||||||
{ 0, nil, 0, 'north' },
|
|
||||||
{ 1, nil, 0, 'north', { facing = 1 } },
|
|
||||||
{ 2, nil, 0, 'north', { facing = 2 } },
|
|
||||||
{ 3, nil, 0, 'north', { facing = 3 } },
|
|
||||||
{ 4, nil, 0, 'east' },
|
|
||||||
{ 5, nil, 0, 'east', { facing = 1 } },
|
|
||||||
{ 6, nil, 0, 'east', { facing = 2 } },
|
|
||||||
{ 7, nil, 0, 'east', { facing = 3 } },
|
|
||||||
{ 8, nil, 0, 'south' },
|
|
||||||
{ 9, nil, 0, 'south', { facing = 1 } },
|
|
||||||
{ 10, nil, 0, 'south', { facing = 2 } },
|
|
||||||
{ 11, nil, 0, 'south', { facing = 3 } },
|
|
||||||
{ 12, nil, 0, 'west' },
|
|
||||||
{ 13, nil, 0, 'west', { facing = 1 } },
|
|
||||||
{ 14, nil, 0, 'west', { facing = 2 } },
|
|
||||||
{ 15, nil, 0, 'west', { facing = 3 } },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('vine', {
|
|
||||||
{ 0, nil, 0 },
|
|
||||||
{ 1, nil, 0, 'south-block-vine' },
|
|
||||||
{ 2, nil, 0, 'west-block-vine' },
|
|
||||||
{ 3, nil, 0, 'south-block-vine' },
|
|
||||||
{ 4, nil, 0, 'north-block-vine' },
|
|
||||||
{ 5, nil, 0, 'south-block-vine' },
|
|
||||||
{ 6, nil, 0, 'north-block-vine' },
|
|
||||||
{ 7, nil, 0, 'south-block-vine' },
|
|
||||||
{ 8, nil, 0, 'east-block-vine' },
|
|
||||||
{ 9, nil, 0, 'south-block-vine' },
|
|
||||||
{ 10, nil, 0, 'east-block-vine' },
|
|
||||||
{ 11, nil, 0, 'east-block-vine' },
|
|
||||||
{ 12, nil, 0, 'east-block-vine' },
|
|
||||||
{ 13, nil, 0, 'east-block-vine' },
|
|
||||||
{ 14, nil, 0, 'east-block-vine' },
|
|
||||||
{ 15, nil, 0, 'east-block-vine' },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('torch', {
|
|
||||||
{ 0, nil, 0 },
|
|
||||||
{ 1, nil, 0, 'west-block' },
|
|
||||||
{ 2, nil, 0, 'east-block' },
|
|
||||||
{ 3, nil, 0, 'north-block' },
|
|
||||||
{ 4, nil, 0, 'south-block' },
|
|
||||||
{ 5, nil, 0 },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('tripwire', {
|
|
||||||
{ 0, nil, 0, 'north-block' },
|
|
||||||
{ 1, nil, 0, 'east-block' },
|
|
||||||
{ 2, nil, 0, 'south-block' },
|
|
||||||
{ 3, nil, 0, 'west-block' },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('trapdoor', {
|
|
||||||
{ 0, nil, 0, 'south-block' },
|
|
||||||
{ 1, nil, 0, 'north-block' },
|
|
||||||
{ 2, nil, 0, 'east-block' },
|
|
||||||
{ 3, nil, 0, 'west-block' },
|
|
||||||
{ 4, nil, 0, 'south-block' },
|
|
||||||
{ 5, nil, 0, 'north-block' },
|
|
||||||
{ 6, nil, 0, 'east-block' },
|
|
||||||
{ 7, nil, 0, 'west-block' },
|
|
||||||
{ 8, nil, 0, 'south' },
|
|
||||||
{ 9, nil, 0, 'north' },
|
|
||||||
{ 10, nil, 0, 'east' },
|
|
||||||
{ 11, nil, 0, 'west' },
|
|
||||||
{ 12, nil, 0, 'south' },
|
|
||||||
{ 13, nil, 0, 'north' },
|
|
||||||
{ 14, nil, 0, 'east' },
|
|
||||||
{ 15, nil, 0, 'west' },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('piston', {
|
|
||||||
{ 0, nil, 0, 'piston-down' },
|
|
||||||
{ 1, nil, 0, 'piston-up' },
|
|
||||||
{ 2, nil, 0, 'piston-north' },
|
|
||||||
{ 3, nil, 0, 'piston-south' },
|
|
||||||
{ 4, nil, 0, 'piston-west' },
|
|
||||||
{ 5, nil, 0, 'piston-east' },
|
|
||||||
{ 8, nil, 0, 'piston-down' },
|
|
||||||
{ 9, nil, 0, 'piston-up' },
|
|
||||||
{ 10, nil, 0, 'piston-north' },
|
|
||||||
{ 11, nil, 0, 'piston-south' },
|
|
||||||
{ 12, nil, 0, 'piston-west' },
|
|
||||||
{ 13, nil, 0, 'piston-east' },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('lever', {
|
|
||||||
{ 0, nil, 0, 'up' },
|
|
||||||
{ 1, nil, 0, 'west-block' },
|
|
||||||
{ 2, nil, 0, 'east-block' },
|
|
||||||
{ 3, nil, 0, 'north-block' },
|
|
||||||
{ 4, nil, 0, 'south-block' },
|
|
||||||
{ 5, nil, 0, 'north' },
|
|
||||||
{ 6, nil, 0, 'west' },
|
|
||||||
{ 7, nil, 0, 'up' },
|
|
||||||
{ 8, nil, 0, 'up' },
|
|
||||||
{ 9, nil, 0, 'west-block' },
|
|
||||||
{ 10, nil, 0, 'east-block' },
|
|
||||||
{ 11, nil, 0, 'north-block' },
|
|
||||||
{ 12, nil, 0, 'south-block' },
|
|
||||||
{ 13, nil, 0, 'north' },
|
|
||||||
{ 14, nil, 0, 'west' },
|
|
||||||
{ 15, nil, 0, 'up' },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('wallsign-ladder', {
|
|
||||||
{ 0, nil, 0 },
|
|
||||||
{ 1, nil, 0 },
|
|
||||||
{ 2, nil, 0, 'south-block' },
|
|
||||||
{ 3, nil, 0, 'north-block' },
|
|
||||||
{ 4, nil, 0, 'east-block' },
|
|
||||||
{ 5, nil, 0, 'west-block' },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('chest-furnace', {
|
|
||||||
{ 0, nil, 0 },
|
|
||||||
{ 2, nil, 0, 'south' },
|
|
||||||
{ 3, nil, 0, 'north' },
|
|
||||||
{ 4, nil, 0, 'east' },
|
|
||||||
{ 5, nil, 0, 'west' },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('repeater', {
|
|
||||||
{ 0, nil, 0, 'north' },
|
|
||||||
{ 1, nil, 0, 'east' },
|
|
||||||
{ 2, nil, 0, 'south' },
|
|
||||||
{ 3, nil, 0, 'west' },
|
|
||||||
{ 4, nil, 0, 'north' },
|
|
||||||
{ 5, nil, 0, 'east' },
|
|
||||||
{ 6, nil, 0, 'south' },
|
|
||||||
{ 7, nil, 0, 'west' },
|
|
||||||
{ 8, nil, 0, 'north' },
|
|
||||||
{ 9, nil, 0, 'east' },
|
|
||||||
{ 10, nil, 0, 'south' },
|
|
||||||
{ 11, nil, 0, 'west' },
|
|
||||||
{ 12, nil, 0, 'north' },
|
|
||||||
{ 13, nil, 0, 'east' },
|
|
||||||
{ 14, nil, 0, 'south' },
|
|
||||||
{ 15, nil, 0, 'west' },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('flatten', {
|
|
||||||
{ 0, nil, 0 },
|
|
||||||
{ 1, nil, 0 },
|
|
||||||
{ 2, nil, 0 },
|
|
||||||
{ 3, nil, 0 },
|
|
||||||
{ 4, nil, 0 },
|
|
||||||
{ 5, nil, 0 },
|
|
||||||
{ 6, nil, 0 },
|
|
||||||
{ 7, nil, 0 },
|
|
||||||
{ 8, nil, 0 },
|
|
||||||
{ 9, nil, 0 },
|
|
||||||
{ 10, nil, 0 },
|
|
||||||
{ 11, nil, 0 },
|
|
||||||
{ 12, nil, 0 },
|
|
||||||
{ 13, nil, 0 },
|
|
||||||
{ 14, nil, 0 },
|
|
||||||
{ 15, nil, 0 },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('sapling', {
|
|
||||||
{ '+0', nil, nil },
|
|
||||||
{ '+8', nil, nil },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('leaves', {
|
|
||||||
{ '+0', nil, nil },
|
|
||||||
{ '+4', nil, nil },
|
|
||||||
{ '+8', nil, nil },
|
|
||||||
{ '+12', nil, nil },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('slab', {
|
|
||||||
{ '+0', nil, nil, 'bottom' },
|
|
||||||
{ '+8', nil, nil, 'top' },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('largeplant', {
|
|
||||||
{ '+0', nil, nil, 'east-door', { twoHigh = true } }, -- should use a generic double tall keyword
|
|
||||||
{ '+8', 'minecraft:air', 0 },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('wood', {
|
|
||||||
{ '+0', nil, nil },
|
|
||||||
{ '+4', nil, nil, 'east-west-block' },
|
|
||||||
{ '+8', nil, nil, 'north-south-block' },
|
|
||||||
{ '+12', nil, nil },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('door', {
|
|
||||||
{ 0, nil, 0, 'east-door', { twoHigh = true } },
|
|
||||||
{ 1, nil, 0, 'south-door', { twoHigh = true } },
|
|
||||||
{ 2, nil, 0, 'west-door', { twoHigh = true } },
|
|
||||||
{ 3, nil, 0, 'north-door', { twoHigh = true } },
|
|
||||||
{ 4, nil, 0, 'east-door', { twoHigh = true } },
|
|
||||||
{ 5, nil, 0, 'south-door', { twoHigh = true } },
|
|
||||||
{ 6, nil, 0, 'west-door', { twoHigh = true } },
|
|
||||||
{ 7, nil, 0, 'north-door', { twoHigh = true } },
|
|
||||||
{ 8,'minecraft:air', 0 },
|
|
||||||
{ 9,'minecraft:air', 0 },
|
|
||||||
{ 10,'minecraft:air', 0 },
|
|
||||||
{ 11,'minecraft:air', 0 },
|
|
||||||
{ 12,'minecraft:air', 0 },
|
|
||||||
{ 13,'minecraft:air', 0 },
|
|
||||||
{ 14,'minecraft:air', 0 },
|
|
||||||
{ 15,'minecraft:air', 0 },
|
|
||||||
})
|
|
||||||
blockTypeDB:addTemp('cocoa', {
|
|
||||||
{ 0, nil, 0, 'south-block' },
|
|
||||||
{ 1, nil, 0, 'west-block' },
|
|
||||||
{ 2, nil, 0, 'north-block' },
|
|
||||||
{ 3, nil, 0, 'east-block' },
|
|
||||||
{ 4, nil, 0, 'south-block' },
|
|
||||||
{ 5, nil, 0, 'west-block' },
|
|
||||||
{ 6, nil, 0, 'north-block' },
|
|
||||||
{ 7, nil, 0, 'east-block' },
|
|
||||||
{ 8, nil, 0, 'south-block' },
|
|
||||||
{ 9, nil, 0, 'west-block' },
|
|
||||||
{ 10, nil, 0, 'north-block' },
|
|
||||||
{ 11, nil, 0, 'east-block' },
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
local Blocks = class()
|
|
||||||
function Blocks:init(args)
|
|
||||||
|
|
||||||
Util.merge(self, args)
|
|
||||||
self.blockDB = blockDB
|
|
||||||
self.nameDB = nameDB
|
|
||||||
|
|
||||||
blockDB:load()
|
|
||||||
blockTypeDB:load()
|
|
||||||
nameDB:load(self.dir, blockDB)
|
|
||||||
placementDB:load2(blockDB, blockTypeDB)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- for an ID / dmg (with placement info) - return the correct block (without the placment info embedded in the dmg)
|
|
||||||
function Blocks:getPlaceableBlock(id, dmg)
|
|
||||||
|
|
||||||
local p = placementDB:get({id, dmg})
|
|
||||||
if p then
|
|
||||||
return Util.shallowCopy(p)
|
|
||||||
end
|
|
||||||
|
|
||||||
local b = blockDB:get({id, dmg})
|
|
||||||
if b then
|
|
||||||
return { id = b.strId, dmg = b.dmg }
|
|
||||||
end
|
|
||||||
|
|
||||||
b = blockDB:get({id, 0})
|
|
||||||
if b then
|
|
||||||
return { id = b.strId, dmg = b.dmg }
|
|
||||||
end
|
|
||||||
|
|
||||||
return { id = id, dmg = dmg }
|
|
||||||
end
|
|
||||||
|
|
||||||
return Blocks
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
local class = require('class')
|
|
||||||
local itemDB = require('itemDB')
|
|
||||||
local Peripheral = require('peripheral')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
local ChestAdapter = class()
|
|
||||||
|
|
||||||
local convertNames = {
|
|
||||||
name = 'id',
|
|
||||||
damage = 'dmg',
|
|
||||||
maxCount = 'max_size',
|
|
||||||
count = 'qty',
|
|
||||||
displayName = 'display_name',
|
|
||||||
maxDamage = 'max_dmg',
|
|
||||||
}
|
|
||||||
local keys = {
|
|
||||||
'damage',
|
|
||||||
'displayName',
|
|
||||||
'maxCount',
|
|
||||||
'maxDamage',
|
|
||||||
'name',
|
|
||||||
'nbtHash',
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Strip off color prefix
|
|
||||||
local function safeString(text)
|
|
||||||
|
|
||||||
local val = text:byte(1)
|
|
||||||
|
|
||||||
if val < 32 or val > 128 then
|
|
||||||
|
|
||||||
local newText = {}
|
|
||||||
for i = 4, #text do
|
|
||||||
local val = text:byte(i)
|
|
||||||
newText[i - 3] = (val > 31 and val < 127) and val or 63
|
|
||||||
end
|
|
||||||
return string.char(unpack(newText))
|
|
||||||
end
|
|
||||||
|
|
||||||
return text
|
|
||||||
end
|
|
||||||
|
|
||||||
local function convertItem(item)
|
|
||||||
for k,v in pairs(convertNames) do
|
|
||||||
item[k] = item[v]
|
|
||||||
item[v] = nil
|
|
||||||
end
|
|
||||||
item.displayName = safeString(item.displayName)
|
|
||||||
end
|
|
||||||
|
|
||||||
function ChestAdapter:init(args)
|
|
||||||
local defaults = {
|
|
||||||
name = 'chest',
|
|
||||||
direction = 'up',
|
|
||||||
wrapSide = 'bottom',
|
|
||||||
}
|
|
||||||
Util.merge(self, defaults)
|
|
||||||
Util.merge(self, args)
|
|
||||||
|
|
||||||
local chest = Peripheral.getBySide(self.wrapSide)
|
|
||||||
if not chest then
|
|
||||||
chest = Peripheral.getByMethod('getAllStacks')
|
|
||||||
end
|
|
||||||
if chest then
|
|
||||||
Util.merge(self, chest)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function ChestAdapter:isValid()
|
|
||||||
return not not self.getAllStacks
|
|
||||||
end
|
|
||||||
|
|
||||||
function ChestAdapter:refresh(throttle)
|
|
||||||
return self:listItems(throttle)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- provide a consolidated list of items
|
|
||||||
function ChestAdapter:listItems(throttle)
|
|
||||||
self.cache = { }
|
|
||||||
|
|
||||||
for _,v in pairs(self.getAllStacks(false)) do
|
|
||||||
convertItem(v)
|
|
||||||
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')
|
|
||||||
|
|
||||||
local entry = self.cache[key]
|
|
||||||
if not entry then
|
|
||||||
self.cache[key] = v
|
|
||||||
|
|
||||||
local ikey = { v.name, v.damage, v.nbtHash }
|
|
||||||
if not itemDB:get(ikey) then
|
|
||||||
local t = { }
|
|
||||||
for _,k in pairs(keys) do
|
|
||||||
t[k] = v[k]
|
|
||||||
end
|
|
||||||
itemDB:add(ikey, t)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
entry.count = entry.count + v.count
|
|
||||||
end
|
|
||||||
end
|
|
||||||
itemDB:flush()
|
|
||||||
return self.cache
|
|
||||||
end
|
|
||||||
|
|
||||||
function ChestAdapter:getItemInfo(item)
|
|
||||||
if not self.cache then
|
|
||||||
self:listItems()
|
|
||||||
end
|
|
||||||
local key = table.concat({ item.name, item.damage, item.nbtHash }, ':')
|
|
||||||
return self.cache[key]
|
|
||||||
end
|
|
||||||
|
|
||||||
function ChestAdapter:craft(id, dmg, qty)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function ChestAdapter:craftItems(items)
|
|
||||||
end
|
|
||||||
|
|
||||||
function ChestAdapter:provide(item, qty, slot, direction)
|
|
||||||
for key,stack in pairs(self.getAllStacks(false)) do
|
|
||||||
if stack.id == item.name and
|
|
||||||
stack.dmg == item.damage and
|
|
||||||
stack.nbt_hash == item.nbtHash then
|
|
||||||
|
|
||||||
local amount = math.min(qty, stack.qty)
|
|
||||||
self.pushItemIntoSlot(direction or self.direction, key, amount, slot)
|
|
||||||
qty = qty - amount
|
|
||||||
if qty <= 0 then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function ChestAdapter:extract(slot, qty, toSlot)
|
|
||||||
if toSlot then
|
|
||||||
self.pushItemIntoSlot(self.direction, slot, qty, toSlot)
|
|
||||||
else
|
|
||||||
self.pushItem(self.direction, slot, qty)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function ChestAdapter:insert(slot, qty)
|
|
||||||
local s, m = pcall(function() self.pullItem(self.direction, slot, qty) end)
|
|
||||||
if not s and m then
|
|
||||||
sleep(1)
|
|
||||||
pcall(function() self.pullItem(self.direction, slot, qty) end)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return ChestAdapter
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
local class = require('class')
|
|
||||||
local Util = require('util')
|
|
||||||
local itemDB = require('itemDB')
|
|
||||||
local Peripheral = require('peripheral')
|
|
||||||
|
|
||||||
local ChestAdapter = class()
|
|
||||||
|
|
||||||
local keys = Util.transpose({
|
|
||||||
'damage',
|
|
||||||
'displayName',
|
|
||||||
'maxCount',
|
|
||||||
'maxDamage',
|
|
||||||
'name',
|
|
||||||
'nbtHash',
|
|
||||||
})
|
|
||||||
|
|
||||||
function ChestAdapter:init(args)
|
|
||||||
local defaults = {
|
|
||||||
name = 'chest',
|
|
||||||
direction = 'up',
|
|
||||||
wrapSide = 'bottom',
|
|
||||||
}
|
|
||||||
Util.merge(self, defaults)
|
|
||||||
Util.merge(self, args)
|
|
||||||
|
|
||||||
local chest = Peripheral.getBySide(self.wrapSide)
|
|
||||||
if not chest then
|
|
||||||
chest = Peripheral.getByMethod('list')
|
|
||||||
end
|
|
||||||
if chest then
|
|
||||||
Util.merge(self, chest)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function ChestAdapter:isValid()
|
|
||||||
return not not self.list
|
|
||||||
end
|
|
||||||
|
|
||||||
function ChestAdapter:getCachedItemDetails(item, k)
|
|
||||||
local key = { item.name, item.damage, item.nbtHash }
|
|
||||||
|
|
||||||
local detail = itemDB:get(key)
|
|
||||||
if not detail then
|
|
||||||
pcall(function() detail = self.getItemMeta(k) end)
|
|
||||||
if not detail then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
-- NOT SUFFICIENT
|
|
||||||
if detail.name ~= item.name then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
for _,k in ipairs(Util.keys(detail)) do
|
|
||||||
if not keys[k] then
|
|
||||||
detail[k] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
itemDB:add(key, detail)
|
|
||||||
end
|
|
||||||
if detail then
|
|
||||||
return Util.shallowCopy(detail)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function ChestAdapter:refresh(throttle)
|
|
||||||
return self:listItems(throttle)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- provide a consolidated list of items
|
|
||||||
function ChestAdapter:listItems(throttle)
|
|
||||||
self.cache = { }
|
|
||||||
local items = { }
|
|
||||||
|
|
||||||
throttle = throttle or Util.throttle()
|
|
||||||
|
|
||||||
for k,v in pairs(self.list()) do
|
|
||||||
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')
|
|
||||||
|
|
||||||
local entry = self.cache[key]
|
|
||||||
if not entry then
|
|
||||||
entry = self:getCachedItemDetails(v, k)
|
|
||||||
if entry then
|
|
||||||
entry.count = 0
|
|
||||||
self.cache[key] = entry
|
|
||||||
table.insert(items, entry)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if entry then
|
|
||||||
entry.count = entry.count + v.count
|
|
||||||
end
|
|
||||||
throttle()
|
|
||||||
end
|
|
||||||
|
|
||||||
itemDB:flush()
|
|
||||||
|
|
||||||
return items
|
|
||||||
end
|
|
||||||
|
|
||||||
function ChestAdapter:getItemInfo(item)
|
|
||||||
if not self.cache then
|
|
||||||
self:listItems()
|
|
||||||
end
|
|
||||||
local key = table.concat({ item.name, item.damage, item.nbtHash }, ':')
|
|
||||||
return self.cache[key]
|
|
||||||
end
|
|
||||||
|
|
||||||
function ChestAdapter:craft(name, damage, qty)
|
|
||||||
end
|
|
||||||
|
|
||||||
function ChestAdapter:craftItems(items)
|
|
||||||
end
|
|
||||||
|
|
||||||
function ChestAdapter:provide(item, qty, slot, direction)
|
|
||||||
local stacks = self.list()
|
|
||||||
for key,stack in pairs(stacks) do
|
|
||||||
if stack.name == item.name and stack.damage == item.damage then
|
|
||||||
local amount = math.min(qty, stack.count)
|
|
||||||
if amount > 0 then
|
|
||||||
self.pushItems(direction or self.direction, key, amount, slot)
|
|
||||||
end
|
|
||||||
qty = qty - amount
|
|
||||||
if qty <= 0 then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function ChestAdapter:extract(slot, qty, toSlot)
|
|
||||||
self.pushItems(self.direction, slot, qty, toSlot)
|
|
||||||
end
|
|
||||||
|
|
||||||
function ChestAdapter:insert(slot, qty)
|
|
||||||
self.pullItems(self.direction, slot, qty)
|
|
||||||
end
|
|
||||||
|
|
||||||
return ChestAdapter
|
|
||||||
@@ -1,870 +0,0 @@
|
|||||||
--[[
|
|
||||||
|
|
||||||
LUA MODULE
|
|
||||||
|
|
||||||
compress.deflatelua - deflate (and gunzip/zlib) implemented in Lua.
|
|
||||||
|
|
||||||
SYNOPSIS
|
|
||||||
|
|
||||||
local DEFLATE = require 'compress.deflatelua'
|
|
||||||
-- uncompress gzip file
|
|
||||||
local fh = assert(io.open'foo.txt.gz', 'rb')
|
|
||||||
local ofh = assert(io.open'foo.txt', 'wb')
|
|
||||||
DEFLATE.gunzip {input=fh, output=ofh}
|
|
||||||
fh:close(); ofh:close()
|
|
||||||
-- can also uncompress from string including zlib and raw DEFLATE formats.
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
|
|
||||||
This is a pure Lua implementation of decompressing the DEFLATE format,
|
|
||||||
including the related zlib and gzip formats.
|
|
||||||
|
|
||||||
Note: This library only supports decompression.
|
|
||||||
Compression is not currently implemented.
|
|
||||||
|
|
||||||
API
|
|
||||||
|
|
||||||
Note: in the following functions, input stream `fh` may be
|
|
||||||
a file handle, string, or an iterator function that returns strings.
|
|
||||||
Output stream `ofh` may be a file handle or a function that
|
|
||||||
consumes one byte (number 0..255) per call.
|
|
||||||
|
|
||||||
DEFLATE.inflate {input=fh, output=ofh}
|
|
||||||
|
|
||||||
Decompresses input stream `fh` in the DEFLATE format
|
|
||||||
while writing to output stream `ofh`.
|
|
||||||
DEFLATE is detailed in http://tools.ietf.org/html/rfc1951 .
|
|
||||||
|
|
||||||
DEFLATE.gunzip {input=fh, output=ofh, disable_crc=disable_crc}
|
|
||||||
|
|
||||||
Decompresses input stream `fh` with the gzip format
|
|
||||||
while writing to output stream `ofh`.
|
|
||||||
`disable_crc` (defaults to `false`) will disable CRC-32 checking
|
|
||||||
to increase speed.
|
|
||||||
gzip is detailed in http://tools.ietf.org/html/rfc1952 .
|
|
||||||
|
|
||||||
DEFLATE.inflate_zlib {input=fh, output=ofh, disable_crc=disable_crc}
|
|
||||||
|
|
||||||
Decompresses input stream `fh` with the zlib format
|
|
||||||
while writing to output stream `ofh`.
|
|
||||||
`disable_crc` (defaults to `false`) will disable CRC-32 checking
|
|
||||||
to increase speed.
|
|
||||||
zlib is detailed in http://tools.ietf.org/html/rfc1950 .
|
|
||||||
|
|
||||||
DEFLATE.adler32(byte, crc) --> rcrc
|
|
||||||
|
|
||||||
Returns adler32 checksum of byte `byte` (number 0..255) appended
|
|
||||||
to string with adler32 checksum `crc`. This is internally used by
|
|
||||||
`inflate_zlib`.
|
|
||||||
ADLER32 in detailed in http://tools.ietf.org/html/rfc1950 .
|
|
||||||
|
|
||||||
COMMAND LINE UTILITY
|
|
||||||
|
|
||||||
A `gunziplua` command line utility (in folder `bin`) is also provided.
|
|
||||||
This mimicks the *nix `gunzip` utility but is a pure Lua implementation
|
|
||||||
that invokes this library. For help do
|
|
||||||
|
|
||||||
gunziplua -h
|
|
||||||
|
|
||||||
DEPENDENCIES
|
|
||||||
|
|
||||||
Requires 'digest.crc32lua' (used for optional CRC-32 checksum checks).
|
|
||||||
https://github.com/davidm/lua-digest-crc32lua
|
|
||||||
|
|
||||||
Will use a bit library ('bit', 'bit32', 'bit.numberlua') if available. This
|
|
||||||
is not that critical for this library but is required by digest.crc32lua.
|
|
||||||
|
|
||||||
'pythonic.optparse' is only required by the optional `gunziplua`
|
|
||||||
command-line utilty for command line parsing.
|
|
||||||
https://github.com/davidm/lua-pythonic-optparse
|
|
||||||
|
|
||||||
INSTALLATION
|
|
||||||
|
|
||||||
Copy the `compress` directory into your LUA_PATH.
|
|
||||||
|
|
||||||
REFERENCES
|
|
||||||
|
|
||||||
[1] DEFLATE Compressed Data Format Specification version 1.3
|
|
||||||
http://tools.ietf.org/html/rfc1951
|
|
||||||
[2] GZIP file format specification version 4.3
|
|
||||||
http://tools.ietf.org/html/rfc1952
|
|
||||||
[3] http://en.wikipedia.org/wiki/DEFLATE
|
|
||||||
[4] pyflate, by Paul Sladen
|
|
||||||
http://www.paul.sladen.org/projects/pyflate/
|
|
||||||
[5] Compress::Zlib::Perl - partial pure Perl implementation of
|
|
||||||
Compress::Zlib
|
|
||||||
http://search.cpan.org/~nwclark/Compress-Zlib-Perl/Perl.pm
|
|
||||||
|
|
||||||
LICENSE
|
|
||||||
|
|
||||||
(c) 2008-2011 David Manura. Licensed under the same terms as Lua (MIT).
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
(end license)
|
|
||||||
--]]
|
|
||||||
|
|
||||||
local M = {_TYPE='module', _NAME='compress.deflatelua', _VERSION='0.3.20111128'}
|
|
||||||
|
|
||||||
local assert = assert
|
|
||||||
local error = error
|
|
||||||
local ipairs = ipairs
|
|
||||||
local pairs = pairs
|
|
||||||
local print = print
|
|
||||||
local require = require
|
|
||||||
local tostring = tostring
|
|
||||||
local type = type
|
|
||||||
local setmetatable = setmetatable
|
|
||||||
local io = io
|
|
||||||
local math = math
|
|
||||||
local table_sort = table.sort
|
|
||||||
local math_max = math.max
|
|
||||||
local string_char = string.char
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Requires the first module listed that exists, else raises like `require`.
|
|
||||||
If a non-string is encountered, it is returned.
|
|
||||||
Second return value is module name loaded (or '').
|
|
||||||
--]]
|
|
||||||
local function requireany(...)
|
|
||||||
local errs = {}
|
|
||||||
for i = 1, select('#', ...) do local name = select(i, ...)
|
|
||||||
if type(name) ~= 'string' then return name, '' end
|
|
||||||
local ok, mod = pcall(require, name)
|
|
||||||
if ok then return mod, name end
|
|
||||||
errs[#errs+1] = mod
|
|
||||||
end
|
|
||||||
error(table.concat(errs, '\n'), 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
--local crc32 = require "digest.crc32lua" . crc32_byte
|
|
||||||
--local bit, name_ = requireany('bit', 'bit32', 'bit.numberlua', nil)
|
|
||||||
local bit
|
|
||||||
local crc32
|
|
||||||
|
|
||||||
local DEBUG = false
|
|
||||||
|
|
||||||
-- Whether to use `bit` library functions in current module.
|
|
||||||
-- Unlike the crc32 library, it doesn't make much difference in this module.
|
|
||||||
local NATIVE_BITOPS = (bit ~= nil)
|
|
||||||
|
|
||||||
local band, lshift, rshift
|
|
||||||
if NATIVE_BITOPS then
|
|
||||||
band = bit.band
|
|
||||||
lshift = bit.lshift
|
|
||||||
rshift = bit.rshift
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function warn(s)
|
|
||||||
io.stderr:write(s, '\n')
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function debug(...)
|
|
||||||
print('DEBUG', ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function runtime_error(s, level)
|
|
||||||
level = level or 1
|
|
||||||
error({s}, level+1)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function make_outstate(outbs)
|
|
||||||
local outstate = {}
|
|
||||||
outstate.outbs = outbs
|
|
||||||
outstate.window = {}
|
|
||||||
outstate.window_pos = 1
|
|
||||||
return outstate
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function output(outstate, byte)
|
|
||||||
-- debug('OUTPUT:', s)
|
|
||||||
local window_pos = outstate.window_pos
|
|
||||||
outstate.outbs(byte)
|
|
||||||
outstate.window[window_pos] = byte
|
|
||||||
outstate.window_pos = window_pos % 32768 + 1 -- 32K
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function noeof(val)
|
|
||||||
return assert(val, 'unexpected end of file')
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function hasbit(bits, bit)
|
|
||||||
return bits % (bit + bit) >= bit
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function memoize(f)
|
|
||||||
local mt = {}
|
|
||||||
local t = setmetatable({}, mt)
|
|
||||||
function mt:__index(k)
|
|
||||||
local v = f(k)
|
|
||||||
t[k] = v
|
|
||||||
return v
|
|
||||||
end
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- small optimization (lookup table for powers of 2)
|
|
||||||
local pow2 = memoize(function(n) return 2^n end)
|
|
||||||
|
|
||||||
--local tbits = memoize(
|
|
||||||
-- function(bits)
|
|
||||||
-- return memoize( function(bit) return getbit(bits, bit) end )
|
|
||||||
-- end )
|
|
||||||
|
|
||||||
|
|
||||||
-- weak metatable marking objects as bitstream type
|
|
||||||
local is_bitstream = setmetatable({}, {__mode='k'})
|
|
||||||
|
|
||||||
|
|
||||||
-- DEBUG
|
|
||||||
-- prints LSB first
|
|
||||||
--[[
|
|
||||||
local function bits_tostring(bits, nbits)
|
|
||||||
local s = ''
|
|
||||||
local tmp = bits
|
|
||||||
local function f()
|
|
||||||
local b = tmp % 2 == 1 and 1 or 0
|
|
||||||
s = s .. b
|
|
||||||
tmp = (tmp - b) / 2
|
|
||||||
end
|
|
||||||
if nbits then
|
|
||||||
for i=1,nbits do f() end
|
|
||||||
else
|
|
||||||
while tmp ~= 0 do f() end
|
|
||||||
end
|
|
||||||
|
|
||||||
return s
|
|
||||||
end
|
|
||||||
--]]
|
|
||||||
|
|
||||||
local function bytestream_from_file(fh)
|
|
||||||
local o = {}
|
|
||||||
function o:read()
|
|
||||||
local sb = fh:read(1)
|
|
||||||
if sb then return sb:byte() end
|
|
||||||
end
|
|
||||||
return o
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function bytestream_from_string(s)
|
|
||||||
local i = 1
|
|
||||||
local o = {}
|
|
||||||
function o:read()
|
|
||||||
local by
|
|
||||||
if i <= #s then
|
|
||||||
by = s:byte(i)
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
return by
|
|
||||||
end
|
|
||||||
return o
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function bytestream_from_function(f)
|
|
||||||
local i = 0
|
|
||||||
local buffer = ''
|
|
||||||
local o = {}
|
|
||||||
function o:read()
|
|
||||||
return f()
|
|
||||||
-- i = i + 1
|
|
||||||
-- if i > #buffer then
|
|
||||||
-- buffer = f()
|
|
||||||
-- if not buffer then return end
|
|
||||||
-- i = 1
|
|
||||||
-- end
|
|
||||||
-- return buffer:byte(i,i)
|
|
||||||
end
|
|
||||||
return o
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function bitstream_from_bytestream(bys)
|
|
||||||
local buf_byte = 0
|
|
||||||
local buf_nbit = 0
|
|
||||||
local o = {}
|
|
||||||
|
|
||||||
function o:nbits_left_in_byte()
|
|
||||||
return buf_nbit
|
|
||||||
end
|
|
||||||
|
|
||||||
if NATIVE_BITOPS then
|
|
||||||
function o:read(nbits)
|
|
||||||
nbits = nbits or 1
|
|
||||||
while buf_nbit < nbits do
|
|
||||||
local byte = bys:read()
|
|
||||||
if not byte then return end -- note: more calls also return nil
|
|
||||||
buf_byte = buf_byte + lshift(byte, buf_nbit)
|
|
||||||
buf_nbit = buf_nbit + 8
|
|
||||||
end
|
|
||||||
local bits
|
|
||||||
if nbits == 0 then
|
|
||||||
bits = 0
|
|
||||||
elseif nbits == 32 then
|
|
||||||
bits = buf_byte
|
|
||||||
buf_byte = 0
|
|
||||||
else
|
|
||||||
bits = band(buf_byte, rshift(0xffffffff, 32 - nbits))
|
|
||||||
buf_byte = rshift(buf_byte, nbits)
|
|
||||||
end
|
|
||||||
buf_nbit = buf_nbit - nbits
|
|
||||||
return bits
|
|
||||||
end
|
|
||||||
else
|
|
||||||
function o:read(nbits)
|
|
||||||
nbits = nbits or 1
|
|
||||||
while buf_nbit < nbits do
|
|
||||||
local byte = bys:read()
|
|
||||||
if not byte then return end -- note: more calls also return nil
|
|
||||||
buf_byte = buf_byte + pow2[buf_nbit] * byte
|
|
||||||
buf_nbit = buf_nbit + 8
|
|
||||||
end
|
|
||||||
local m = pow2[nbits]
|
|
||||||
local bits = buf_byte % m
|
|
||||||
buf_byte = (buf_byte - bits) / m
|
|
||||||
buf_nbit = buf_nbit - nbits
|
|
||||||
return bits
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
is_bitstream[o] = true
|
|
||||||
|
|
||||||
return o
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function get_bitstream(o)
|
|
||||||
local bs
|
|
||||||
if is_bitstream[o] then
|
|
||||||
return o
|
|
||||||
elseif io.type(o) == 'file' then
|
|
||||||
bs = bitstream_from_bytestream(bytestream_from_file(o))
|
|
||||||
elseif type(o) == 'string' then
|
|
||||||
bs = bitstream_from_bytestream(bytestream_from_string(o))
|
|
||||||
elseif type(o) == 'function' then
|
|
||||||
bs = bitstream_from_bytestream(bytestream_from_function(o))
|
|
||||||
else
|
|
||||||
runtime_error 'unrecognized type'
|
|
||||||
end
|
|
||||||
return bs
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function get_obytestream(o)
|
|
||||||
local bs
|
|
||||||
if io.type(o) == 'file' then
|
|
||||||
bs = function(sbyte) o:write(string_char(sbyte)) end
|
|
||||||
elseif type(o) == 'function' then
|
|
||||||
bs = o
|
|
||||||
else
|
|
||||||
runtime_error('unrecognized type: ' .. tostring(o))
|
|
||||||
end
|
|
||||||
return bs
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function HuffmanTable(init, is_full)
|
|
||||||
local t = {}
|
|
||||||
if is_full then
|
|
||||||
for val,nbits in pairs(init) do
|
|
||||||
if nbits ~= 0 then
|
|
||||||
t[#t+1] = {val=val, nbits=nbits}
|
|
||||||
--debug('*',val,nbits)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
for i=1,#init-2,2 do
|
|
||||||
local firstval, nbits, nextval = init[i], init[i+1], init[i+2]
|
|
||||||
--debug(val, nextval, nbits)
|
|
||||||
if nbits ~= 0 then
|
|
||||||
for val=firstval,nextval-1 do
|
|
||||||
t[#t+1] = {val=val, nbits=nbits}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
table_sort(t, function(a,b)
|
|
||||||
return a.nbits == b.nbits and a.val < b.val or a.nbits < b.nbits
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- assign codes
|
|
||||||
local code = 1 -- leading 1 marker
|
|
||||||
local nbits = 0
|
|
||||||
for i,s in ipairs(t) do
|
|
||||||
if s.nbits ~= nbits then
|
|
||||||
code = code * pow2[s.nbits - nbits]
|
|
||||||
nbits = s.nbits
|
|
||||||
end
|
|
||||||
s.code = code
|
|
||||||
--debug('huffman code:', i, s.nbits, s.val, code, bits_tostring(code))
|
|
||||||
code = code + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
local minbits = math.huge
|
|
||||||
local look = {}
|
|
||||||
for i,s in ipairs(t) do
|
|
||||||
minbits = math.min(minbits, s.nbits)
|
|
||||||
look[s.code] = s.val
|
|
||||||
end
|
|
||||||
|
|
||||||
--for _,o in ipairs(t) do
|
|
||||||
-- debug(':', o.nbits, o.val)
|
|
||||||
--end
|
|
||||||
|
|
||||||
-- function t:lookup(bits) return look[bits] end
|
|
||||||
|
|
||||||
local msb = NATIVE_BITOPS and function(bits, nbits)
|
|
||||||
local res = 0
|
|
||||||
for i=1,nbits do
|
|
||||||
res = lshift(res, 1) + band(bits, 1)
|
|
||||||
bits = rshift(bits, 1)
|
|
||||||
end
|
|
||||||
return res
|
|
||||||
end or function(bits, nbits)
|
|
||||||
local res = 0
|
|
||||||
for i=1,nbits do
|
|
||||||
local b = bits % 2
|
|
||||||
bits = (bits - b) / 2
|
|
||||||
res = res * 2 + b
|
|
||||||
end
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
local tfirstcode = memoize(
|
|
||||||
function(bits) return pow2[minbits] + msb(bits, minbits) end)
|
|
||||||
|
|
||||||
function t:read(bs)
|
|
||||||
local code = 1 -- leading 1 marker
|
|
||||||
local nbits = 0
|
|
||||||
while 1 do
|
|
||||||
if nbits == 0 then -- small optimization (optional)
|
|
||||||
code = tfirstcode[noeof(bs:read(minbits))]
|
|
||||||
nbits = nbits + minbits
|
|
||||||
else
|
|
||||||
local b = noeof(bs:read())
|
|
||||||
nbits = nbits + 1
|
|
||||||
code = code * 2 + b -- MSB first
|
|
||||||
--[[NATIVE_BITOPS
|
|
||||||
code = lshift(code, 1) + b -- MSB first
|
|
||||||
--]]
|
|
||||||
end
|
|
||||||
--debug('code?', code, bits_tostring(code))
|
|
||||||
local val = look[code]
|
|
||||||
if val then
|
|
||||||
--debug('FOUND', val)
|
|
||||||
return val
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function parse_gzip_header(bs)
|
|
||||||
-- local FLG_FTEXT = 2^0
|
|
||||||
local FLG_FHCRC = 2^1
|
|
||||||
local FLG_FEXTRA = 2^2
|
|
||||||
local FLG_FNAME = 2^3
|
|
||||||
local FLG_FCOMMENT = 2^4
|
|
||||||
|
|
||||||
local id1 = bs:read(8)
|
|
||||||
local id2 = bs:read(8)
|
|
||||||
if id1 ~= 31 or id2 ~= 139 then
|
|
||||||
runtime_error 'not in gzip format'
|
|
||||||
end
|
|
||||||
local cm = bs:read(8) -- compression method
|
|
||||||
local flg = bs:read(8) -- FLaGs
|
|
||||||
local mtime = bs:read(32) -- Modification TIME
|
|
||||||
local xfl = bs:read(8) -- eXtra FLags
|
|
||||||
local os = bs:read(8) -- Operating System
|
|
||||||
|
|
||||||
if DEBUG then
|
|
||||||
debug("CM=", cm)
|
|
||||||
debug("FLG=", flg)
|
|
||||||
debug("MTIME=", mtime)
|
|
||||||
-- debug("MTIME_str=",os.date("%Y-%m-%d %H:%M:%S",mtime)) -- non-portable
|
|
||||||
debug("XFL=", xfl)
|
|
||||||
debug("OS=", os)
|
|
||||||
end
|
|
||||||
|
|
||||||
if not os then runtime_error 'invalid header' end
|
|
||||||
|
|
||||||
if hasbit(flg, FLG_FEXTRA) then
|
|
||||||
local xlen = bs:read(16)
|
|
||||||
local extra = 0
|
|
||||||
for i=1,xlen do
|
|
||||||
extra = bs:read(8)
|
|
||||||
end
|
|
||||||
if not extra then runtime_error 'invalid header' end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function parse_zstring(bs)
|
|
||||||
repeat
|
|
||||||
local by = bs:read(8)
|
|
||||||
if not by then runtime_error 'invalid header' end
|
|
||||||
until by == 0
|
|
||||||
end
|
|
||||||
|
|
||||||
if hasbit(flg, FLG_FNAME) then
|
|
||||||
parse_zstring(bs)
|
|
||||||
end
|
|
||||||
|
|
||||||
if hasbit(flg, FLG_FCOMMENT) then
|
|
||||||
parse_zstring(bs)
|
|
||||||
end
|
|
||||||
|
|
||||||
if hasbit(flg, FLG_FHCRC) then
|
|
||||||
local crc16 = bs:read(16)
|
|
||||||
if not crc16 then runtime_error 'invalid header' end
|
|
||||||
-- IMPROVE: check CRC. where is an example .gz file that
|
|
||||||
-- has this set?
|
|
||||||
if DEBUG then
|
|
||||||
debug("CRC16=", crc16)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function parse_zlib_header(bs)
|
|
||||||
local cm = bs:read(4) -- Compression Method
|
|
||||||
local cinfo = bs:read(4) -- Compression info
|
|
||||||
local fcheck = bs:read(5) -- FLaGs: FCHECK (check bits for CMF and FLG)
|
|
||||||
local fdict = bs:read(1) -- FLaGs: FDICT (present dictionary)
|
|
||||||
local flevel = bs:read(2) -- FLaGs: FLEVEL (compression level)
|
|
||||||
local cmf = cinfo * 16 + cm -- CMF (Compresion Method and flags)
|
|
||||||
local flg = fcheck + fdict * 32 + flevel * 64 -- FLaGs
|
|
||||||
|
|
||||||
if cm ~= 8 then -- not "deflate"
|
|
||||||
runtime_error("unrecognized zlib compression method: " + cm)
|
|
||||||
end
|
|
||||||
if cinfo > 7 then
|
|
||||||
runtime_error("invalid zlib window size: cinfo=" + cinfo)
|
|
||||||
end
|
|
||||||
local window_size = 2^(cinfo + 8)
|
|
||||||
|
|
||||||
if (cmf*256 + flg) % 31 ~= 0 then
|
|
||||||
runtime_error("invalid zlib header (bad fcheck sum)")
|
|
||||||
end
|
|
||||||
|
|
||||||
if fdict == 1 then
|
|
||||||
runtime_error("FIX:TODO - FDICT not currently implemented")
|
|
||||||
local dictid_ = bs:read(32)
|
|
||||||
end
|
|
||||||
|
|
||||||
return window_size
|
|
||||||
end
|
|
||||||
|
|
||||||
local function parse_huffmantables(bs)
|
|
||||||
local hlit = bs:read(5) -- # of literal/length codes - 257
|
|
||||||
local hdist = bs:read(5) -- # of distance codes - 1
|
|
||||||
local hclen = noeof(bs:read(4)) -- # of code length codes - 4
|
|
||||||
|
|
||||||
local ncodelen_codes = hclen + 4
|
|
||||||
local codelen_init = {}
|
|
||||||
local codelen_vals = {
|
|
||||||
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}
|
|
||||||
for i=1,ncodelen_codes do
|
|
||||||
local nbits = bs:read(3)
|
|
||||||
local val = codelen_vals[i]
|
|
||||||
codelen_init[val] = nbits
|
|
||||||
end
|
|
||||||
local codelentable = HuffmanTable(codelen_init, true)
|
|
||||||
|
|
||||||
local function decode(ncodes)
|
|
||||||
local init = {}
|
|
||||||
local nbits
|
|
||||||
local val = 0
|
|
||||||
while val < ncodes do
|
|
||||||
local codelen = codelentable:read(bs)
|
|
||||||
--FIX:check nil?
|
|
||||||
local nrepeat
|
|
||||||
if codelen <= 15 then
|
|
||||||
nrepeat = 1
|
|
||||||
nbits = codelen
|
|
||||||
--debug('w', nbits)
|
|
||||||
elseif codelen == 16 then
|
|
||||||
nrepeat = 3 + noeof(bs:read(2))
|
|
||||||
-- nbits unchanged
|
|
||||||
elseif codelen == 17 then
|
|
||||||
nrepeat = 3 + noeof(bs:read(3))
|
|
||||||
nbits = 0
|
|
||||||
elseif codelen == 18 then
|
|
||||||
nrepeat = 11 + noeof(bs:read(7))
|
|
||||||
nbits = 0
|
|
||||||
else
|
|
||||||
error 'ASSERT'
|
|
||||||
end
|
|
||||||
for i=1,nrepeat do
|
|
||||||
init[val] = nbits
|
|
||||||
val = val + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local huffmantable = HuffmanTable(init, true)
|
|
||||||
return huffmantable
|
|
||||||
end
|
|
||||||
|
|
||||||
local nlit_codes = hlit + 257
|
|
||||||
local ndist_codes = hdist + 1
|
|
||||||
|
|
||||||
local littable = decode(nlit_codes)
|
|
||||||
local disttable = decode(ndist_codes)
|
|
||||||
|
|
||||||
return littable, disttable
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local tdecode_len_base
|
|
||||||
local tdecode_len_nextrabits
|
|
||||||
local tdecode_dist_base
|
|
||||||
local tdecode_dist_nextrabits
|
|
||||||
local function parse_compressed_item(bs, outstate, littable, disttable)
|
|
||||||
local val = littable:read(bs)
|
|
||||||
--debug(val, val < 256 and string_char(val))
|
|
||||||
if val < 256 then -- literal
|
|
||||||
output(outstate, val)
|
|
||||||
elseif val == 256 then -- end of block
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
if not tdecode_len_base then
|
|
||||||
local t = {[257]=3}
|
|
||||||
local skip = 1
|
|
||||||
for i=258,285,4 do
|
|
||||||
for j=i,i+3 do t[j] = t[j-1] + skip end
|
|
||||||
if i ~= 258 then skip = skip * 2 end
|
|
||||||
end
|
|
||||||
t[285] = 258
|
|
||||||
tdecode_len_base = t
|
|
||||||
--for i=257,285 do debug('T1',i,t[i]) end
|
|
||||||
end
|
|
||||||
if not tdecode_len_nextrabits then
|
|
||||||
local t = {}
|
|
||||||
if NATIVE_BITOPS then
|
|
||||||
for i=257,285 do
|
|
||||||
local j = math_max(i - 261, 0)
|
|
||||||
t[i] = rshift(j, 2)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
for i=257,285 do
|
|
||||||
local j = math_max(i - 261, 0)
|
|
||||||
t[i] = (j - (j % 4)) / 4
|
|
||||||
end
|
|
||||||
end
|
|
||||||
t[285] = 0
|
|
||||||
tdecode_len_nextrabits = t
|
|
||||||
--for i=257,285 do debug('T2',i,t[i]) end
|
|
||||||
end
|
|
||||||
local len_base = tdecode_len_base[val]
|
|
||||||
local nextrabits = tdecode_len_nextrabits[val]
|
|
||||||
local extrabits = bs:read(nextrabits)
|
|
||||||
local len = len_base + extrabits
|
|
||||||
|
|
||||||
if not tdecode_dist_base then
|
|
||||||
local t = {[0]=1}
|
|
||||||
local skip = 1
|
|
||||||
for i=1,29,2 do
|
|
||||||
for j=i,i+1 do t[j] = t[j-1] + skip end
|
|
||||||
if i ~= 1 then skip = skip * 2 end
|
|
||||||
end
|
|
||||||
tdecode_dist_base = t
|
|
||||||
--for i=0,29 do debug('T3',i,t[i]) end
|
|
||||||
end
|
|
||||||
if not tdecode_dist_nextrabits then
|
|
||||||
local t = {}
|
|
||||||
if NATIVE_BITOPS then
|
|
||||||
for i=0,29 do
|
|
||||||
local j = math_max(i - 2, 0)
|
|
||||||
t[i] = rshift(j, 1)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
for i=0,29 do
|
|
||||||
local j = math_max(i - 2, 0)
|
|
||||||
t[i] = (j - (j % 2)) / 2
|
|
||||||
end
|
|
||||||
end
|
|
||||||
tdecode_dist_nextrabits = t
|
|
||||||
--for i=0,29 do debug('T4',i,t[i]) end
|
|
||||||
end
|
|
||||||
local dist_val = disttable:read(bs)
|
|
||||||
local dist_base = tdecode_dist_base[dist_val]
|
|
||||||
local dist_nextrabits = tdecode_dist_nextrabits[dist_val]
|
|
||||||
local dist_extrabits = bs:read(dist_nextrabits)
|
|
||||||
local dist = dist_base + dist_extrabits
|
|
||||||
|
|
||||||
--debug('BACK', len, dist)
|
|
||||||
for i=1,len do
|
|
||||||
local pos = (outstate.window_pos - 1 - dist) % 32768 + 1 -- 32K
|
|
||||||
output(outstate, assert(outstate.window[pos], 'invalid distance'))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function parse_block(bs, outstate)
|
|
||||||
local bfinal = bs:read(1)
|
|
||||||
local btype = bs:read(2)
|
|
||||||
|
|
||||||
local BTYPE_NO_COMPRESSION = 0
|
|
||||||
local BTYPE_FIXED_HUFFMAN = 1
|
|
||||||
local BTYPE_DYNAMIC_HUFFMAN = 2
|
|
||||||
local BTYPE_RESERVED_ = 3
|
|
||||||
|
|
||||||
if DEBUG then
|
|
||||||
debug('bfinal=', bfinal)
|
|
||||||
debug('btype=', btype)
|
|
||||||
end
|
|
||||||
|
|
||||||
if btype == BTYPE_NO_COMPRESSION then
|
|
||||||
bs:read(bs:nbits_left_in_byte())
|
|
||||||
local len = bs:read(16)
|
|
||||||
local nlen_ = noeof(bs:read(16))
|
|
||||||
|
|
||||||
for i=1,len do
|
|
||||||
local by = noeof(bs:read(8))
|
|
||||||
output(outstate, by)
|
|
||||||
end
|
|
||||||
elseif btype == BTYPE_FIXED_HUFFMAN or btype == BTYPE_DYNAMIC_HUFFMAN then
|
|
||||||
local littable, disttable
|
|
||||||
if btype == BTYPE_DYNAMIC_HUFFMAN then
|
|
||||||
littable, disttable = parse_huffmantables(bs)
|
|
||||||
else
|
|
||||||
littable = HuffmanTable {0,8, 144,9, 256,7, 280,8, 288,nil}
|
|
||||||
disttable = HuffmanTable {0,5, 32,nil}
|
|
||||||
end
|
|
||||||
|
|
||||||
repeat
|
|
||||||
local is_done = parse_compressed_item(
|
|
||||||
bs, outstate, littable, disttable)
|
|
||||||
until is_done
|
|
||||||
else
|
|
||||||
runtime_error 'unrecognized compression type'
|
|
||||||
end
|
|
||||||
|
|
||||||
return bfinal ~= 0
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function M.inflate(t)
|
|
||||||
local bs = get_bitstream(t.input)
|
|
||||||
local outbs = get_obytestream(t.output)
|
|
||||||
local outstate = make_outstate(outbs)
|
|
||||||
|
|
||||||
repeat
|
|
||||||
local is_final = parse_block(bs, outstate)
|
|
||||||
until is_final
|
|
||||||
end
|
|
||||||
local inflate = M.inflate
|
|
||||||
|
|
||||||
|
|
||||||
function M.gunzip(t)
|
|
||||||
local bs = get_bitstream(t.input)
|
|
||||||
local outbs = get_obytestream(t.output)
|
|
||||||
local disable_crc = t.disable_crc
|
|
||||||
if disable_crc == nil then disable_crc = false end
|
|
||||||
|
|
||||||
parse_gzip_header(bs)
|
|
||||||
|
|
||||||
local data_crc32 = 0
|
|
||||||
|
|
||||||
inflate{input=bs, output=
|
|
||||||
disable_crc and outbs or
|
|
||||||
function(byte)
|
|
||||||
data_crc32 = crc32(byte, data_crc32)
|
|
||||||
outbs(byte)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
bs:read(bs:nbits_left_in_byte())
|
|
||||||
|
|
||||||
local expected_crc32 = bs:read(32)
|
|
||||||
local isize = bs:read(32) -- ignored
|
|
||||||
if DEBUG then
|
|
||||||
debug('crc32=', expected_crc32)
|
|
||||||
debug('isize=', isize)
|
|
||||||
end
|
|
||||||
if not disable_crc and data_crc32 then
|
|
||||||
if data_crc32 ~= expected_crc32 then
|
|
||||||
runtime_error('invalid compressed data--crc error')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if bs:read() then
|
|
||||||
warn 'trailing garbage ignored'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function M.adler32(byte, crc)
|
|
||||||
local s1 = crc % 65536
|
|
||||||
local s2 = (crc - s1) / 65536
|
|
||||||
s1 = (s1 + byte) % 65521
|
|
||||||
s2 = (s2 + s1) % 65521
|
|
||||||
return s2*65536 + s1
|
|
||||||
end -- 65521 is the largest prime smaller than 2^16
|
|
||||||
|
|
||||||
|
|
||||||
function M.inflate_zlib(t)
|
|
||||||
local bs = get_bitstream(t.input)
|
|
||||||
local outbs = get_obytestream(t.output)
|
|
||||||
local disable_crc = t.disable_crc
|
|
||||||
if disable_crc == nil then disable_crc = false end
|
|
||||||
|
|
||||||
local window_size_ = parse_zlib_header(bs)
|
|
||||||
|
|
||||||
local data_adler32 = 1
|
|
||||||
|
|
||||||
inflate{input=bs, output=
|
|
||||||
disable_crc and outbs or
|
|
||||||
function(byte)
|
|
||||||
data_adler32 = M.adler32(byte, data_adler32)
|
|
||||||
outbs(byte)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
bs:read(bs:nbits_left_in_byte())
|
|
||||||
|
|
||||||
local b3 = bs:read(8)
|
|
||||||
local b2 = bs:read(8)
|
|
||||||
local b1 = bs:read(8)
|
|
||||||
local b0 = bs:read(8)
|
|
||||||
local expected_adler32 = ((b3*256 + b2)*256 + b1)*256 + b0
|
|
||||||
if DEBUG then
|
|
||||||
debug('alder32=', expected_adler32)
|
|
||||||
end
|
|
||||||
if not disable_crc then
|
|
||||||
if data_adler32 ~= expected_adler32 then
|
|
||||||
runtime_error('invalid compressed data--crc error')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if bs:read() then
|
|
||||||
warn 'trailing garbage ignored'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
return M
|
|
||||||
104
apis/itemDB.lua
104
apis/itemDB.lua
@@ -1,104 +0,0 @@
|
|||||||
local nameDB = require('nameDB')
|
|
||||||
local TableDB = require('tableDB')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
local itemDB = TableDB({ fileName = 'usr/config/items.db' })
|
|
||||||
|
|
||||||
local function splitKey(key, item)
|
|
||||||
|
|
||||||
item = item or { }
|
|
||||||
|
|
||||||
local t = Util.split(key, '(.-):')
|
|
||||||
if #t[#t] > 8 then
|
|
||||||
item.nbtHash = table.remove(t)
|
|
||||||
end
|
|
||||||
item.damage = tonumber(table.remove(t))
|
|
||||||
item.name = table.concat(t, ':')
|
|
||||||
|
|
||||||
return item
|
|
||||||
end
|
|
||||||
|
|
||||||
function itemDB:get(key)
|
|
||||||
|
|
||||||
local item = TableDB.get(self, key)
|
|
||||||
|
|
||||||
if item then
|
|
||||||
return item
|
|
||||||
end
|
|
||||||
|
|
||||||
if key[2] ~= 0 then
|
|
||||||
item = TableDB.get(self, { key[1], 0, key[3] })
|
|
||||||
if item and item.maxDamage > 0 then
|
|
||||||
item = Util.shallowCopy(item)
|
|
||||||
item.damage = key[2]
|
|
||||||
item.displayName = string.format('%s (damage: %d)', item.displayName, item.damage)
|
|
||||||
return item
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function itemDB:add(key, item)
|
|
||||||
|
|
||||||
if item.maxDamage > 0 then
|
|
||||||
key = { key[1], 0, key[3] }
|
|
||||||
end
|
|
||||||
TableDB.add(self, key, item)
|
|
||||||
end
|
|
||||||
|
|
||||||
function itemDB:makeKey(item)
|
|
||||||
return { item.name, item.damage, item.nbtHash }
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Accepts: "minecraft:stick:0" or { name = 'minecraft:stick', damage = 0 }
|
|
||||||
function itemDB:getName(item)
|
|
||||||
|
|
||||||
if type(item) == 'string' then
|
|
||||||
item = splitKey(item)
|
|
||||||
end
|
|
||||||
|
|
||||||
local detail = self:get(self:makeKey(item))
|
|
||||||
if detail then
|
|
||||||
return detail.displayName
|
|
||||||
end
|
|
||||||
|
|
||||||
-- fallback to nameDB
|
|
||||||
return nameDB:getName(item.name .. ':' .. item.damage)
|
|
||||||
end
|
|
||||||
|
|
||||||
function itemDB:load()
|
|
||||||
|
|
||||||
TableDB.load(self)
|
|
||||||
|
|
||||||
for key,item in pairs(self.data) do
|
|
||||||
splitKey(key, item)
|
|
||||||
item.maxDamage = item.maxDamage or 0
|
|
||||||
item.maxCount = item.maxCount or 64
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function itemDB:flush()
|
|
||||||
if self.dirty then
|
|
||||||
|
|
||||||
local t = { }
|
|
||||||
for k,v in pairs(self.data) do
|
|
||||||
v = Util.shallowCopy(v)
|
|
||||||
v.name = nil
|
|
||||||
v.damage = nil
|
|
||||||
v.nbtHash = nil
|
|
||||||
if v.maxDamage == 0 then
|
|
||||||
v.maxDamage = nil
|
|
||||||
end
|
|
||||||
if v.maxCount == 64 then
|
|
||||||
v.maxCount = nil
|
|
||||||
end
|
|
||||||
t[k] = v
|
|
||||||
end
|
|
||||||
|
|
||||||
Util.writeTable(self.fileName, t)
|
|
||||||
self.dirty = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
itemDB:load()
|
|
||||||
|
|
||||||
return itemDB
|
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
local class = require('class')
|
|
||||||
local itemDB = require('itemDB')
|
|
||||||
local Peripheral = require('peripheral')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
local MEAdapter = class()
|
|
||||||
|
|
||||||
local convertNames = {
|
|
||||||
name = 'id',
|
|
||||||
damage = 'dmg',
|
|
||||||
maxCount = 'max_size',
|
|
||||||
count = 'qty',
|
|
||||||
displayName = 'display_name',
|
|
||||||
maxDamage = 'max_dmg',
|
|
||||||
}
|
|
||||||
local keys = {
|
|
||||||
'damage',
|
|
||||||
'displayName',
|
|
||||||
'maxCount',
|
|
||||||
'maxDamage',
|
|
||||||
'name',
|
|
||||||
'nbtHash',
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Strip off color prefix
|
|
||||||
local function safeString(text)
|
|
||||||
|
|
||||||
local val = text:byte(1)
|
|
||||||
|
|
||||||
if val < 32 or val > 128 then
|
|
||||||
|
|
||||||
local newText = {}
|
|
||||||
for i = 4, #text do
|
|
||||||
local val = text:byte(i)
|
|
||||||
newText[i - 3] = (val > 31 and val < 127) and val or 63
|
|
||||||
end
|
|
||||||
return string.char(unpack(newText))
|
|
||||||
end
|
|
||||||
|
|
||||||
return text
|
|
||||||
end
|
|
||||||
|
|
||||||
local function convertItem(item)
|
|
||||||
for k,v in pairs(convertNames) do
|
|
||||||
item[k] = item[v]
|
|
||||||
item[v] = nil
|
|
||||||
end
|
|
||||||
item.displayName = safeString(item.displayName)
|
|
||||||
end
|
|
||||||
|
|
||||||
function MEAdapter:init(args)
|
|
||||||
local defaults = {
|
|
||||||
items = { },
|
|
||||||
name = 'ME',
|
|
||||||
jobList = { },
|
|
||||||
direction = 'up',
|
|
||||||
wrapSide = 'bottom',
|
|
||||||
auto = false,
|
|
||||||
}
|
|
||||||
Util.merge(self, defaults)
|
|
||||||
Util.merge(self, args)
|
|
||||||
|
|
||||||
if self.auto then
|
|
||||||
local mep = Peripheral.getByMethod('getAvailableItems')
|
|
||||||
if mep then
|
|
||||||
Util.merge(self, mep)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local mep = peripheral.wrap(self.wrapSide)
|
|
||||||
if mep then
|
|
||||||
Util.merge(self, mep)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function MEAdapter:isValid()
|
|
||||||
return self.getAvailableItems and self.getAvailableItems()
|
|
||||||
end
|
|
||||||
|
|
||||||
function MEAdapter:refresh()
|
|
||||||
|
|
||||||
self.items = self.getAvailableItems('all')
|
|
||||||
for _,v in pairs(self.items) do
|
|
||||||
Util.merge(v, v.item)
|
|
||||||
convertItem(v)
|
|
||||||
|
|
||||||
local key = { v.name, v.damage, v.nbtHash }
|
|
||||||
if not itemDB:get(key) then
|
|
||||||
local t = { }
|
|
||||||
for _,k in pairs(keys) do
|
|
||||||
t[k] = v[k]
|
|
||||||
end
|
|
||||||
itemDB:add(key, t)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
itemDB:flush()
|
|
||||||
|
|
||||||
return self.items
|
|
||||||
end
|
|
||||||
|
|
||||||
function MEAdapter:listItems()
|
|
||||||
self:refresh()
|
|
||||||
return self.items
|
|
||||||
end
|
|
||||||
|
|
||||||
function MEAdapter:getItemInfo(item)
|
|
||||||
for key,i in pairs(self.items) do
|
|
||||||
if item.name == i.name and item.damage == i.damage and item.nbtHash == i.nbtHash then
|
|
||||||
return i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function MEAdapter:isCPUAvailable()
|
|
||||||
local cpus = self.getCraftingCPUs() or { }
|
|
||||||
local available = false
|
|
||||||
|
|
||||||
for cpu,v in pairs(cpus) do
|
|
||||||
if not v.busy then
|
|
||||||
available = true
|
|
||||||
elseif not self.jobList[cpu] then -- something else is crafting something (don't know what)
|
|
||||||
return false -- return false since we are in an unknown state
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return available
|
|
||||||
end
|
|
||||||
|
|
||||||
function MEAdapter:craft(item, count)
|
|
||||||
|
|
||||||
if not self:isCPUAvailable() then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
self:refresh()
|
|
||||||
|
|
||||||
local item = self:getItemInfo(item)
|
|
||||||
if item and item.is_craftable then
|
|
||||||
|
|
||||||
local cpus = self.getCraftingCPUs() or { }
|
|
||||||
for cpu,v in pairs(cpus) do
|
|
||||||
if not v.busy then
|
|
||||||
self.requestCrafting({
|
|
||||||
id = item.name,
|
|
||||||
dmg = item.damage,
|
|
||||||
nbt_hash = item.nbtHash,
|
|
||||||
},
|
|
||||||
count or 1,
|
|
||||||
cpu
|
|
||||||
)
|
|
||||||
|
|
||||||
os.sleep(0) -- tell it to craft, yet it doesn't show busy - try waiting a cycle...
|
|
||||||
cpus = self.getCraftingCPUs() or { }
|
|
||||||
if not cpus[cpu].busy then
|
|
||||||
-- print('sleeping again')
|
|
||||||
os.sleep(.1) -- sigh
|
|
||||||
cpus = self.getCraftingCPUs() or { }
|
|
||||||
end
|
|
||||||
|
|
||||||
-- not working :(
|
|
||||||
if cpus[cpu].busy then
|
|
||||||
self.jobList[cpu] = {
|
|
||||||
name = item.name,
|
|
||||||
damage = item.damage,
|
|
||||||
nbtHash = item.nbtHash,
|
|
||||||
count = count,
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
break -- only need to try the first available cpu
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function MEAdapter:getJobList()
|
|
||||||
local cpus = self.getCraftingCPUs() or { }
|
|
||||||
for cpu,v in pairs(cpus) do
|
|
||||||
if not v.busy then
|
|
||||||
self.jobList[cpu] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return self.jobList
|
|
||||||
end
|
|
||||||
|
|
||||||
function MEAdapter:isCrafting(item)
|
|
||||||
for _,v in pairs(self:getJobList()) do
|
|
||||||
if v.name == item.name and
|
|
||||||
v.damage == item.damage and
|
|
||||||
v.nbtHash == item.nbtHash then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function MEAdapter:craftItems(items)
|
|
||||||
local cpus = self.getCraftingCPUs() or { }
|
|
||||||
local count = 0
|
|
||||||
|
|
||||||
for _,cpu in pairs(cpus) do
|
|
||||||
if cpu.busy then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for _,item in pairs(items) do
|
|
||||||
if count >= #cpus then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
if not self:isCrafting(item) then
|
|
||||||
if self:craft(item, item.count) then
|
|
||||||
count = count + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function MEAdapter:provide(item, count, slot, direction)
|
|
||||||
return pcall(function()
|
|
||||||
while count > 0 do
|
|
||||||
local qty = math.min(count, 64)
|
|
||||||
local s, m = self.exportItem({
|
|
||||||
id = item.name,
|
|
||||||
dmg = item.damage
|
|
||||||
}, direction or self.direction, qty, slot)
|
|
||||||
|
|
||||||
if not s or s.size ~= qty then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
count = count - 64
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function MEAdapter:insert(slot, count)
|
|
||||||
local s, m = pcall(function() self.pullItem(self.direction, slot, count) end)
|
|
||||||
if not s and m then
|
|
||||||
print('MEAdapter:pullItem')
|
|
||||||
print(m)
|
|
||||||
sleep(1)
|
|
||||||
s, m = pcall(function() self.pullItem(self.direction, slot, count) end)
|
|
||||||
if not s and m then
|
|
||||||
print('MEAdapter:pullItem')
|
|
||||||
print(m)
|
|
||||||
read()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return MEAdapter
|
|
||||||
106
apis/message.lua
106
apis/message.lua
@@ -1,106 +0,0 @@
|
|||||||
local Event = require('event')
|
|
||||||
local Logger = require('logger')
|
|
||||||
|
|
||||||
local Message = { }
|
|
||||||
|
|
||||||
local messageHandlers = {}
|
|
||||||
|
|
||||||
function Message.enable()
|
|
||||||
if not device.wireless_modem.isOpen(os.getComputerID()) then
|
|
||||||
device.wireless_modem.open(os.getComputerID())
|
|
||||||
end
|
|
||||||
if not device.wireless_modem.isOpen(60000) then
|
|
||||||
device.wireless_modem.open(60000)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if device and device.wireless_modem then
|
|
||||||
Message.enable()
|
|
||||||
end
|
|
||||||
|
|
||||||
Event.on('device_attach', function(event, deviceName)
|
|
||||||
if deviceName == 'wireless_modem' then
|
|
||||||
Message.enable()
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
function Message.addHandler(type, f)
|
|
||||||
table.insert(messageHandlers, {
|
|
||||||
type = type,
|
|
||||||
f = f,
|
|
||||||
enabled = true
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
function Message.removeHandler(h)
|
|
||||||
for k,v in pairs(messageHandlers) do
|
|
||||||
if v == h then
|
|
||||||
messageHandlers[k] = nil
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Event.on('modem_message',
|
|
||||||
function(event, side, sendChannel, replyChannel, msg, distance)
|
|
||||||
if msg and msg.type then -- filter out messages from other systems
|
|
||||||
local id = replyChannel
|
|
||||||
Logger.log('modem_receive', { id, msg.type })
|
|
||||||
--Logger.log('modem_receive', msg.contents)
|
|
||||||
for k,h in pairs(messageHandlers) do
|
|
||||||
if h.type == msg.type then
|
|
||||||
-- should provide msg.contents instead of message - type is already known
|
|
||||||
h.f(h, id, msg, distance)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
)
|
|
||||||
|
|
||||||
function Message.send(id, msgType, contents)
|
|
||||||
if not device.wireless_modem then
|
|
||||||
error('No modem attached', 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
if id then
|
|
||||||
Logger.log('modem_send', { tostring(id), msgType })
|
|
||||||
device.wireless_modem.transmit(id, os.getComputerID(), {
|
|
||||||
type = msgType, contents = contents
|
|
||||||
})
|
|
||||||
else
|
|
||||||
Logger.log('modem_send', { 'broadcast', msgType })
|
|
||||||
device.wireless_modem.transmit(60000, os.getComputerID(), {
|
|
||||||
type = msgType, contents = contents
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Message.broadcast(t, contents)
|
|
||||||
if not device.wireless_modem then
|
|
||||||
error('No modem attached', 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
Message.send(nil, t, contents)
|
|
||||||
-- Logger.log('rednet_send', { 'broadcast', t })
|
|
||||||
-- rednet.broadcast({ type = t, contents = contents })
|
|
||||||
end
|
|
||||||
|
|
||||||
function Message.waitForMessage(msgType, timeout, fromId)
|
|
||||||
local timerId = os.startTimer(timeout)
|
|
||||||
repeat
|
|
||||||
local e, side, _id, id, msg, distance = os.pullEvent()
|
|
||||||
if e == 'modem_message' then
|
|
||||||
if msg and msg.type and msg.type == msgType then
|
|
||||||
if not fromId or id == fromId then
|
|
||||||
return e, id, msg, distance
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
until e == 'timer' and side == timerId
|
|
||||||
end
|
|
||||||
|
|
||||||
function Message.enableWirelessLogging()
|
|
||||||
Logger.setWirelessLogging()
|
|
||||||
end
|
|
||||||
|
|
||||||
return Message
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
local JSON = require('json')
|
|
||||||
local TableDB = require('tableDB')
|
|
||||||
|
|
||||||
local nameDB = TableDB()
|
|
||||||
|
|
||||||
function nameDB:load()
|
|
||||||
|
|
||||||
local blocks = JSON.decodeFromFile('usr/etc/blocks.json')
|
|
||||||
|
|
||||||
if not blocks then
|
|
||||||
error('Unable to read usr/etc/blocks.json')
|
|
||||||
end
|
|
||||||
|
|
||||||
for strId, block in pairs(blocks) do
|
|
||||||
local strId = 'minecraft:' .. strId
|
|
||||||
if type(block.name) == 'string' then
|
|
||||||
self.data[strId .. ':0'] = block.name
|
|
||||||
else
|
|
||||||
for nid,name in pairs(block.name) do
|
|
||||||
self.data[strId .. ':' .. (nid-1)] = name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function nameDB:getName(strId)
|
|
||||||
return self.data[strId] or strId
|
|
||||||
end
|
|
||||||
|
|
||||||
nameDB:load()
|
|
||||||
|
|
||||||
return nameDB
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
local class = require('class')
|
|
||||||
local Util = require('util')
|
|
||||||
local Peripheral = require('peripheral')
|
|
||||||
local itemDB = require('itemDB')
|
|
||||||
|
|
||||||
local RefinedAdapter = class()
|
|
||||||
|
|
||||||
local keys = {
|
|
||||||
'damage',
|
|
||||||
'displayName',
|
|
||||||
'maxCount',
|
|
||||||
'maxDamage',
|
|
||||||
'name',
|
|
||||||
'nbtHash',
|
|
||||||
}
|
|
||||||
|
|
||||||
function RefinedAdapter:init(args)
|
|
||||||
local defaults = {
|
|
||||||
items = { },
|
|
||||||
name = 'refinedStorage',
|
|
||||||
}
|
|
||||||
Util.merge(self, defaults)
|
|
||||||
Util.merge(self, args)
|
|
||||||
|
|
||||||
local controller = Peripheral.getByType('refinedstorage:controller')
|
|
||||||
if controller then
|
|
||||||
Util.merge(self, controller)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function RefinedAdapter:isValid()
|
|
||||||
return not not self.listAvailableItems
|
|
||||||
end
|
|
||||||
|
|
||||||
function RefinedAdapter:isOnline()
|
|
||||||
return self.getNetworkEnergyStored() > 0
|
|
||||||
end
|
|
||||||
|
|
||||||
function RefinedAdapter:getCachedItemDetails(item)
|
|
||||||
local key = { item.name, item.damage, item.nbtHash }
|
|
||||||
|
|
||||||
local detail = itemDB:get(key)
|
|
||||||
if not detail then
|
|
||||||
detail = self.findItem(item)
|
|
||||||
if detail then
|
|
||||||
local meta
|
|
||||||
pcall(function() meta = detail.getMetadata() end)
|
|
||||||
if not meta then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
Util.merge(detail, meta)
|
|
||||||
|
|
||||||
local t = { }
|
|
||||||
for _,k in pairs(keys) do
|
|
||||||
t[k] = detail[k]
|
|
||||||
end
|
|
||||||
|
|
||||||
detail = t
|
|
||||||
itemDB:add(key, detail)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if detail then
|
|
||||||
return Util.shallowCopy(detail)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function RefinedAdapter:listItems()
|
|
||||||
local items = { }
|
|
||||||
local list
|
|
||||||
|
|
||||||
pcall(function()
|
|
||||||
list = self.listAvailableItems()
|
|
||||||
end)
|
|
||||||
|
|
||||||
if list then
|
|
||||||
|
|
||||||
local throttle = Util.throttle()
|
|
||||||
|
|
||||||
for _,v in pairs(list) do
|
|
||||||
local item = self:getCachedItemDetails(v)
|
|
||||||
if item then
|
|
||||||
item.count = v.count
|
|
||||||
table.insert(items, item)
|
|
||||||
end
|
|
||||||
throttle()
|
|
||||||
end
|
|
||||||
itemDB:flush()
|
|
||||||
end
|
|
||||||
|
|
||||||
return items
|
|
||||||
end
|
|
||||||
|
|
||||||
function RefinedAdapter:getItemInfo(fingerprint)
|
|
||||||
|
|
||||||
local key = { fingerprint.name, fingerprint.damage, fingerprint.nbtHash }
|
|
||||||
|
|
||||||
local item = itemDB:get(key)
|
|
||||||
if not item then
|
|
||||||
return self:getCachedItemDetails(fingerprint)
|
|
||||||
end
|
|
||||||
|
|
||||||
local detail = self.findItem(item)
|
|
||||||
if detail then
|
|
||||||
item.count = detail.count
|
|
||||||
return item
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function RefinedAdapter:isCrafting(item)
|
|
||||||
for _,task in pairs(self.getCraftingTasks()) do
|
|
||||||
local output = task.getPattern().outputs[1]
|
|
||||||
if output.name == item.name and
|
|
||||||
output.damage == item.damage and
|
|
||||||
output.nbtHash == item.nbtHash then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function RefinedAdapter:craft(item, qty)
|
|
||||||
local detail = self.findItem(item)
|
|
||||||
if detail then
|
|
||||||
return detail.craft(qty)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function RefinedAdapter:craftItems(items)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function RefinedAdapter:provide(item, qty, slot)
|
|
||||||
end
|
|
||||||
|
|
||||||
function RefinedAdapter:extract(slot, qty)
|
|
||||||
-- self.pushItems(self.direction, slot, qty)
|
|
||||||
end
|
|
||||||
|
|
||||||
function RefinedAdapter:insert(slot, qty)
|
|
||||||
-- self.pullItems(self.direction, slot, qty)
|
|
||||||
end
|
|
||||||
|
|
||||||
return RefinedAdapter
|
|
||||||
1175
apis/schematic.lua
1175
apis/schematic.lua
File diff suppressed because it is too large
Load Diff
@@ -1,49 +0,0 @@
|
|||||||
local class = require('class')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
local TableDB = class()
|
|
||||||
function TableDB:init(args)
|
|
||||||
local defaults = {
|
|
||||||
fileName = '',
|
|
||||||
dirty = false,
|
|
||||||
data = { },
|
|
||||||
}
|
|
||||||
Util.merge(defaults, args)
|
|
||||||
Util.merge(self, defaults)
|
|
||||||
end
|
|
||||||
|
|
||||||
function TableDB:load()
|
|
||||||
local t = Util.readTable(self.fileName)
|
|
||||||
if t then
|
|
||||||
self.data = t.data or t
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function TableDB:add(key, entry)
|
|
||||||
if type(key) == 'table' then
|
|
||||||
key = table.concat(key, ':')
|
|
||||||
end
|
|
||||||
self.data[key] = entry
|
|
||||||
self.dirty = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function TableDB:get(key)
|
|
||||||
if type(key) == 'table' then
|
|
||||||
key = table.concat(key, ':')
|
|
||||||
end
|
|
||||||
return self.data[key]
|
|
||||||
end
|
|
||||||
|
|
||||||
function TableDB:remove(key)
|
|
||||||
self.data[key] = nil
|
|
||||||
self.dirty = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function TableDB:flush()
|
|
||||||
if self.dirty then
|
|
||||||
Util.writeTable(self.fileName, self.data)
|
|
||||||
self.dirty = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return TableDB
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
local itemDB = require('itemDB')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
local Craft = { }
|
|
||||||
|
|
||||||
local function clearGrid(inventoryAdapter)
|
|
||||||
for i = 1, 16 do
|
|
||||||
local count = turtle.getItemCount(i)
|
|
||||||
if count > 0 then
|
|
||||||
inventoryAdapter:insert(i, count)
|
|
||||||
if turtle.getItemCount(i) ~= 0 then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function splitKey(key)
|
|
||||||
local t = Util.split(key, '(.-):')
|
|
||||||
local item = { }
|
|
||||||
if #t[#t] > 8 then
|
|
||||||
item.nbtHash = table.remove(t)
|
|
||||||
end
|
|
||||||
item.damage = tonumber(table.remove(t))
|
|
||||||
item.name = table.concat(t, ':')
|
|
||||||
return item
|
|
||||||
end
|
|
||||||
|
|
||||||
local function getItemCount(items, key)
|
|
||||||
local item = splitKey(key)
|
|
||||||
for _,v in pairs(items) do
|
|
||||||
if v.name == item.name and
|
|
||||||
v.damage == item.damage and
|
|
||||||
v.nbtHash == item.nbtHash then
|
|
||||||
return v.count
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
|
|
||||||
local function turtleCraft(recipe, qty, inventoryAdapter)
|
|
||||||
|
|
||||||
clearGrid(inventoryAdapter)
|
|
||||||
|
|
||||||
for k,v in pairs(recipe.ingredients) do
|
|
||||||
local item = splitKey(v)
|
|
||||||
inventoryAdapter:provide(item, qty, k)
|
|
||||||
if turtle.getItemCount(k) == 0 then -- ~= qty then
|
|
||||||
-- FIX: ingredients cannot be stacked
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return turtle.craft()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Craft.craftRecipe(recipe, count, inventoryAdapter)
|
|
||||||
|
|
||||||
local items = inventoryAdapter:listItems()
|
|
||||||
|
|
||||||
local function sumItems(items)
|
|
||||||
-- produces { ['minecraft:planks:0'] = 8 }
|
|
||||||
local t = {}
|
|
||||||
for _,item in pairs(items) do
|
|
||||||
t[item] = (t[item] or 0) + 1
|
|
||||||
end
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
count = math.ceil(count / recipe.count)
|
|
||||||
|
|
||||||
local maxCount = recipe.maxCount or math.floor(64 / recipe.count)
|
|
||||||
local summedItems = sumItems(recipe.ingredients)
|
|
||||||
|
|
||||||
for key,icount in pairs(summedItems) do
|
|
||||||
local itemCount = getItemCount(items, key)
|
|
||||||
if itemCount < icount * count then
|
|
||||||
local irecipe = Craft.recipes[key]
|
|
||||||
if irecipe then
|
|
||||||
--Util.print('Crafting %d %s', icount * count - itemCount, key)
|
|
||||||
if not Craft.craftRecipe(irecipe,
|
|
||||||
icount * count - itemCount,
|
|
||||||
inventoryAdapter) then
|
|
||||||
turtle.select(1)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
repeat
|
|
||||||
if not turtleCraft(recipe, math.min(count, maxCount), inventoryAdapter) then
|
|
||||||
turtle.select(1)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
count = count - maxCount
|
|
||||||
until count <= 0
|
|
||||||
|
|
||||||
turtle.select(1)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
-- given a certain quantity, return how many of those can be crafted
|
|
||||||
function Craft.getCraftableAmount(recipe, count, items, missing)
|
|
||||||
|
|
||||||
local function sumItems(recipe, items, summedItems, count)
|
|
||||||
|
|
||||||
local canCraft = 0
|
|
||||||
|
|
||||||
for i = 1, count do
|
|
||||||
for _,item in pairs(recipe.ingredients) do
|
|
||||||
local summedItem = summedItems[item] or getItemCount(items, item)
|
|
||||||
|
|
||||||
local irecipe = Craft.recipes[item]
|
|
||||||
if irecipe and summedItem <= 0 then
|
|
||||||
summedItem = summedItem + sumItems(irecipe, items, summedItems, 1)
|
|
||||||
end
|
|
||||||
if summedItem <= 0 then
|
|
||||||
if missing then
|
|
||||||
missing.name = item
|
|
||||||
end
|
|
||||||
return canCraft
|
|
||||||
end
|
|
||||||
summedItems[item] = summedItem - 1
|
|
||||||
end
|
|
||||||
canCraft = canCraft + recipe.count
|
|
||||||
end
|
|
||||||
|
|
||||||
return canCraft
|
|
||||||
end
|
|
||||||
|
|
||||||
return sumItems(recipe, items, { }, math.ceil(count / recipe.count), missing)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Craft.canCraft(item, count, items)
|
|
||||||
return Craft.getCraftableAmount(Craft.recipes[item], count, items) == count
|
|
||||||
end
|
|
||||||
|
|
||||||
function Craft.setRecipes(recipes)
|
|
||||||
Craft.recipes = recipes
|
|
||||||
end
|
|
||||||
|
|
||||||
function Craft.getCraftableAmountTest()
|
|
||||||
local results = { }
|
|
||||||
Craft.setRecipes(Util.readTable('usr/etc/recipes.db'))
|
|
||||||
|
|
||||||
local items = {
|
|
||||||
{ name = 'minecraft:planks', damage = 0, count = 5 },
|
|
||||||
{ name = 'minecraft:log', damage = 0, count = 2 },
|
|
||||||
}
|
|
||||||
results[1] = { item = 'chest', expected = 1, got = Craft.getCraftableAmount(Craft.recipes['minecraft:chest:0'], 2, items) }
|
|
||||||
|
|
||||||
items = {
|
|
||||||
{ name = 'minecraft:log', damage = 0, count = 1 },
|
|
||||||
{ name = 'minecraft:coal', damage = 1, count = 1 },
|
|
||||||
}
|
|
||||||
results[2] = { item = 'torch', expected = 4, got = Craft.getCraftableAmount(Craft.recipes['minecraft:torch:0'], 4, items) }
|
|
||||||
|
|
||||||
return results
|
|
||||||
end
|
|
||||||
|
|
||||||
function Craft.craftRecipeTest(name, count)
|
|
||||||
local ChestAdapter = require('chestAdapter18')
|
|
||||||
local chestAdapter = ChestAdapter({ wrapSide = 'top', direction = 'down' })
|
|
||||||
Craft.setRecipes(Util.readTable('usr/etc/recipes.db'))
|
|
||||||
return { Craft.craftRecipe(Craft.recipes[name], count, chestAdapter) }
|
|
||||||
end
|
|
||||||
|
|
||||||
return Craft
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
local Point = require('point')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
local checkedNodes = { }
|
|
||||||
local nodes = { }
|
|
||||||
local box = { }
|
|
||||||
local oldCallback
|
|
||||||
|
|
||||||
local function toKey(pt)
|
|
||||||
return table.concat({ pt.x, pt.y, pt.z }, ':')
|
|
||||||
end
|
|
||||||
|
|
||||||
local function addNode(node)
|
|
||||||
|
|
||||||
for i = 0, 5 do
|
|
||||||
local hi = turtle.getHeadingInfo(i)
|
|
||||||
local testNode = { x = node.x + hi.xd, y = node.y + hi.yd, z = node.z + hi.zd }
|
|
||||||
|
|
||||||
if Point.inBox(testNode, box) then
|
|
||||||
local key = toKey(testNode)
|
|
||||||
if not checkedNodes[key] then
|
|
||||||
nodes[key] = testNode
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function dig(action)
|
|
||||||
|
|
||||||
local directions = {
|
|
||||||
top = 'up',
|
|
||||||
bottom = 'down',
|
|
||||||
}
|
|
||||||
|
|
||||||
-- convert to up, down, north, south, east, west
|
|
||||||
local direction = directions[action.side] or
|
|
||||||
turtle.getHeadingInfo(turtle.point.heading).direction
|
|
||||||
|
|
||||||
local hi = turtle.getHeadingInfo(direction)
|
|
||||||
local node = { x = turtle.point.x + hi.xd, y = turtle.point.y + hi.yd, z = turtle.point.z + hi.zd }
|
|
||||||
|
|
||||||
if Point.inBox(node, box) then
|
|
||||||
|
|
||||||
local key = toKey(node)
|
|
||||||
checkedNodes[key] = true
|
|
||||||
nodes[key] = nil
|
|
||||||
|
|
||||||
if action.dig() then
|
|
||||||
addNode(node)
|
|
||||||
repeat until not action.dig() -- sand, etc
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function move(action)
|
|
||||||
if action == 'turn' then
|
|
||||||
dig(turtle.getAction('forward'))
|
|
||||||
elseif action == 'up' then
|
|
||||||
dig(turtle.getAction('up'))
|
|
||||||
dig(turtle.getAction('forward'))
|
|
||||||
elseif action == 'down' then
|
|
||||||
dig(turtle.getAction('down'))
|
|
||||||
dig(turtle.getAction('forward'))
|
|
||||||
elseif action == 'back' then
|
|
||||||
dig(turtle.getAction('up'))
|
|
||||||
dig(turtle.getAction('down'))
|
|
||||||
end
|
|
||||||
|
|
||||||
if oldCallback then
|
|
||||||
oldCallback(action)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- find the closest block
|
|
||||||
-- * favor same plane
|
|
||||||
-- * going backwards only if the dest is above or below
|
|
||||||
function closestPoint(reference, pts)
|
|
||||||
local lpt, lm -- lowest
|
|
||||||
for _,pt in pairs(pts) do
|
|
||||||
local m = Point.turtleDistance(reference, pt)
|
|
||||||
local h = Point.calculateHeading(reference, pt)
|
|
||||||
local t = Point.calculateTurns(reference.heading, h)
|
|
||||||
if pt.y ~= reference.y then -- try and stay on same plane
|
|
||||||
m = m + .01
|
|
||||||
end
|
|
||||||
if t ~= 2 or pt.y == reference.y then
|
|
||||||
m = m + t
|
|
||||||
if t > 0 then
|
|
||||||
m = m + .01
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not lm or m < lm then
|
|
||||||
lpt = pt
|
|
||||||
lm = m
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return lpt
|
|
||||||
end
|
|
||||||
|
|
||||||
local function getAdjacentPoint(pt)
|
|
||||||
local t = { }
|
|
||||||
table.insert(t, pt)
|
|
||||||
for i = 0, 5 do
|
|
||||||
local hi = turtle.getHeadingInfo(i)
|
|
||||||
local heading
|
|
||||||
if i < 4 then
|
|
||||||
heading = (hi.heading + 2) % 4
|
|
||||||
end
|
|
||||||
table.insert(t, { x = pt.x + hi.xd, z = pt.z + hi.zd, y = pt.y + hi.yd, heading = heading })
|
|
||||||
end
|
|
||||||
|
|
||||||
return closestPoint(turtle.getPoint(), t)
|
|
||||||
end
|
|
||||||
|
|
||||||
return function(startPt, endPt, firstPt, verbose)
|
|
||||||
|
|
||||||
checkedNodes = { }
|
|
||||||
nodes = { }
|
|
||||||
box = { }
|
|
||||||
|
|
||||||
box.x = math.min(startPt.x, endPt.x)
|
|
||||||
box.y = math.min(startPt.y, endPt.y)
|
|
||||||
box.z = math.min(startPt.z, endPt.z)
|
|
||||||
box.ex = math.max(startPt.x, endPt.x)
|
|
||||||
box.ey = math.max(startPt.y, endPt.y)
|
|
||||||
box.ez = math.max(startPt.z, endPt.z)
|
|
||||||
|
|
||||||
if not turtle.pathfind(firstPt) then
|
|
||||||
error('failed to reach starting point')
|
|
||||||
end
|
|
||||||
|
|
||||||
turtle.setPolicy("attack", { dig = dig }, "assuredMove")
|
|
||||||
|
|
||||||
oldCallback = turtle.getMoveCallback()
|
|
||||||
turtle.setMoveCallback(move)
|
|
||||||
|
|
||||||
repeat
|
|
||||||
local key = toKey(turtle.point)
|
|
||||||
|
|
||||||
checkedNodes[key] = true
|
|
||||||
nodes[key] = nil
|
|
||||||
|
|
||||||
dig(turtle.getAction('down'))
|
|
||||||
dig(turtle.getAction('up'))
|
|
||||||
dig(turtle.getAction('forward'))
|
|
||||||
|
|
||||||
if verbose then
|
|
||||||
print(string.format('%d nodes remaining', Util.size(nodes)))
|
|
||||||
end
|
|
||||||
|
|
||||||
if not next(nodes) then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
local node = closestPoint(turtle.point, nodes)
|
|
||||||
node = getAdjacentPoint(node)
|
|
||||||
if not turtle.gotoPoint(node) then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
until turtle.abort
|
|
||||||
|
|
||||||
turtle.resetState()
|
|
||||||
turtle.setMoveCallback(oldCallback)
|
|
||||||
end
|
|
||||||
@@ -1,418 +0,0 @@
|
|||||||
requireInjector(getfenv(1))
|
|
||||||
|
|
||||||
local Ansi = require('ansi')
|
|
||||||
local Config = require('config')
|
|
||||||
local SHA1 = require('sha1')
|
|
||||||
local UI = require('ui')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
-- scrap this entire file. don't muck with standard apis
|
|
||||||
|
|
||||||
local REGISTRY_DIR = 'usr/.registry'
|
|
||||||
|
|
||||||
|
|
||||||
-- FIX SOMEDAY
|
|
||||||
function os.registerApp(app, key)
|
|
||||||
|
|
||||||
app.key = SHA1.sha1(key)
|
|
||||||
Util.writeTable(fs.combine(REGISTRY_DIR, app.key), app)
|
|
||||||
os.queueEvent('os_register_app')
|
|
||||||
end
|
|
||||||
|
|
||||||
function os.unregisterApp(key)
|
|
||||||
|
|
||||||
local filename = fs.combine(REGISTRY_DIR, SHA1.sha1(key))
|
|
||||||
if fs.exists(filename) then
|
|
||||||
fs.delete(filename)
|
|
||||||
os.queueEvent('os_register_app')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local sandboxEnv = Util.shallowCopy(getfenv(1))
|
|
||||||
setmetatable(sandboxEnv, { __index = _G })
|
|
||||||
|
|
||||||
multishell.setTitle(multishell.getCurrent(), 'App Store')
|
|
||||||
UI:configure('Appstore', ...)
|
|
||||||
|
|
||||||
local APP_DIR = 'usr/apps'
|
|
||||||
|
|
||||||
local sources = {
|
|
||||||
|
|
||||||
{ text = "STD Default",
|
|
||||||
event = 'source',
|
|
||||||
url = "http://pastebin.com/raw/zVws7eLq" }, --stock
|
|
||||||
--[[
|
|
||||||
{ text = "Discover",
|
|
||||||
event = 'source',
|
|
||||||
generateName = true,
|
|
||||||
url = "http://pastebin.com/raw/9bXfCz6M" }, --owned by dannysmc95
|
|
||||||
|
|
||||||
{ text = "Opus",
|
|
||||||
event = 'source',
|
|
||||||
url = "http://pastebin.com/raw/ajQ91Rmn" },
|
|
||||||
]]
|
|
||||||
}
|
|
||||||
|
|
||||||
shell.setDir(APP_DIR)
|
|
||||||
|
|
||||||
function downloadApp(app)
|
|
||||||
local h
|
|
||||||
|
|
||||||
if type(app.url) == "table" then
|
|
||||||
h = contextualGet(app.url[1])
|
|
||||||
else
|
|
||||||
h = http.get(app.url)
|
|
||||||
end
|
|
||||||
|
|
||||||
if h then
|
|
||||||
local contents = h.readAll()
|
|
||||||
h:close()
|
|
||||||
return contents
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function runApp(app, checkExists, ...)
|
|
||||||
|
|
||||||
local path, fn
|
|
||||||
local args = { ... }
|
|
||||||
|
|
||||||
if checkExists and fs.exists(fs.combine(APP_DIR, app.name)) then
|
|
||||||
path = fs.combine(APP_DIR, app.name)
|
|
||||||
else
|
|
||||||
local program = downloadApp(app)
|
|
||||||
|
|
||||||
fn = function()
|
|
||||||
|
|
||||||
if not program then
|
|
||||||
error('Failed to download')
|
|
||||||
end
|
|
||||||
|
|
||||||
local fn = loadstring(program, app.name)
|
|
||||||
|
|
||||||
if not fn then
|
|
||||||
error('Failed to download')
|
|
||||||
end
|
|
||||||
|
|
||||||
setfenv(fn, sandboxEnv)
|
|
||||||
fn(unpack(args))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
multishell.openTab({
|
|
||||||
title = app.name,
|
|
||||||
env = sandboxEnv,
|
|
||||||
path = path,
|
|
||||||
fn = fn,
|
|
||||||
focused = true,
|
|
||||||
})
|
|
||||||
|
|
||||||
return true, 'Running program'
|
|
||||||
end
|
|
||||||
|
|
||||||
local installApp = function(app)
|
|
||||||
|
|
||||||
local program = downloadApp(app)
|
|
||||||
if not program then
|
|
||||||
return false, "Failed to download"
|
|
||||||
end
|
|
||||||
|
|
||||||
local fullPath = fs.combine(APP_DIR, app.name)
|
|
||||||
Util.writeFile(fullPath, program)
|
|
||||||
return true, 'Installed as ' .. fullPath
|
|
||||||
end
|
|
||||||
|
|
||||||
local viewApp = function(app)
|
|
||||||
|
|
||||||
local program = downloadApp(app)
|
|
||||||
if not program then
|
|
||||||
return false, "Failed to download"
|
|
||||||
end
|
|
||||||
|
|
||||||
Util.writeFile('/.source', program)
|
|
||||||
shell.openForegroundTab('edit /.source')
|
|
||||||
fs.delete('/.source')
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local getSourceListing = function(source)
|
|
||||||
local contents = http.get(source.url)
|
|
||||||
if contents then
|
|
||||||
|
|
||||||
local fn = loadstring(contents.readAll(), source.text)
|
|
||||||
contents.close()
|
|
||||||
|
|
||||||
local env = { std = { } }
|
|
||||||
setmetatable(env, { __index = _G })
|
|
||||||
setfenv(fn, env)
|
|
||||||
fn()
|
|
||||||
|
|
||||||
if env.contextualGet then
|
|
||||||
contextualGet = env.contextualGet
|
|
||||||
end
|
|
||||||
|
|
||||||
source.storeURLs = env.std.storeURLs
|
|
||||||
source.storeCatagoryNames = env.std.storeCatagoryNames
|
|
||||||
|
|
||||||
if source.storeURLs and source.storeCatagoryNames then
|
|
||||||
for k,v in pairs(source.storeURLs) do
|
|
||||||
if source.generateName then
|
|
||||||
v.name = v.title:match('(%w+)')
|
|
||||||
if not v.name or #v.name == 0 then
|
|
||||||
v.name = tostring(k)
|
|
||||||
else
|
|
||||||
v.name = v.name:lower()
|
|
||||||
end
|
|
||||||
else
|
|
||||||
v.name = k
|
|
||||||
end
|
|
||||||
v.categoryName = source.storeCatagoryNames[v.catagory]
|
|
||||||
v.ltitle = v.title:lower()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local appPage = UI.Page({
|
|
||||||
menuBar = UI.MenuBar({
|
|
||||||
showBackButton = not pocket,
|
|
||||||
buttons = {
|
|
||||||
{ text = 'Install', event = 'install' },
|
|
||||||
{ text = 'Run', event = 'run' },
|
|
||||||
{ text = 'View', event = 'view' },
|
|
||||||
{ text = 'Remove', event = 'uninstall', name = 'removeButton' },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
container = UI.Window({
|
|
||||||
x = 2,
|
|
||||||
y = 3,
|
|
||||||
height = UI.term.height - 3,
|
|
||||||
width = UI.term.width - 2,
|
|
||||||
viewport = UI.ViewportWindow(),
|
|
||||||
}),
|
|
||||||
notification = UI.Notification(),
|
|
||||||
accelerators = {
|
|
||||||
q = 'back',
|
|
||||||
backspace = 'back',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
function appPage.container.viewport:draw()
|
|
||||||
local app = self.parent.parent.app
|
|
||||||
local str = string.format(
|
|
||||||
'%s \nBy: %s \nCategory: %s\nFile name: %s\n\n%s',
|
|
||||||
Ansi.yellow .. app.title .. Ansi.reset,
|
|
||||||
app.creator,
|
|
||||||
app.categoryName, app.name,
|
|
||||||
Ansi.yellow .. app.description .. Ansi.reset)
|
|
||||||
|
|
||||||
self:clear()
|
|
||||||
self:setCursorPos(1, 1)
|
|
||||||
self:print(str)
|
|
||||||
self.ymax = self.cursorY
|
|
||||||
|
|
||||||
if appPage.notification.enabled then
|
|
||||||
appPage.notification:draw()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function appPage:enable(source, app)
|
|
||||||
self.source = source
|
|
||||||
self.app = app
|
|
||||||
UI.Page.enable(self)
|
|
||||||
|
|
||||||
self.container.viewport:setScrollPosition(0)
|
|
||||||
if fs.exists(fs.combine(APP_DIR, app.name)) then
|
|
||||||
self.menuBar.removeButton:enable('Remove')
|
|
||||||
else
|
|
||||||
self.menuBar.removeButton:disable('Remove')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function appPage:eventHandler(event)
|
|
||||||
if event.type == 'back' then
|
|
||||||
UI:setPreviousPage()
|
|
||||||
|
|
||||||
elseif event.type == 'run' then
|
|
||||||
self.notification:info('Running program', 3)
|
|
||||||
self:sync()
|
|
||||||
runApp(self.app, true)
|
|
||||||
|
|
||||||
elseif event.type == 'view' then
|
|
||||||
self.notification:info('Downloading program', 3)
|
|
||||||
self:sync()
|
|
||||||
viewApp(self.app)
|
|
||||||
|
|
||||||
elseif event.type == 'uninstall' then
|
|
||||||
if self.app.runOnly then
|
|
||||||
s,m = runApp(self.app, false, 'uninstall')
|
|
||||||
else
|
|
||||||
fs.delete(fs.combine(APP_DIR, self.app.name))
|
|
||||||
self.notification:success("Uninstalled " .. self.app.name, 3)
|
|
||||||
self:focusFirst(self)
|
|
||||||
self.menuBar.removeButton:disable('Remove')
|
|
||||||
self.menuBar:draw()
|
|
||||||
|
|
||||||
os.unregisterApp(self.app.creator .. '.' .. self.app.name)
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif event.type == 'install' then
|
|
||||||
self.notification:info("Installing", 3)
|
|
||||||
self:sync()
|
|
||||||
local s, m
|
|
||||||
if self.app.runOnly then
|
|
||||||
s,m = runApp(self.app, false)
|
|
||||||
else
|
|
||||||
s,m = installApp(self.app)
|
|
||||||
end
|
|
||||||
if s then
|
|
||||||
self.notification:success(m, 3)
|
|
||||||
|
|
||||||
if not self.app.runOnly then
|
|
||||||
self.menuBar.removeButton:enable('Remove')
|
|
||||||
self.menuBar:draw()
|
|
||||||
|
|
||||||
local category = 'Apps'
|
|
||||||
if self.app.catagoryName == 'Game' then
|
|
||||||
category = 'Games'
|
|
||||||
end
|
|
||||||
|
|
||||||
os.registerApp({
|
|
||||||
run = fs.combine(APP_DIR, self.app.name),
|
|
||||||
title = self.app.title,
|
|
||||||
category = category,
|
|
||||||
icon = self.app.icon,
|
|
||||||
}, self.app.creator .. '.' .. self.app.name)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self.notification:error(m, 3)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return UI.Page.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local categoryPage = UI.Page({
|
|
||||||
menuBar = UI.MenuBar({
|
|
||||||
buttons = {
|
|
||||||
{ text = 'Catalog', event = 'dropdown', dropdown = 'sourceMenu' },
|
|
||||||
{ text = 'Category', event = 'dropdown', dropdown = 'categoryMenu' },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
sourceMenu = UI.DropMenu({
|
|
||||||
buttons = sources,
|
|
||||||
}),
|
|
||||||
grid = UI.ScrollingGrid({
|
|
||||||
y = 2,
|
|
||||||
height = UI.term.height - 2,
|
|
||||||
columns = {
|
|
||||||
{ heading = 'Title', key = 'title' },
|
|
||||||
},
|
|
||||||
sortColumn = 'title',
|
|
||||||
autospace = true,
|
|
||||||
}),
|
|
||||||
statusBar = UI.StatusBar(),
|
|
||||||
accelerators = {
|
|
||||||
l = 'lua',
|
|
||||||
q = 'quit',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
function categoryPage:setCategory(source, name, index)
|
|
||||||
self.grid.values = { }
|
|
||||||
for k,v in pairs(source.storeURLs) do
|
|
||||||
if index == 0 or index == v.catagory then
|
|
||||||
table.insert(self.grid.values, v)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self.statusBar:setStatus(string.format('%s: %s', source.text, name))
|
|
||||||
self.grid:update()
|
|
||||||
self.grid:setIndex(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
function categoryPage:setSource(source)
|
|
||||||
|
|
||||||
if not source.categoryMenu then
|
|
||||||
|
|
||||||
self.statusBar:setStatus('Loading...')
|
|
||||||
self.statusBar:draw()
|
|
||||||
self:sync()
|
|
||||||
|
|
||||||
getSourceListing(source)
|
|
||||||
|
|
||||||
if not source.storeURLs then
|
|
||||||
error('Unable to download application list')
|
|
||||||
end
|
|
||||||
|
|
||||||
local buttons = { }
|
|
||||||
for k,v in Util.spairs(source.storeCatagoryNames,
|
|
||||||
function(a, b) return a:lower() < b:lower() end) do
|
|
||||||
|
|
||||||
if v ~= 'Operating System' then
|
|
||||||
table.insert(buttons, {
|
|
||||||
text = v,
|
|
||||||
event = 'category',
|
|
||||||
index = k,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
source.categoryMenu = UI.DropMenu({
|
|
||||||
y = 2,
|
|
||||||
x = 1,
|
|
||||||
buttons = buttons,
|
|
||||||
})
|
|
||||||
source.index, source.name = Util.first(source.storeCatagoryNames)
|
|
||||||
|
|
||||||
categoryPage:add({
|
|
||||||
categoryMenu = source.categoryMenu
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
self.source = source
|
|
||||||
self.categoryMenu = source.categoryMenu
|
|
||||||
categoryPage:setCategory(source, source.name, source.index)
|
|
||||||
end
|
|
||||||
|
|
||||||
function categoryPage.grid:sortCompare(a, b)
|
|
||||||
return a.ltitle < b.ltitle
|
|
||||||
end
|
|
||||||
|
|
||||||
function categoryPage.grid:getRowTextColor(row, selected)
|
|
||||||
if fs.exists(fs.combine(APP_DIR, row.name)) then
|
|
||||||
return colors.orange
|
|
||||||
end
|
|
||||||
return UI.Grid:getRowTextColor(row, selected)
|
|
||||||
end
|
|
||||||
|
|
||||||
function categoryPage:eventHandler(event)
|
|
||||||
|
|
||||||
if event.type == 'grid_select' or event.type == 'select' then
|
|
||||||
UI:setPage(appPage, self.source, self.grid:getSelected())
|
|
||||||
|
|
||||||
elseif event.type == 'category' then
|
|
||||||
self:setCategory(self.source, event.button.text, event.button.index)
|
|
||||||
self:setFocus(self.grid)
|
|
||||||
self:draw()
|
|
||||||
|
|
||||||
elseif event.type == 'source' then
|
|
||||||
self:setFocus(self.grid)
|
|
||||||
self:setSource(event.button)
|
|
||||||
self:draw()
|
|
||||||
|
|
||||||
elseif event.type == 'quit' then
|
|
||||||
UI:exitPullEvents()
|
|
||||||
|
|
||||||
else
|
|
||||||
return UI.Page.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
print("Retrieving catalog list")
|
|
||||||
categoryPage:setSource(sources[1])
|
|
||||||
|
|
||||||
UI:setPage(categoryPage)
|
|
||||||
UI:pullEvents()
|
|
||||||
UI.term:reset()
|
|
||||||
137
apps/Events.lua
137
apps/Events.lua
@@ -1,137 +0,0 @@
|
|||||||
requireInjector(getfenv(1))
|
|
||||||
|
|
||||||
local Event = require('event')
|
|
||||||
local UI = require('ui')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
multishell.setTitle(multishell.getCurrent(), 'Events')
|
|
||||||
UI:configure('Events', ...)
|
|
||||||
|
|
||||||
local page = UI.Page {
|
|
||||||
menuBar = UI.MenuBar {
|
|
||||||
buttons = {
|
|
||||||
{ text = 'Filter', event = 'filter' },
|
|
||||||
{ text = 'Reset', event = 'reset' },
|
|
||||||
{ text = 'Pause ', event = 'toggle', name = 'pauseButton' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
grid = UI.Grid {
|
|
||||||
y = 2,
|
|
||||||
columns = {
|
|
||||||
{ key = 'event' },
|
|
||||||
{ key = 'p1' },
|
|
||||||
{ key = 'p2' },
|
|
||||||
{ key = 'p3' },
|
|
||||||
{ key = 'p4' },
|
|
||||||
{ key = 'p5' },
|
|
||||||
},
|
|
||||||
autospace = true,
|
|
||||||
disableHeader = true,
|
|
||||||
},
|
|
||||||
accelerators = {
|
|
||||||
f = 'filter',
|
|
||||||
p = 'toggle',
|
|
||||||
r = 'reset',
|
|
||||||
c = 'clear',
|
|
||||||
q = 'quit',
|
|
||||||
},
|
|
||||||
filtered = { },
|
|
||||||
}
|
|
||||||
|
|
||||||
function page:eventHandler(event)
|
|
||||||
|
|
||||||
if event.type == 'filter' then
|
|
||||||
local entry = self.grid:getSelected()
|
|
||||||
self.filtered[entry.event] = true
|
|
||||||
|
|
||||||
elseif event.type == 'toggle' then
|
|
||||||
self.paused = not self.paused
|
|
||||||
if self.paused then
|
|
||||||
self.menuBar.pauseButton.text = 'Resume'
|
|
||||||
else
|
|
||||||
self.menuBar.pauseButton.text = 'Pause '
|
|
||||||
end
|
|
||||||
self.menuBar:draw()
|
|
||||||
|
|
||||||
elseif event.type == 'grid_select' then
|
|
||||||
multishell.openTab({
|
|
||||||
path = 'sys/apps/Lua.lua',
|
|
||||||
args = { event.selected },
|
|
||||||
focused = true,
|
|
||||||
})
|
|
||||||
|
|
||||||
elseif event.type == 'reset' then
|
|
||||||
self.filtered = { }
|
|
||||||
self.grid:setValues({ })
|
|
||||||
self.grid:draw()
|
|
||||||
if self.paused then
|
|
||||||
self:emit({ type = 'toggle' })
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif event.type == 'clear' then
|
|
||||||
self.grid:setValues({ })
|
|
||||||
self.grid:draw()
|
|
||||||
|
|
||||||
elseif event.type == 'quit' then
|
|
||||||
UI:exitPullEvents()
|
|
||||||
|
|
||||||
elseif event.type == 'focus_change' then
|
|
||||||
if event.focused == self.grid then
|
|
||||||
if not self.paused then
|
|
||||||
self:emit({ type = 'toggle' })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
else
|
|
||||||
return UI.Page.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.grid:getDisplayValues(row)
|
|
||||||
row = Util.shallowCopy(row)
|
|
||||||
|
|
||||||
local function tovalue(s)
|
|
||||||
if type(s) == 'table' then
|
|
||||||
return 'table'
|
|
||||||
end
|
|
||||||
return s
|
|
||||||
end
|
|
||||||
|
|
||||||
for k,v in pairs(row) do
|
|
||||||
row[k] = tovalue(v)
|
|
||||||
end
|
|
||||||
|
|
||||||
return row
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.grid:draw()
|
|
||||||
self:adjustWidth()
|
|
||||||
UI.Grid.draw(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
Event.addRoutine(function()
|
|
||||||
|
|
||||||
while true do
|
|
||||||
local e = { os.pullEvent() }
|
|
||||||
if not page.paused and not page.filtered[e[1]] then
|
|
||||||
table.insert(page.grid.values, 1, {
|
|
||||||
event = e[1],
|
|
||||||
p1 = e[2],
|
|
||||||
p2 = e[3],
|
|
||||||
p3 = e[4],
|
|
||||||
p4 = e[5],
|
|
||||||
p5 = e[6],
|
|
||||||
})
|
|
||||||
if #page.grid.values > page.grid.height - 1 then
|
|
||||||
table.remove(page.grid.values, #page.grid.values)
|
|
||||||
end
|
|
||||||
page.grid:update()
|
|
||||||
page.grid:draw()
|
|
||||||
page:sync()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
UI:setPage(page)
|
|
||||||
UI:pullEvents()
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
requireInjector(getfenv(1))
|
|
||||||
|
|
||||||
local Event = require('event')
|
|
||||||
local UI = require('ui')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
multishell.setTitle(multishell.getCurrent(), 'Devices')
|
|
||||||
|
|
||||||
--[[ -- PeripheralsPage -- ]] --
|
|
||||||
local peripheralsPage = UI.Page {
|
|
||||||
grid = UI.ScrollingGrid {
|
|
||||||
columns = {
|
|
||||||
{ heading = 'Type', key = 'type' },
|
|
||||||
{ heading = 'Side', key = 'side' },
|
|
||||||
},
|
|
||||||
sortColumn = 'type',
|
|
||||||
height = UI.term.height - 1,
|
|
||||||
autospace = true,
|
|
||||||
},
|
|
||||||
statusBar = UI.StatusBar {
|
|
||||||
status = 'Select peripheral'
|
|
||||||
},
|
|
||||||
accelerators = {
|
|
||||||
q = 'quit',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
function peripheralsPage.grid:draw()
|
|
||||||
local sides = peripheral.getNames()
|
|
||||||
|
|
||||||
Util.clear(self.values)
|
|
||||||
for _,side in pairs(sides) do
|
|
||||||
table.insert(self.values, {
|
|
||||||
type = peripheral.getType(side),
|
|
||||||
side = side
|
|
||||||
})
|
|
||||||
end
|
|
||||||
self:update()
|
|
||||||
self:adjustWidth()
|
|
||||||
UI.Grid.draw(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function peripheralsPage:updatePeripherals()
|
|
||||||
if UI:getCurrentPage() == self then
|
|
||||||
self.grid:draw()
|
|
||||||
self:sync()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function peripheralsPage:eventHandler(event)
|
|
||||||
if event.type == 'quit' then
|
|
||||||
Event.exitPullEvents()
|
|
||||||
|
|
||||||
elseif event.type == 'grid_select' then
|
|
||||||
UI:setPage('methods', event.selected)
|
|
||||||
|
|
||||||
end
|
|
||||||
return UI.Page.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[ -- MethodsPage -- ]] --
|
|
||||||
local methodsPage = UI.Page {
|
|
||||||
grid = UI.ScrollingGrid {
|
|
||||||
columns = {
|
|
||||||
{ heading = 'Name', key = 'name', width = UI.term.width }
|
|
||||||
},
|
|
||||||
sortColumn = 'name',
|
|
||||||
height = 7,
|
|
||||||
},
|
|
||||||
viewportConsole = UI.ViewportWindow {
|
|
||||||
y = 8,
|
|
||||||
height = UI.term.height - 8,
|
|
||||||
},
|
|
||||||
statusBar = UI.StatusBar {
|
|
||||||
status = 'q to return',
|
|
||||||
},
|
|
||||||
accelerators = {
|
|
||||||
q = 'back',
|
|
||||||
backspace = 'back',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
function methodsPage:enable(p)
|
|
||||||
|
|
||||||
self.peripheral = p or self.peripheral
|
|
||||||
|
|
||||||
local p = peripheral.wrap(self.peripheral.side)
|
|
||||||
if p.getDocs then
|
|
||||||
self.grid.values = { }
|
|
||||||
for k,v in pairs(p.getDocs()) do
|
|
||||||
table.insert(self.grid.values, {
|
|
||||||
name = k,
|
|
||||||
doc = v,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
elseif not p.getAdvancedMethodsData then
|
|
||||||
self.grid.values = { }
|
|
||||||
for name,f in pairs(p) do
|
|
||||||
table.insert(self.grid.values, {
|
|
||||||
name = name,
|
|
||||||
noext = true,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self.grid.values = p.getAdvancedMethodsData()
|
|
||||||
for name,f in pairs(self.grid.values) do
|
|
||||||
f.name = name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self.viewportConsole.offy = 0
|
|
||||||
|
|
||||||
self.grid:update()
|
|
||||||
self.grid:setIndex(1)
|
|
||||||
|
|
||||||
self.statusBar:setStatus(self.peripheral.type)
|
|
||||||
UI.Page.enable(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function methodsPage:eventHandler(event)
|
|
||||||
if event.type == 'back' then
|
|
||||||
UI:setPage(peripheralsPage)
|
|
||||||
return true
|
|
||||||
elseif event.type == 'grid_focus_row' then
|
|
||||||
self.viewportConsole.offy = 0
|
|
||||||
self.viewportConsole:draw()
|
|
||||||
end
|
|
||||||
return UI.Page.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
|
|
||||||
function methodsPage.viewportConsole:draw()
|
|
||||||
local c = self
|
|
||||||
local method = methodsPage.grid:getSelected()
|
|
||||||
|
|
||||||
c:clear()
|
|
||||||
c:setCursorPos(1, 1)
|
|
||||||
|
|
||||||
if method.noext then
|
|
||||||
c.cursorY = 2
|
|
||||||
c:print('No extended Information')
|
|
||||||
return 2
|
|
||||||
end
|
|
||||||
|
|
||||||
if method.doc then
|
|
||||||
c:print(method.doc, nil, colors.yellow)
|
|
||||||
c.ymax = c.cursorY + 1
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if method.description then
|
|
||||||
c:print(method.description)
|
|
||||||
end
|
|
||||||
|
|
||||||
c.cursorY = c.cursorY + 2
|
|
||||||
c.cursorX = 1
|
|
||||||
|
|
||||||
if method.returnTypes ~= '()' then
|
|
||||||
c:print(method.returnTypes .. ' ', nil, colors.yellow)
|
|
||||||
end
|
|
||||||
c:print(method.name, nil, colors.black)
|
|
||||||
c:print('(')
|
|
||||||
|
|
||||||
local maxArgLen = 1
|
|
||||||
|
|
||||||
for k,arg in ipairs(method.args) do
|
|
||||||
if #arg.description > 0 then
|
|
||||||
maxArgLen = math.max(#arg.name, maxArgLen)
|
|
||||||
end
|
|
||||||
local argName = arg.name
|
|
||||||
local fg = colors.green
|
|
||||||
if arg.optional then
|
|
||||||
argName = string.format('[%s]', arg.name)
|
|
||||||
fg = colors.orange
|
|
||||||
end
|
|
||||||
c:print(argName, nil, fg)
|
|
||||||
if k < #method.args then
|
|
||||||
c:print(', ')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
c:print(')')
|
|
||||||
|
|
||||||
c.cursorY = c.cursorY + 1
|
|
||||||
|
|
||||||
if #method.args > 0 then
|
|
||||||
for _,arg in ipairs(method.args) do
|
|
||||||
if #arg.description > 0 then
|
|
||||||
c.cursorY = c.cursorY + 1
|
|
||||||
c.cursorX = 1
|
|
||||||
local fg = colors.green
|
|
||||||
if arg.optional then
|
|
||||||
fg = colors.orange
|
|
||||||
end
|
|
||||||
c:print(arg.name .. ': ', nil, fg)
|
|
||||||
c.cursorX = maxArgLen + 3
|
|
||||||
c:print(arg.description, nil, nil, maxArgLen + 3)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
c.ymax = c.cursorY + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
Event.on('peripheral', function()
|
|
||||||
peripheralsPage:updatePeripherals()
|
|
||||||
end)
|
|
||||||
|
|
||||||
Event.on('peripheral_detach', function()
|
|
||||||
peripheralsPage:updatePeripherals()
|
|
||||||
end)
|
|
||||||
|
|
||||||
UI:setPage(peripheralsPage)
|
|
||||||
|
|
||||||
UI:setPages({
|
|
||||||
methods = methodsPage,
|
|
||||||
})
|
|
||||||
|
|
||||||
UI:pullEvents()
|
|
||||||
391
apps/Turtles.lua
391
apps/Turtles.lua
@@ -1,391 +0,0 @@
|
|||||||
requireInjector(getfenv(1))
|
|
||||||
|
|
||||||
local Config = require('config')
|
|
||||||
local Event = require('event')
|
|
||||||
local itemDB = require('itemDB')
|
|
||||||
local Socket = require('socket')
|
|
||||||
local Terminal = require('terminal')
|
|
||||||
local UI = require('ui')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
multishell.setTitle(multishell.getCurrent(), 'Turtles')
|
|
||||||
UI.Button.defaults.focusIndicator = ' '
|
|
||||||
UI:configure('Turtles', ...)
|
|
||||||
|
|
||||||
local config = { }
|
|
||||||
Config.load('Turtles', config)
|
|
||||||
|
|
||||||
local options = {
|
|
||||||
turtle = { arg = 'i', type = 'number', value = config.id or -1,
|
|
||||||
desc = 'Turtle ID' },
|
|
||||||
tab = { arg = 's', type = 'string', value = config.tab or 'Sel',
|
|
||||||
desc = 'Selected tab to display' },
|
|
||||||
help = { arg = 'h', type = 'flag', value = false,
|
|
||||||
desc = 'Displays the options' },
|
|
||||||
}
|
|
||||||
|
|
||||||
local SCRIPTS_PATH = 'usr/etc/scripts'
|
|
||||||
|
|
||||||
local nullTerm = Terminal.getNullTerm(term.current())
|
|
||||||
local turtles = { }
|
|
||||||
local socket
|
|
||||||
local policies = {
|
|
||||||
{ label = 'none' },
|
|
||||||
{ label = 'digOnly' },
|
|
||||||
{ label = 'attackOnly' },
|
|
||||||
{ label = 'digAttack' },
|
|
||||||
{ label = 'turtleSafe' },
|
|
||||||
}
|
|
||||||
|
|
||||||
local page = UI.Page {
|
|
||||||
coords = UI.Window {
|
|
||||||
backgroundColor = colors.black,
|
|
||||||
height = 4,
|
|
||||||
},
|
|
||||||
tabs = UI.Tabs {
|
|
||||||
x = 1, y = 5, ey = -2,
|
|
||||||
scripts = UI.Grid {
|
|
||||||
tabTitle = 'Run',
|
|
||||||
backgroundColor = UI.TabBar.defaults.selectedBackgroundColor,
|
|
||||||
columns = {
|
|
||||||
{ heading = '', key = 'label' },
|
|
||||||
},
|
|
||||||
disableHeader = true,
|
|
||||||
sortColumn = 'label',
|
|
||||||
autospace = true,
|
|
||||||
},
|
|
||||||
turtles = UI.Grid {
|
|
||||||
tabTitle = 'Select',
|
|
||||||
backgroundColor = UI.TabBar.defaults.selectedBackgroundColor,
|
|
||||||
columns = {
|
|
||||||
{ heading = 'label', key = 'label' },
|
|
||||||
{ heading = 'Dist', key = 'distance' },
|
|
||||||
{ heading = 'Status', key = 'status' },
|
|
||||||
{ heading = 'Fuel', key = 'fuel' },
|
|
||||||
},
|
|
||||||
disableHeader = true,
|
|
||||||
sortColumn = 'label',
|
|
||||||
autospace = true,
|
|
||||||
},
|
|
||||||
inventory = UI.Grid {
|
|
||||||
backgroundColor = UI.TabBar.defaults.selectedBackgroundColor,
|
|
||||||
tabTitle = 'Inv',
|
|
||||||
columns = {
|
|
||||||
{ heading = '', key = 'index', width = 2 },
|
|
||||||
{ heading = '', key = 'qty', width = 2 },
|
|
||||||
{ heading = 'Inventory', key = 'id', width = UI.term.width - 7 },
|
|
||||||
},
|
|
||||||
disableHeader = true,
|
|
||||||
sortColumn = 'index',
|
|
||||||
},
|
|
||||||
--[[
|
|
||||||
policy = UI.Grid {
|
|
||||||
tabTitle = 'Mod',
|
|
||||||
backgroundColor = UI.TabBar.defaults.selectedBackgroundColor,
|
|
||||||
columns = {
|
|
||||||
{ heading = 'label', key = 'label' },
|
|
||||||
},
|
|
||||||
values = policies,
|
|
||||||
disableHeader = true,
|
|
||||||
sortColumn = 'label',
|
|
||||||
autospace = true,
|
|
||||||
},
|
|
||||||
]]
|
|
||||||
action = UI.Window {
|
|
||||||
tabTitle = 'Action',
|
|
||||||
backgroundColor = UI.TabBar.defaults.selectedBackgroundColor,
|
|
||||||
moveUp = UI.Button {
|
|
||||||
x = 5, y = 2,
|
|
||||||
text = 'up',
|
|
||||||
fn = 'turtle.up',
|
|
||||||
},
|
|
||||||
moveDown = UI.Button {
|
|
||||||
x = 5, y = 4,
|
|
||||||
text = 'dn',
|
|
||||||
fn = 'turtle.down',
|
|
||||||
},
|
|
||||||
moveForward = UI.Button {
|
|
||||||
x = 9, y = 3,
|
|
||||||
text = 'f',
|
|
||||||
fn = 'turtle.forward',
|
|
||||||
},
|
|
||||||
moveBack = UI.Button {
|
|
||||||
x = 2, y = 3,
|
|
||||||
text = 'b',
|
|
||||||
fn = 'turtle.back',
|
|
||||||
},
|
|
||||||
turnLeft = UI.Button {
|
|
||||||
x = 2, y = 6,
|
|
||||||
text = 'lt',
|
|
||||||
fn = 'turtle.turnLeft',
|
|
||||||
},
|
|
||||||
turnRight = UI.Button {
|
|
||||||
x = 8, y = 6,
|
|
||||||
text = 'rt',
|
|
||||||
fn = 'turtle.turnRight',
|
|
||||||
},
|
|
||||||
info = UI.TextArea {
|
|
||||||
x = 2, y = 9
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
statusBar = UI.StatusBar(),
|
|
||||||
notification = UI.Notification(),
|
|
||||||
accelerators = {
|
|
||||||
q = 'quit',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
function page:enable(turtle)
|
|
||||||
self.turtle = turtle
|
|
||||||
UI.Page.enable(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:runFunction(script, nowrap)
|
|
||||||
for i = 1, 2 do
|
|
||||||
if not socket then
|
|
||||||
socket = Socket.connect(self.turtle.id, 161)
|
|
||||||
end
|
|
||||||
|
|
||||||
if socket then
|
|
||||||
if not nowrap then
|
|
||||||
script = 'turtle.run(' .. script .. ')'
|
|
||||||
end
|
|
||||||
if socket:write({ type = 'scriptEx', args = script }) then
|
|
||||||
local t = socket:read(3)
|
|
||||||
if t then
|
|
||||||
return table.unpack(t)
|
|
||||||
end
|
|
||||||
return false, 'Socket timeout'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
socket = nil
|
|
||||||
end
|
|
||||||
self.notification:error('Unable to connect')
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:runScript(scriptName)
|
|
||||||
if self.turtle then
|
|
||||||
self.notification:info('Connecting')
|
|
||||||
self:sync()
|
|
||||||
|
|
||||||
local cmd = string.format('Script %d %s', self.turtle.id, scriptName)
|
|
||||||
local ot = term.redirect(nullTerm)
|
|
||||||
pcall(function() shell.run(cmd) end)
|
|
||||||
term.redirect(ot)
|
|
||||||
self.notification:success('Sent')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.coords:draw()
|
|
||||||
local t = self.parent.turtle
|
|
||||||
self:clear()
|
|
||||||
if t then
|
|
||||||
self:setCursorPos(2, 2)
|
|
||||||
local ind = 'GPS'
|
|
||||||
if not t.point.gps then
|
|
||||||
ind = 'REL'
|
|
||||||
end
|
|
||||||
self:print(string.format('%s : %d,%d,%d\n Fuel: %s\n',
|
|
||||||
ind, t.point.x, t.point.y, t.point.z, Util.toBytes(t.fuel)))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[ Inventory Tab ]]--
|
|
||||||
function page.tabs.inventory:getRowTextColor(row, selected)
|
|
||||||
if page.turtle and row.selected then
|
|
||||||
return colors.yellow
|
|
||||||
end
|
|
||||||
return UI.Grid.getRowTextColor(self, row, selected)
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.tabs.inventory:draw()
|
|
||||||
local t = page.turtle
|
|
||||||
Util.clear(self.values)
|
|
||||||
if t then
|
|
||||||
for _,v in ipairs(t.inventory) do
|
|
||||||
if v.qty > 0 then
|
|
||||||
table.insert(self.values, v)
|
|
||||||
if v.index == t.slotIndex then
|
|
||||||
v.selected = true
|
|
||||||
end
|
|
||||||
if v.id then
|
|
||||||
-- local item = itemDB:get({ v.id, v.dmg })
|
|
||||||
-- if item then
|
|
||||||
-- v.id = item.displayName
|
|
||||||
-- else
|
|
||||||
-- v.id = v.id:gsub('.*:(.*)', '%1')
|
|
||||||
-- end
|
|
||||||
v.id = itemDB:getName(v)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self:adjustWidth()
|
|
||||||
self:update()
|
|
||||||
UI.Grid.draw(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.tabs.inventory:eventHandler(event)
|
|
||||||
if event.type == 'grid_select' then
|
|
||||||
local fn = string.format('turtle.select(%d)', event.selected.index)
|
|
||||||
page:runFunction(fn)
|
|
||||||
else
|
|
||||||
return UI.Grid.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.tabs.scripts:draw()
|
|
||||||
|
|
||||||
Util.clear(self.values)
|
|
||||||
local files = fs.list(SCRIPTS_PATH)
|
|
||||||
for _,path in pairs(files) do
|
|
||||||
table.insert(self.values, { label = path, path = fs.combine(SCRIPTS_PATH, path) })
|
|
||||||
end
|
|
||||||
self:update()
|
|
||||||
UI.Grid.draw(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.tabs.scripts:eventHandler(event)
|
|
||||||
if event.type == 'grid_select' then
|
|
||||||
page:runScript(event.selected.label)
|
|
||||||
else
|
|
||||||
return UI.Grid.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.tabs.turtles:getDisplayValues(row)
|
|
||||||
row = Util.shallowCopy(row)
|
|
||||||
if row.fuel then
|
|
||||||
row.fuel = Util.toBytes(row.fuel)
|
|
||||||
end
|
|
||||||
if row.distance then
|
|
||||||
row.distance = Util.round(row.distance, 1)
|
|
||||||
end
|
|
||||||
return row
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.tabs.turtles:draw()
|
|
||||||
Util.clear(self.values)
|
|
||||||
for _,v in pairs(network) do
|
|
||||||
if v.fuel then
|
|
||||||
table.insert(self.values, v)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self:update()
|
|
||||||
UI.Grid.draw(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.tabs.turtles:eventHandler(event)
|
|
||||||
if event.type == 'grid_select' then
|
|
||||||
page.turtle = event.selected
|
|
||||||
config.id = event.selected.id
|
|
||||||
Config.update('Turtles', config)
|
|
||||||
multishell.setTitle(multishell.getCurrent(), page.turtle.label)
|
|
||||||
if socket then
|
|
||||||
socket:close()
|
|
||||||
socket = nil
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return UI.Grid.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.statusBar:draw()
|
|
||||||
local t = self.parent.turtle
|
|
||||||
if t then
|
|
||||||
local status = string.format('%s [ %s ]', t.status, Util.round(t.distance, 2))
|
|
||||||
self:setStatus(status, true)
|
|
||||||
end
|
|
||||||
UI.StatusBar.draw(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.tabs.tabBar:selectTab(tabTitle)
|
|
||||||
if tabTitle then
|
|
||||||
config.tab = tabTitle
|
|
||||||
Config.update('Turtles', config)
|
|
||||||
return UI.TabBar.selectTab(self, tabTitle)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:showBlocks()
|
|
||||||
|
|
||||||
local script = [[
|
|
||||||
local function inspect(direction)
|
|
||||||
local s,b = turtle['inspect' .. (direction or '')]()
|
|
||||||
if not s then
|
|
||||||
return 'minecraft:air:0'
|
|
||||||
end
|
|
||||||
return string.format('%s:%d', b.name, b.metadata)
|
|
||||||
end
|
|
||||||
|
|
||||||
local bu, bf, bd = inspect('Up'), inspect(), inspect('Down')
|
|
||||||
return string.format('%s\n%s\n%s', bu, bf, bd)
|
|
||||||
]]
|
|
||||||
|
|
||||||
local s, m = self:runFunction(script, true)
|
|
||||||
self.tabs.action.info:setText(s or m)
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:eventHandler(event)
|
|
||||||
if event.type == 'quit' then
|
|
||||||
UI:exitPullEvents()
|
|
||||||
elseif event.type == 'button_press' then
|
|
||||||
if event.button.fn then
|
|
||||||
self:runFunction(event.button.fn, event.button.nowrap)
|
|
||||||
self:showBlocks()
|
|
||||||
elseif event.button.script then
|
|
||||||
self:runScript(event.button.script)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return UI.Page.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:enable()
|
|
||||||
UI.Page.enable(self)
|
|
||||||
-- self.tabs:activateTab(page.tabs.turtles)
|
|
||||||
end
|
|
||||||
|
|
||||||
if not Util.getOptions(options, { ... }, true) then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if options.turtle.value >= 0 then
|
|
||||||
for i = 1, 10 do
|
|
||||||
page.turtle = _G.network[options.turtle.value]
|
|
||||||
if page.turtle then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
os.sleep(1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Event.onInterval(1, function()
|
|
||||||
if page.turtle then
|
|
||||||
local t = _G.network[page.turtle.id]
|
|
||||||
page.turtle = t
|
|
||||||
page:draw()
|
|
||||||
page:sync()
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
UI:setPage(page)
|
|
||||||
|
|
||||||
local lookup = {
|
|
||||||
Run = page.tabs.scripts,
|
|
||||||
Select = page.tabs.turtles,
|
|
||||||
Inv = page.tabs.inventory,
|
|
||||||
-- Mod = page.tabs.policy,
|
|
||||||
Action = page.tabs.action,
|
|
||||||
}
|
|
||||||
|
|
||||||
if lookup[options.tab.value] then
|
|
||||||
page.tabs:activateTab(lookup[options.tab.value])
|
|
||||||
end
|
|
||||||
|
|
||||||
UI:pullEvents()
|
|
||||||
2182
apps/builder.lua
2182
apps/builder.lua
File diff suppressed because it is too large
Load Diff
@@ -1,955 +0,0 @@
|
|||||||
requireInjector(getfenv(1))
|
|
||||||
|
|
||||||
local ChestAdapter = require('chestAdapter18')
|
|
||||||
local Config = require('config')
|
|
||||||
local Craft = require('turtle.craft')
|
|
||||||
local Event = require('event')
|
|
||||||
local itemDB = require('itemDB')
|
|
||||||
local MEAdapater = require('meAdapter')
|
|
||||||
local Peripheral = require('peripheral')
|
|
||||||
local RefinedAdapter = require('refinedAdapter')
|
|
||||||
local Terminal = require('terminal')
|
|
||||||
local UI = require('ui')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
multishell.setTitle(multishell.getCurrent(), 'Resource Manager')
|
|
||||||
|
|
||||||
-- 3 wide monitor (any side of turtle)
|
|
||||||
|
|
||||||
-- Config location is /sys/config/chestManager
|
|
||||||
-- adjust directions in that file if needed
|
|
||||||
|
|
||||||
local config = {
|
|
||||||
trashDirection = 'up', -- trash /chest in relation to chest
|
|
||||||
inventoryDirection = { direction = 'north', wrapSide = 'back' },
|
|
||||||
chestDirection = { direction = 'down', wrapSide = 'top' },
|
|
||||||
}
|
|
||||||
|
|
||||||
Config.load('chestManager', config)
|
|
||||||
|
|
||||||
local inventoryAdapter = ChestAdapter(config.inventoryDirection)
|
|
||||||
local turtleChestAdapter = ChestAdapter(config.chestDirection)
|
|
||||||
local duckAntenna
|
|
||||||
|
|
||||||
local controller = RefinedAdapter()
|
|
||||||
if not controller:isValid() then
|
|
||||||
controller = MEAdapater(config.inventoryDirection)
|
|
||||||
if not controller:isValid() then
|
|
||||||
controller = nil
|
|
||||||
else
|
|
||||||
inventoryAdapter = controller -- ME functions as inventory and crafting
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if device.workbench then
|
|
||||||
|
|
||||||
local oppositeSide = {
|
|
||||||
[ 'left' ] = 'right',
|
|
||||||
[ 'right' ] = 'left',
|
|
||||||
}
|
|
||||||
|
|
||||||
local duckAntennaSide = oppositeSide[device.workbench.side]
|
|
||||||
duckAntenna = peripheral.wrap(duckAntennaSide)
|
|
||||||
if not duckAntenna or not duckAntenna.getAllStacks then
|
|
||||||
duckAntenna = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local RESOURCE_FILE = 'usr/config/resources.db'
|
|
||||||
local RECIPES_FILE = 'usr/etc/recipes.db'
|
|
||||||
|
|
||||||
local craftingPaused = false
|
|
||||||
local canCraft = not not duckAntenna or turtleChestAdapter:isValid()
|
|
||||||
local recipes = Util.readTable(RECIPES_FILE) or { }
|
|
||||||
local jobListGrid
|
|
||||||
local resources
|
|
||||||
|
|
||||||
Craft.setRecipes(recipes)
|
|
||||||
|
|
||||||
local function getItem(items, inItem, ignoreDamage)
|
|
||||||
for _,item in pairs(items) do
|
|
||||||
if item.name == inItem.name then
|
|
||||||
if ignoreDamage then
|
|
||||||
return item
|
|
||||||
elseif item.damage == inItem.damage and item.nbtHash == inItem.nbtHash then
|
|
||||||
return item
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function splitKey(key)
|
|
||||||
local t = Util.split(key, '(.-):')
|
|
||||||
local item = { }
|
|
||||||
if #t[#t] > 8 then
|
|
||||||
item.nbtHash = table.remove(t)
|
|
||||||
end
|
|
||||||
item.damage = tonumber(table.remove(t))
|
|
||||||
item.name = table.concat(t, ':')
|
|
||||||
return item
|
|
||||||
end
|
|
||||||
|
|
||||||
local function getItemQuantity(items, item)
|
|
||||||
item = getItem(items, item)
|
|
||||||
if item then
|
|
||||||
return item.count
|
|
||||||
end
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
|
|
||||||
local function getItemDetails(items, item)
|
|
||||||
local cItem = getItem(items, item)
|
|
||||||
if cItem then
|
|
||||||
return cItem
|
|
||||||
end
|
|
||||||
cItem = itemDB:get(itemDB:makeKey(item))
|
|
||||||
if cItem then
|
|
||||||
return { count = 0, maxCount = cItem.maxCount }
|
|
||||||
end
|
|
||||||
return { count = 0, maxCount = 64 }
|
|
||||||
end
|
|
||||||
|
|
||||||
local function uniqueKey(item)
|
|
||||||
return table.concat({ item.name, item.damage, item.nbtHash }, ':')
|
|
||||||
end
|
|
||||||
|
|
||||||
local function mergeResources(t)
|
|
||||||
for _,v in pairs(resources) do
|
|
||||||
local item = getItem(t, v)
|
|
||||||
if item then
|
|
||||||
Util.merge(item, v)
|
|
||||||
else
|
|
||||||
item = Util.shallowCopy(v)
|
|
||||||
item.count = 0
|
|
||||||
table.insert(t, item)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for k in pairs(recipes) do
|
|
||||||
local v = splitKey(k)
|
|
||||||
local item = getItem(t, v)
|
|
||||||
if not item then
|
|
||||||
item = Util.shallowCopy(v)
|
|
||||||
item.count = 0
|
|
||||||
table.insert(t, item)
|
|
||||||
end
|
|
||||||
item.has_recipe = true
|
|
||||||
end
|
|
||||||
|
|
||||||
for _,v in pairs(t) do
|
|
||||||
if not v.displayName then
|
|
||||||
v.displayName = itemDB:getName(v)
|
|
||||||
end
|
|
||||||
v.lname = v.displayName:lower()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function filterItems(t, filter)
|
|
||||||
if filter then
|
|
||||||
local r = {}
|
|
||||||
filter = filter:lower()
|
|
||||||
for k,v in pairs(t) do
|
|
||||||
if string.find(v.lname, filter) then
|
|
||||||
table.insert(r, v)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return r
|
|
||||||
end
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
local function sumItems3(ingredients, items, summedItems, count)
|
|
||||||
|
|
||||||
local canCraft = 0
|
|
||||||
for _,key in pairs(ingredients) do
|
|
||||||
local item = splitKey(key)
|
|
||||||
local summedItem = summedItems[key]
|
|
||||||
if not summedItem then
|
|
||||||
summedItem = Util.shallowCopy(item)
|
|
||||||
summedItem.recipe = recipes[key]
|
|
||||||
summedItem.count = getItemQuantity(items, summedItem)
|
|
||||||
summedItems[key] = summedItem
|
|
||||||
end
|
|
||||||
summedItem.count = summedItem.count - count
|
|
||||||
if summedItem.recipe and summedItem.count < 0 then
|
|
||||||
local need = math.ceil(-summedItem.count / summedItem.recipe.count)
|
|
||||||
summedItem.count = 0
|
|
||||||
sumItems3(summedItem.recipe.ingredients, items, summedItems, need)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function isGridClear()
|
|
||||||
for i = 1, 16 do
|
|
||||||
if turtle.getItemCount(i) ~= 0 then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function clearGrid()
|
|
||||||
for i = 1, 16 do
|
|
||||||
local count = turtle.getItemCount(i)
|
|
||||||
if count > 0 then
|
|
||||||
inventoryAdapter:insert(i, count)
|
|
||||||
if turtle.getItemCount(i) ~= 0 then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function addCraftingRequest(item, craftList, count)
|
|
||||||
local key = uniqueKey(item)
|
|
||||||
local request = craftList[key]
|
|
||||||
if not craftList[key] then
|
|
||||||
request = { name = item.name, damage = item.damage, nbtHash = nbtHash, count = 0 }
|
|
||||||
request.displayName = itemDB:getName(request)
|
|
||||||
craftList[key] = request
|
|
||||||
end
|
|
||||||
request.count = request.count + count
|
|
||||||
end
|
|
||||||
|
|
||||||
local function craftItem(recipe, items, originalItem, craftList, count)
|
|
||||||
|
|
||||||
if craftingPaused or not canCraft or not isGridClear() then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local missing = { }
|
|
||||||
local toCraft = Craft.getCraftableAmount(recipe, count, items, missing)
|
|
||||||
|
|
||||||
if missing.name then
|
|
||||||
originalItem.status = string.format('%s missing', itemDB:getName(missing.name))
|
|
||||||
originalItem.statusCode = 'missing'
|
|
||||||
end
|
|
||||||
|
|
||||||
if toCraft > 0 then
|
|
||||||
Craft.craftRecipe(recipe, toCraft, inventoryAdapter)
|
|
||||||
clearGrid()
|
|
||||||
items = inventoryAdapter:listItems()
|
|
||||||
end
|
|
||||||
|
|
||||||
count = count - toCraft
|
|
||||||
|
|
||||||
if count > 0 then
|
|
||||||
local summedItems = { }
|
|
||||||
sumItems3(recipe.ingredients, items, summedItems, math.ceil(count / recipe.count))
|
|
||||||
|
|
||||||
for key,ingredient in pairs(summedItems) do
|
|
||||||
if not ingredient.recipe and ingredient.count < 0 then
|
|
||||||
addCraftingRequest(ingredient, craftList, -ingredient.count)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function craftItems(craftList, allItems)
|
|
||||||
|
|
||||||
for _,key in pairs(Util.keys(craftList)) do
|
|
||||||
local item = craftList[key]
|
|
||||||
local recipe = recipes[key]
|
|
||||||
if recipe then
|
|
||||||
craftItem(recipe, allItems, item, craftList, item.count)
|
|
||||||
allItems = inventoryAdapter:listItems() -- refresh counts
|
|
||||||
elseif item.rsControl then
|
|
||||||
item.status = 'Activated'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for key,item in pairs(craftList) do
|
|
||||||
|
|
||||||
if not recipes[key] and not item.rsControl then
|
|
||||||
if not controller then
|
|
||||||
item.status = '(no recipe)'
|
|
||||||
else
|
|
||||||
if controller:isCrafting(item) then
|
|
||||||
item.status = '(crafting)'
|
|
||||||
else
|
|
||||||
local count = item.count
|
|
||||||
while count >= 1 do -- try to request smaller quantities until successful
|
|
||||||
local s, m = pcall(function()
|
|
||||||
item.status = '(no recipe)'
|
|
||||||
if not controller:craft(item, count) then
|
|
||||||
item.status = '(missing ingredients)'
|
|
||||||
error('failed')
|
|
||||||
end
|
|
||||||
item.status = '(crafting)'
|
|
||||||
end)
|
|
||||||
if s then
|
|
||||||
break -- successfully requested crafting
|
|
||||||
end
|
|
||||||
count = math.floor(count / 2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function jobMonitor(jobList)
|
|
||||||
|
|
||||||
local mon = Peripheral.getByType('monitor')
|
|
||||||
|
|
||||||
if mon then
|
|
||||||
mon = UI.Device({
|
|
||||||
device = mon,
|
|
||||||
textScale = .5,
|
|
||||||
})
|
|
||||||
else
|
|
||||||
mon = UI.Device({
|
|
||||||
device = Terminal.getNullTerm(term.current())
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
jobListGrid = UI.Grid({
|
|
||||||
parent = mon,
|
|
||||||
sortColumn = 'displayName',
|
|
||||||
columns = {
|
|
||||||
{ heading = 'Qty', key = 'count', width = 6 },
|
|
||||||
{ heading = 'Crafting', key = 'displayName', width = mon.width / 2 - 10 },
|
|
||||||
{ heading = 'Status', key = 'status', width = mon.width - 10 },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
function jobListGrid:getRowTextColor(row, selected)
|
|
||||||
|
|
||||||
if row.status == '(no recipe)'then
|
|
||||||
return colors.red
|
|
||||||
elseif row.statusCode == 'missing' then
|
|
||||||
return colors.yellow
|
|
||||||
end
|
|
||||||
|
|
||||||
return UI.Grid:getRowTextColor(row, selected)
|
|
||||||
end
|
|
||||||
|
|
||||||
jobListGrid:draw()
|
|
||||||
jobListGrid:sync()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function getAutocraftItems()
|
|
||||||
local craftList = { }
|
|
||||||
|
|
||||||
for _,res in pairs(resources) do
|
|
||||||
|
|
||||||
if res.auto then
|
|
||||||
res.count = 64 -- this could be higher to increase autocrafting speed
|
|
||||||
local key = uniqueKey(res)
|
|
||||||
craftList[key] = res
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return craftList
|
|
||||||
end
|
|
||||||
|
|
||||||
local function getItemWithQty(items, res, ignoreDamage)
|
|
||||||
|
|
||||||
local item = getItem(items, res, ignoreDamage)
|
|
||||||
|
|
||||||
if item and ignoreDamage then
|
|
||||||
local count = 0
|
|
||||||
|
|
||||||
for _,v in pairs(items) do
|
|
||||||
if item.name == v.name and item.nbtHash == v.nbtHash then
|
|
||||||
if item.maxDamage > 0 or item.damage == v.damage then
|
|
||||||
count = count + v.count
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
item.count = count
|
|
||||||
end
|
|
||||||
|
|
||||||
return item
|
|
||||||
end
|
|
||||||
|
|
||||||
local function watchResources(items)
|
|
||||||
|
|
||||||
local craftList = { }
|
|
||||||
local outputs = { }
|
|
||||||
|
|
||||||
for k, res in pairs(resources) do
|
|
||||||
local item = getItemWithQty(items, res, res.ignoreDamage)
|
|
||||||
if not item then
|
|
||||||
item = {
|
|
||||||
damage = res.damage,
|
|
||||||
nbtHash = res.nbtHash,
|
|
||||||
name = res.name,
|
|
||||||
displayName = itemDB:getName(res),
|
|
||||||
count = 0
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
if res.limit and item.count > res.limit then
|
|
||||||
local s, m = inventoryAdapter:provide(
|
|
||||||
{ name = item.name, damage = item.damage },
|
|
||||||
item.count - res.limit,
|
|
||||||
nil,
|
|
||||||
config.trashDirection)
|
|
||||||
|
|
||||||
elseif res.low and item.count < res.low then
|
|
||||||
if res.ignoreDamage then
|
|
||||||
item.damage = 0
|
|
||||||
end
|
|
||||||
local key = uniqueKey(res)
|
|
||||||
craftList[key] = {
|
|
||||||
damage = item.damage,
|
|
||||||
nbtHash = item.nbtHash,
|
|
||||||
count = res.low - item.count,
|
|
||||||
name = item.name,
|
|
||||||
displayName = item.displayName,
|
|
||||||
status = '',
|
|
||||||
rsControl = res.rsControl,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
if res.rsControl and res.rsDevice and res.rsSide then
|
|
||||||
local enable = item.count < res.low
|
|
||||||
if not outputs[res.rsDevice] then
|
|
||||||
outputs[res.rsDevice] = { }
|
|
||||||
end
|
|
||||||
outputs[res.rsDevice][res.rsSide] = outputs[res.rsDevice][res.rsSide] or enable
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for rsDevice, sides in pairs(outputs) do
|
|
||||||
for side, enable in pairs(sides) do
|
|
||||||
pcall(function()
|
|
||||||
device[rsDevice].setOutput(side, enable)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return craftList
|
|
||||||
end
|
|
||||||
|
|
||||||
local function loadResources()
|
|
||||||
resources = Util.readTable(RESOURCE_FILE) or { }
|
|
||||||
for k,v in pairs(resources) do
|
|
||||||
Util.merge(v, splitKey(k))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function saveResources()
|
|
||||||
local t = { }
|
|
||||||
|
|
||||||
for k,v in pairs(resources) do
|
|
||||||
v = Util.shallowCopy(v)
|
|
||||||
v.name = nil
|
|
||||||
v.damage = nil
|
|
||||||
v.nbtHash = nil
|
|
||||||
t[k] = v
|
|
||||||
end
|
|
||||||
|
|
||||||
Util.writeTable(RESOURCE_FILE, t)
|
|
||||||
end
|
|
||||||
|
|
||||||
local itemPage = UI.Page {
|
|
||||||
titleBar = UI.TitleBar {
|
|
||||||
title = 'Limit Resource',
|
|
||||||
previousPage = true,
|
|
||||||
event = 'form_cancel',
|
|
||||||
},
|
|
||||||
form = UI.Form {
|
|
||||||
x = 1, y = 2, height = 10, ex = -1,
|
|
||||||
[1] = UI.TextEntry {
|
|
||||||
width = 7,
|
|
||||||
formLabel = 'Min', formKey = 'low', help = 'Craft if below min'
|
|
||||||
},
|
|
||||||
[2] = UI.TextEntry {
|
|
||||||
width = 7,
|
|
||||||
formLabel = 'Max', formKey = 'limit', help = 'Eject if above max'
|
|
||||||
},
|
|
||||||
[3] = UI.Chooser {
|
|
||||||
width = 7,
|
|
||||||
formLabel = 'Autocraft', formKey = 'auto',
|
|
||||||
nochoice = 'No',
|
|
||||||
choices = {
|
|
||||||
{ name = 'Yes', value = true },
|
|
||||||
{ name = 'No', value = false },
|
|
||||||
},
|
|
||||||
help = 'Craft until out of ingredients'
|
|
||||||
},
|
|
||||||
[4] = UI.Chooser {
|
|
||||||
width = 7,
|
|
||||||
formLabel = 'Ignore Dmg', formKey = 'ignoreDamage',
|
|
||||||
nochoice = 'No',
|
|
||||||
choices = {
|
|
||||||
{ name = 'Yes', value = true },
|
|
||||||
{ name = 'No', value = false },
|
|
||||||
},
|
|
||||||
help = 'Ignore damage of item'
|
|
||||||
},
|
|
||||||
[5] = UI.Chooser {
|
|
||||||
width = 7,
|
|
||||||
formLabel = 'RS Control', formKey = 'rsControl',
|
|
||||||
nochoice = 'No',
|
|
||||||
choices = {
|
|
||||||
{ name = 'Yes', value = true },
|
|
||||||
{ name = 'No', value = false },
|
|
||||||
},
|
|
||||||
help = 'Control via redstone'
|
|
||||||
},
|
|
||||||
[6] = UI.Chooser {
|
|
||||||
width = 25,
|
|
||||||
formLabel = 'RS Device', formKey = 'rsDevice',
|
|
||||||
--choices = devices,
|
|
||||||
help = 'Redstone Device'
|
|
||||||
},
|
|
||||||
[7] = UI.Chooser {
|
|
||||||
width = 10,
|
|
||||||
formLabel = 'RS Side', formKey = 'rsSide',
|
|
||||||
--nochoice = 'No',
|
|
||||||
choices = {
|
|
||||||
{ name = 'up', value = 'up' },
|
|
||||||
{ name = 'down', value = 'down' },
|
|
||||||
{ name = 'east', value = 'east' },
|
|
||||||
{ name = 'north', value = 'north' },
|
|
||||||
{ name = 'west', value = 'west' },
|
|
||||||
{ name = 'south', value = 'south' },
|
|
||||||
},
|
|
||||||
help = 'Output side'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
statusBar = UI.StatusBar { }
|
|
||||||
}
|
|
||||||
|
|
||||||
function itemPage:enable(item)
|
|
||||||
self.item = item
|
|
||||||
|
|
||||||
self.form:setValues(item)
|
|
||||||
self.titleBar.title = item.displayName or item.name
|
|
||||||
|
|
||||||
local devices = self.form[6].choices
|
|
||||||
Util.clear(devices)
|
|
||||||
for _,device in pairs(device) do
|
|
||||||
if device.setOutput then
|
|
||||||
table.insert(devices, { name = device.name, value = device.name })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if Util.size(devices) == 0 then
|
|
||||||
table.insert(devices, { name = 'None found', values = '' })
|
|
||||||
end
|
|
||||||
|
|
||||||
UI.Page.enable(self)
|
|
||||||
self:focusFirst()
|
|
||||||
end
|
|
||||||
|
|
||||||
function itemPage:eventHandler(event)
|
|
||||||
if event.type == 'form_cancel' then
|
|
||||||
UI:setPreviousPage()
|
|
||||||
|
|
||||||
elseif event.type == 'focus_change' then
|
|
||||||
self.statusBar:setStatus(event.focused.help)
|
|
||||||
self.statusBar:draw()
|
|
||||||
|
|
||||||
elseif event.type == 'form_complete' then
|
|
||||||
local values = self.form.values
|
|
||||||
local keys = { 'name', 'auto', 'low', 'limit', 'damage',
|
|
||||||
'nbtHash',
|
|
||||||
'rsControl', 'rsDevice', 'rsSide', }
|
|
||||||
|
|
||||||
local filtered = { }
|
|
||||||
for _,key in pairs(keys) do
|
|
||||||
filtered[key] = values[key]
|
|
||||||
end
|
|
||||||
filtered.low = tonumber(filtered.low)
|
|
||||||
filtered.limit = tonumber(filtered.limit)
|
|
||||||
|
|
||||||
--filtered.ignoreDamage = filtered.ignoreDamage == true
|
|
||||||
--filtered.auto = filtered.auto == true
|
|
||||||
--filtered.rsControl = filtered.rsControl == true
|
|
||||||
|
|
||||||
if filtered.auto ~= true then
|
|
||||||
filtered.auto = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
if filtered.rsControl ~= true then
|
|
||||||
filtered.rsControl = nil
|
|
||||||
filtered.rsSide = nil
|
|
||||||
filtered.rsDevice = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
if values.ignoreDamage == true then
|
|
||||||
filtered.damage = 0
|
|
||||||
filtered.ignoreDamage = true
|
|
||||||
end
|
|
||||||
|
|
||||||
resources[uniqueKey(filtered)] = filtered
|
|
||||||
saveResources()
|
|
||||||
|
|
||||||
UI:setPreviousPage()
|
|
||||||
|
|
||||||
else
|
|
||||||
return UI.Page.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local listingPage = UI.Page {
|
|
||||||
menuBar = UI.MenuBar {
|
|
||||||
buttons = {
|
|
||||||
{ text = 'Learn', event = 'learn' },
|
|
||||||
{ text = 'Forget', event = 'forget' },
|
|
||||||
{ text = 'Craft', event = 'craft' },
|
|
||||||
{ text = 'Refresh', event = 'refresh', x = -9 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
grid = UI.Grid {
|
|
||||||
y = 2, height = UI.term.height - 2,
|
|
||||||
columns = {
|
|
||||||
{ heading = 'Name', key = 'displayName' , width = 22 },
|
|
||||||
{ heading = 'Qty', key = 'count' , width = 5 },
|
|
||||||
{ heading = 'Min', key = 'low' , width = 4 },
|
|
||||||
{ heading = 'Max', key = 'limit' , width = 4 },
|
|
||||||
},
|
|
||||||
sortColumn = 'displayName',
|
|
||||||
},
|
|
||||||
statusBar = UI.StatusBar {
|
|
||||||
filterText = UI.Text {
|
|
||||||
x = 2,
|
|
||||||
value = 'Filter',
|
|
||||||
},
|
|
||||||
filter = UI.TextEntry {
|
|
||||||
x = 9, ex = -2,
|
|
||||||
limit = 50,
|
|
||||||
backgroundColor = colors.gray,
|
|
||||||
backgroundFocusColor = colors.gray,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
accelerators = {
|
|
||||||
r = 'refresh',
|
|
||||||
q = 'quit',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function listingPage.grid:getRowTextColor(row, selected)
|
|
||||||
if row.is_craftable then
|
|
||||||
return colors.yellow
|
|
||||||
end
|
|
||||||
if row.has_recipe then
|
|
||||||
if selected then
|
|
||||||
return colors.cyan
|
|
||||||
end
|
|
||||||
return colors.cyan
|
|
||||||
end
|
|
||||||
return UI.Grid:getRowTextColor(row, selected)
|
|
||||||
end
|
|
||||||
|
|
||||||
function listingPage.grid:getDisplayValues(row)
|
|
||||||
row = Util.shallowCopy(row)
|
|
||||||
row.count = Util.toBytes(row.count)
|
|
||||||
if row.low then
|
|
||||||
row.low = Util.toBytes(row.low)
|
|
||||||
end
|
|
||||||
if row.limit then
|
|
||||||
row.limit = Util.toBytes(row.limit)
|
|
||||||
end
|
|
||||||
return row
|
|
||||||
end
|
|
||||||
|
|
||||||
function listingPage.statusBar:draw()
|
|
||||||
return UI.Window.draw(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function listingPage.statusBar.filter:eventHandler(event)
|
|
||||||
if event.type == 'mouse_rightclick' then
|
|
||||||
self.value = ''
|
|
||||||
self:draw()
|
|
||||||
local page = UI:getCurrentPage()
|
|
||||||
page.filter = nil
|
|
||||||
page:applyFilter()
|
|
||||||
page.grid:draw()
|
|
||||||
page:setFocus(self)
|
|
||||||
end
|
|
||||||
return UI.TextEntry.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
|
|
||||||
function listingPage:eventHandler(event)
|
|
||||||
if event.type == 'quit' then
|
|
||||||
UI:exitPullEvents()
|
|
||||||
|
|
||||||
elseif event.type == 'grid_select' then
|
|
||||||
local selected = event.selected
|
|
||||||
UI:setPage('item', selected)
|
|
||||||
|
|
||||||
elseif event.type == 'refresh' then
|
|
||||||
self:refresh()
|
|
||||||
self.grid:draw()
|
|
||||||
self.statusBar.filter:focus()
|
|
||||||
|
|
||||||
elseif event.type == 'learn' then
|
|
||||||
UI:setPage('learn')
|
|
||||||
|
|
||||||
elseif event.type == 'craft' then
|
|
||||||
UI:setPage('craft')
|
|
||||||
|
|
||||||
elseif event.type == 'forget' then
|
|
||||||
local item = self.grid:getSelected()
|
|
||||||
if item then
|
|
||||||
local key = uniqueKey(item)
|
|
||||||
|
|
||||||
if recipes[key] then
|
|
||||||
recipes[key] = nil
|
|
||||||
Util.writeTable(RECIPES_FILE, recipes)
|
|
||||||
end
|
|
||||||
|
|
||||||
if resources[key] then
|
|
||||||
resources[key] = nil
|
|
||||||
Util.writeTable(RESOURCE_FILE, resources)
|
|
||||||
end
|
|
||||||
|
|
||||||
self.statusBar:timedStatus('Forgot: ' .. item.name, 3)
|
|
||||||
self:refresh()
|
|
||||||
self.grid:draw()
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif event.type == 'text_change' then
|
|
||||||
self.filter = event.text
|
|
||||||
if #self.filter == 0 then
|
|
||||||
self.filter = nil
|
|
||||||
end
|
|
||||||
self:applyFilter()
|
|
||||||
self.grid:draw()
|
|
||||||
self.statusBar.filter:focus()
|
|
||||||
|
|
||||||
else
|
|
||||||
UI.Page.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function listingPage:enable()
|
|
||||||
self:refresh()
|
|
||||||
self:setFocus(self.statusBar.filter)
|
|
||||||
UI.Page.enable(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function listingPage:refresh()
|
|
||||||
self.allItems = inventoryAdapter:listItems()
|
|
||||||
mergeResources(self.allItems)
|
|
||||||
self:applyFilter()
|
|
||||||
end
|
|
||||||
|
|
||||||
function listingPage:applyFilter()
|
|
||||||
local t = filterItems(self.allItems, self.filter)
|
|
||||||
self.grid:setValues(t)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function getTurtleInventory()
|
|
||||||
|
|
||||||
if duckAntenna then
|
|
||||||
local list = duckAntenna.getAllStacks(false)
|
|
||||||
for k,v in pairs(list) do
|
|
||||||
v.name = v.id
|
|
||||||
v.damage = v.dmg
|
|
||||||
v.displayName = v.display_name
|
|
||||||
v.count = v.qty
|
|
||||||
v.maxDamage = v.max_dmg
|
|
||||||
v.maxCount = v.max_size
|
|
||||||
end
|
|
||||||
return list
|
|
||||||
end
|
|
||||||
|
|
||||||
local inventory = { }
|
|
||||||
for i = 1,16 do
|
|
||||||
local qty = turtle.getItemCount(i)
|
|
||||||
if qty > 0 then
|
|
||||||
turtleChestAdapter:insert(i, qty)
|
|
||||||
local items = turtleChestAdapter:listItems()
|
|
||||||
_, inventory[i] = next(items)
|
|
||||||
turtleChestAdapter:extract(1, qty, i)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return inventory
|
|
||||||
end
|
|
||||||
|
|
||||||
local function filter(t, filter)
|
|
||||||
local keys = Util.keys(t)
|
|
||||||
for _,key in pairs(keys) do
|
|
||||||
if not Util.key(filter, key) then
|
|
||||||
t[key] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function learnRecipe(page)
|
|
||||||
local recipe = { }
|
|
||||||
local ingredients = getTurtleInventory()
|
|
||||||
if ingredients then
|
|
||||||
turtle.select(1)
|
|
||||||
if canCraft and turtle.craft() then
|
|
||||||
recipe = getTurtleInventory()
|
|
||||||
if recipe and recipe[1] then
|
|
||||||
clearGrid()
|
|
||||||
|
|
||||||
local key = uniqueKey(recipe[1])
|
|
||||||
local newRecipe = {
|
|
||||||
count = recipe[1].count,
|
|
||||||
ingredients = ingredients,
|
|
||||||
}
|
|
||||||
if recipe[1].maxCount ~= 64 then
|
|
||||||
newRecipe.maxCount = recipe[1].maxCount
|
|
||||||
end
|
|
||||||
|
|
||||||
for k,ingredient in pairs(ingredients) do
|
|
||||||
ingredients[k] = uniqueKey(ingredient)
|
|
||||||
end
|
|
||||||
|
|
||||||
recipes[key] = newRecipe
|
|
||||||
|
|
||||||
Util.writeTable(RECIPES_FILE, recipes)
|
|
||||||
|
|
||||||
local displayName = itemDB:getName(recipe[1])
|
|
||||||
|
|
||||||
listingPage.statusBar.filter:setValue(displayName)
|
|
||||||
listingPage.statusBar:timedStatus('Learned: ' .. displayName, 3)
|
|
||||||
listingPage.filter = displayName
|
|
||||||
listingPage:refresh()
|
|
||||||
listingPage.grid:draw()
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
else
|
|
||||||
page.statusBar:timedStatus('Failed to craft', 3)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
page.statusBar:timedStatus('No recipe defined', 3)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local learnPage = UI.Dialog {
|
|
||||||
height = 7, width = UI.term.width - 6,
|
|
||||||
title = 'Learn Recipe',
|
|
||||||
idField = UI.Text {
|
|
||||||
x = 5,
|
|
||||||
y = 3,
|
|
||||||
width = UI.term.width - 10,
|
|
||||||
value = 'Place recipe in turtle'
|
|
||||||
},
|
|
||||||
accept = UI.Button {
|
|
||||||
x = -14, y = -3,
|
|
||||||
text = 'Ok', event = 'accept',
|
|
||||||
},
|
|
||||||
cancel = UI.Button {
|
|
||||||
x = -9, y = -3,
|
|
||||||
text = 'Cancel', event = 'cancel'
|
|
||||||
},
|
|
||||||
statusBar = UI.StatusBar {
|
|
||||||
status = 'Crafting paused'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function learnPage:enable()
|
|
||||||
craftingPaused = true
|
|
||||||
self:focusFirst()
|
|
||||||
UI.Dialog.enable(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function learnPage:disable()
|
|
||||||
craftingPaused = false
|
|
||||||
UI.Dialog.disable(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function learnPage:eventHandler(event)
|
|
||||||
if event.type == 'cancel' then
|
|
||||||
UI:setPreviousPage()
|
|
||||||
elseif event.type == 'accept' then
|
|
||||||
if learnRecipe(self) then
|
|
||||||
UI:setPreviousPage()
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return UI.Dialog.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local craftPage = UI.Dialog {
|
|
||||||
height = 6, width = UI.term.width - 10,
|
|
||||||
title = 'Enter amount to craft',
|
|
||||||
idField = UI.TextEntry {
|
|
||||||
x = 15,
|
|
||||||
y = 3,
|
|
||||||
width = 10,
|
|
||||||
limit = 6,
|
|
||||||
value = '1',
|
|
||||||
},
|
|
||||||
accept = UI.Button {
|
|
||||||
x = -8, y = -2,
|
|
||||||
backgroundColor = colors.green,
|
|
||||||
text = '+', event = 'accept',
|
|
||||||
},
|
|
||||||
cancel = UI.Button {
|
|
||||||
x = -4, y = -2,
|
|
||||||
backgroundColor = colors.red,
|
|
||||||
text = '\215', event = 'cancel'
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
function craftPage:draw()
|
|
||||||
UI.Dialog.draw(self)
|
|
||||||
self:write(6, 3, 'Quantity')
|
|
||||||
end
|
|
||||||
|
|
||||||
function craftPage:enable()
|
|
||||||
craftingPaused = true
|
|
||||||
self:focusFirst()
|
|
||||||
UI.Dialog.enable(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function craftPage:disable()
|
|
||||||
craftingPaused = false
|
|
||||||
UI.Dialog.disable(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function craftPage:eventHandler(event)
|
|
||||||
if event.type == 'cancel' then
|
|
||||||
UI:setPreviousPage()
|
|
||||||
elseif event.type == 'accept' then
|
|
||||||
|
|
||||||
else
|
|
||||||
return UI.Dialog.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
loadResources()
|
|
||||||
clearGrid()
|
|
||||||
|
|
||||||
UI:setPages({
|
|
||||||
listing = listingPage,
|
|
||||||
item = itemPage,
|
|
||||||
learn = learnPage,
|
|
||||||
craft = craftPage,
|
|
||||||
})
|
|
||||||
|
|
||||||
UI:setPage(listingPage)
|
|
||||||
listingPage:setFocus(listingPage.statusBar.filter)
|
|
||||||
|
|
||||||
jobMonitor()
|
|
||||||
|
|
||||||
Event.onInterval(5, function()
|
|
||||||
|
|
||||||
if not craftingPaused then
|
|
||||||
local items = inventoryAdapter:listItems()
|
|
||||||
if Util.size(items) == 0 then
|
|
||||||
jobListGrid.parent:clear()
|
|
||||||
jobListGrid.parent:centeredWrite(math.ceil(jobListGrid.parent.height/2), 'No items in system')
|
|
||||||
jobListGrid:sync()
|
|
||||||
|
|
||||||
else
|
|
||||||
local craftList = watchResources(items)
|
|
||||||
craftItems(craftList, items)
|
|
||||||
jobListGrid:setValues(craftList)
|
|
||||||
jobListGrid:update()
|
|
||||||
jobListGrid:draw()
|
|
||||||
jobListGrid:sync()
|
|
||||||
craftList = getAutocraftItems(items) -- autocrafted items don't show on job monitor
|
|
||||||
craftItems(craftList, items)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
UI:pullEvents()
|
|
||||||
jobListGrid.parent:reset()
|
|
||||||
1275
apps/edit.lua
1275
apps/edit.lua
File diff suppressed because it is too large
Load Diff
@@ -1,101 +0,0 @@
|
|||||||
requireInjector(getfenv(1))
|
|
||||||
|
|
||||||
local Event = require('event')
|
|
||||||
local Message = require('message')
|
|
||||||
local UI = require('ui')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
multishell.setTitle(multishell.getCurrent(), 'Log Monitor')
|
|
||||||
|
|
||||||
if not device.wireless_modem then
|
|
||||||
error('Wireless modem is required')
|
|
||||||
end
|
|
||||||
device.wireless_modem.open(59998)
|
|
||||||
|
|
||||||
local ids = { }
|
|
||||||
local messages = { }
|
|
||||||
local terminal = UI.term
|
|
||||||
|
|
||||||
if device.openperipheral_bridge then
|
|
||||||
|
|
||||||
UI.Glasses = require('glasses')
|
|
||||||
|
|
||||||
terminal = UI.Glasses({
|
|
||||||
x = 4,
|
|
||||||
y = 175,
|
|
||||||
height = 40,
|
|
||||||
width = 64,
|
|
||||||
textScale = .5,
|
|
||||||
backgroundOpacity = .65,
|
|
||||||
|
|
||||||
})
|
|
||||||
elseif device.monitor then
|
|
||||||
terminal = UI.Device({
|
|
||||||
deviceType = 'monitor',
|
|
||||||
textScale = .5
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
terminal:clear()
|
|
||||||
|
|
||||||
function getClient(id)
|
|
||||||
if not ids[id] then
|
|
||||||
ids[id] = {
|
|
||||||
titleBar = UI.TitleBar({ title = 'ID: ' .. id, parent = terminal }),
|
|
||||||
scrollingText = UI.ScrollingText({ parent = terminal })
|
|
||||||
}
|
|
||||||
local clientCount = Util.size(ids)
|
|
||||||
local clientHeight = math.floor((terminal.height - clientCount) / clientCount)
|
|
||||||
terminal:clear()
|
|
||||||
local y = 1
|
|
||||||
for k,v in pairs(ids) do
|
|
||||||
v.titleBar.y = y
|
|
||||||
y = y + 1
|
|
||||||
v.scrollingText.height = clientHeight
|
|
||||||
v.scrollingText.y = y
|
|
||||||
y = y + clientHeight
|
|
||||||
v.scrollingText:clear()
|
|
||||||
|
|
||||||
v.titleBar:draw()
|
|
||||||
v.scrollingText:draw()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return ids[id]
|
|
||||||
end
|
|
||||||
|
|
||||||
Event.on('logMessage', function()
|
|
||||||
local t = { }
|
|
||||||
while #messages > 0 do
|
|
||||||
local msg = messages[1]
|
|
||||||
table.remove(messages, 1)
|
|
||||||
local client = getClient(msg.id)
|
|
||||||
client.scrollingText:appendLine(string.format('%d %s', math.floor(os.clock()), msg.text))
|
|
||||||
t[msg.id] = client
|
|
||||||
end
|
|
||||||
for _,client in pairs(t) do
|
|
||||||
client.scrollingText:draw()
|
|
||||||
end
|
|
||||||
terminal:sync()
|
|
||||||
end)
|
|
||||||
|
|
||||||
Message.addHandler('log', function(h, id, msg)
|
|
||||||
table.insert(messages, { id = id, text = msg.contents })
|
|
||||||
os.queueEvent('logMessage')
|
|
||||||
end)
|
|
||||||
|
|
||||||
Event.on('monitor_touch', function()
|
|
||||||
terminal:reset()
|
|
||||||
ids = { }
|
|
||||||
end)
|
|
||||||
|
|
||||||
Event.on('mouse_click', function()
|
|
||||||
terminal:reset()
|
|
||||||
ids = { }
|
|
||||||
end)
|
|
||||||
|
|
||||||
Event.on('char', function()
|
|
||||||
Event.exitPullEvents()
|
|
||||||
end)
|
|
||||||
|
|
||||||
Event.pullEvents(logWriter)
|
|
||||||
terminal:reset()
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
requireInjector(getfenv(1))
|
|
||||||
|
|
||||||
local Terminal = require('terminal')
|
|
||||||
|
|
||||||
local args = { ... }
|
|
||||||
local mon = device[table.remove(args, 1) or 'monitor']
|
|
||||||
if not mon then
|
|
||||||
error('mirror: Invalid device')
|
|
||||||
end
|
|
||||||
|
|
||||||
mon.clear()
|
|
||||||
mon.setTextScale(.5)
|
|
||||||
mon.setCursorPos(1, 1)
|
|
||||||
|
|
||||||
local oterm = Terminal.copy(term.current())
|
|
||||||
Terminal.mirror(term.current(), mon)
|
|
||||||
|
|
||||||
term.current().getSize = mon.getSize
|
|
||||||
|
|
||||||
if #args > 0 then
|
|
||||||
shell.run(unpack(args))
|
|
||||||
Terminal.copy(oterm, term.current())
|
|
||||||
|
|
||||||
mon.setCursorBlink(false)
|
|
||||||
end
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
requireInjector(getfenv(1))
|
|
||||||
|
|
||||||
local Event = require('event')
|
|
||||||
local Logger = require('logger')
|
|
||||||
local Socket = require('socket')
|
|
||||||
local Terminal = require('terminal')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
Logger.setScreenLogging()
|
|
||||||
|
|
||||||
local remoteId
|
|
||||||
local args = { ... }
|
|
||||||
if #args == 1 then
|
|
||||||
remoteId = tonumber(args[1])
|
|
||||||
else
|
|
||||||
print('Enter host ID')
|
|
||||||
remoteId = tonumber(read())
|
|
||||||
end
|
|
||||||
|
|
||||||
if not remoteId then
|
|
||||||
error('Syntax: mirrorClient <host ID>')
|
|
||||||
end
|
|
||||||
|
|
||||||
local function wrapTerm(socket)
|
|
||||||
local methods = { 'blit', 'clear', 'clearLine', 'setCursorPos', 'write',
|
|
||||||
'setTextColor', 'setTextColour', 'setBackgroundColor',
|
|
||||||
'setBackgroundColour', 'scroll', 'setCursorBlink', }
|
|
||||||
|
|
||||||
socket.term = multishell.term
|
|
||||||
socket.oldTerm = Util.shallowCopy(socket.term)
|
|
||||||
|
|
||||||
for _,k in pairs(methods) do
|
|
||||||
socket.term[k] = function(...)
|
|
||||||
if not socket.queue then
|
|
||||||
socket.queue = { }
|
|
||||||
Event.onTimeout(0, function()
|
|
||||||
if socket.queue then
|
|
||||||
socket:write(socket.queue)
|
|
||||||
socket.queue = nil
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
table.insert(socket.queue, {
|
|
||||||
f = k,
|
|
||||||
args = { ... },
|
|
||||||
})
|
|
||||||
socket.oldTerm[k](...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
while true do
|
|
||||||
print('connecting...')
|
|
||||||
local socket
|
|
||||||
|
|
||||||
while true do
|
|
||||||
socket = Socket.connect(remoteId, 5901)
|
|
||||||
if socket then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
os.sleep(3)
|
|
||||||
end
|
|
||||||
|
|
||||||
print('connected')
|
|
||||||
|
|
||||||
wrapTerm(socket)
|
|
||||||
|
|
||||||
os.queueEvent('term_resize')
|
|
||||||
|
|
||||||
while true do
|
|
||||||
local e = Event.pullEvent()
|
|
||||||
if e[1] == 'terminate' then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
if not socket.connected then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for k,v in pairs(socket.oldTerm) do
|
|
||||||
socket.term[k] = v
|
|
||||||
end
|
|
||||||
|
|
||||||
socket:close()
|
|
||||||
|
|
||||||
end
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
requireInjector(getfenv(1))
|
|
||||||
|
|
||||||
local Event = require('event')
|
|
||||||
local Logger = require('logger')
|
|
||||||
local Socket = require('socket')
|
|
||||||
|
|
||||||
Logger.setScreenLogging()
|
|
||||||
|
|
||||||
local mon = term.current()
|
|
||||||
local args = { ... }
|
|
||||||
if args[1] then
|
|
||||||
mon = device[args[1]]
|
|
||||||
end
|
|
||||||
|
|
||||||
if not mon then
|
|
||||||
error('Invalid monitor')
|
|
||||||
end
|
|
||||||
|
|
||||||
mon.setBackgroundColor(colors.black)
|
|
||||||
mon.clear()
|
|
||||||
|
|
||||||
while true do
|
|
||||||
local socket = Socket.server(5901)
|
|
||||||
|
|
||||||
print('mirror: connection from ' .. socket.dhost)
|
|
||||||
|
|
||||||
Event.addRoutine(function()
|
|
||||||
while true do
|
|
||||||
local data = socket:read()
|
|
||||||
if not data then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
for _,v in ipairs(data) do
|
|
||||||
mon[v.f](unpack(v.args))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- ensure socket is connected
|
|
||||||
Event.onInterval(3, function(h)
|
|
||||||
if not socket:ping() then
|
|
||||||
Event.off(h)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
while true do
|
|
||||||
Event.pullEvent()
|
|
||||||
if not socket.connected then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
print('connection lost')
|
|
||||||
|
|
||||||
socket:close()
|
|
||||||
end
|
|
||||||
529
apps/mwm.lua
529
apps/mwm.lua
@@ -1,529 +0,0 @@
|
|||||||
local injector = requireInjector or load(http.get('https://raw.githubusercontent.com/kepler155c/opus/master/sys/apis/injector.lua').readAll())()
|
|
||||||
injector(getfenv(1))
|
|
||||||
|
|
||||||
local Canvas = require('ui.canvas')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
local function syntax()
|
|
||||||
printError('Syntax:')
|
|
||||||
error('mwm sessionName [monitor]')
|
|
||||||
end
|
|
||||||
|
|
||||||
local args = { ... }
|
|
||||||
local UID = 0
|
|
||||||
local multishell = { }
|
|
||||||
local processes = { }
|
|
||||||
local parentTerm = term.current()
|
|
||||||
local sessionFile = args[1] or syntax()
|
|
||||||
local running
|
|
||||||
local monitor
|
|
||||||
|
|
||||||
local defaultEnv = Util.shallowCopy(getfenv(1))
|
|
||||||
defaultEnv.multishell = multishell
|
|
||||||
|
|
||||||
if args[2] then
|
|
||||||
monitor = peripheral.wrap(args[2]) or syntax()
|
|
||||||
else
|
|
||||||
monitor = peripheral.find('monitor') or syntax()
|
|
||||||
end
|
|
||||||
|
|
||||||
monitor.setTextScale(.5)
|
|
||||||
monitor.clear()
|
|
||||||
|
|
||||||
local monDim, termDim = { }, { }
|
|
||||||
monDim.width, monDim.height = monitor.getSize()
|
|
||||||
termDim.width, termDim.height = parentTerm.getSize()
|
|
||||||
|
|
||||||
local function nextUID()
|
|
||||||
UID = UID + 1
|
|
||||||
return UID
|
|
||||||
end
|
|
||||||
|
|
||||||
local function write(win, x, y, text)
|
|
||||||
win.setCursorPos(x, y)
|
|
||||||
win.write(text)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function redraw()
|
|
||||||
monitor.clear()
|
|
||||||
for k,process in ipairs(processes) do
|
|
||||||
process.container.canvas:dirty()
|
|
||||||
process:focus(k == #processes)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function getProcessAt(x, y)
|
|
||||||
for k = #processes, 1, -1 do
|
|
||||||
local process = processes[k]
|
|
||||||
if x >= process.x and
|
|
||||||
y >= process.y and
|
|
||||||
x <= process.x + process.width - 1 and
|
|
||||||
y <= process.y + process.height - 1 then
|
|
||||||
return k, process
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[ A runnable process ]]--
|
|
||||||
local Process = { }
|
|
||||||
|
|
||||||
function Process:new(args)
|
|
||||||
|
|
||||||
args.env = args.env or Util.shallowCopy(defaultEnv)
|
|
||||||
args.width = args.width or termDim.width
|
|
||||||
args.height = args.height or termDim.height
|
|
||||||
|
|
||||||
local self = setmetatable({
|
|
||||||
uid = nextUID(),
|
|
||||||
x = args.x or 1,
|
|
||||||
y = args.y or 1,
|
|
||||||
width = args.width + 2,
|
|
||||||
height = args.height + 3,
|
|
||||||
path = args.path,
|
|
||||||
args = args.args or { },
|
|
||||||
title = args.title or 'shell',
|
|
||||||
}, { __index = Process })
|
|
||||||
|
|
||||||
self:adjustDimensions()
|
|
||||||
|
|
||||||
self.container = window.create(monitor, self.x, self.y, self.width, self.height, false)
|
|
||||||
self.window = window.create(self.container, 2, 3, args.width, args.height, true)
|
|
||||||
|
|
||||||
self.terminal = self.window
|
|
||||||
Canvas.convertWindow(self.container, monitor, self.x, self.y)
|
|
||||||
|
|
||||||
self.co = coroutine.create(function()
|
|
||||||
|
|
||||||
local result, err
|
|
||||||
|
|
||||||
if args.fn then
|
|
||||||
result, err = Util.runFunction(args.env, args.fn, table.unpack(self.args))
|
|
||||||
elseif args.path then
|
|
||||||
result, err = Util.run(args.env, args.path, table.unpack(self.args))
|
|
||||||
end
|
|
||||||
|
|
||||||
if not result and err and err ~= 'Terminated' then
|
|
||||||
printError('\n' .. tostring(err))
|
|
||||||
os.pullEventRaw('terminate')
|
|
||||||
end
|
|
||||||
multishell.removeProcess(self)
|
|
||||||
end)
|
|
||||||
|
|
||||||
self:focus(false)
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function Process:focus(focused)
|
|
||||||
if focused then
|
|
||||||
self.container.setBackgroundColor(colors.yellow)
|
|
||||||
else
|
|
||||||
self.container.setBackgroundColor(colors.gray)
|
|
||||||
end
|
|
||||||
self.container.setTextColor(colors.black)
|
|
||||||
write(self.container, 2, 2, string.rep(' ', self.width - 2))
|
|
||||||
write(self.container, 3, 2, self.title)
|
|
||||||
write(self.container, self.width - 2, 2, '*')
|
|
||||||
|
|
||||||
if focused then
|
|
||||||
self.window.restoreCursor()
|
|
||||||
elseif self.showSizers then
|
|
||||||
self:drawSizers(false)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Process:drawSizers(showSizers)
|
|
||||||
|
|
||||||
local sizeChars = {
|
|
||||||
'\135', '\139', '\141', '\142'
|
|
||||||
}
|
|
||||||
|
|
||||||
if Util.getVersion() < 1.8 then
|
|
||||||
sizeChars = {
|
|
||||||
'#', '#', '#', '#'
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
self.showSizers = showSizers
|
|
||||||
|
|
||||||
self.container.setBackgroundColor(colors.black)
|
|
||||||
self.container.setTextColor(colors.white)
|
|
||||||
|
|
||||||
if self.showSizers then
|
|
||||||
write(self.container, 1, 1, sizeChars[1])
|
|
||||||
write(self.container, self.width, 1, sizeChars[2])
|
|
||||||
write(self.container, 1, self.height, sizeChars[3])
|
|
||||||
write(self.container, self.width, self.height, sizeChars[4])
|
|
||||||
|
|
||||||
self.container.setTextColor(colors.yellow)
|
|
||||||
write(self.container, 1, 3, '+')
|
|
||||||
write(self.container, 1, 5, '-')
|
|
||||||
write(self.container, 3, 1, '+')
|
|
||||||
write(self.container, 5, 1, '-')
|
|
||||||
|
|
||||||
local str = string.format('%d x %d', self.width - 2, self.height - 3)
|
|
||||||
write(self.container, (self.width - #str) / 2, 1, str)
|
|
||||||
|
|
||||||
else
|
|
||||||
write(self.container, 1, 1, string.rep(' ', self.width))
|
|
||||||
write(self.container, self.width, 1, ' ')
|
|
||||||
write(self.container, 1, self.height, ' ')
|
|
||||||
write(self.container, self.width, self.height, ' ')
|
|
||||||
write(self.container, 1, 3, ' ')
|
|
||||||
write(self.container, 1, 5, ' ')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Process:adjustDimensions()
|
|
||||||
|
|
||||||
self.width = math.min(self.width, monDim.width)
|
|
||||||
self.height = math.min(self.height, monDim.height)
|
|
||||||
|
|
||||||
self.x = math.max(1, self.x)
|
|
||||||
self.y = math.max(1, self.y)
|
|
||||||
self.x = math.min(self.x, monDim.width - self.width + 1)
|
|
||||||
self.y = math.min(self.y, monDim.height - self.height + 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Process:reposition()
|
|
||||||
|
|
||||||
self:adjustDimensions()
|
|
||||||
self.container.reposition(self.x, self.y, self.width, self.height)
|
|
||||||
self.container.setBackgroundColor(colors.black)
|
|
||||||
self.container.clear()
|
|
||||||
|
|
||||||
self.window.reposition(2, 3, self.width - 2, self.height - 3)
|
|
||||||
|
|
||||||
redraw()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Process:click(x, y)
|
|
||||||
if y == 2 then -- title bar
|
|
||||||
if x == self.width - 2 then
|
|
||||||
self:resume('terminate')
|
|
||||||
else
|
|
||||||
self:drawSizers(not self.showSizers)
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif x == 1 or y == 1 then -- sizers
|
|
||||||
self:resizeClick(x, y)
|
|
||||||
|
|
||||||
elseif x > 1 and x < self.width then
|
|
||||||
if self.showSizers then
|
|
||||||
self:drawSizers(false)
|
|
||||||
end
|
|
||||||
self:resume('mouse_click', 1, x - 1, y - 2)
|
|
||||||
self:resume('mouse_up', 1, x - 1, y - 2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Process:resizeClick(x, y)
|
|
||||||
if x == 1 and y == 3 then
|
|
||||||
self.height = self.height + 1
|
|
||||||
elseif x == 1 and y == 5 then
|
|
||||||
self.height = self.height - 1
|
|
||||||
elseif x == 3 and y == 1 then
|
|
||||||
self.width = self.width + 1
|
|
||||||
elseif x == 5 and y == 1 then
|
|
||||||
self.width = self.width - 1
|
|
||||||
else
|
|
||||||
return
|
|
||||||
end
|
|
||||||
self:reposition()
|
|
||||||
self:resume('term_resize')
|
|
||||||
self:drawSizers(true)
|
|
||||||
multishell.saveSession(sessionFile)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Process:resume(event, ...)
|
|
||||||
if coroutine.status(self.co) == 'dead' then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if not self.filter or self.filter == event or event == "terminate" then
|
|
||||||
term.redirect(self.terminal)
|
|
||||||
|
|
||||||
local previous = running
|
|
||||||
running = self -- stupid shell set title
|
|
||||||
local ok, result = coroutine.resume(self.co, event, ...)
|
|
||||||
running = previous
|
|
||||||
|
|
||||||
self.terminal = term.current()
|
|
||||||
if ok then
|
|
||||||
self.filter = result
|
|
||||||
else
|
|
||||||
printError(result)
|
|
||||||
end
|
|
||||||
return ok, result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[ Install a multishell manager for the monitor ]]--
|
|
||||||
function multishell.getFocus()
|
|
||||||
return processes[#processes].uid
|
|
||||||
end
|
|
||||||
|
|
||||||
function multishell.setFocus(uid)
|
|
||||||
local process = Util.find(processes, 'uid', uid)
|
|
||||||
|
|
||||||
if process then
|
|
||||||
local lastFocused = processes[#processes]
|
|
||||||
if lastFocused ~= process then
|
|
||||||
|
|
||||||
if lastFocused then
|
|
||||||
lastFocused:focus(false)
|
|
||||||
end
|
|
||||||
|
|
||||||
Util.removeByValue(processes, process)
|
|
||||||
table.insert(processes, process)
|
|
||||||
multishell.restack()
|
|
||||||
|
|
||||||
process:focus(true)
|
|
||||||
process.container.canvas:dirty()
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function multishell.getTitle(uid)
|
|
||||||
local process = Util.find(processes, 'uid', uid)
|
|
||||||
if process then
|
|
||||||
return process.title
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function multishell.setTitle(uid, title)
|
|
||||||
local process = Util.find(processes, 'uid', uid)
|
|
||||||
if process then
|
|
||||||
process.title = title or ''
|
|
||||||
process:focus(process == processes[#processes])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function multishell.getCurrent()
|
|
||||||
if running then
|
|
||||||
return running.uid
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function multishell.getCount()
|
|
||||||
return #processes
|
|
||||||
end
|
|
||||||
|
|
||||||
function multishell.getTabs()
|
|
||||||
return processes
|
|
||||||
end
|
|
||||||
|
|
||||||
function multishell.launch(env, file, ...)
|
|
||||||
return multishell.openTab({
|
|
||||||
path = file,
|
|
||||||
env = env,
|
|
||||||
title = 'shell',
|
|
||||||
args = { ... },
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
function multishell.openTab(tabInfo)
|
|
||||||
local process = Process:new(tabInfo)
|
|
||||||
|
|
||||||
table.insert(processes, 1, process)
|
|
||||||
multishell.restack()
|
|
||||||
|
|
||||||
process.container.setVisible(true)
|
|
||||||
|
|
||||||
local previousTerm = term.current()
|
|
||||||
process:resume()
|
|
||||||
term.redirect(previousTerm)
|
|
||||||
|
|
||||||
multishell.saveSession(sessionFile)
|
|
||||||
return process.uid
|
|
||||||
end
|
|
||||||
|
|
||||||
function multishell.restack() -- reset the stacking order
|
|
||||||
for k,v in ipairs(processes) do
|
|
||||||
v.container.canvas.layers = { }
|
|
||||||
for l = k + 1, #processes do
|
|
||||||
table.insert(v.container.canvas.layers, processes[l].container.canvas)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function multishell.removeProcess(process)
|
|
||||||
Util.removeByValue(processes, process)
|
|
||||||
multishell.restack()
|
|
||||||
multishell.saveSession(sessionFile)
|
|
||||||
redraw()
|
|
||||||
end
|
|
||||||
|
|
||||||
function multishell.saveSession(sessionFile)
|
|
||||||
local t = { }
|
|
||||||
for _,process in pairs(processes) do
|
|
||||||
if process.path and not process.isShell then
|
|
||||||
table.insert(t, {
|
|
||||||
x = process.x,
|
|
||||||
y = process.y,
|
|
||||||
width = process.width - 2,
|
|
||||||
height = process.height - 3,
|
|
||||||
path = process.path,
|
|
||||||
args = process.args,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
Util.writeTable(sessionFile, t)
|
|
||||||
end
|
|
||||||
|
|
||||||
function multishell.loadSession(sessionFile)
|
|
||||||
local config = Util.readTable(sessionFile)
|
|
||||||
if config then
|
|
||||||
for _,v in pairs(config) do
|
|
||||||
multishell.openTab(v)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function multishell.stop()
|
|
||||||
multishell._stop = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function multishell.start()
|
|
||||||
while not multishell._stop do
|
|
||||||
|
|
||||||
local event = { os.pullEventRaw() }
|
|
||||||
|
|
||||||
if event[1] == 'terminate' then
|
|
||||||
local focused = processes[#processes]
|
|
||||||
if focused.isShell then
|
|
||||||
focused:resume('terminate')
|
|
||||||
else
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif event[1] == 'monitor_touch' then
|
|
||||||
local x, y = event[3], event[4]
|
|
||||||
|
|
||||||
local key, process = getProcessAt(x, y)
|
|
||||||
if process then
|
|
||||||
if key ~= #processes then
|
|
||||||
multishell.setFocus(process.uid)
|
|
||||||
end
|
|
||||||
process:click(x - process.x + 1, y - process.y + 1)
|
|
||||||
|
|
||||||
else
|
|
||||||
process = processes[#processes]
|
|
||||||
if process and process.showSizers then
|
|
||||||
process.x = math.floor(x - (process.width) / 2)
|
|
||||||
process.y = y
|
|
||||||
process:reposition()
|
|
||||||
process:drawSizers(true)
|
|
||||||
multishell.saveSession(sessionFile)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif event[1] == 'mouse_click' or
|
|
||||||
event[1] == 'mouse_up' then
|
|
||||||
|
|
||||||
local focused = processes[#processes]
|
|
||||||
if not focused.isShell then
|
|
||||||
multishell.setFocus(1) -- shell is always 1
|
|
||||||
else
|
|
||||||
focused:resume(unpack(event))
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif event[1] == 'char' or
|
|
||||||
event[1] == 'key' or
|
|
||||||
event[1] == 'key_up' or
|
|
||||||
event[1] == 'paste' then
|
|
||||||
|
|
||||||
local focused = processes[#processes]
|
|
||||||
if focused then
|
|
||||||
focused:resume(unpack(event))
|
|
||||||
end
|
|
||||||
|
|
||||||
else
|
|
||||||
for _,process in pairs(Util.shallowCopy(processes)) do
|
|
||||||
process:resume(unpack(event))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local didRedraw
|
|
||||||
for _,process in pairs(processes) do
|
|
||||||
if process.container.canvas:isDirty() then
|
|
||||||
process.container.canvas:redraw(monitor)
|
|
||||||
didRedraw = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local focused = processes[#processes]
|
|
||||||
if didRedraw and focused then
|
|
||||||
--focused.container.canvas:dirty()
|
|
||||||
--focused.container.canvas:redraw(parentTerm)
|
|
||||||
focused.window.restoreCursor()
|
|
||||||
local cx, cy = focused.container.getCursorPos()
|
|
||||||
monitor.setCursorPos(
|
|
||||||
focused.container.canvas.x + cx - 1,
|
|
||||||
focused.container.canvas.y + cy - 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[ Special shell process for launching programs ]]--
|
|
||||||
local function addShell()
|
|
||||||
|
|
||||||
local process = setmetatable({
|
|
||||||
x = monDim.width,
|
|
||||||
y = monDim.height,
|
|
||||||
width = 1,
|
|
||||||
height = 1,
|
|
||||||
isShell = true,
|
|
||||||
uid = nextUID(),
|
|
||||||
title = 'Terminal',
|
|
||||||
}, { __index = Process })
|
|
||||||
|
|
||||||
function process:focus(focused)
|
|
||||||
self.window.setVisible(focused)
|
|
||||||
if focused then
|
|
||||||
self.window.restoreCursor()
|
|
||||||
else
|
|
||||||
parentTerm.clear()
|
|
||||||
parentTerm.setCursorBlink(false)
|
|
||||||
local str = 'Click screen for shell'
|
|
||||||
write(parentTerm,
|
|
||||||
math.floor((termDim.width - #str) / 2),
|
|
||||||
math.floor(termDim.height / 2),
|
|
||||||
str)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function process:click()
|
|
||||||
end
|
|
||||||
|
|
||||||
process.container = window.create(monitor, process.x, process.y+1, process.width, process.height, true)
|
|
||||||
process.window = window.create(parentTerm, 1, 1, termDim.width, termDim.height, true)
|
|
||||||
process.terminal = process.window
|
|
||||||
|
|
||||||
Canvas.convertWindow(process.container, monitor, process.x, process.y)
|
|
||||||
|
|
||||||
process.co = coroutine.create(function()
|
|
||||||
print('To run a program on the monitor, type "fg <program>"')
|
|
||||||
print('To quit, type "exit"')
|
|
||||||
os.run(Util.shallowCopy(defaultEnv), shell.resolveProgram('shell'))
|
|
||||||
multishell.stop()
|
|
||||||
end)
|
|
||||||
|
|
||||||
table.insert(processes, process)
|
|
||||||
process:focus(true)
|
|
||||||
|
|
||||||
local previousTerm = term.current()
|
|
||||||
process:resume()
|
|
||||||
term.redirect(previousTerm)
|
|
||||||
end
|
|
||||||
|
|
||||||
addShell()
|
|
||||||
|
|
||||||
multishell.loadSession(sessionFile)
|
|
||||||
multishell.start()
|
|
||||||
|
|
||||||
term.redirect(parentTerm)
|
|
||||||
parentTerm.clear()
|
|
||||||
parentTerm.setCursorPos(1, 1)
|
|
||||||
393
apps/shapes.lua
393
apps/shapes.lua
@@ -1,393 +0,0 @@
|
|||||||
requireInjector(getfenv(1))
|
|
||||||
|
|
||||||
local GPS = require('gps')
|
|
||||||
local Socket = require('socket')
|
|
||||||
local UI = require('ui')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
multishell.setTitle(multishell.getCurrent(), 'Shapes')
|
|
||||||
|
|
||||||
local args = { ... }
|
|
||||||
local turtleId = args[1] or error('Supply turtle ID')
|
|
||||||
turtleId = tonumber(turtleId)
|
|
||||||
|
|
||||||
local script = [[
|
|
||||||
|
|
||||||
requireInjector(getfenv(1))
|
|
||||||
|
|
||||||
local GPS = require('gps')
|
|
||||||
local ChestAdapter = require('chestAdapter18')
|
|
||||||
local Point = require('point')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
local itemAdapter
|
|
||||||
|
|
||||||
function dumpInventory()
|
|
||||||
|
|
||||||
for i = 1, 16 do
|
|
||||||
local qty = turtle.getItemCount(i)
|
|
||||||
if qty > 0 then
|
|
||||||
itemAdapter:insert(i, qty)
|
|
||||||
end
|
|
||||||
if turtle.getItemCount(i) ~= 0 then
|
|
||||||
print('Adapter is full or missing - make space or replace')
|
|
||||||
print('Press enter to continue')
|
|
||||||
read()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
turtle.select(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function refuel()
|
|
||||||
while turtle.getFuelLevel() < 4000 do
|
|
||||||
print('Refueling')
|
|
||||||
turtle.select(1)
|
|
||||||
|
|
||||||
itemAdapter:provide({ name = 'minecraft:coal', damage = 0 }, 64, 1)
|
|
||||||
if turtle.getItemCount(1) == 0 then
|
|
||||||
print('Out of fuel, add fuel to chest/ME system')
|
|
||||||
turtle.status = 'waiting'
|
|
||||||
os.sleep(5)
|
|
||||||
else
|
|
||||||
turtle.refuel(64)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function goto(pt)
|
|
||||||
while not turtle.gotoPoint(pt) do
|
|
||||||
print('stuck')
|
|
||||||
os.sleep(5)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function pathTo(pt)
|
|
||||||
while not turtle.pathfind(pt) do
|
|
||||||
print('stuck')
|
|
||||||
os.sleep(5)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function resupply()
|
|
||||||
|
|
||||||
if data.suppliesPt then
|
|
||||||
pathTo(data.suppliesPt)
|
|
||||||
|
|
||||||
itemAdapter = ChestAdapter({ direction = 'up', wrapSide = 'bottom' })
|
|
||||||
dumpInventory()
|
|
||||||
refuel()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function makePlane(y)
|
|
||||||
local pt = { x = math.min(data.startPt.x, data.endPt.x),
|
|
||||||
ex = math.max(data.startPt.x, data.endPt.x),
|
|
||||||
z = math.min(data.startPt.z, data.endPt.z),
|
|
||||||
ez = math.max(data.startPt.z, data.endPt.z) }
|
|
||||||
|
|
||||||
local blocks = { }
|
|
||||||
for z = pt.z, pt.ez do
|
|
||||||
for x = pt.x, pt.ex do
|
|
||||||
table.insert(blocks, { x = x, y = y, z = z })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return blocks
|
|
||||||
end
|
|
||||||
|
|
||||||
local function optimizeRoute(plane, ptb)
|
|
||||||
|
|
||||||
local maxDistance = 99999999
|
|
||||||
|
|
||||||
local function getNearestNeighbor(p, pt, threshold)
|
|
||||||
local key, block, heading
|
|
||||||
local moves = maxDistance
|
|
||||||
|
|
||||||
local function getMoves(b, k)
|
|
||||||
local distance = math.abs(pt.x - b.x) + math.abs(pt.z - b.z)
|
|
||||||
|
|
||||||
if distance < moves then
|
|
||||||
-- this operation is expensive - only run if distance is close
|
|
||||||
local c, h = Point.calculateMoves(pt, b, distance)
|
|
||||||
if c < moves then
|
|
||||||
block = b
|
|
||||||
key = k
|
|
||||||
moves = c
|
|
||||||
heading = h
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function blockReady(b)
|
|
||||||
return not b.u
|
|
||||||
end
|
|
||||||
|
|
||||||
local mid = pt.index
|
|
||||||
local forward = mid + 1
|
|
||||||
local backward = mid - 1
|
|
||||||
while forward <= #p or backward > 0 do
|
|
||||||
if forward <= #p then
|
|
||||||
local b = p[forward]
|
|
||||||
if blockReady(b) then
|
|
||||||
getMoves(b, forward)
|
|
||||||
if moves <= threshold then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
if moves < maxDistance and math.abs(b.z - pt.z) > moves and pt.index > 0 then
|
|
||||||
forward = #p
|
|
||||||
end
|
|
||||||
end
|
|
||||||
forward = forward + 1
|
|
||||||
end
|
|
||||||
if backward > 0 then
|
|
||||||
local b = p[backward]
|
|
||||||
if blockReady(b) then
|
|
||||||
getMoves(b, backward)
|
|
||||||
if moves <= threshold then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
if moves < maxDistance and math.abs(pt.z - b.z) > moves then
|
|
||||||
backward = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
backward = backward - 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
pt.x = block.x
|
|
||||||
pt.z = block.z
|
|
||||||
pt.y = block.y
|
|
||||||
pt.heading = heading
|
|
||||||
pt.index = key
|
|
||||||
block.u = true
|
|
||||||
return block
|
|
||||||
end
|
|
||||||
|
|
||||||
local throttle = Util.throttle()
|
|
||||||
local t = { }
|
|
||||||
ptb.index = 0
|
|
||||||
local threshold = 0
|
|
||||||
for i = 1, #plane do
|
|
||||||
local b = getNearestNeighbor(plane, ptb, threshold)
|
|
||||||
table.insert(t, b)
|
|
||||||
throttle()
|
|
||||||
threshold = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
local function clear()
|
|
||||||
|
|
||||||
local pt = Util.shallowCopy(data.startPt)
|
|
||||||
pt.y = math.min(data.startPt.y, data.endPt.y)
|
|
||||||
pt.heading = 0
|
|
||||||
|
|
||||||
local osy = pt.y
|
|
||||||
local sy = osy + 1
|
|
||||||
local ey = math.max(data.startPt.y, data.endPt.y)
|
|
||||||
local firstPlane = true
|
|
||||||
|
|
||||||
resupply()
|
|
||||||
|
|
||||||
while true do
|
|
||||||
|
|
||||||
if sy > ey then
|
|
||||||
sy = ey
|
|
||||||
end
|
|
||||||
|
|
||||||
local plane = makePlane(sy)
|
|
||||||
plane = optimizeRoute(plane, pt)
|
|
||||||
|
|
||||||
if firstPlane then
|
|
||||||
turtle.pathfind(plane[1])
|
|
||||||
turtle.setPolicy(turtle.policies.digAttack)
|
|
||||||
firstPlane = false
|
|
||||||
end
|
|
||||||
|
|
||||||
for _,b in ipairs(plane) do
|
|
||||||
turtle.gotoPoint(b)
|
|
||||||
if sy < ey then
|
|
||||||
turtle.digUp()
|
|
||||||
end
|
|
||||||
if sy > osy then
|
|
||||||
turtle.digDown()
|
|
||||||
end
|
|
||||||
if turtle.abort then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if turtle.abort then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
if sy + 1 >= ey then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
sy = sy + 3
|
|
||||||
end
|
|
||||||
turtle.setPolicy(turtle.policies.none)
|
|
||||||
resupply()
|
|
||||||
end
|
|
||||||
|
|
||||||
turtle.run(function()
|
|
||||||
turtle.status = 'Clearing'
|
|
||||||
|
|
||||||
if turtle.enableGPS() then
|
|
||||||
|
|
||||||
local pt = Util.shallowCopy(turtle.point)
|
|
||||||
local s, m = pcall(clear)
|
|
||||||
pathTo(pt)
|
|
||||||
|
|
||||||
if not s and m then
|
|
||||||
error(m)
|
|
||||||
read()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
]]
|
|
||||||
|
|
||||||
local levelScript = [[
|
|
||||||
|
|
||||||
requireInjector(getfenv(1))
|
|
||||||
|
|
||||||
local Level = require('turtle.level')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
local s, m = turtle.run(function()
|
|
||||||
turtle.status = 'Leveling'
|
|
||||||
|
|
||||||
if turtle.enableGPS() then
|
|
||||||
|
|
||||||
local pt = Util.shallowCopy(turtle.point)
|
|
||||||
local s, m = pcall(function()
|
|
||||||
Level(data.startPt, data.endPt, data.firstPt)
|
|
||||||
end)
|
|
||||||
|
|
||||||
turtle.pathfind(pt)
|
|
||||||
|
|
||||||
if not s and m then
|
|
||||||
error(m)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
if not s then
|
|
||||||
error(m)
|
|
||||||
end
|
|
||||||
]]
|
|
||||||
|
|
||||||
|
|
||||||
local data = Util.readTable('/usr/config/shapes') or { }
|
|
||||||
|
|
||||||
local page = UI.Page {
|
|
||||||
titleBar = UI.TitleBar { title = 'Shapes' },
|
|
||||||
info = UI.Window { x = 5, y = 3, height = 1 },
|
|
||||||
startCoord = UI.Button { x = 2, y = 6, text = 'Start ', event = 'startCoord' },
|
|
||||||
endCoord = UI.Button { x = 2, y = 8, text = 'End ', event = 'endCoord' },
|
|
||||||
supplies = UI.Button { x = 2, y = 10, text = 'Supplies', event = 'supplies' },
|
|
||||||
first = UI.Button { x = 2, y = 11, text = 'First', event = 'firstCoord' },
|
|
||||||
cancel = UI.Button { x = 2, y = -3, text = 'Abort', event = 'cancel' },
|
|
||||||
begin = UI.Button { x = -8, y = -3, text = 'Begin', event = 'begin' },
|
|
||||||
accelerators = { q = 'quit' },
|
|
||||||
notification = UI.Notification(),
|
|
||||||
statusBar = UI.StatusBar(),
|
|
||||||
}
|
|
||||||
|
|
||||||
function page.info:draw()
|
|
||||||
|
|
||||||
local function size(a, b)
|
|
||||||
return (math.abs(a.x - b.x) + 1) *
|
|
||||||
(math.abs(a.y - b.y) + 1) *
|
|
||||||
(math.abs(a.z - b.z) + 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
self:clear()
|
|
||||||
if not data.startPt then
|
|
||||||
self:write(1, 1, 'Set starting corner')
|
|
||||||
elseif not data.endPt then
|
|
||||||
self:write(1, 1, 'Set ending corner')
|
|
||||||
else
|
|
||||||
self:write(1, 1, 'Blocks: ' .. size(data.startPt, data.endPt))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:getPoint()
|
|
||||||
local pt = GPS.getPoint()
|
|
||||||
if not pt then
|
|
||||||
self.notification:error('GPS not available')
|
|
||||||
end
|
|
||||||
return pt
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:runFunction(id, script)
|
|
||||||
|
|
||||||
--Util.writeFile('script.tmp', script)
|
|
||||||
self.notification:info('Connecting')
|
|
||||||
local fn, msg = loadstring(script, 'script')
|
|
||||||
if not fn then
|
|
||||||
self.notification:error('Error in script')
|
|
||||||
-- debug(msg)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local socket = Socket.connect(id, 161)
|
|
||||||
if not socket then
|
|
||||||
self.notification:error('Unable to connect')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
socket:write({ type = 'script', args = script })
|
|
||||||
socket:close()
|
|
||||||
|
|
||||||
self.notification:success('Sent')
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:eventHandler(event)
|
|
||||||
if event.type == 'startCoord' then
|
|
||||||
data.startPt = self:getPoint()
|
|
||||||
if data.startPt then
|
|
||||||
self.statusBar:setStatus('starting corner set')
|
|
||||||
Util.writeTable('/usr/config/shapes', data)
|
|
||||||
end
|
|
||||||
self:draw()
|
|
||||||
elseif event.type == 'endCoord' then
|
|
||||||
data.endPt = self:getPoint()
|
|
||||||
if data.endPt then
|
|
||||||
self.statusBar:setStatus('ending corner set')
|
|
||||||
Util.writeTable('/usr/config/shapes', data)
|
|
||||||
end
|
|
||||||
self:draw()
|
|
||||||
elseif event.type == 'firstCoord' then
|
|
||||||
data.firstPt = self:getPoint()
|
|
||||||
if data.firstPt then
|
|
||||||
self.statusBar:setStatus('first point set')
|
|
||||||
Util.writeTable('/usr/config/shapes', data)
|
|
||||||
end
|
|
||||||
self:draw()
|
|
||||||
elseif event.type == 'supplies' then
|
|
||||||
data.suppliesPt = self:getPoint()
|
|
||||||
if data.suppliesPt then
|
|
||||||
self.statusBar:setStatus('supplies location set')
|
|
||||||
Util.writeTable('/usr/config/shapes', data)
|
|
||||||
end
|
|
||||||
elseif event.type == 'begin' then
|
|
||||||
if data.startPt and data.endPt then
|
|
||||||
local s = 'local data = ' .. textutils.serialize(data) .. levelScript
|
|
||||||
self:runFunction(turtleId, s)
|
|
||||||
else
|
|
||||||
self.notification:error('Corners not set')
|
|
||||||
end
|
|
||||||
self.statusBar:setStatus('')
|
|
||||||
elseif event.type == 'cancel' then
|
|
||||||
self:runFunction(turtleId, 'turtle.abortAction()')
|
|
||||||
self.statusBar:setStatus('')
|
|
||||||
else
|
|
||||||
return UI.Page.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
UI:setPage(page)
|
|
||||||
|
|
||||||
UI:pullEvents()
|
|
||||||
UI.term:reset()
|
|
||||||
@@ -1,645 +0,0 @@
|
|||||||
requireInjector(getfenv(1))
|
|
||||||
|
|
||||||
local Logger = require('logger')
|
|
||||||
local Pathing = require('turtle.pathfind')
|
|
||||||
local Point = require('point')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
if device and device.wireless_modem then
|
|
||||||
Logger.setWirelessLogging()
|
|
||||||
end
|
|
||||||
|
|
||||||
local args = { ... }
|
|
||||||
local options = {
|
|
||||||
chunks = { arg = 'c', type = 'number', value = -1,
|
|
||||||
desc = 'Number of chunks to mine' },
|
|
||||||
depth = { arg = 'd', type = 'number', value = 9000,
|
|
||||||
desc = 'Mining depth' },
|
|
||||||
-- enderChest = { arg = 'e', type = 'flag', value = false,
|
|
||||||
-- desc = 'Use ender chest' },
|
|
||||||
resume = { arg = 'r', type = 'flag', value = false,
|
|
||||||
desc = 'Resume mining' },
|
|
||||||
fortunePick = { arg = 'p', type = 'string', value = nil,
|
|
||||||
desc = 'Pick to use with CCTweaks toolhost' },
|
|
||||||
setTrash = { arg = 's', type = 'flag', value = false,
|
|
||||||
desc = 'Set trash items' },
|
|
||||||
help = { arg = 'h', type = 'flag', value = false,
|
|
||||||
desc = 'Displays the options' },
|
|
||||||
}
|
|
||||||
|
|
||||||
local fortuneBlocks = {
|
|
||||||
[ 'minecraft:redstone_ore' ] = true,
|
|
||||||
[ 'minecraft:lapis_ore' ] = true,
|
|
||||||
[ 'minecraft:coal_ore' ] = true,
|
|
||||||
[ 'minecraft:diamond_ore' ] = true,
|
|
||||||
[ 'minecraft:emerald_ore' ] = true,
|
|
||||||
}
|
|
||||||
|
|
||||||
local MIN_FUEL = 7500
|
|
||||||
local LOW_FUEL = 1500
|
|
||||||
local MAX_FUEL = 100000
|
|
||||||
|
|
||||||
local PROGRESS_FILE = 'usr/config/mining.progress'
|
|
||||||
local TRASH_FILE = 'usr/config/mining.trash'
|
|
||||||
|
|
||||||
if not term.isColor() then
|
|
||||||
MAX_FUEL = 20000
|
|
||||||
end
|
|
||||||
|
|
||||||
local mining = {
|
|
||||||
diameter = 1,
|
|
||||||
chunkIndex = 0,
|
|
||||||
chunks = -1,
|
|
||||||
}
|
|
||||||
|
|
||||||
local trash
|
|
||||||
local boreDirection
|
|
||||||
|
|
||||||
function getChunkCoordinates(diameter, index, x, z)
|
|
||||||
local dirs = { -- circumference of grid
|
|
||||||
{ xd = 0, zd = 1, heading = 1 }, -- south
|
|
||||||
{ xd = -1, zd = 0, heading = 2 },
|
|
||||||
{ xd = 0, zd = -1, heading = 3 },
|
|
||||||
{ xd = 1, zd = 0, heading = 0 } -- east
|
|
||||||
}
|
|
||||||
-- always move east when entering the next diameter
|
|
||||||
if index == 0 then
|
|
||||||
dirs[4].x = x + 16
|
|
||||||
dirs[4].z = z
|
|
||||||
return dirs[4]
|
|
||||||
end
|
|
||||||
dir = dirs[math.floor(index / (diameter - 1)) + 1]
|
|
||||||
dir.x = x + dir.xd * 16
|
|
||||||
dir.z = z + dir.zd * 16
|
|
||||||
return dir
|
|
||||||
end
|
|
||||||
|
|
||||||
function getBoreLocations(x, z)
|
|
||||||
|
|
||||||
local locations = {}
|
|
||||||
|
|
||||||
while true do
|
|
||||||
local a = math.abs(z)
|
|
||||||
local b = math.abs(x)
|
|
||||||
|
|
||||||
if x > 0 and z > 0 or
|
|
||||||
x < 0 and z < 0 then
|
|
||||||
-- rotate coords
|
|
||||||
a = math.abs(x)
|
|
||||||
b = math.abs(z)
|
|
||||||
end
|
|
||||||
if (a % 5 == 0 and b % 5 == 0) or
|
|
||||||
(a % 5 == 2 and b % 5 == 1) or
|
|
||||||
(a % 5 == 4 and b % 5 == 2) or
|
|
||||||
(a % 5 == 1 and b % 5 == 3) or
|
|
||||||
(a % 5 == 3 and b % 5 == 4) then
|
|
||||||
table.insert(locations, { x = x, z = z, y = 0 })
|
|
||||||
end
|
|
||||||
if z % 2 == 0 then -- forward dir
|
|
||||||
if (x + 1) % 16 == 0 then
|
|
||||||
z = z + 1
|
|
||||||
else
|
|
||||||
x = x + 1
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if (x - 1) % 16 == 15 then
|
|
||||||
if (z + 1) % 16 == 0 then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
z = z + 1
|
|
||||||
else
|
|
||||||
x = x - 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return locations
|
|
||||||
end
|
|
||||||
|
|
||||||
-- get the bore location closest to the miner
|
|
||||||
local function getClosestLocation(points, b)
|
|
||||||
local key = 1
|
|
||||||
local leastMoves = 9000
|
|
||||||
for k,pt in pairs(points) do
|
|
||||||
|
|
||||||
local moves = Point.calculateMoves(turtle.point, pt)
|
|
||||||
|
|
||||||
if moves < leastMoves then
|
|
||||||
key = k
|
|
||||||
leastMoves = moves
|
|
||||||
if leastMoves == 0 then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return table.remove(points, key)
|
|
||||||
end
|
|
||||||
|
|
||||||
function getCornerOf(c)
|
|
||||||
return math.floor(c.x / 16) * 16, math.floor(c.z / 16) * 16
|
|
||||||
end
|
|
||||||
|
|
||||||
function nextChunk()
|
|
||||||
|
|
||||||
local x, z = getCornerOf({ x = mining.x, z = mining.z })
|
|
||||||
local points = math.pow(mining.diameter, 2) - math.pow(mining.diameter-2, 2)
|
|
||||||
mining.chunkIndex = mining.chunkIndex + 1
|
|
||||||
|
|
||||||
if mining.chunkIndex >= points then
|
|
||||||
mining.diameter = mining.diameter + 2
|
|
||||||
mining.chunkIndex = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
if mining.chunks ~= -1 then
|
|
||||||
local chunks = math.pow(mining.diameter-2, 2) + mining.chunkIndex
|
|
||||||
if chunks >= mining.chunks then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local nc = getChunkCoordinates(mining.diameter, mining.chunkIndex, x, z)
|
|
||||||
mining.locations = getBoreLocations(nc.x, nc.z)
|
|
||||||
|
|
||||||
-- enter next chunk
|
|
||||||
mining.x = nc.x
|
|
||||||
mining.z = nc.z
|
|
||||||
|
|
||||||
Util.writeTable(PROGRESS_FILE, mining)
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function addTrash()
|
|
||||||
|
|
||||||
if not trash then
|
|
||||||
trash = { }
|
|
||||||
end
|
|
||||||
|
|
||||||
local slots = turtle.getFilledSlots()
|
|
||||||
|
|
||||||
for k,slot in pairs(slots) do
|
|
||||||
trash[slot.iddmg] = true
|
|
||||||
end
|
|
||||||
|
|
||||||
trash['minecraft:bucket:0'] = nil
|
|
||||||
Util.writeTable(TRASH_FILE, trash)
|
|
||||||
end
|
|
||||||
|
|
||||||
function log(text)
|
|
||||||
print(text)
|
|
||||||
Logger.log('mineWorker', text)
|
|
||||||
end
|
|
||||||
|
|
||||||
function status(status)
|
|
||||||
turtle.status = status
|
|
||||||
log(status)
|
|
||||||
end
|
|
||||||
|
|
||||||
function refuel()
|
|
||||||
if turtle.getFuelLevel() < MIN_FUEL then
|
|
||||||
local oldStatus = turtle.status
|
|
||||||
status('refueling')
|
|
||||||
|
|
||||||
if turtle.select('minecraft:coal:0') then
|
|
||||||
local qty = turtle.getItemCount()
|
|
||||||
print('refueling ' .. qty)
|
|
||||||
turtle.refuel(qty)
|
|
||||||
end
|
|
||||||
if turtle.getFuelLevel() < MIN_FUEL then
|
|
||||||
log('desperate fueling')
|
|
||||||
|
|
||||||
turtle.eachFilledSlot(function(slot)
|
|
||||||
if turtle.getFuelLevel() < MIN_FUEL then
|
|
||||||
turtle.select(slot.index)
|
|
||||||
turtle.refuel(64)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
log('Fuel: ' .. turtle.getFuelLevel())
|
|
||||||
status(oldStatus)
|
|
||||||
end
|
|
||||||
|
|
||||||
turtle.select(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
function enderChestUnload()
|
|
||||||
log('unloading')
|
|
||||||
turtle.select(1)
|
|
||||||
if not Util.tryTimed(5, function()
|
|
||||||
turtle.digDown()
|
|
||||||
return turtle.placeDown()
|
|
||||||
end) then
|
|
||||||
log('placedown failed')
|
|
||||||
else
|
|
||||||
turtle.reconcileInventory(slots, turtle.dropDown)
|
|
||||||
|
|
||||||
turtle.select(1)
|
|
||||||
turtle.drop(64)
|
|
||||||
turtle.digDown()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function safeGoto(x, z, y, h)
|
|
||||||
local oldStatus = turtle.status
|
|
||||||
|
|
||||||
-- only pathfind above or around other turtles (never down)
|
|
||||||
Pathing.setBox({ x = 0, y = 0, z = 0, ex = x, ey = y + 1, ez = z })
|
|
||||||
while not turtle.pathfind({ x = x, z = z, y = y or turtle.point.y, heading = h }) do
|
|
||||||
--status('stuck')
|
|
||||||
if turtle.abort then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
--os.sleep(1)
|
|
||||||
end
|
|
||||||
turtle.status = oldStatus
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function safeGotoY(y)
|
|
||||||
local oldStatus = turtle.status
|
|
||||||
while not turtle.gotoY(y) do
|
|
||||||
status('stuck')
|
|
||||||
if turtle.abort then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
os.sleep(1)
|
|
||||||
end
|
|
||||||
turtle.status = oldStatus
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function makeWalkableTunnel(action, tpt, pt)
|
|
||||||
if action ~= 'turn' and not Point.compare(tpt, { x = 0, z = 0 }) then -- not at source
|
|
||||||
if not Point.compare(tpt, pt) then -- not at dest
|
|
||||||
local r, block = turtle.inspectUp()
|
|
||||||
if r and not turtle.isTurtleAtSide('top') then
|
|
||||||
if block.name ~= 'minecraft:cobblestone' and
|
|
||||||
block.name ~= 'minecraft:chest' then
|
|
||||||
turtle.digUp()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function normalChestUnload()
|
|
||||||
local oldStatus = turtle.status
|
|
||||||
status('unloading')
|
|
||||||
local pt = Util.shallowCopy(turtle.point)
|
|
||||||
safeGotoY(0)
|
|
||||||
|
|
||||||
turtle.setMoveCallback(function(action, tpt)
|
|
||||||
makeWalkableTunnel(action, tpt, { x = pt.x, z = pt.z })
|
|
||||||
end)
|
|
||||||
|
|
||||||
safeGoto(0, 0)
|
|
||||||
if not turtle.detectUp() then
|
|
||||||
error('no chest')
|
|
||||||
end
|
|
||||||
local slots = turtle.getFilledSlots()
|
|
||||||
for _,slot in pairs(slots) do
|
|
||||||
if not trash[slot.iddmg] and
|
|
||||||
slot.iddmg ~= 'minecraft:bucket:0' and
|
|
||||||
slot.id ~= 'minecraft:diamond_pickaxe' and
|
|
||||||
slot.id ~= 'cctweaks:toolHost' then
|
|
||||||
if slot.id ~= options.fortunePick.value then
|
|
||||||
turtle.select(slot.index)
|
|
||||||
turtle.dropUp(64)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
turtle.condense()
|
|
||||||
turtle.select(1)
|
|
||||||
safeGoto(pt.x, pt.z, 0, pt.heading)
|
|
||||||
|
|
||||||
turtle.clearMoveCallback()
|
|
||||||
|
|
||||||
safeGotoY(pt.y)
|
|
||||||
status(oldStatus)
|
|
||||||
end
|
|
||||||
|
|
||||||
function ejectTrash()
|
|
||||||
|
|
||||||
local cobbleSlotCount = 0
|
|
||||||
|
|
||||||
turtle.eachFilledSlot(function(slot)
|
|
||||||
if slot.iddmg == 'minecraft:cobblestone:0' then
|
|
||||||
if cobbleSlotCount == 0 and slot.count > 36 then
|
|
||||||
turtle.select(slot.index)
|
|
||||||
turtle.dropDown(32)
|
|
||||||
end
|
|
||||||
cobbleSlotCount = cobbleSlotCount + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if trash[slot.iddmg] then
|
|
||||||
-- retain 1 slot with cobble in order to indicate active mining
|
|
||||||
if slot.iddmg ~= 'minecraft:cobblestone:0' or cobbleSlotCount > 1 then
|
|
||||||
turtle.select(slot.index)
|
|
||||||
turtle.dropDown(64)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function mineable(action)
|
|
||||||
local r, block = action.inspect()
|
|
||||||
if not r then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
if block.name == 'minecraft:chest' then
|
|
||||||
collectDrops(action.suck)
|
|
||||||
end
|
|
||||||
|
|
||||||
if turtle.getFuelLevel() < (MAX_FUEL - 1000) then
|
|
||||||
if block.name == 'minecraft:lava' or block.name == 'minecraft:flowing_lava' then
|
|
||||||
if turtle.select('minecraft:bucket:0') then
|
|
||||||
if action.place() then
|
|
||||||
log('Lava! ' .. turtle.getFuelLevel())
|
|
||||||
turtle.refuel()
|
|
||||||
log(turtle.getFuelLevel())
|
|
||||||
end
|
|
||||||
turtle.select(1)
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if action.side == 'bottom' then
|
|
||||||
return block.name
|
|
||||||
end
|
|
||||||
|
|
||||||
if trash[block.name .. ':0'] then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
return block.name
|
|
||||||
end
|
|
||||||
|
|
||||||
function fortuneDig(action, blockName)
|
|
||||||
if options.fortunePick.value and fortuneBlocks[blockName] then
|
|
||||||
turtle.select('cctweaks:toolHost')
|
|
||||||
turtle.equipRight()
|
|
||||||
turtle.select(options.fortunePick.value)
|
|
||||||
repeat until not turtle.dig()
|
|
||||||
turtle.select('minecraft:diamond_pickaxe')
|
|
||||||
turtle.equipRight()
|
|
||||||
turtle.select(1)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function mine(action)
|
|
||||||
local blockName = mineable(action)
|
|
||||||
if blockName then
|
|
||||||
checkSpace()
|
|
||||||
--collectDrops(action.suck)
|
|
||||||
if not fortuneDig(action, blockName) then
|
|
||||||
action.dig()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function bore()
|
|
||||||
|
|
||||||
local loc = turtle.point
|
|
||||||
local level = loc.y
|
|
||||||
|
|
||||||
turtle.select(1)
|
|
||||||
status('boring down')
|
|
||||||
boreDirection = 'down'
|
|
||||||
|
|
||||||
while true do
|
|
||||||
if turtle.abort then
|
|
||||||
status('aborting')
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
if loc.y <= -mining.depth then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
if turtle.point.y < -2 then
|
|
||||||
-- turtle.setDigPolicy(turtle.digPolicies.turtleSafe)
|
|
||||||
end
|
|
||||||
|
|
||||||
mine(turtle.getAction('down'))
|
|
||||||
if not Util.tryTimed(3, turtle.down) then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
if loc.y < level - 1 then
|
|
||||||
mine(turtle.getAction('forward'))
|
|
||||||
turtle.turnRight()
|
|
||||||
mine(turtle.getAction('forward'))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
boreDirection = 'up'
|
|
||||||
status('boring up')
|
|
||||||
|
|
||||||
turtle.turnRight()
|
|
||||||
mine(turtle.getAction('forward'))
|
|
||||||
|
|
||||||
turtle.turnRight()
|
|
||||||
mine(turtle.getAction('forward'))
|
|
||||||
|
|
||||||
turtle.turnLeft()
|
|
||||||
|
|
||||||
while true do
|
|
||||||
if turtle.abort then
|
|
||||||
status('aborting')
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
if turtle.point.y > -2 then
|
|
||||||
-- turtle.setDigPolicy(turtle.digPolicies.turtleSafe)
|
|
||||||
end
|
|
||||||
|
|
||||||
while not Util.tryTimed(3, turtle.up) do
|
|
||||||
status('stuck')
|
|
||||||
end
|
|
||||||
if turtle.status == 'stuck' then
|
|
||||||
status('boring up')
|
|
||||||
end
|
|
||||||
|
|
||||||
if loc.y >= level - 1 then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
mine(turtle.getAction('forward'))
|
|
||||||
turtle.turnLeft()
|
|
||||||
mine(turtle.getAction('forward'))
|
|
||||||
end
|
|
||||||
|
|
||||||
if turtle.getFuelLevel() < LOW_FUEL then
|
|
||||||
refuel()
|
|
||||||
local veryMinFuel = Point.turtleDistance(turtle.point, { x = 0, y = 0, z = 0}) + 512
|
|
||||||
if turtle.getFuelLevel() < veryMinFuel then
|
|
||||||
log('Not enough fuel to continue')
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function checkSpace()
|
|
||||||
if turtle.getItemCount(16) > 0 then
|
|
||||||
refuel()
|
|
||||||
local oldStatus = turtle.status
|
|
||||||
status('condensing')
|
|
||||||
ejectTrash()
|
|
||||||
turtle.condense()
|
|
||||||
local lastSlot = 16
|
|
||||||
if boreDirection == 'down' then
|
|
||||||
lastSlot = 15
|
|
||||||
end
|
|
||||||
if turtle.getItemCount(lastSlot) > 0 then
|
|
||||||
unload()
|
|
||||||
end
|
|
||||||
status(oldStatus)
|
|
||||||
turtle.select(1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function collectDrops(suckAction)
|
|
||||||
for i = 1, 50 do
|
|
||||||
if not suckAction() then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
checkSpace()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Point.compare(pta, ptb)
|
|
||||||
if pta.x == ptb.x and pta.z == ptb.z then
|
|
||||||
if pta.y and ptb.y then
|
|
||||||
return pta.y == ptb.y
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function inspect(action, name)
|
|
||||||
local r, block = action.inspect()
|
|
||||||
if r and block.name == name then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function boreCommand()
|
|
||||||
local pt = getClosestLocation(mining.locations, turtle.point)
|
|
||||||
|
|
||||||
turtle.setMoveCallback(function(action, tpt)
|
|
||||||
makeWalkableTunnel(action, tpt, pt)
|
|
||||||
end)
|
|
||||||
|
|
||||||
safeGotoY(0)
|
|
||||||
safeGoto(pt.x, pt.z, 0)
|
|
||||||
|
|
||||||
turtle.clearMoveCallback()
|
|
||||||
|
|
||||||
-- location is either mined, currently being mined or is the
|
|
||||||
-- dropoff point for a turtle
|
|
||||||
if inspect(turtle.getAction('up'), 'minecraft:cobblestone') or
|
|
||||||
inspect(turtle.getAction('up'), 'minecraft:chest') or
|
|
||||||
inspect(turtle.getAction('down'), 'minecraft:cobblestone') then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
turtle.digUp()
|
|
||||||
turtle.placeUp('minecraft:cobblestone:0')
|
|
||||||
|
|
||||||
local success = bore()
|
|
||||||
|
|
||||||
safeGotoY(0) -- may have aborted
|
|
||||||
turtle.digUp()
|
|
||||||
|
|
||||||
if success then
|
|
||||||
turtle.placeDown('minecraft:cobblestone:0') -- cap with cobblestone to indicate this spot was mined out
|
|
||||||
end
|
|
||||||
|
|
||||||
return success
|
|
||||||
end
|
|
||||||
|
|
||||||
if not Util.getOptions(options, args) then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
mining.depth = options.depth.value
|
|
||||||
mining.chunks = options.chunks.value
|
|
||||||
|
|
||||||
unload = normalChestUnload
|
|
||||||
--if options.enderChest.value then
|
|
||||||
-- unload = enderChestUnload
|
|
||||||
--end
|
|
||||||
|
|
||||||
mining.x = 0
|
|
||||||
mining.z = 0
|
|
||||||
mining.locations = getBoreLocations(0, 0)
|
|
||||||
trash = Util.readTable(TRASH_FILE)
|
|
||||||
|
|
||||||
if options.resume.value then
|
|
||||||
mining = Util.readTable(PROGRESS_FILE)
|
|
||||||
elseif fs.exists(PROGRESS_FILE) then
|
|
||||||
print('Use -r to resume')
|
|
||||||
print('Teminate or enter to continue')
|
|
||||||
read()
|
|
||||||
end
|
|
||||||
|
|
||||||
if not trash or options.setTrash.value then
|
|
||||||
print('Add blocks to ignore, press enter when ready')
|
|
||||||
read()
|
|
||||||
addTrash()
|
|
||||||
end
|
|
||||||
|
|
||||||
if not turtle.getSlot('minecraft:bucket:0') or
|
|
||||||
not turtle.getSlot('minecraft:cobblestone:0') then
|
|
||||||
print('Add bucket and cobblestone, press enter when ready')
|
|
||||||
read()
|
|
||||||
end
|
|
||||||
|
|
||||||
if options.fortunePick.value then
|
|
||||||
local s = turtle.getSlot(options.fortunePick.value)
|
|
||||||
if not s then
|
|
||||||
error('fortunePick not found: ' .. options.fortunePick.value)
|
|
||||||
end
|
|
||||||
if not turtle.getSlot('cctweaks:toolHost:0') then
|
|
||||||
error('CCTweaks tool host not found')
|
|
||||||
end
|
|
||||||
trash[s.iddmg] = nil
|
|
||||||
trash['minecraft:diamond_pickaxe:0'] = nil
|
|
||||||
trash['cctweaks:toolHost:0'] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local function main()
|
|
||||||
repeat
|
|
||||||
while #mining.locations > 0 do
|
|
||||||
status('searching')
|
|
||||||
if not boreCommand() then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
Util.writeTable(PROGRESS_FILE, mining)
|
|
||||||
end
|
|
||||||
until not nextChunk()
|
|
||||||
end
|
|
||||||
|
|
||||||
turtle.run(function()
|
|
||||||
turtle.reset()
|
|
||||||
turtle.setPolicy(turtle.policies.digAttack)
|
|
||||||
turtle.setDigPolicy(turtle.digPolicies.turtleSafe)
|
|
||||||
|
|
||||||
unload()
|
|
||||||
status('mining')
|
|
||||||
|
|
||||||
local s, m = pcall(function() main() end)
|
|
||||||
if not s and m then
|
|
||||||
printError(m)
|
|
||||||
end
|
|
||||||
|
|
||||||
turtle.abort = false
|
|
||||||
safeGotoY(0)
|
|
||||||
safeGoto(0, 0, 0, 0)
|
|
||||||
unload()
|
|
||||||
turtle.reset()
|
|
||||||
end)
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
requireInjector(getfenv(1))
|
|
||||||
|
|
||||||
local ChestAdapter = require('chestAdapter18')
|
|
||||||
local Event = require('event')
|
|
||||||
local MEAdapter = require('meAdapter')
|
|
||||||
local RefinedAdapter = require('refinedAdapter')
|
|
||||||
local UI = require('ui')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
local storage = RefinedAdapter()
|
|
||||||
if not storage:isValid() then
|
|
||||||
storage = MEAdapter({ auto = true })
|
|
||||||
if not storage:isValid() then
|
|
||||||
storage = ChestAdapter()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not storage:isValid() then
|
|
||||||
error('Not connected to a storage device')
|
|
||||||
end
|
|
||||||
|
|
||||||
multishell.setTitle(multishell.getCurrent(), 'Storage Activity')
|
|
||||||
UI:configure('StorageActivity', ...)
|
|
||||||
|
|
||||||
local changedPage = UI.Page {
|
|
||||||
grid = UI.Grid {
|
|
||||||
ey = -6,
|
|
||||||
columns = {
|
|
||||||
{ heading = 'Qty', key = 'count', width = 5 },
|
|
||||||
{ heading = 'Change', key = 'change', width = 6 },
|
|
||||||
{ heading = 'Name', key = 'displayName' },
|
|
||||||
},
|
|
||||||
sortColumn = 'displayName',
|
|
||||||
},
|
|
||||||
buttons = UI.Window {
|
|
||||||
y = -5, height = 5,
|
|
||||||
backgroundColor = colors.gray,
|
|
||||||
prevButton = UI.Button {
|
|
||||||
x = 2, y = 2, height = 3, width = 5,
|
|
||||||
event = 'previous',
|
|
||||||
backgroundColor = colors.lightGray,
|
|
||||||
text = ' < '
|
|
||||||
},
|
|
||||||
resetButton = UI.Button {
|
|
||||||
x = 8, y = 2, height = 3, ex = -8,
|
|
||||||
event = 'reset',
|
|
||||||
backgroundColor = colors.lightGray,
|
|
||||||
text = 'Reset'
|
|
||||||
},
|
|
||||||
nextButton = UI.Button {
|
|
||||||
x = -6, y = 2, height = 3, width = 5,
|
|
||||||
event = 'next',
|
|
||||||
backgroundColor = colors.lightGray,
|
|
||||||
text = ' > '
|
|
||||||
},
|
|
||||||
},
|
|
||||||
accelerators = {
|
|
||||||
q = 'quit',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function changedPage.grid:getDisplayValues(row)
|
|
||||||
row = Util.shallowCopy(row)
|
|
||||||
|
|
||||||
local ind = '+'
|
|
||||||
if row.change < 0 then
|
|
||||||
ind = ''
|
|
||||||
end
|
|
||||||
row.change = ind .. Util.toBytes(row.change)
|
|
||||||
row.count = Util.toBytes(row.count)
|
|
||||||
|
|
||||||
return row
|
|
||||||
end
|
|
||||||
|
|
||||||
function changedPage:eventHandler(event)
|
|
||||||
|
|
||||||
if event.type == 'reset' then
|
|
||||||
self.lastItems = nil
|
|
||||||
self.grid:setValues({ })
|
|
||||||
self.grid:clear()
|
|
||||||
self.grid:draw()
|
|
||||||
|
|
||||||
elseif event.type == 'next' then
|
|
||||||
self.grid:nextPage()
|
|
||||||
|
|
||||||
elseif event.type == 'previous' then
|
|
||||||
self.grid:previousPage()
|
|
||||||
|
|
||||||
elseif event.type == 'quit' then
|
|
||||||
Event.exitPullEvents()
|
|
||||||
|
|
||||||
else
|
|
||||||
return UI.Page.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function uniqueKey(item)
|
|
||||||
return table.concat({ item.name, item.damage, item.nbtHash }, ':')
|
|
||||||
end
|
|
||||||
|
|
||||||
function changedPage:refresh()
|
|
||||||
local t = storage:listItems()
|
|
||||||
|
|
||||||
if not t or Util.empty(t) then
|
|
||||||
self:clear()
|
|
||||||
self:centeredWrite(math.ceil(self.height/2), 'Communication failure')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
for k,v in pairs(t) do
|
|
||||||
t[k] = Util.shallowCopy(v)
|
|
||||||
end
|
|
||||||
|
|
||||||
if not self.lastItems then
|
|
||||||
self.lastItems = t
|
|
||||||
self.grid:setValues({ })
|
|
||||||
else
|
|
||||||
local changedItems = {}
|
|
||||||
for _,v in pairs(self.lastItems) do
|
|
||||||
found = false
|
|
||||||
for k2,v2 in pairs(t) do
|
|
||||||
if uniqueKey(v) == uniqueKey(v2) then
|
|
||||||
if v.count ~= v2.count then
|
|
||||||
local c = Util.shallowCopy(v2)
|
|
||||||
c.lastCount = v.count
|
|
||||||
table.insert(changedItems, c)
|
|
||||||
end
|
|
||||||
table.remove(t, k2)
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- New item
|
|
||||||
if not found then
|
|
||||||
local c = Util.shallowCopy(v)
|
|
||||||
c.lastCount = v.count
|
|
||||||
c.count = 0
|
|
||||||
table.insert(changedItems, c)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- No items left
|
|
||||||
for k,v in pairs(t) do
|
|
||||||
v.lastCount = 0
|
|
||||||
table.insert(changedItems, v)
|
|
||||||
end
|
|
||||||
|
|
||||||
for k,v in pairs(changedItems) do
|
|
||||||
v.change = v.count - v.lastCount
|
|
||||||
end
|
|
||||||
|
|
||||||
self.grid:setValues(changedItems)
|
|
||||||
end
|
|
||||||
self.grid:draw()
|
|
||||||
end
|
|
||||||
|
|
||||||
Event.onInterval(5, function()
|
|
||||||
changedPage:refresh()
|
|
||||||
changedPage:sync()
|
|
||||||
end)
|
|
||||||
|
|
||||||
UI:setPage(changedPage)
|
|
||||||
UI:pullEvents()
|
|
||||||
@@ -1,438 +0,0 @@
|
|||||||
requireInjector(getfenv(1))
|
|
||||||
|
|
||||||
local Event = require('event')
|
|
||||||
local Logger = require('logger')
|
|
||||||
local MEProvider = require('meProvider')
|
|
||||||
local Message = require('message')
|
|
||||||
local Point = require('point')
|
|
||||||
local TableDB = require('tableDB')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
--[[
|
|
||||||
A supplier turtle for the builder turtle. For larger builds, use
|
|
||||||
ender modems.
|
|
||||||
|
|
||||||
Setup:
|
|
||||||
|
|
||||||
1. chest or ME interface at level 0 (bottom of build area)
|
|
||||||
2. builder turtle on top facing the build area
|
|
||||||
3. If facing the build turtle, the supplier turtle is to the right
|
|
||||||
pointing at the chest/interface
|
|
||||||
]]--
|
|
||||||
|
|
||||||
local ChestProvider = require('chestProvider')
|
|
||||||
if Util.getVersion() == 1.8 then
|
|
||||||
ChestProvider = require('chestProvider18')
|
|
||||||
end
|
|
||||||
|
|
||||||
if not device.wireless_modem then
|
|
||||||
error('No wireless modem detected')
|
|
||||||
end
|
|
||||||
|
|
||||||
Logger.filter('modem_send', 'event', 'ui')
|
|
||||||
Logger.setWirelessLogging()
|
|
||||||
|
|
||||||
local __BUILDER_ID = 6
|
|
||||||
local itemInfoDB
|
|
||||||
|
|
||||||
local Builder = {
|
|
||||||
version = '1.70',
|
|
||||||
ccVersion = nil,
|
|
||||||
slots = { },
|
|
||||||
index = 1,
|
|
||||||
fuelItem = { id = 'minecraft:coal', dmg = 0 },
|
|
||||||
resupplying = true,
|
|
||||||
ready = true,
|
|
||||||
}
|
|
||||||
|
|
||||||
--[[-- maxStackDB --]]--
|
|
||||||
local maxStackDB = TableDB({
|
|
||||||
fileName = 'maxstack.db',
|
|
||||||
tabledef = {
|
|
||||||
autokeys = false,
|
|
||||||
type = 'simple',
|
|
||||||
columns = {
|
|
||||||
{ label = 'Key', type = 'key', length = 8 },
|
|
||||||
{ label = 'Quantity', type = 'number', length = 2 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function maxStackDB:get(id, dmg)
|
|
||||||
return self.data[id .. ':' .. dmg] or 64
|
|
||||||
end
|
|
||||||
|
|
||||||
function Builder:dumpInventory()
|
|
||||||
|
|
||||||
local success = true
|
|
||||||
|
|
||||||
for i = 1, 16 do
|
|
||||||
local qty = turtle.getItemCount(i)
|
|
||||||
if qty > 0 then
|
|
||||||
self.itemProvider:insert(i, qty)
|
|
||||||
end
|
|
||||||
if turtle.getItemCount(i) ~= 0 then
|
|
||||||
success = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
turtle.select(1)
|
|
||||||
|
|
||||||
return success
|
|
||||||
end
|
|
||||||
|
|
||||||
function Builder:dumpInventoryWithCheck()
|
|
||||||
while not self:dumpInventory() do
|
|
||||||
Builder:log('Unable to dump inventory')
|
|
||||||
print('Provider is full or missing - make space or replace')
|
|
||||||
print('Press enter to continue')
|
|
||||||
--turtle.setHeading(0)
|
|
||||||
self.ready = false
|
|
||||||
read()
|
|
||||||
end
|
|
||||||
self.ready = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function Builder:autocraft(supplies)
|
|
||||||
local t = { }
|
|
||||||
|
|
||||||
for i,s in pairs(supplies) do
|
|
||||||
local key = s.id .. ':' .. s.dmg
|
|
||||||
local item = t[key]
|
|
||||||
if not item then
|
|
||||||
item = {
|
|
||||||
id = s.id,
|
|
||||||
dmg = s.dmg,
|
|
||||||
qty = 0,
|
|
||||||
}
|
|
||||||
t[key] = item
|
|
||||||
end
|
|
||||||
item.qty = item.qty + (s.need-s.qty)
|
|
||||||
end
|
|
||||||
|
|
||||||
Builder.itemProvider:craftItems(t)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Builder:refuel()
|
|
||||||
while turtle.getFuelLevel() < 4000 and self.fuelItem do
|
|
||||||
Builder:log('Refueling')
|
|
||||||
turtle.select(1)
|
|
||||||
self.itemProvider:provide(self.fuelItem, 64, 1)
|
|
||||||
if turtle.getItemCount(1) == 0 then
|
|
||||||
Builder:log('Out of fuel, add coal to chest/ME system')
|
|
||||||
--turtle.setHeading(0)
|
|
||||||
os.sleep(5)
|
|
||||||
else
|
|
||||||
turtle.refuel(64)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Builder:log(...)
|
|
||||||
Logger.log('supplier', ...)
|
|
||||||
Util.print(...)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Builder:getSupplies()
|
|
||||||
|
|
||||||
Builder.itemProvider:refresh()
|
|
||||||
|
|
||||||
local t = { }
|
|
||||||
for _,s in ipairs(self.slots) do
|
|
||||||
if s.need > 0 then
|
|
||||||
local item = Builder.itemProvider:getItemInfo(s)
|
|
||||||
if item then
|
|
||||||
if item.name then
|
|
||||||
s.name = item.name
|
|
||||||
end
|
|
||||||
|
|
||||||
local qty = math.min(s.need-s.qty, item.qty)
|
|
||||||
|
|
||||||
if qty + s.qty > item.max_size then
|
|
||||||
maxStackDB:add({ s.id, s.dmg }, item.max_size)
|
|
||||||
maxStackDB.dirty = true
|
|
||||||
maxStackDB:flush()
|
|
||||||
qty = item.max_size
|
|
||||||
s.need = qty
|
|
||||||
end
|
|
||||||
if qty > 0 then
|
|
||||||
self.itemProvider:provide(item, qty, s.index)
|
|
||||||
s.qty = turtle.getItemCount(s.index)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if s.qty < s.need then
|
|
||||||
table.insert(t, s)
|
|
||||||
local name = s.name or s.id .. ':' .. s.dmg
|
|
||||||
local item = itemInfoDB:get({ s.id, s.dmg })
|
|
||||||
if item then
|
|
||||||
name = item.displayName
|
|
||||||
end
|
|
||||||
|
|
||||||
Builder:log('Need %d %s', s.need - s.qty, name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
local function moveTowardsX(dx)
|
|
||||||
|
|
||||||
local direction = dx - turtle.point.x
|
|
||||||
local move
|
|
||||||
|
|
||||||
if direction == 0 then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
if direction > 0 and turtle.point.heading == 0 or
|
|
||||||
direction < 0 and turtle.point.heading == 2 then
|
|
||||||
move = turtle.forward
|
|
||||||
else
|
|
||||||
move = turtle.back
|
|
||||||
end
|
|
||||||
|
|
||||||
return move()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function moveTowardsZ(dz)
|
|
||||||
|
|
||||||
local direction = dz - turtle.point.z
|
|
||||||
local move
|
|
||||||
|
|
||||||
if direction == 0 then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
if direction > 0 and turtle.point.heading == 1 or
|
|
||||||
direction < 0 and turtle.point.heading == 3 then
|
|
||||||
move = turtle.forward
|
|
||||||
else
|
|
||||||
move = turtle.back
|
|
||||||
end
|
|
||||||
|
|
||||||
return move()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Builder:finish()
|
|
||||||
|
|
||||||
Builder.resupplying = true
|
|
||||||
Builder.ready = false
|
|
||||||
if turtle.gotoLocation('supplies') then
|
|
||||||
turtle.setHeading(1)
|
|
||||||
os.sleep(.1) -- random 'Computer is not connected' error...
|
|
||||||
Builder:dumpInventory()
|
|
||||||
Event.exitPullEvents()
|
|
||||||
print('Finished')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Builder:gotoBuilder()
|
|
||||||
|
|
||||||
if Builder.lastPoint then
|
|
||||||
turtle.status = 'tracking'
|
|
||||||
while true do
|
|
||||||
local pt = Point.copy(Builder.lastPoint)
|
|
||||||
pt.y = pt.y + 3
|
|
||||||
if turtle.point.y ~= pt.y then
|
|
||||||
turtle.gotoY(pt.y)
|
|
||||||
else
|
|
||||||
local distance = Point.turtleDistance(turtle.point, pt)
|
|
||||||
if distance <= 3 then
|
|
||||||
Builder:log('Synchronized')
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
if turtle.point.heading % 2 == 0 then
|
|
||||||
if turtle.point.x == pt.x then
|
|
||||||
turtle.headTowardsZ(pt.z)
|
|
||||||
moveTowardsZ(pt.z)
|
|
||||||
else
|
|
||||||
moveTowardsX(pt.x)
|
|
||||||
end
|
|
||||||
elseif turtle.point.z ~= pt.z then
|
|
||||||
moveTowardsZ(pt.z)
|
|
||||||
else
|
|
||||||
turtle.headTowardsX(pt.x)
|
|
||||||
moveTowardsX(pt.x)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Message.addHandler('builder',
|
|
||||||
function(h, id, msg, distance)
|
|
||||||
if not id or id ~= __BUILDER_ID then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if not Builder.resupplying then
|
|
||||||
local pt = msg.contents
|
|
||||||
pt.y = pt.y + 3
|
|
||||||
|
|
||||||
turtle.status = 'supervising'
|
|
||||||
turtle.gotoYfirst(pt)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
Message.addHandler('supplyList',
|
|
||||||
function(h, id, msg, distance)
|
|
||||||
if not id or id ~= __BUILDER_ID then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
turtle.status = 'resupplying'
|
|
||||||
Builder.resupplying = true
|
|
||||||
Builder.slots = msg.contents.slots
|
|
||||||
Builder.slotUid = msg.contents.uid
|
|
||||||
|
|
||||||
Builder:log('Received supply list ' .. Builder.slotUid)
|
|
||||||
|
|
||||||
os.sleep(0)
|
|
||||||
if not turtle.gotoLocation('supplies') then
|
|
||||||
Builder:log('Failed to go to supply location')
|
|
||||||
self.ready = false
|
|
||||||
Event.exitPullEvents()
|
|
||||||
end
|
|
||||||
turtle.setHeading(1)
|
|
||||||
os.sleep(.2) -- random 'Computer is not connected' error...
|
|
||||||
Builder:dumpInventoryWithCheck()
|
|
||||||
Builder:refuel()
|
|
||||||
|
|
||||||
while true do
|
|
||||||
local supplies = Builder:getSupplies()
|
|
||||||
if #supplies == 0 then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
Builder:autocraft(supplies)
|
|
||||||
turtle.status = 'waiting'
|
|
||||||
os.sleep(5)
|
|
||||||
end
|
|
||||||
Builder:log('Got all supplies')
|
|
||||||
os.sleep(0)
|
|
||||||
Builder:gotoBuilder()
|
|
||||||
Builder.resupplying = false
|
|
||||||
end)
|
|
||||||
|
|
||||||
Message.addHandler('needSupplies',
|
|
||||||
function(h, id, msg, distance)
|
|
||||||
if not id or id ~= __BUILDER_ID then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if Builder.resupplying or msg.contents.uid ~= Builder.slotUid then
|
|
||||||
|
|
||||||
Builder:log('No supplies ready')
|
|
||||||
|
|
||||||
Message.send(__BUILDER_ID, 'gotSupplies')
|
|
||||||
else
|
|
||||||
turtle.status = 'supplying'
|
|
||||||
Builder:log('Supplying')
|
|
||||||
os.sleep(0)
|
|
||||||
|
|
||||||
local pt = msg.contents.point
|
|
||||||
pt.y = turtle.getPoint().y
|
|
||||||
pt.heading = nil
|
|
||||||
if not turtle.gotoYfirst(pt) then -- location of builder
|
|
||||||
Builder.resupplying = true
|
|
||||||
Message.send(__BUILDER_ID, 'gotSupplies')
|
|
||||||
os.sleep(0)
|
|
||||||
if not turtle.gotoLocation('supplies') then
|
|
||||||
Builder:log('failed to go to supply location')
|
|
||||||
--self.ready = false
|
|
||||||
Event.exitPullEvents()
|
|
||||||
end
|
|
||||||
turtle.setHeading(1)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
pt.y = pt.y - 2 -- location where builder should go for the chest to be above
|
|
||||||
|
|
||||||
turtle.select(15)
|
|
||||||
turtle.placeDown()
|
|
||||||
os.sleep(.1) -- random computer not connected error
|
|
||||||
local p = ChestProvider({ direction = 'up', wrapSide = 'bottom' })
|
|
||||||
for i = 1, 16 do
|
|
||||||
p:insert(i, 64)
|
|
||||||
end
|
|
||||||
|
|
||||||
Message.send(__BUILDER_ID, 'gotSupplies', { supplies = true, point = pt })
|
|
||||||
|
|
||||||
Message.waitForMessage('thanks', 5, __BUILDER_ID)
|
|
||||||
--os.sleep(0)
|
|
||||||
|
|
||||||
--p.condenseItems()
|
|
||||||
for i = 1, 16 do
|
|
||||||
p:extract(i, 64)
|
|
||||||
end
|
|
||||||
turtle.digDown()
|
|
||||||
turtle.status = 'waiting'
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
Message.addHandler('finished',
|
|
||||||
function(h, id)
|
|
||||||
if not id or id ~= __BUILDER_ID then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
Builder:finish()
|
|
||||||
end)
|
|
||||||
|
|
||||||
Event.on('turtle_abort',
|
|
||||||
function()
|
|
||||||
turtle.abort = false
|
|
||||||
turtle.status = 'aborting'
|
|
||||||
Builder:finish()
|
|
||||||
end)
|
|
||||||
|
|
||||||
local function onTheWay() -- parallel routine
|
|
||||||
while true do
|
|
||||||
local e, side, _id, id, msg, distance = os.pullEvent('modem_message')
|
|
||||||
if Builder.ready then
|
|
||||||
if id == __BUILDER_ID and msg and msg.type then
|
|
||||||
if msg.type == 'needSupplies' then
|
|
||||||
Message.send(__BUILDER_ID, 'gotSupplies', { supplies = true })
|
|
||||||
elseif msg.type == 'builder' then
|
|
||||||
Builder.lastPoint = msg.contents
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local args = {...}
|
|
||||||
if #args < 2 then
|
|
||||||
error('syntax: <builder id> <facing>')
|
|
||||||
end
|
|
||||||
|
|
||||||
__BUILDER_ID = tonumber(args[1])
|
|
||||||
|
|
||||||
maxStackDB:load()
|
|
||||||
|
|
||||||
itemInfoDB = TableDB({
|
|
||||||
fileName = 'items.db'
|
|
||||||
})
|
|
||||||
|
|
||||||
itemInfoDB:load()
|
|
||||||
|
|
||||||
Builder.itemProvider = MEProvider({ direction = args[2] })
|
|
||||||
if not Builder.itemProvider:isValid() then
|
|
||||||
local sides = {
|
|
||||||
east = 'west',
|
|
||||||
west = 'east',
|
|
||||||
north = 'south',
|
|
||||||
south = 'north',
|
|
||||||
}
|
|
||||||
|
|
||||||
Builder.itemProvider = ChestProvider({ direction = sides[args[2]], wrapSide = 'front' })
|
|
||||||
if not Builder.itemProvider:isValid() then
|
|
||||||
error('A chest or ME interface must be in front of turtle')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
turtle.run(function()
|
|
||||||
turtle.setPoint({ x = -1, z = -2, y = -1, heading = 1 })
|
|
||||||
|
|
||||||
turtle.saveLocation('supplies')
|
|
||||||
|
|
||||||
Event.pullEvents(onTheWay)
|
|
||||||
end)
|
|
||||||
89
apps/t.lua
89
apps/t.lua
@@ -1,89 +0,0 @@
|
|||||||
function doCommand(command, moves)
|
|
||||||
|
|
||||||
local function format(value)
|
|
||||||
if type(value) == 'boolean' then
|
|
||||||
if value then return 'true' end
|
|
||||||
return 'false'
|
|
||||||
end
|
|
||||||
if type(value) ~= 'table' then
|
|
||||||
return value
|
|
||||||
end
|
|
||||||
local str
|
|
||||||
for k,v in pairs(value) do
|
|
||||||
if not str then
|
|
||||||
str = '{ '
|
|
||||||
else
|
|
||||||
str = str .. ', '
|
|
||||||
end
|
|
||||||
str = str .. k .. '=' .. tostring(v)
|
|
||||||
end
|
|
||||||
if str then
|
|
||||||
str = str .. ' }'
|
|
||||||
else
|
|
||||||
str = '{ }'
|
|
||||||
end
|
|
||||||
|
|
||||||
return str
|
|
||||||
end
|
|
||||||
|
|
||||||
local function runCommand(fn, arg)
|
|
||||||
local r = { fn(arg) }
|
|
||||||
if r[2] then
|
|
||||||
print(format(r[1]) .. ': ' .. format(r[2]))
|
|
||||||
elseif r[1] then
|
|
||||||
print(format(r[1]))
|
|
||||||
end
|
|
||||||
return r[1]
|
|
||||||
end
|
|
||||||
|
|
||||||
local cmds = {
|
|
||||||
[ 's' ] = turtle.select,
|
|
||||||
[ 'rf' ] = turtle.refuel,
|
|
||||||
[ 'gh' ] = function() turtle.pathfind({ x = 0, y = 0, z = 0, heading = 0}) end,
|
|
||||||
}
|
|
||||||
|
|
||||||
local repCmds = {
|
|
||||||
[ 'u' ] = turtle.up,
|
|
||||||
[ 'd' ] = turtle.down,
|
|
||||||
[ 'f' ] = turtle.forward,
|
|
||||||
[ 'r' ] = turtle.turnRight,
|
|
||||||
[ 'l' ] = turtle.turnLeft,
|
|
||||||
[ 'ta' ] = turtle.turnAround,
|
|
||||||
[ 'DD' ] = turtle.digDown,
|
|
||||||
[ 'DU' ] = turtle.digUp,
|
|
||||||
[ 'D' ] = turtle.dig,
|
|
||||||
[ 'p' ] = turtle.place,
|
|
||||||
[ 'pu' ] = turtle.placeUp,
|
|
||||||
[ 'pd' ] = turtle.placeDown,
|
|
||||||
[ 'b' ] = turtle.back,
|
|
||||||
[ 'gfl' ] = turtle.getFuelLevel,
|
|
||||||
[ 'gp' ] = turtle.getPoint,
|
|
||||||
[ 'R' ] = function() turtle.setPoint({x = 0, y = 0, z = 0, heading = 0}) return turtle.point end
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmds[command] then
|
|
||||||
runCommand(cmds[command], moves)
|
|
||||||
elseif repCmds[command] then
|
|
||||||
for i = 1, moves do
|
|
||||||
if not runCommand(repCmds[command]) then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local args = {...}
|
|
||||||
|
|
||||||
if #args > 0 then
|
|
||||||
doCommand(args[1], args[2] or 1)
|
|
||||||
else
|
|
||||||
print('Enter command (q to quit):')
|
|
||||||
while true do
|
|
||||||
local cmd = read()
|
|
||||||
if cmd == 'q' then break
|
|
||||||
end
|
|
||||||
args = { }
|
|
||||||
cmd:gsub('%w+', function(w) table.insert(args, w) end)
|
|
||||||
doCommand(args[1], args[2] or 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
local args = {...}
|
|
||||||
|
|
||||||
if not args[1] then
|
|
||||||
print("Usage:")
|
|
||||||
print(shell.getRunningProgram() .. " <program> [program arguments, ...]")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local path = shell.resolveProgram(args[1]) or shell.resolve(args[1])
|
|
||||||
|
|
||||||
-- here be dragons
|
|
||||||
if fs.exists(path) then
|
|
||||||
local eshell = setmetatable({getRunningProgram=function() return path end}, {__index = shell})
|
|
||||||
local env = setmetatable({shell=eshell}, {__index=_ENV})
|
|
||||||
|
|
||||||
local f = fs.open(path, "r")
|
|
||||||
local d = f.readAll()
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
local func, e = load(d, fs.getName(path), nil, env)
|
|
||||||
if not func then
|
|
||||||
printError("Syntax error:")
|
|
||||||
printError(" " .. e)
|
|
||||||
else
|
|
||||||
table.remove(args, 1)
|
|
||||||
xpcall(function() func(unpack(args)) end, function(err)
|
|
||||||
local trace = {}
|
|
||||||
local i, hitEnd, _, e = 4, false
|
|
||||||
repeat
|
|
||||||
_, e = pcall(function() error("<tracemarker>", i) end)
|
|
||||||
i = i + 1
|
|
||||||
if e == "xpcall: <tracemarker>" then
|
|
||||||
hitEnd = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
table.insert(trace, e)
|
|
||||||
until i > 10
|
|
||||||
table.remove(trace)
|
|
||||||
if err:match("^" .. trace[1]:match("^(.-:%d+)")) then table.remove(trace, 1) end
|
|
||||||
printError("\nProgram has crashed! Stack trace:")
|
|
||||||
printError(err)
|
|
||||||
for i, v in ipairs(trace) do
|
|
||||||
printError(" at " .. v:match("^(.-:%d+)"))
|
|
||||||
end
|
|
||||||
if not hitEnd then
|
|
||||||
printError(" ...")
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
printError("program not found")
|
|
||||||
end
|
|
||||||
@@ -1,745 +0,0 @@
|
|||||||
requireInjector(getfenv(1))
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Requirements:
|
|
||||||
Place turtle against an oak tree or oak sapling
|
|
||||||
Area around turtle must be flat and can only be dirt or grass
|
|
||||||
(10 blocks in each direction from turtle)
|
|
||||||
Turtle must have: crafting table, chest
|
|
||||||
Turtle must have a pick equipped on the left side
|
|
||||||
|
|
||||||
Optional:
|
|
||||||
Add additional sapling types that can grow with a single sapling
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
If the turtle does not get any saplings from the initial tree, place
|
|
||||||
down another sapling in front of the turtle.
|
|
||||||
|
|
||||||
The program will be able to survive server restarts as long as it has
|
|
||||||
created the cobblestone line. If the program is stopped before that time,
|
|
||||||
place the turtle in the original position before restarting the program.
|
|
||||||
]]--
|
|
||||||
|
|
||||||
local ChestAdapter = require('chestAdapter18')
|
|
||||||
local Craft = require('turtle.craft')
|
|
||||||
local Level = require('turtle.level')
|
|
||||||
local Pathing = require('turtle.pathfind')
|
|
||||||
local Point = require('point')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
local FUEL_BASE = 0
|
|
||||||
local FUEL_DIRE = FUEL_BASE + 10
|
|
||||||
local FUEL_GOOD = FUEL_BASE + 2000
|
|
||||||
|
|
||||||
local MIN_CHARCOAL = 24
|
|
||||||
local MAX_SAPLINGS = 32
|
|
||||||
|
|
||||||
local GRID_WIDTH = 8
|
|
||||||
local GRID_LENGTH = 10
|
|
||||||
local GRID = {
|
|
||||||
TL = { x = 8, y = 0, z = -8 },
|
|
||||||
TR = { x = 8, y = 0, z = 8 },
|
|
||||||
BL = { x = -10, y = 0, z = -8 },
|
|
||||||
BR = { x = -10, y = 0, z = 8 },
|
|
||||||
}
|
|
||||||
|
|
||||||
local HOME_PT = { x = 0, y = 0, z = 0, heading = 0 }
|
|
||||||
|
|
||||||
local DIG_BLACKLIST = {
|
|
||||||
[ 'minecraft:furnace' ] = true,
|
|
||||||
[ 'minecraft:lit_furnace' ] = true,
|
|
||||||
[ 'minecraft:chest' ] = true,
|
|
||||||
}
|
|
||||||
|
|
||||||
local COBBLESTONE = 'minecraft:cobblestone:0'
|
|
||||||
local CHARCOAL = 'minecraft:coal:1'
|
|
||||||
local OAK_LOG = 'minecraft:log:0'
|
|
||||||
local OAK_PLANK = 'minecraft:planks:0'
|
|
||||||
local CHEST = 'minecraft:chest:0'
|
|
||||||
local FURNACE = 'minecraft:furnace:0'
|
|
||||||
local SAPLING = 'minecraft:sapling:0'
|
|
||||||
local STONE = 'minecraft:stone:0'
|
|
||||||
local TORCH = 'minecraft:torch:0'
|
|
||||||
local DIRT = 'minecraft:dirt:0'
|
|
||||||
local APPLE = 'minecraft:apple:0'
|
|
||||||
local STICK = 'minecraft:stick:0'
|
|
||||||
local CRAFTING_TABLE = 'minecraft:crafting_table:0'
|
|
||||||
|
|
||||||
local ALL_SAPLINGS = {
|
|
||||||
SAPLING
|
|
||||||
}
|
|
||||||
|
|
||||||
local state = Util.readTable('usr/config/treefarm') or {
|
|
||||||
trees = {
|
|
||||||
{ x = 1, y = 0, z = 0 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
local clock = os.clock()
|
|
||||||
local recipes = Util.readTable('usr/etc/recipes.db') or { }
|
|
||||||
|
|
||||||
Craft.setRecipes(recipes)
|
|
||||||
|
|
||||||
local function inspect(fn)
|
|
||||||
local s, item = fn()
|
|
||||||
if s and item then
|
|
||||||
return item.name .. ':' .. item.metadata
|
|
||||||
end
|
|
||||||
return 'minecraft:air:0'
|
|
||||||
end
|
|
||||||
|
|
||||||
local function setState(key, value)
|
|
||||||
state[key] = value
|
|
||||||
Util.writeTable('usr/config/treefarm', state)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function refuel()
|
|
||||||
if turtle.getFuelLevel() < FUEL_GOOD then
|
|
||||||
local charcoal = turtle.getItemCount(CHARCOAL)
|
|
||||||
if charcoal > 1 then
|
|
||||||
turtle.refuel(CHARCOAL, math.min(charcoal - 1, MIN_CHARCOAL / 2))
|
|
||||||
print('fuel: ' .. turtle.getFuelLevel())
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function safePlaceBlock(item)
|
|
||||||
|
|
||||||
if turtle.placeUp(item) then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local s, m = turtle.inspectUp()
|
|
||||||
if s and not DIG_BLACKLIST[m.name] then
|
|
||||||
turtle.digUp()
|
|
||||||
return turtle.placeUp(item)
|
|
||||||
end
|
|
||||||
|
|
||||||
turtle.forward()
|
|
||||||
return turtle.placeUp(item)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function craftItem(item, qty)
|
|
||||||
|
|
||||||
local success
|
|
||||||
|
|
||||||
if safePlaceBlock(CHEST) then
|
|
||||||
|
|
||||||
if turtle.equip('left', 'minecraft:crafting_table') then
|
|
||||||
|
|
||||||
local chestAdapter = ChestAdapter({
|
|
||||||
wrapSide = 'top',
|
|
||||||
direction = 'down',
|
|
||||||
})
|
|
||||||
if not chestAdapter:isValid() then
|
|
||||||
print('invalid chestAdapter')
|
|
||||||
read()
|
|
||||||
end
|
|
||||||
|
|
||||||
Util.print('Crafting %d %s', (qty or 1), item)
|
|
||||||
success = Craft.craftRecipe(recipes[item], qty or 1, chestAdapter)
|
|
||||||
|
|
||||||
repeat until not turtle.suckUp()
|
|
||||||
end
|
|
||||||
turtle.equip('left', 'minecraft:diamond_pickaxe')
|
|
||||||
turtle.digUp()
|
|
||||||
end
|
|
||||||
|
|
||||||
return success
|
|
||||||
end
|
|
||||||
|
|
||||||
local function cook(item, count, result, fuel, fuelCount)
|
|
||||||
|
|
||||||
setState('cooking', true)
|
|
||||||
|
|
||||||
fuel = fuel or CHARCOAL
|
|
||||||
fuelCount = fuelCount or math.ceil(count / 8)
|
|
||||||
Util.print('Making %d %s', count, result)
|
|
||||||
|
|
||||||
turtle.dropForwardAt(state.furnace, fuel, fuelCount)
|
|
||||||
turtle.dropDownAt(state.furnace, item, count)
|
|
||||||
|
|
||||||
count = count + turtle.getItemCount(result)
|
|
||||||
turtle.select(1)
|
|
||||||
turtle.pathfind(Point.below(state.furnace))
|
|
||||||
repeat
|
|
||||||
os.sleep(1)
|
|
||||||
turtle.suckUp()
|
|
||||||
until turtle.getItemCount(result) >= count
|
|
||||||
|
|
||||||
setState('cooking')
|
|
||||||
end
|
|
||||||
|
|
||||||
local function makeSingleCharcoal()
|
|
||||||
|
|
||||||
local slots = turtle.getSummedInventory()
|
|
||||||
|
|
||||||
if not state.furnace or
|
|
||||||
slots[CHARCOAL] or
|
|
||||||
not slots[OAK_LOG] or
|
|
||||||
slots[OAK_LOG].count < 2 then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
turtle.faceAgainst(state.furnace)
|
|
||||||
if craftItem(OAK_PLANK) then
|
|
||||||
cook(OAK_LOG, 1, CHARCOAL, OAK_PLANK, 1)
|
|
||||||
turtle.refuel(OAK_PLANK)
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function makeCharcoal()
|
|
||||||
|
|
||||||
local slots = turtle.getSummedInventory()
|
|
||||||
|
|
||||||
if not state.furnace or
|
|
||||||
not slots[CHARCOAL] or
|
|
||||||
slots[CHARCOAL].count >= MIN_CHARCOAL then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function getLogSlot(slots)
|
|
||||||
local maxslot = { count = 0 }
|
|
||||||
for k,slot in pairs(slots) do
|
|
||||||
if string.match(k, 'minecraft:log') then
|
|
||||||
if slot.count > maxslot.count then
|
|
||||||
maxslot = slot
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return maxslot
|
|
||||||
end
|
|
||||||
|
|
||||||
repeat
|
|
||||||
local slots = turtle.getSummedInventory()
|
|
||||||
local charcoal = slots[CHARCOAL].count
|
|
||||||
local slot = getLogSlot(slots)
|
|
||||||
|
|
||||||
if slot.count < 8 then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
local toCook = math.min(charcoal, math.floor(slot.count / 8))
|
|
||||||
toCook = math.min(toCook, math.floor((MIN_CHARCOAL + 8 - charcoal) / 8))
|
|
||||||
toCook = toCook * 8
|
|
||||||
|
|
||||||
cook(slot.key, toCook, CHARCOAL)
|
|
||||||
|
|
||||||
until charcoal + toCook >= MIN_CHARCOAL
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function emptyFurnace()
|
|
||||||
if state.cooking then
|
|
||||||
|
|
||||||
print('Emptying furnace')
|
|
||||||
|
|
||||||
turtle.suckDownAt(state.furnace)
|
|
||||||
turtle.suckForwardAt(state.furnace)
|
|
||||||
turtle.suckUpAt(state.furnace)
|
|
||||||
setState('cooking')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function getCobblestone(count)
|
|
||||||
|
|
||||||
local slots = turtle.getSummedInventory()
|
|
||||||
|
|
||||||
if not slots[COBBLESTONE] or slots[COBBLESTONE].count < count then
|
|
||||||
|
|
||||||
print('Collecting cobblestone')
|
|
||||||
|
|
||||||
slots[COBBLESTONE] = true
|
|
||||||
slots[DIRT] = true
|
|
||||||
|
|
||||||
local pt = Point.copy(GRID.BR)
|
|
||||||
pt.x = GRID.BR.x + 2
|
|
||||||
pt.z = GRID.BR.z - 2
|
|
||||||
|
|
||||||
turtle.pathfind(pt)
|
|
||||||
|
|
||||||
repeat
|
|
||||||
turtle.select(1)
|
|
||||||
turtle.digDown()
|
|
||||||
turtle.down()
|
|
||||||
for i = 1, 4 do
|
|
||||||
if inspect(turtle.inspect) == STONE then
|
|
||||||
turtle.dig()
|
|
||||||
end
|
|
||||||
turtle.turnRight()
|
|
||||||
end
|
|
||||||
|
|
||||||
for item in pairs(turtle.getSummedInventory()) do
|
|
||||||
if not slots[item] then
|
|
||||||
turtle.drop(item)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
until turtle.getItemCount(COBBLESTONE) >= count
|
|
||||||
|
|
||||||
turtle.gotoPoint(pt)
|
|
||||||
turtle.placeDown(DIRT)
|
|
||||||
|
|
||||||
turtle.drop(DIRT)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function createFurnace()
|
|
||||||
|
|
||||||
if not state.furnace then
|
|
||||||
if turtle.getFuelLevel() < FUEL_BASE + 100 then
|
|
||||||
return true -- try again later
|
|
||||||
end
|
|
||||||
print('Adding a furnace')
|
|
||||||
getCobblestone(8)
|
|
||||||
|
|
||||||
if craftItem(FURNACE) then
|
|
||||||
turtle.drop(COBBLESTONE)
|
|
||||||
local furnacePt = { x = GRID.BL.x + 2, y = 1, z = GRID.BL.z + 2 }
|
|
||||||
turtle.placeAt(furnacePt, FURNACE)
|
|
||||||
setState('furnace', furnacePt)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function createPerimeter()
|
|
||||||
|
|
||||||
if not state.perimeter then
|
|
||||||
if not state.furnace or
|
|
||||||
turtle.getFuelLevel() < FUEL_BASE + 500 or
|
|
||||||
turtle.getItemCount(OAK_LOG) == 0 or
|
|
||||||
not craftItem(OAK_PLANK, 2) then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
print('Creating a perimeter')
|
|
||||||
|
|
||||||
getCobblestone(GRID_WIDTH * 2 + 1)
|
|
||||||
cook(COBBLESTONE, 2, STONE, OAK_PLANK, 2)
|
|
||||||
turtle.refuel(OAK_PLANK)
|
|
||||||
|
|
||||||
turtle.pathfind(GRID.BL)
|
|
||||||
turtle.digDown()
|
|
||||||
turtle.placeDown(STONE)
|
|
||||||
|
|
||||||
turtle.setMoveCallback(function()
|
|
||||||
local target = COBBLESTONE
|
|
||||||
if math.abs(turtle.point.x) == GRID_LENGTH and
|
|
||||||
math.abs(turtle.point.z) == GRID_WIDTH then
|
|
||||||
target = STONE
|
|
||||||
end
|
|
||||||
|
|
||||||
if inspect(turtle.inspectDown) ~= target then
|
|
||||||
turtle.digDown()
|
|
||||||
turtle.placeDown(target)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
turtle.pathfind(GRID.BR)
|
|
||||||
|
|
||||||
turtle.clearMoveCallback()
|
|
||||||
turtle.drop(COBBLESTONE)
|
|
||||||
turtle.drop(DIRT)
|
|
||||||
|
|
||||||
setState('perimeter', true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function createChests()
|
|
||||||
if state.chest_1 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if state.perimeter and
|
|
||||||
turtle.getFuelLevel() > FUEL_GOOD and
|
|
||||||
Craft.canCraft(CHEST, 4, turtle.getSummedInventory()) then
|
|
||||||
|
|
||||||
print('Adding storage')
|
|
||||||
if craftItem(CHEST, 4) then
|
|
||||||
|
|
||||||
local pt = Point.copy(GRID.BL)
|
|
||||||
pt.x = pt.x + 1
|
|
||||||
pt.y = pt.y - 1
|
|
||||||
|
|
||||||
for i = 1, 2 do
|
|
||||||
pt.z = pt.z + 1
|
|
||||||
|
|
||||||
turtle.digDownAt(pt)
|
|
||||||
turtle.placeDown(CHEST)
|
|
||||||
|
|
||||||
pt.z = pt.z + 1
|
|
||||||
|
|
||||||
turtle.digDownAt(pt)
|
|
||||||
turtle.placeDown(CHEST)
|
|
||||||
|
|
||||||
setState('chest_' .. i, Util.shallowCopy(pt))
|
|
||||||
|
|
||||||
pt.z = pt.z + 1
|
|
||||||
end
|
|
||||||
turtle.drop(DIRT)
|
|
||||||
turtle.refuel(OAK_PLANK)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function dropOffItems()
|
|
||||||
|
|
||||||
if state.chest_1 then
|
|
||||||
local slots = turtle.getSummedInventory()
|
|
||||||
|
|
||||||
if state.chest_1 and
|
|
||||||
slots[CHARCOAL] and
|
|
||||||
slots[CHARCOAL].count >= MIN_CHARCOAL and
|
|
||||||
(turtle.getItemCount('minecraft:log') > 0 or
|
|
||||||
turtle.getItemCount('minecraft:log2') > 0) then
|
|
||||||
|
|
||||||
print('Storing logs')
|
|
||||||
turtle.pathfind(Point.above(state.chest_1))
|
|
||||||
turtle.dropDown('minecraft:log')
|
|
||||||
turtle.dropDown('minecraft:log2')
|
|
||||||
end
|
|
||||||
|
|
||||||
if slots[APPLE] then
|
|
||||||
print('Storing apples')
|
|
||||||
turtle.dropDownAt(state.chest_2, APPLE)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function eatSaplings()
|
|
||||||
|
|
||||||
local slots = turtle.getSummedInventory()
|
|
||||||
|
|
||||||
for _, sapling in pairs(ALL_SAPLINGS) do
|
|
||||||
if slots[sapling] and slots[sapling].count > MAX_SAPLINGS then
|
|
||||||
turtle.refuel(sapling, slots[sapling].count - MAX_SAPLINGS)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function placeTorches()
|
|
||||||
if state.torches then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if turtle.getFuelLevel() > 100 and
|
|
||||||
Craft.canCraft(TORCH, 4, turtle.getSummedInventory()) then
|
|
||||||
|
|
||||||
print('Placing torches')
|
|
||||||
|
|
||||||
if craftItem(TORCH, 4) then
|
|
||||||
local pts = { }
|
|
||||||
for x = -4, 4, 8 do
|
|
||||||
for z = -4, 4, 8 do
|
|
||||||
table.insert(pts, { x = x, y = 0, z = z })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
Point.eachClosest(turtle.point, pts, function(pt)
|
|
||||||
turtle.placeAt(pt, TORCH)
|
|
||||||
end)
|
|
||||||
turtle.refuel(STICK)
|
|
||||||
turtle.refuel(OAK_PLANK)
|
|
||||||
setState('torches', true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function randomSapling()
|
|
||||||
|
|
||||||
local sapling = SAPLING
|
|
||||||
|
|
||||||
if #state.trees > 1 then
|
|
||||||
ALL_SAPLINGS = { }
|
|
||||||
|
|
||||||
local slots = turtle.getFilledSlots()
|
|
||||||
for _, slot in pairs(slots) do
|
|
||||||
if slot.name == 'minecraft:sapling' then
|
|
||||||
table.insert(ALL_SAPLINGS, slot.key)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if #ALL_SAPLINGS > 0 then
|
|
||||||
sapling = ALL_SAPLINGS[math.random(1, #ALL_SAPLINGS)]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return sapling
|
|
||||||
end
|
|
||||||
|
|
||||||
local function fellTree(pt)
|
|
||||||
|
|
||||||
local function desperateRefuel(min)
|
|
||||||
if turtle.getFuelLevel() < min then
|
|
||||||
local logs = turtle.getItemCount(OAK_LOG)
|
|
||||||
if logs > 0 then
|
|
||||||
if craftItem(OAK_PLANK, math.min(8, logs * 4)) then
|
|
||||||
turtle.refuel(OAK_PLANK)
|
|
||||||
print('fuel: ' .. turtle.getFuelLevel())
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
turtle.setMoveCallback(function() desperateRefuel(FUEL_DIRE) end)
|
|
||||||
|
|
||||||
desperateRefuel(FUEL_DIRE)
|
|
||||||
|
|
||||||
if turtle.digUpAt(Point.above(pt)) then
|
|
||||||
Level(
|
|
||||||
{ x = GRID_WIDTH-1, y = 1, z = GRID_WIDTH-1 },
|
|
||||||
{ x = -(GRID_WIDTH-1), y = 50, z = -(GRID_WIDTH-1) },
|
|
||||||
Point.above(pt))
|
|
||||||
end
|
|
||||||
|
|
||||||
desperateRefuel(FUEL_BASE + 100)
|
|
||||||
turtle.clearMoveCallback()
|
|
||||||
turtle.setPolicy("attack")
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function fell()
|
|
||||||
|
|
||||||
local pts = Util.shallowCopy(state.trees)
|
|
||||||
|
|
||||||
local pt = table.remove(pts, math.random(1, #pts))
|
|
||||||
|
|
||||||
-- give the pathfinder hints about what to avoid (state.trees)
|
|
||||||
if not turtle.faceAgainst(pt, { blocks = Util.shallowCopy(state.trees) }) or
|
|
||||||
not string.match(inspect(turtle.inspect), 'minecraft:log') then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
print('Chopping')
|
|
||||||
|
|
||||||
local fuel = turtle.getFuelLevel()
|
|
||||||
|
|
||||||
-- push this point to the start of this list
|
|
||||||
table.insert(pts, 1, pt)
|
|
||||||
|
|
||||||
Point.eachClosest(turtle.point, pts, function(pt)
|
|
||||||
if turtle.faceAgainst(pt, { blocks = Util.shallowCopy(state.trees) }) and
|
|
||||||
string.match(inspect(turtle.inspect), 'minecraft:log') then
|
|
||||||
turtle.dig()
|
|
||||||
fellTree(pt)
|
|
||||||
end
|
|
||||||
turtle.placeAt(pt, randomSapling())
|
|
||||||
turtle.select(1)
|
|
||||||
end)
|
|
||||||
|
|
||||||
print('Used ' .. (fuel - turtle.getFuelLevel()) .. ' fuel')
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function moreTrees()
|
|
||||||
|
|
||||||
if #state.trees > 1 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if not state.chest_1 or turtle.getItemCount('minecraft:sapling') < 15 then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
print('Adding more trees')
|
|
||||||
|
|
||||||
local singleTree = state.trees[1]
|
|
||||||
|
|
||||||
state.trees = { }
|
|
||||||
for x = -2, 2, 1 do
|
|
||||||
for z = -2, 2, 2 do
|
|
||||||
table.insert(state.trees, { x = x, y = 0, z = z })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
turtle.digAt(singleTree)
|
|
||||||
fellTree(singleTree)
|
|
||||||
|
|
||||||
setState('trees', state.trees)
|
|
||||||
|
|
||||||
Point.eachClosest(turtle.point, state.trees, function(pt)
|
|
||||||
turtle.placeDownAt(pt, randomSapling())
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function getTurtleFacing(block)
|
|
||||||
local directions = {
|
|
||||||
[5] = 2,
|
|
||||||
[3] = 3,
|
|
||||||
[4] = 0,
|
|
||||||
[2] = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
if not safePlaceBlock(block) then
|
|
||||||
error('unable to place chest above')
|
|
||||||
end
|
|
||||||
local _, bi = turtle.inspectUp()
|
|
||||||
turtle.digUp()
|
|
||||||
return directions[bi.metadata]
|
|
||||||
end
|
|
||||||
|
|
||||||
function saveTurtleFacing()
|
|
||||||
if not state.facing then
|
|
||||||
setState('facing', getTurtleFacing(CHEST))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function findGround()
|
|
||||||
print('Locating ground level')
|
|
||||||
turtle.setPoint(HOME_PT)
|
|
||||||
|
|
||||||
while true do
|
|
||||||
local s, block = turtle.inspectDown()
|
|
||||||
|
|
||||||
if not s then block = { name = 'minecraft:air', metadata = 0 } end
|
|
||||||
b = block.name .. ':' .. block.metadata
|
|
||||||
|
|
||||||
if b == 'minecraft:dirt:0' or
|
|
||||||
b == 'minecraft:grass:0' or
|
|
||||||
block.name == 'minecraft:chest' then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
if b == COBBLESTONE or b == STONE then
|
|
||||||
error('lost')
|
|
||||||
end
|
|
||||||
|
|
||||||
if b == TORCH or DIG_BLACKLIST[block.name] then
|
|
||||||
turtle.forward()
|
|
||||||
else
|
|
||||||
turtle.digDown()
|
|
||||||
turtle.down()
|
|
||||||
end
|
|
||||||
|
|
||||||
if turtle.point.y < -20 then
|
|
||||||
error('lost')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
turtle.setPoint(HOME_PT)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function findHome()
|
|
||||||
|
|
||||||
if not state.perimeter then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
print('Determining location')
|
|
||||||
|
|
||||||
turtle.point.heading = getTurtleFacing(CHEST)
|
|
||||||
turtle.setHeading(state.facing)
|
|
||||||
turtle.point.heading = 0
|
|
||||||
|
|
||||||
local pt = Point.copy(turtle.point)
|
|
||||||
|
|
||||||
while inspect(turtle.inspectDown) ~= COBBLESTONE do
|
|
||||||
pt.x = pt.x - 1
|
|
||||||
turtle.pathfind(pt)
|
|
||||||
if pt.x < -20 then
|
|
||||||
error('lost')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
while inspect(turtle.inspectDown) == COBBLESTONE do
|
|
||||||
pt.z = pt.z - 1
|
|
||||||
turtle.pathfind(pt)
|
|
||||||
if pt.z < -20 then
|
|
||||||
error('lost')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
turtle.setPoint({
|
|
||||||
x = -(GRID_LENGTH),
|
|
||||||
y = 0,
|
|
||||||
z = -GRID_WIDTH,
|
|
||||||
heading = turtle.point.heading
|
|
||||||
})
|
|
||||||
|
|
||||||
-- when pathfinding - don't leave this box
|
|
||||||
Pathing.setBox({
|
|
||||||
x = GRID.TL.x,
|
|
||||||
y = GRID.TL.y,
|
|
||||||
z = GRID.TL.z,
|
|
||||||
ex = GRID.BR.x,
|
|
||||||
ey = 5,
|
|
||||||
ez = GRID.BR.z,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
local function updateClock()
|
|
||||||
|
|
||||||
local ONE_HOUR = 50
|
|
||||||
|
|
||||||
if os.clock() - clock > ONE_HOUR then
|
|
||||||
clock = os.clock()
|
|
||||||
else
|
|
||||||
print('sleeping for ' .. math.floor(ONE_HOUR - (os.clock() - clock)))
|
|
||||||
os.sleep(ONE_HOUR - (os.clock() - clock))
|
|
||||||
clock = os.clock()
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function startupCheck()
|
|
||||||
local slots = turtle.getSummedInventory()
|
|
||||||
|
|
||||||
if not slots[CHEST] or not slots[CRAFTING_TABLE] then
|
|
||||||
error('A chest and crafting table must be in inventory')
|
|
||||||
end
|
|
||||||
|
|
||||||
if state.facing and not state.perimeter then
|
|
||||||
print('Perimeter has not been established.')
|
|
||||||
print('Enter to continue if turtle is in the original starting position.')
|
|
||||||
read()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local tasks = {
|
|
||||||
{ desc = 'Startup check', fn = startupCheck },
|
|
||||||
{ desc = 'Finding ground', fn = findGround },
|
|
||||||
{ desc = 'Determine facing', fn = saveTurtleFacing },
|
|
||||||
{ desc = 'Finding home', fn = findHome },
|
|
||||||
{ desc = 'Emptying furnace', fn = emptyFurnace },
|
|
||||||
{ desc = 'Adding trees', fn = moreTrees },
|
|
||||||
{ desc = 'Chopping', fn = fell },
|
|
||||||
{ desc = 'Snacking', fn = eatSaplings },
|
|
||||||
{ desc = 'Creating chest', fn = createChests },
|
|
||||||
{ desc = 'Creating furnace', fn = createFurnace },
|
|
||||||
{ desc = 'Making charcoal', fn = makeSingleCharcoal },
|
|
||||||
{ desc = 'Making charcoal', fn = makeCharcoal },
|
|
||||||
{ desc = 'Creating perimeter', fn = createPerimeter },
|
|
||||||
{ desc = 'Placing torches', fn = placeTorches },
|
|
||||||
{ desc = 'Refueling', fn = refuel },
|
|
||||||
{ desc = 'Dropping off items', fn = dropOffItems },
|
|
||||||
{ desc = 'Condensing', fn = turtle.condense },
|
|
||||||
{ desc = 'Sleeping', fn = updateClock },
|
|
||||||
}
|
|
||||||
|
|
||||||
local s, m = turtle.run(function()
|
|
||||||
|
|
||||||
turtle.setPolicy("attack")
|
|
||||||
|
|
||||||
while not turtle.abort do
|
|
||||||
print('fuel: ' .. turtle.getFuelLevel())
|
|
||||||
for _,task in ipairs(Util.shallowCopy(tasks)) do
|
|
||||||
--print(task.desc)
|
|
||||||
turtle.status = task.desc
|
|
||||||
turtle.select(1)
|
|
||||||
if not task.fn() then
|
|
||||||
Util.filterInplace(tasks, function(v) return v.fn ~= task.fn end)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
if not s then
|
|
||||||
error('Failed')
|
|
||||||
end
|
|
||||||
10
builder/.package
Normal file
10
builder/.package
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
required = {
|
||||||
|
'core',
|
||||||
|
'turtle',
|
||||||
|
},
|
||||||
|
title = 'Schematic Builder',
|
||||||
|
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/builder',
|
||||||
|
description = [[Build structures from schematic files using a turtle or command computer. ]],
|
||||||
|
license = 'MIT',
|
||||||
|
}
|
||||||
154
builder/apis/base64.lua
Normal file
154
builder/apis/base64.lua
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
-- Base64 Encoder / Decoder
|
||||||
|
-- By KillaVanilla
|
||||||
|
-- see: http://www.computercraft.info/forums2/index.php?/topic/12450-killavanillas-various-apis/
|
||||||
|
|
||||||
|
local Base64 = { }
|
||||||
|
|
||||||
|
local bit = _G.bit
|
||||||
|
local _brshift = bit.brshift
|
||||||
|
local _bor = bit.bor
|
||||||
|
local _blshift = bit.blshift
|
||||||
|
local _band = bit.band
|
||||||
|
local _sub = string.sub
|
||||||
|
local os = _G.os
|
||||||
|
|
||||||
|
local alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||||
|
|
||||||
|
local function sixBitToBase64(input)
|
||||||
|
return _sub(alphabet, input+1, input+1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function base64ToSixBit(input)
|
||||||
|
for i=1, 64 do
|
||||||
|
if input == _sub(alphabet, i, i) then
|
||||||
|
return i-1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function octetToBase64(o1, o2, o3)
|
||||||
|
local i1 = sixBitToBase64(_brshift(_band(o1, 0xFC), 2))
|
||||||
|
local i2
|
||||||
|
local i3 = "="
|
||||||
|
local i4 = "="
|
||||||
|
if o2 then
|
||||||
|
i2 = sixBitToBase64(_bor( _blshift(_band(o1, 3), 4), _brshift(_band(o2, 0xF0), 4) ))
|
||||||
|
if not o3 then
|
||||||
|
i3 = sixBitToBase64(_blshift(_band(o2, 0x0F), 2))
|
||||||
|
else
|
||||||
|
i3 = sixBitToBase64(_bor( _blshift(_band(o2, 0x0F), 2), _brshift(_band(o3, 0xC0), 6) ))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
i2 = sixBitToBase64(_blshift(_band(o1, 3), 4))
|
||||||
|
end
|
||||||
|
if o3 then
|
||||||
|
i4 = sixBitToBase64(_band(o3, 0x3F))
|
||||||
|
end
|
||||||
|
|
||||||
|
return i1..i2..i3..i4
|
||||||
|
end
|
||||||
|
|
||||||
|
-- octet 1 needs characters 1/2
|
||||||
|
-- octet 2 needs characters 2/3
|
||||||
|
-- octet 3 needs characters 3/4
|
||||||
|
|
||||||
|
local function base64ToThreeOctet(s1)
|
||||||
|
local c1 = base64ToSixBit(_sub(s1, 1, 1))
|
||||||
|
local c2 = base64ToSixBit(_sub(s1, 2, 2))
|
||||||
|
local c3
|
||||||
|
local c4
|
||||||
|
local o1
|
||||||
|
local o2
|
||||||
|
local o3
|
||||||
|
if _sub(s1, 3, 3) == "=" then
|
||||||
|
c3 = nil
|
||||||
|
c4 = nil
|
||||||
|
elseif _sub(s1, 4, 4) == "=" then
|
||||||
|
c3 = base64ToSixBit(_sub(s1, 3, 3))
|
||||||
|
c4 = nil
|
||||||
|
else
|
||||||
|
c3 = base64ToSixBit(_sub(s1, 3, 3))
|
||||||
|
c4 = base64ToSixBit(_sub(s1, 4, 4))
|
||||||
|
end
|
||||||
|
o1 = _bor( _blshift(c1, 2), _brshift(_band( c2, 0x30 ), 4) )
|
||||||
|
if c3 then
|
||||||
|
o2 = _bor( _blshift(_band(c2, 0x0F), 4), _brshift(_band( c3, 0x3C ), 2) )
|
||||||
|
else
|
||||||
|
o2 = nil
|
||||||
|
end
|
||||||
|
if c4 then
|
||||||
|
o3 = _bor( _blshift(_band(c3, 3), 6), c4 )
|
||||||
|
else
|
||||||
|
o3 = nil
|
||||||
|
end
|
||||||
|
return o1, o2, o3
|
||||||
|
end
|
||||||
|
|
||||||
|
local function splitIntoBlocks(bytes)
|
||||||
|
local blockNum = 1
|
||||||
|
local blocks = {}
|
||||||
|
for i=1, #bytes, 3 do
|
||||||
|
blocks[blockNum] = {bytes[i], bytes[i+1], bytes[i+2]}
|
||||||
|
--[[
|
||||||
|
if #blocks[blockNum] < 3 then
|
||||||
|
for j=#blocks[blockNum]+1, 3 do
|
||||||
|
table.insert(blocks[blockNum], 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
]]
|
||||||
|
blockNum = blockNum+1
|
||||||
|
end
|
||||||
|
return blocks
|
||||||
|
end
|
||||||
|
|
||||||
|
function Base64.encode(bytes)
|
||||||
|
local blocks = splitIntoBlocks(bytes)
|
||||||
|
local output = ""
|
||||||
|
for i=1, #blocks do
|
||||||
|
output = output..octetToBase64( unpack(blocks[i]) )
|
||||||
|
end
|
||||||
|
return output
|
||||||
|
end
|
||||||
|
|
||||||
|
local function Throttle()
|
||||||
|
local ts = os.clock()
|
||||||
|
local timeout = .095
|
||||||
|
return function()
|
||||||
|
local nts = os.clock()
|
||||||
|
if nts > ts + timeout then
|
||||||
|
os.sleep(0)
|
||||||
|
ts = os.clock()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Base64.decode(str)
|
||||||
|
local bytes = {}
|
||||||
|
local blocks = {}
|
||||||
|
local blockNum = 1
|
||||||
|
local throttle = Throttle()
|
||||||
|
for i=1, #str, 4 do
|
||||||
|
blocks[blockNum] = _sub(str, i, i+3)
|
||||||
|
blockNum = blockNum+1
|
||||||
|
end
|
||||||
|
for i=1, #blocks do
|
||||||
|
local o1, o2, o3 = base64ToThreeOctet(blocks[i])
|
||||||
|
table.insert(bytes, o1)
|
||||||
|
table.insert(bytes, o2)
|
||||||
|
table.insert(bytes, o3)
|
||||||
|
throttle()
|
||||||
|
end
|
||||||
|
-- Remove padding:
|
||||||
|
--[[
|
||||||
|
for i=#bytes, 1, -1 do
|
||||||
|
if bytes[i] ~= 0 then
|
||||||
|
break
|
||||||
|
else
|
||||||
|
bytes[i] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
]]
|
||||||
|
return bytes
|
||||||
|
end
|
||||||
|
|
||||||
|
return Base64
|
||||||
549
builder/apis/blocks.lua
Normal file
549
builder/apis/blocks.lua
Normal file
@@ -0,0 +1,549 @@
|
|||||||
|
local class = require('opus.class')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
local TableDB = require('core.tableDB')
|
||||||
|
local JSON = require('opus.json')
|
||||||
|
|
||||||
|
-- see https://github.com/Khroki/MCEdit-Unified/blob/master/pymclevel/minecraft.yaml
|
||||||
|
-- see https://github.com/Khroki/MCEdit-Unified/blob/master/Items/minecraft/blocks.json
|
||||||
|
|
||||||
|
--[[-- blockDB --]]--
|
||||||
|
local blockDB = TableDB()
|
||||||
|
|
||||||
|
function blockDB:load()
|
||||||
|
local blocks = JSON.decodeFromFile('packages/core/etc/names/minecraft.json')
|
||||||
|
|
||||||
|
if not blocks then
|
||||||
|
error('Unable to read blocks.json')
|
||||||
|
end
|
||||||
|
|
||||||
|
for strId, block in pairs(blocks) do
|
||||||
|
strId = 'minecraft:' .. strId
|
||||||
|
if type(block.name) == 'string' then
|
||||||
|
self:add(block.id, 0, block.name, strId, block.place)
|
||||||
|
else
|
||||||
|
for nid,name in pairs(block.name) do
|
||||||
|
self:add(block.id, nid - 1, name, strId, block.place)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function blockDB:lookup(id, dmg)
|
||||||
|
if not id then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
return self.data[id .. ':' .. dmg]
|
||||||
|
end
|
||||||
|
|
||||||
|
function blockDB:add(id, dmg, name, strId, place)
|
||||||
|
local key = id .. ':' .. dmg
|
||||||
|
|
||||||
|
TableDB.add(self, key, {
|
||||||
|
id = id,
|
||||||
|
dmg = dmg,
|
||||||
|
name = name,
|
||||||
|
strId = strId,
|
||||||
|
place = place,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[-- placementDB --]]--
|
||||||
|
-- in memory table that expands the standardBlock and blockType tables for each item/dmg/placement combination
|
||||||
|
local placementDB = TableDB()
|
||||||
|
|
||||||
|
function placementDB:load(sbDB, btDB)
|
||||||
|
for k,v in pairs(sbDB.data) do
|
||||||
|
if v.place then
|
||||||
|
local bt = btDB.data[v.place]
|
||||||
|
if not bt then
|
||||||
|
error('missing block type: ' .. v.place)
|
||||||
|
end
|
||||||
|
local id, dmg = string.match(k, '(%d+):*(%d+)')
|
||||||
|
self:addSubsForBlockType(tonumber(id), tonumber(dmg), bt)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- special case for quartz pillars
|
||||||
|
self:addSubsForBlockType(155, 2, btDB.data['quartz-pillar'])
|
||||||
|
end
|
||||||
|
|
||||||
|
function placementDB:addSubsForBlockType(id, dmg, bt)
|
||||||
|
for _,sub in pairs(bt) do
|
||||||
|
local odmg = sub.odmg
|
||||||
|
if type(sub.odmg) == 'string' then
|
||||||
|
odmg = dmg + tonumber(string.match(odmg, '+(%d+)'))
|
||||||
|
end
|
||||||
|
|
||||||
|
local b = blockDB:lookup(id, dmg)
|
||||||
|
local strId = tostring(id)
|
||||||
|
if b then
|
||||||
|
strId = b.strId
|
||||||
|
end
|
||||||
|
|
||||||
|
self:add(
|
||||||
|
id,
|
||||||
|
odmg,
|
||||||
|
sub.sid or strId,
|
||||||
|
sub.sdmg or dmg,
|
||||||
|
sub.dir,
|
||||||
|
sub.extra)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function placementDB:add(id, dmg, sid, sdmg, direction, extra)
|
||||||
|
if direction and #direction == 0 then
|
||||||
|
direction = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local entry = {
|
||||||
|
oid = id, -- numeric ID
|
||||||
|
odmg = dmg, -- dmg with placement info
|
||||||
|
id = sid, -- string ID
|
||||||
|
dmg = sdmg, -- dmg without placement info
|
||||||
|
direction = direction,
|
||||||
|
}
|
||||||
|
if extra then
|
||||||
|
Util.merge(entry, extra)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.data[id .. ':' .. dmg] = entry
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[-- BlockTypeDB --]]--
|
||||||
|
local blockTypeDB = TableDB()
|
||||||
|
|
||||||
|
function blockTypeDB:addTemp(blockType, subs)
|
||||||
|
local bt = self.data[blockType]
|
||||||
|
if not bt then
|
||||||
|
bt = { }
|
||||||
|
self.data[blockType] = bt
|
||||||
|
end
|
||||||
|
for _,sub in pairs(subs) do
|
||||||
|
table.insert(bt, {
|
||||||
|
odmg = sub[1],
|
||||||
|
sid = sub[2],
|
||||||
|
sdmg = sub[3],
|
||||||
|
dir = sub[4],
|
||||||
|
extra = sub[5]
|
||||||
|
})
|
||||||
|
end
|
||||||
|
self.dirty = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function blockTypeDB:load()
|
||||||
|
blockTypeDB:addTemp('stairs', {
|
||||||
|
{ 0, nil, 0, 'east-up' },
|
||||||
|
{ 1, nil, 0, 'west-up' },
|
||||||
|
{ 2, nil, 0, 'south-up' },
|
||||||
|
{ 3, nil, 0, 'north-up' },
|
||||||
|
{ 4, nil, 0, 'east-down' },
|
||||||
|
{ 5, nil, 0, 'west-down' },
|
||||||
|
{ 6, nil, 0, 'south-down' },
|
||||||
|
{ 7, nil, 0, 'north-down' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('gate', {
|
||||||
|
{ 0, nil, 0, 'north' },
|
||||||
|
{ 1, nil, 0, 'east' },
|
||||||
|
{ 2, nil, 0, 'south' },
|
||||||
|
{ 3, nil, 0, 'west' },
|
||||||
|
{ 4, nil, 0, 'north' },
|
||||||
|
{ 5, nil, 0, 'east' },
|
||||||
|
{ 6, nil, 0, 'south' },
|
||||||
|
{ 7, nil, 0, 'west' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('pumpkin', {
|
||||||
|
{ 0, nil, 0, 'north-block' },
|
||||||
|
{ 1, nil, 0, 'east-block' },
|
||||||
|
{ 2, nil, 0, 'south-block' },
|
||||||
|
{ 3, nil, 0, 'west-block' },
|
||||||
|
{ 4, nil, 0, 'north-block' },
|
||||||
|
{ 5, nil, 0, 'east-block' },
|
||||||
|
{ 6, nil, 0, 'south-block' },
|
||||||
|
{ 7, nil, 0, 'west-block' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('anvil', {
|
||||||
|
{ 0, nil, 0, 'south' },
|
||||||
|
{ 1, nil, 0, 'east' },
|
||||||
|
{ 2, nil, 0, 'south'},
|
||||||
|
{ 3, nil, 0, 'east' },
|
||||||
|
{ 4, nil, 0, 'south' },
|
||||||
|
{ 5, nil, 0, 'east' },
|
||||||
|
{ 6, nil, 0, 'east' },
|
||||||
|
{ 7, nil, 0, 'south' },
|
||||||
|
{ 8, nil, 0, 'south' },
|
||||||
|
{ 9, nil, 0, 'east' },
|
||||||
|
{ 10, nil, 0, 'east' },
|
||||||
|
{ 11, nil, 0, 'south' },
|
||||||
|
{ 12, nil, 0 },
|
||||||
|
{ 13, nil, 0 },
|
||||||
|
{ 14, nil, 0 },
|
||||||
|
{ 15, nil, 0 },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('bed', {
|
||||||
|
{ 0, nil, 0, 'south' },
|
||||||
|
{ 1, nil, 0, 'west' },
|
||||||
|
{ 2, nil, 0, 'north' },
|
||||||
|
{ 3, nil, 0, 'east' },
|
||||||
|
{ 4, nil, 0, 'south' },
|
||||||
|
{ 5, nil, 0, 'west' },
|
||||||
|
{ 6, nil, 0, 'north' },
|
||||||
|
{ 7, nil, 0, 'east' },
|
||||||
|
{ 8, 'minecraft:air', 0 },
|
||||||
|
{ 9, 'minecraft:air', 0 },
|
||||||
|
{ 10, 'minecraft:air', 0 },
|
||||||
|
{ 11, 'minecraft:air', 0 },
|
||||||
|
{ 12, 'minecraft:air', 0 },
|
||||||
|
{ 13, 'minecraft:air', 0 },
|
||||||
|
{ 14, 'minecraft:air', 0 },
|
||||||
|
{ 15, 'minecraft:air', 0 },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('comparator', {
|
||||||
|
{ 0, nil, 0, 'south' },
|
||||||
|
{ 1, nil, 0, 'west' },
|
||||||
|
{ 2, nil, 0, 'north' },
|
||||||
|
{ 3, nil, 0, 'east' },
|
||||||
|
{ 4, nil, 0, 'south' },
|
||||||
|
{ 5, nil, 0, 'west' },
|
||||||
|
{ 6, nil, 0, 'north' },
|
||||||
|
{ 7, nil, 0, 'east' },
|
||||||
|
{ 8, nil, 0, 'south' },
|
||||||
|
{ 9, nil, 0, 'west' },
|
||||||
|
{ 10, nil, 0, 'north' },
|
||||||
|
{ 11, nil, 0, 'east' },
|
||||||
|
{ 12, nil, 0, 'south' },
|
||||||
|
{ 13, nil, 0, 'west' },
|
||||||
|
{ 14, nil, 0, 'north' },
|
||||||
|
{ 15, nil, 0, 'east' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('quartz-pillar', {
|
||||||
|
{ 2, nil, 2 },
|
||||||
|
{ 3, nil, 2, 'north-south-block' },
|
||||||
|
{ 4, nil, 2, 'east-west-block' }, -- should be east-west-block
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('hay-bale', {
|
||||||
|
{ 0, nil, 0 },
|
||||||
|
{ 4, nil, 0, 'east-west-block' }, -- should be east-west-block
|
||||||
|
{ 8, nil, 0, 'north-south-block' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('button', {
|
||||||
|
{ 1, nil, 0, 'west-block' },
|
||||||
|
{ 2, nil, 0, 'east-block' },
|
||||||
|
{ 3, nil, 0, 'north-block' },
|
||||||
|
{ 4, nil, 0, 'south-block' },
|
||||||
|
{ 5, nil, 0 }, -- block top
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('cauldron', {
|
||||||
|
{ 0, nil, 0 },
|
||||||
|
{ 1, nil, 0 },
|
||||||
|
{ 2, nil, 0 },
|
||||||
|
{ 3, nil, 0 },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('dispenser', {
|
||||||
|
{ 0, nil, 0, 'wrench-down' },
|
||||||
|
{ 1, nil, 0, 'wrench-up' },
|
||||||
|
{ 2, nil, 0, 'south' },
|
||||||
|
{ 3, nil, 0, 'north' },
|
||||||
|
{ 4, nil, 0, 'east' },
|
||||||
|
{ 5, nil, 0, 'west' },
|
||||||
|
{ 9, nil, 0 },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('end_rod', {
|
||||||
|
{ 0, nil, 0, 'wrench-down' },
|
||||||
|
{ 1, nil, 0, 'wrench-up' },
|
||||||
|
{ 2, nil, 0, 'south-block-flip' },
|
||||||
|
{ 3, nil, 0, 'north-block-flip' },
|
||||||
|
{ 4, nil, 0, 'east-block-flip' },
|
||||||
|
{ 5, nil, 0, 'west-block-flip' },
|
||||||
|
{ 9, nil, 0 },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('hopper', {
|
||||||
|
{ 0, nil, 0 },
|
||||||
|
{ 1, nil, 0 },
|
||||||
|
{ 2, nil, 0, 'south-block' },
|
||||||
|
{ 3, nil, 0, 'north-block' },
|
||||||
|
{ 4, nil, 0, 'east-block' },
|
||||||
|
{ 5, nil, 0, 'west-block' },
|
||||||
|
{ 8, nil, 0 },
|
||||||
|
{ 9, nil, 0 },
|
||||||
|
{ 10, nil, 0 },
|
||||||
|
{ 11, nil, 0, 'south-block' },
|
||||||
|
{ 12, nil, 0, 'north-block' },
|
||||||
|
{ 13, nil, 0, 'east-block' },
|
||||||
|
{ 14, nil, 0, 'west-block' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('mobhead', {
|
||||||
|
{ 0, nil, 0 },
|
||||||
|
{ 1, nil, 0 },
|
||||||
|
{ 2, nil, 0, 'south-block' },
|
||||||
|
{ 3, nil, 0, 'north-block' },
|
||||||
|
{ 4, nil, 0, 'west-block' },
|
||||||
|
{ 5, nil, 0, 'east-block' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('rail', {
|
||||||
|
{ 0, nil, 0, 'south' },
|
||||||
|
{ 1, nil, 0, 'east' },
|
||||||
|
{ 2, nil, 0, 'east' },
|
||||||
|
{ 3, nil, 0, 'east' },
|
||||||
|
{ 4, nil, 0, 'south' },
|
||||||
|
{ 5, nil, 0, 'south' },
|
||||||
|
{ 6, nil, 0, 'east' },
|
||||||
|
{ 7, nil, 0, 'south' },
|
||||||
|
{ 8, nil, 0, 'east' },
|
||||||
|
{ 9, nil, 0, 'south' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('adp-rail', {
|
||||||
|
{ 0, nil, 0, 'south' },
|
||||||
|
{ 1, nil, 0, 'east' },
|
||||||
|
{ 2, nil, 0, 'east' },
|
||||||
|
{ 3, nil, 0, 'east' },
|
||||||
|
{ 4, nil, 0, 'south' },
|
||||||
|
{ 5, nil, 0, 'south' },
|
||||||
|
{ 8, nil, 0, 'south' },
|
||||||
|
{ 9, nil, 0, 'east' },
|
||||||
|
{ 10, nil, 0, 'east' },
|
||||||
|
{ 11, nil, 0, 'east' },
|
||||||
|
{ 12, nil, 0, 'south' },
|
||||||
|
{ 13, nil, 0, 'south' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('signpost', {
|
||||||
|
{ 0, nil, 0, 'north' },
|
||||||
|
{ 1, nil, 0, 'north', { facing = 1 } },
|
||||||
|
{ 2, nil, 0, 'north', { facing = 2 } },
|
||||||
|
{ 3, nil, 0, 'north', { facing = 3 } },
|
||||||
|
{ 4, nil, 0, 'east' },
|
||||||
|
{ 5, nil, 0, 'east', { facing = 1 } },
|
||||||
|
{ 6, nil, 0, 'east', { facing = 2 } },
|
||||||
|
{ 7, nil, 0, 'east', { facing = 3 } },
|
||||||
|
{ 8, nil, 0, 'south' },
|
||||||
|
{ 9, nil, 0, 'south', { facing = 1 } },
|
||||||
|
{ 10, nil, 0, 'south', { facing = 2 } },
|
||||||
|
{ 11, nil, 0, 'south', { facing = 3 } },
|
||||||
|
{ 12, nil, 0, 'west' },
|
||||||
|
{ 13, nil, 0, 'west', { facing = 1 } },
|
||||||
|
{ 14, nil, 0, 'west', { facing = 2 } },
|
||||||
|
{ 15, nil, 0, 'west', { facing = 3 } },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('vine', {
|
||||||
|
{ 0, nil, 0 },
|
||||||
|
{ 1, nil, 0, 'south-block-vine' },
|
||||||
|
{ 2, nil, 0, 'west-block-vine' },
|
||||||
|
{ 3, nil, 0, 'south-block-vine' },
|
||||||
|
{ 4, nil, 0, 'north-block-vine' },
|
||||||
|
{ 5, nil, 0, 'south-block-vine' },
|
||||||
|
{ 6, nil, 0, 'north-block-vine' },
|
||||||
|
{ 7, nil, 0, 'south-block-vine' },
|
||||||
|
{ 8, nil, 0, 'east-block-vine' },
|
||||||
|
{ 9, nil, 0, 'south-block-vine' },
|
||||||
|
{ 10, nil, 0, 'east-block-vine' },
|
||||||
|
{ 11, nil, 0, 'east-block-vine' },
|
||||||
|
{ 12, nil, 0, 'east-block-vine' },
|
||||||
|
{ 13, nil, 0, 'east-block-vine' },
|
||||||
|
{ 14, nil, 0, 'east-block-vine' },
|
||||||
|
{ 15, nil, 0, 'east-block-vine' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('torch', {
|
||||||
|
{ 0, nil, 0 },
|
||||||
|
{ 1, nil, 0, 'west-block' },
|
||||||
|
{ 2, nil, 0, 'east-block' },
|
||||||
|
{ 3, nil, 0, 'north-block' },
|
||||||
|
{ 4, nil, 0, 'south-block' },
|
||||||
|
{ 5, nil, 0 },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('tripwire', {
|
||||||
|
{ 0, nil, 0, 'north-block' },
|
||||||
|
{ 1, nil, 0, 'east-block' },
|
||||||
|
{ 2, nil, 0, 'south-block' },
|
||||||
|
{ 3, nil, 0, 'west-block' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('trapdoor', {
|
||||||
|
{ 0, nil, 0, 'south-block' },
|
||||||
|
{ 1, nil, 0, 'north-block' },
|
||||||
|
{ 2, nil, 0, 'east-block' },
|
||||||
|
{ 3, nil, 0, 'west-block' },
|
||||||
|
{ 4, nil, 0, 'south-block' },
|
||||||
|
{ 5, nil, 0, 'north-block' },
|
||||||
|
{ 6, nil, 0, 'east-block' },
|
||||||
|
{ 7, nil, 0, 'west-block' },
|
||||||
|
{ 8, nil, 0, 'south' },
|
||||||
|
{ 9, nil, 0, 'north' },
|
||||||
|
{ 10, nil, 0, 'east' },
|
||||||
|
{ 11, nil, 0, 'west' },
|
||||||
|
{ 12, nil, 0, 'south' },
|
||||||
|
{ 13, nil, 0, 'north' },
|
||||||
|
{ 14, nil, 0, 'east' },
|
||||||
|
{ 15, nil, 0, 'west' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('piston', {
|
||||||
|
{ 0, nil, 0, 'piston-down' },
|
||||||
|
{ 1, nil, 0, 'piston-up' },
|
||||||
|
{ 2, nil, 0, 'piston-north' },
|
||||||
|
{ 3, nil, 0, 'piston-south' },
|
||||||
|
{ 4, nil, 0, 'piston-west' },
|
||||||
|
{ 5, nil, 0, 'piston-east' },
|
||||||
|
{ 8, nil, 0, 'piston-down' },
|
||||||
|
{ 9, nil, 0, 'piston-up' },
|
||||||
|
{ 10, nil, 0, 'piston-north' },
|
||||||
|
{ 11, nil, 0, 'piston-south' },
|
||||||
|
{ 12, nil, 0, 'piston-west' },
|
||||||
|
{ 13, nil, 0, 'piston-east' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('lever', {
|
||||||
|
{ 0, nil, 0, 'up' },
|
||||||
|
{ 1, nil, 0, 'west-block' },
|
||||||
|
{ 2, nil, 0, 'east-block' },
|
||||||
|
{ 3, nil, 0, 'north-block' },
|
||||||
|
{ 4, nil, 0, 'south-block' },
|
||||||
|
{ 5, nil, 0, 'north' },
|
||||||
|
{ 6, nil, 0, 'west' },
|
||||||
|
{ 7, nil, 0, 'up' },
|
||||||
|
{ 8, nil, 0, 'up' },
|
||||||
|
{ 9, nil, 0, 'west-block' },
|
||||||
|
{ 10, nil, 0, 'east-block' },
|
||||||
|
{ 11, nil, 0, 'north-block' },
|
||||||
|
{ 12, nil, 0, 'south-block' },
|
||||||
|
{ 13, nil, 0, 'north' },
|
||||||
|
{ 14, nil, 0, 'west' },
|
||||||
|
{ 15, nil, 0, 'up' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('wallsign-ladder', {
|
||||||
|
{ 0, nil, 0 },
|
||||||
|
{ 1, nil, 0 },
|
||||||
|
{ 2, nil, 0, 'south-block' },
|
||||||
|
{ 3, nil, 0, 'north-block' },
|
||||||
|
{ 4, nil, 0, 'east-block' },
|
||||||
|
{ 5, nil, 0, 'west-block' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('chest-furnace', {
|
||||||
|
{ 0, nil, 0 },
|
||||||
|
{ 2, nil, 0, 'south' },
|
||||||
|
{ 3, nil, 0, 'north' },
|
||||||
|
{ 4, nil, 0, 'east' },
|
||||||
|
{ 5, nil, 0, 'west' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('repeater', {
|
||||||
|
{ 0, nil, 0, 'north' },
|
||||||
|
{ 1, nil, 0, 'east' },
|
||||||
|
{ 2, nil, 0, 'south' },
|
||||||
|
{ 3, nil, 0, 'west' },
|
||||||
|
{ 4, nil, 0, 'north' },
|
||||||
|
{ 5, nil, 0, 'east' },
|
||||||
|
{ 6, nil, 0, 'south' },
|
||||||
|
{ 7, nil, 0, 'west' },
|
||||||
|
{ 8, nil, 0, 'north' },
|
||||||
|
{ 9, nil, 0, 'east' },
|
||||||
|
{ 10, nil, 0, 'south' },
|
||||||
|
{ 11, nil, 0, 'west' },
|
||||||
|
{ 12, nil, 0, 'north' },
|
||||||
|
{ 13, nil, 0, 'east' },
|
||||||
|
{ 14, nil, 0, 'south' },
|
||||||
|
{ 15, nil, 0, 'west' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('flatten', {
|
||||||
|
{ 0, nil, 0 },
|
||||||
|
{ 1, nil, 0 },
|
||||||
|
{ 2, nil, 0 },
|
||||||
|
{ 3, nil, 0 },
|
||||||
|
{ 4, nil, 0 },
|
||||||
|
{ 5, nil, 0 },
|
||||||
|
{ 6, nil, 0 },
|
||||||
|
{ 7, nil, 0 },
|
||||||
|
{ 8, nil, 0 },
|
||||||
|
{ 9, nil, 0 },
|
||||||
|
{ 10, nil, 0 },
|
||||||
|
{ 11, nil, 0 },
|
||||||
|
{ 12, nil, 0 },
|
||||||
|
{ 13, nil, 0 },
|
||||||
|
{ 14, nil, 0 },
|
||||||
|
{ 15, nil, 0 },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('sapling', {
|
||||||
|
{ '+0', nil, nil },
|
||||||
|
{ '+8', nil, nil },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('leaves', {
|
||||||
|
{ '+0', nil, nil },
|
||||||
|
{ '+4', nil, nil },
|
||||||
|
{ '+8', nil, nil },
|
||||||
|
{ '+12', nil, nil },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('slab', {
|
||||||
|
{ '+0', nil, nil, 'bottom' },
|
||||||
|
{ '+8', nil, nil, 'top' },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('largeplant', {
|
||||||
|
{ '+0', nil, nil, 'east-door', { twoHigh = true } }, -- should use a generic double tall keyword
|
||||||
|
{ '+8', 'minecraft:air', 0 },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('wood', {
|
||||||
|
{ '+0', nil, nil },
|
||||||
|
{ '+4', nil, nil, 'east-west-block' },
|
||||||
|
{ '+8', nil, nil, 'north-south-block' },
|
||||||
|
{ '+12', nil, nil },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('door', {
|
||||||
|
{ 0, nil, 0, 'east-door', { twoHigh = true } },
|
||||||
|
{ 1, nil, 0, 'south-door', { twoHigh = true } },
|
||||||
|
{ 2, nil, 0, 'west-door', { twoHigh = true } },
|
||||||
|
{ 3, nil, 0, 'north-door', { twoHigh = true } },
|
||||||
|
{ 4, nil, 0, 'east-door', { twoHigh = true } },
|
||||||
|
{ 5, nil, 0, 'south-door', { twoHigh = true } },
|
||||||
|
{ 6, nil, 0, 'west-door', { twoHigh = true } },
|
||||||
|
{ 7, nil, 0, 'north-door', { twoHigh = true } },
|
||||||
|
{ 8,'minecraft:air', 0 },
|
||||||
|
{ 9,'minecraft:air', 0 },
|
||||||
|
{ 10,'minecraft:air', 0 },
|
||||||
|
{ 11,'minecraft:air', 0 },
|
||||||
|
{ 12,'minecraft:air', 0 },
|
||||||
|
{ 13,'minecraft:air', 0 },
|
||||||
|
{ 14,'minecraft:air', 0 },
|
||||||
|
{ 15,'minecraft:air', 0 },
|
||||||
|
})
|
||||||
|
blockTypeDB:addTemp('cocoa', {
|
||||||
|
{ 0, nil, 0, 'south-block' },
|
||||||
|
{ 1, nil, 0, 'west-block' },
|
||||||
|
{ 2, nil, 0, 'north-block' },
|
||||||
|
{ 3, nil, 0, 'east-block' },
|
||||||
|
{ 4, nil, 0, 'south-block' },
|
||||||
|
{ 5, nil, 0, 'west-block' },
|
||||||
|
{ 6, nil, 0, 'north-block' },
|
||||||
|
{ 7, nil, 0, 'east-block' },
|
||||||
|
{ 8, nil, 0, 'south-block' },
|
||||||
|
{ 9, nil, 0, 'west-block' },
|
||||||
|
{ 10, nil, 0, 'north-block' },
|
||||||
|
{ 11, nil, 0, 'east-block' },
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local Blocks = class()
|
||||||
|
function Blocks:init(args)
|
||||||
|
Util.merge(self, args)
|
||||||
|
self.blockDB = blockDB
|
||||||
|
|
||||||
|
blockDB:load()
|
||||||
|
blockTypeDB:load()
|
||||||
|
placementDB:load(blockDB, blockTypeDB)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- for an ID / dmg (with placement info)
|
||||||
|
-- return the correct block (without the placment info embedded in the dmg)
|
||||||
|
function Blocks:getPlaceableBlock(id, dmg)
|
||||||
|
local p = placementDB:get({id, dmg})
|
||||||
|
if p then
|
||||||
|
return Util.shallowCopy(p)
|
||||||
|
end
|
||||||
|
|
||||||
|
local b = blockDB:get({id, dmg})
|
||||||
|
if b then
|
||||||
|
return { id = b.strId, dmg = b.dmg }
|
||||||
|
end
|
||||||
|
|
||||||
|
b = blockDB:get({id, 0})
|
||||||
|
if b then
|
||||||
|
return { id = b.strId, dmg = b.dmg }
|
||||||
|
end
|
||||||
|
|
||||||
|
return { id = id, dmg = dmg }
|
||||||
|
end
|
||||||
|
|
||||||
|
return Blocks
|
||||||
101
builder/apis/builder.lua
Normal file
101
builder/apis/builder.lua
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
local Blocks = require('builder.blocks')
|
||||||
|
local class = require('opus.class')
|
||||||
|
local Message = require('core.message')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local device = _G.device
|
||||||
|
local fs = _G.fs
|
||||||
|
local turtle = _G.turtle
|
||||||
|
|
||||||
|
local Builder = class()
|
||||||
|
Util.merge(Builder, {
|
||||||
|
isCommandComputer = not turtle,
|
||||||
|
loc = { },
|
||||||
|
index = 1,
|
||||||
|
mode = 'build',
|
||||||
|
})
|
||||||
|
|
||||||
|
local BUILDER_DIR = 'usr/builder'
|
||||||
|
|
||||||
|
local blockInfo = Blocks()
|
||||||
|
|
||||||
|
function Builder:getBlockCounts()
|
||||||
|
local blocks = { }
|
||||||
|
|
||||||
|
for k = self.index, #self.schematic.blocks do
|
||||||
|
local b = self.schematic.blocks[k]
|
||||||
|
local key = tostring(b.id) .. ':' .. b.dmg
|
||||||
|
local block = blocks[key]
|
||||||
|
if not block then
|
||||||
|
block = Util.shallowCopy(b)
|
||||||
|
block.qty = 0
|
||||||
|
block.need = 0
|
||||||
|
blocks[key] = block
|
||||||
|
end
|
||||||
|
block.need = block.need + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return blocks
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:substituteBlocks(throttle)
|
||||||
|
for _,b in pairs(self.schematic.blocks) do
|
||||||
|
|
||||||
|
-- replace schematic block type with substitution
|
||||||
|
local pb = blockInfo:getPlaceableBlock(b.id, b.dmg)
|
||||||
|
|
||||||
|
Util.merge(b, pb)
|
||||||
|
|
||||||
|
b.odmg = pb.odmg or pb.dmg
|
||||||
|
|
||||||
|
local sub = self.subDB:get({ b.id, b.dmg })
|
||||||
|
if sub then
|
||||||
|
b.id, b.dmg = self.subDB:extract(sub)
|
||||||
|
end
|
||||||
|
throttle()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:reloadSchematic(throttle)
|
||||||
|
self.schematic:reload(throttle)
|
||||||
|
self:substituteBlocks(throttle)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:log(...)
|
||||||
|
Util.print(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:dumpInventory()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:logBlock(index, b)
|
||||||
|
local bdir = b.direction or ''
|
||||||
|
local logText = string.format('%d %s:%d (x:%d,z:%d:y:%d) %s',
|
||||||
|
index, b.id, b.dmg, b.x, b.z, b.y, bdir)
|
||||||
|
self:log(logText)
|
||||||
|
-- self:log(b.index) -- unique identifier of block
|
||||||
|
|
||||||
|
if device.wireless_modem then
|
||||||
|
Message.broadcast('builder', { x = b.x, y = b.y, z = b.z, heading = b.heading })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:saveProgress(index)
|
||||||
|
Util.writeTable(
|
||||||
|
fs.combine(BUILDER_DIR, self.schematic.filename .. '.progress'),
|
||||||
|
{ index = index, loc = self.loc }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:loadProgress(filename)
|
||||||
|
local progress = Util.readTable(fs.combine(BUILDER_DIR, filename))
|
||||||
|
if progress then
|
||||||
|
self.index = progress.index
|
||||||
|
if self.index > #self.schematic.blocks then
|
||||||
|
self.index = 1
|
||||||
|
end
|
||||||
|
self.loc = progress.loc or { }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return Builder
|
||||||
84
builder/apis/commands.lua
Normal file
84
builder/apis/commands.lua
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
local Builder = require('builder.builder')
|
||||||
|
local Event = require('opus.event')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local commands = _G.commands
|
||||||
|
local fs = _G.fs
|
||||||
|
local os = _G.os
|
||||||
|
local read = _G.read
|
||||||
|
|
||||||
|
function Builder:begin()
|
||||||
|
local direction = 1
|
||||||
|
local last = #self.schematic.blocks
|
||||||
|
local throttle = Util.throttle()
|
||||||
|
|
||||||
|
local cx, cy, cz = commands.getBlockPosition()
|
||||||
|
if self.loc.x then
|
||||||
|
cx, cy, cz = self.loc.rx, self.loc.ry, self.loc.rz
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.mode == 'destroy' then
|
||||||
|
direction = -1
|
||||||
|
last = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = self.index, last, direction do
|
||||||
|
self.index = i
|
||||||
|
|
||||||
|
local b = self.schematic:getComputedBlock(i)
|
||||||
|
|
||||||
|
if b.id ~= 'minecraft:air' then
|
||||||
|
|
||||||
|
self:logBlock(self.index, b)
|
||||||
|
|
||||||
|
local id = b.id
|
||||||
|
if self.mode == 'destroy' then
|
||||||
|
id = 'minecraft:air'
|
||||||
|
end
|
||||||
|
|
||||||
|
local function placeBlock(bid, dmg, x, y, z)
|
||||||
|
local command = table.concat({
|
||||||
|
"setblock",
|
||||||
|
cx + x + 1,
|
||||||
|
cy + y,
|
||||||
|
cz + z + 1,
|
||||||
|
bid,
|
||||||
|
dmg,
|
||||||
|
}, ' ')
|
||||||
|
|
||||||
|
commands.execAsync(command)
|
||||||
|
|
||||||
|
local result = { os.pullEvent("task_complete") }
|
||||||
|
if not result[4] then
|
||||||
|
Util.print(result[5])
|
||||||
|
if self.mode ~= 'destroy' then
|
||||||
|
read()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
placeBlock(id, b.odmg, b.x, b.y, b.z)
|
||||||
|
|
||||||
|
if b.twoHigh then
|
||||||
|
local _, topBlock = self.schematic:findIndexAt(b.x, b.z, b.y + 1, true)
|
||||||
|
if topBlock then
|
||||||
|
placeBlock(id, topBlock.odmg, b.x, b.y + 1, b.z)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.mode == 'destroy' then
|
||||||
|
self:saveProgress(math.max(self.index, 1))
|
||||||
|
else
|
||||||
|
self:saveProgress(self.index + 1)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
throttle() -- sleep in case there are a large # of skipped blocks
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
fs.delete(self.schematic.filename .. '.progress')
|
||||||
|
print('Finished')
|
||||||
|
Event.exitPullEvents()
|
||||||
|
end
|
||||||
|
|
||||||
|
return Builder
|
||||||
769
builder/apis/deflatelua.lua
Normal file
769
builder/apis/deflatelua.lua
Normal file
@@ -0,0 +1,769 @@
|
|||||||
|
--[[
|
||||||
|
|
||||||
|
LUA MODULE
|
||||||
|
|
||||||
|
compress.deflatelua - deflate (and gunzip/zlib) implemented in Lua.
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
|
||||||
|
local DEFLATE = require 'compress.deflatelua'
|
||||||
|
-- uncompress gzip file
|
||||||
|
local fh = assert(io.open'foo.txt.gz', 'rb')
|
||||||
|
local ofh = assert(io.open'foo.txt', 'wb')
|
||||||
|
DEFLATE.gunzip {input=fh, output=ofh}
|
||||||
|
fh:close(); ofh:close()
|
||||||
|
-- can also uncompress from string including zlib and raw DEFLATE formats.
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
|
||||||
|
This is a pure Lua implementation of decompressing the DEFLATE format,
|
||||||
|
including the related zlib and gzip formats.
|
||||||
|
|
||||||
|
Note: This library only supports decompression.
|
||||||
|
Compression is not currently implemented.
|
||||||
|
|
||||||
|
API
|
||||||
|
|
||||||
|
Note: in the following functions, input stream `fh` may be
|
||||||
|
a file handle, string, or an iterator function that returns strings.
|
||||||
|
Output stream `ofh` may be a file handle or a function that
|
||||||
|
consumes one byte (number 0..255) per call.
|
||||||
|
|
||||||
|
DEFLATE.inflate {input=fh, output=ofh}
|
||||||
|
|
||||||
|
Decompresses input stream `fh` in the DEFLATE format
|
||||||
|
while writing to output stream `ofh`.
|
||||||
|
DEFLATE is detailed in http://tools.ietf.org/html/rfc1951 .
|
||||||
|
|
||||||
|
DEFLATE.gunzip {input=fh, output=ofh, disable_crc=disable_crc}
|
||||||
|
|
||||||
|
Decompresses input stream `fh` with the gzip format
|
||||||
|
while writing to output stream `ofh`.
|
||||||
|
`disable_crc` (defaults to `false`) will disable CRC-32 checking
|
||||||
|
to increase speed.
|
||||||
|
gzip is detailed in http://tools.ietf.org/html/rfc1952 .
|
||||||
|
|
||||||
|
DEFLATE.inflate_zlib {input=fh, output=ofh, disable_crc=disable_crc}
|
||||||
|
|
||||||
|
Decompresses input stream `fh` with the zlib format
|
||||||
|
while writing to output stream `ofh`.
|
||||||
|
`disable_crc` (defaults to `false`) will disable CRC-32 checking
|
||||||
|
to increase speed.
|
||||||
|
zlib is detailed in http://tools.ietf.org/html/rfc1950 .
|
||||||
|
|
||||||
|
DEFLATE.adler32(byte, crc) --> rcrc
|
||||||
|
|
||||||
|
Returns adler32 checksum of byte `byte` (number 0..255) appended
|
||||||
|
to string with adler32 checksum `crc`. This is internally used by
|
||||||
|
`inflate_zlib`.
|
||||||
|
ADLER32 in detailed in http://tools.ietf.org/html/rfc1950 .
|
||||||
|
|
||||||
|
COMMAND LINE UTILITY
|
||||||
|
|
||||||
|
A `gunziplua` command line utility (in folder `bin`) is also provided.
|
||||||
|
This mimicks the *nix `gunzip` utility but is a pure Lua implementation
|
||||||
|
that invokes this library. For help do
|
||||||
|
|
||||||
|
gunziplua -h
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
|
||||||
|
Requires 'digest.crc32lua' (used for optional CRC-32 checksum checks).
|
||||||
|
https://github.com/davidm/lua-digest-crc32lua
|
||||||
|
|
||||||
|
Will use a bit library ('bit', 'bit32', 'bit.numberlua') if available. This
|
||||||
|
is not that critical for this library but is required by digest.crc32lua.
|
||||||
|
|
||||||
|
'pythonic.optparse' is only required by the optional `gunziplua`
|
||||||
|
command-line utilty for command line parsing.
|
||||||
|
https://github.com/davidm/lua-pythonic-optparse
|
||||||
|
|
||||||
|
INSTALLATION
|
||||||
|
|
||||||
|
Copy the `compress` directory into your LUA_PATH.
|
||||||
|
|
||||||
|
REFERENCES
|
||||||
|
|
||||||
|
[1] DEFLATE Compressed Data Format Specification version 1.3
|
||||||
|
http://tools.ietf.org/html/rfc1951
|
||||||
|
[2] GZIP file format specification version 4.3
|
||||||
|
http://tools.ietf.org/html/rfc1952
|
||||||
|
[3] http://en.wikipedia.org/wiki/DEFLATE
|
||||||
|
[4] pyflate, by Paul Sladen
|
||||||
|
http://www.paul.sladen.org/projects/pyflate/
|
||||||
|
[5] Compress::Zlib::Perl - partial pure Perl implementation of
|
||||||
|
Compress::Zlib
|
||||||
|
http://search.cpan.org/~nwclark/Compress-Zlib-Perl/Perl.pm
|
||||||
|
|
||||||
|
LICENSE
|
||||||
|
|
||||||
|
(c) 2008-2011 David Manura. Licensed under the same terms as Lua (MIT).
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
(end license)
|
||||||
|
--]]
|
||||||
|
|
||||||
|
local M = {_TYPE='module', _NAME='compress.deflatelua', _VERSION='0.3.20111128'}
|
||||||
|
|
||||||
|
local assert = assert
|
||||||
|
local error = error
|
||||||
|
local ipairs = ipairs
|
||||||
|
local pairs = pairs
|
||||||
|
local tostring = tostring
|
||||||
|
local type = type
|
||||||
|
local setmetatable = setmetatable
|
||||||
|
local io = io
|
||||||
|
local math = math
|
||||||
|
local table_sort = table.sort
|
||||||
|
local math_max = math.max
|
||||||
|
local string_char = string.char
|
||||||
|
|
||||||
|
|
||||||
|
--local crc32 = require "digest.crc32lua" . crc32_byte
|
||||||
|
--local bit, name_ = requireany('bit', 'bit32', 'bit.numberlua', nil)
|
||||||
|
local bit
|
||||||
|
local crc32
|
||||||
|
|
||||||
|
-- Whether to use `bit` library functions in current module.
|
||||||
|
-- Unlike the crc32 library, it doesn't make much difference in this module.
|
||||||
|
local NATIVE_BITOPS = (bit ~= nil)
|
||||||
|
|
||||||
|
local band, lshift, rshift
|
||||||
|
if NATIVE_BITOPS then
|
||||||
|
band = bit.band
|
||||||
|
lshift = bit.lshift
|
||||||
|
rshift = bit.rshift
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function warn(s)
|
||||||
|
io.stderr:write(s, '\n')
|
||||||
|
end
|
||||||
|
|
||||||
|
local function runtime_error(s, level)
|
||||||
|
level = level or 1
|
||||||
|
error({s}, level+1)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function make_outstate(outbs)
|
||||||
|
local outstate = {}
|
||||||
|
outstate.outbs = outbs
|
||||||
|
outstate.window = {}
|
||||||
|
outstate.window_pos = 1
|
||||||
|
return outstate
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function output(outstate, byte)
|
||||||
|
local window_pos = outstate.window_pos
|
||||||
|
outstate.outbs(byte)
|
||||||
|
outstate.window[window_pos] = byte
|
||||||
|
outstate.window_pos = window_pos % 32768 + 1 -- 32K
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function noeof(val)
|
||||||
|
return assert(val, 'unexpected end of file')
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function hasbit(bits, bit)
|
||||||
|
return bits % (bit + bit) >= bit
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function memoize(f)
|
||||||
|
local mt = {}
|
||||||
|
local t = setmetatable({}, mt)
|
||||||
|
function mt:__index(k)
|
||||||
|
local v = f(k)
|
||||||
|
t[k] = v
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- small optimization (lookup table for powers of 2)
|
||||||
|
local pow2 = memoize(function(n) return 2^n end)
|
||||||
|
|
||||||
|
--local tbits = memoize(
|
||||||
|
-- function(bits)
|
||||||
|
-- return memoize( function(bit) return getbit(bits, bit) end )
|
||||||
|
-- end )
|
||||||
|
|
||||||
|
|
||||||
|
-- weak metatable marking objects as bitstream type
|
||||||
|
local is_bitstream = setmetatable({}, {__mode='k'})
|
||||||
|
|
||||||
|
local function bytestream_from_file(fh)
|
||||||
|
local o = {}
|
||||||
|
function o:read()
|
||||||
|
local sb = fh:read(1)
|
||||||
|
if sb then return sb:byte() end
|
||||||
|
end
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function bytestream_from_string(s)
|
||||||
|
local i = 1
|
||||||
|
local o = {}
|
||||||
|
function o:read()
|
||||||
|
local by
|
||||||
|
if i <= #s then
|
||||||
|
by = s:byte(i)
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
return by
|
||||||
|
end
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function bytestream_from_function(f)
|
||||||
|
local o = {}
|
||||||
|
function o:read()
|
||||||
|
return f()
|
||||||
|
end
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function bitstream_from_bytestream(bys)
|
||||||
|
local buf_byte = 0
|
||||||
|
local buf_nbit = 0
|
||||||
|
local o = {}
|
||||||
|
|
||||||
|
function o:nbits_left_in_byte()
|
||||||
|
return buf_nbit
|
||||||
|
end
|
||||||
|
|
||||||
|
if NATIVE_BITOPS then
|
||||||
|
function o:read(nbits)
|
||||||
|
nbits = nbits or 1
|
||||||
|
while buf_nbit < nbits do
|
||||||
|
local byte = bys:read()
|
||||||
|
if not byte then return end -- note: more calls also return nil
|
||||||
|
buf_byte = buf_byte + lshift(byte, buf_nbit)
|
||||||
|
buf_nbit = buf_nbit + 8
|
||||||
|
end
|
||||||
|
local bits
|
||||||
|
if nbits == 0 then
|
||||||
|
bits = 0
|
||||||
|
elseif nbits == 32 then
|
||||||
|
bits = buf_byte
|
||||||
|
buf_byte = 0
|
||||||
|
else
|
||||||
|
bits = band(buf_byte, rshift(0xffffffff, 32 - nbits))
|
||||||
|
buf_byte = rshift(buf_byte, nbits)
|
||||||
|
end
|
||||||
|
buf_nbit = buf_nbit - nbits
|
||||||
|
return bits
|
||||||
|
end
|
||||||
|
else
|
||||||
|
function o:read(nbits)
|
||||||
|
nbits = nbits or 1
|
||||||
|
while buf_nbit < nbits do
|
||||||
|
local byte = bys:read()
|
||||||
|
if not byte then return end -- note: more calls also return nil
|
||||||
|
buf_byte = buf_byte + pow2[buf_nbit] * byte
|
||||||
|
buf_nbit = buf_nbit + 8
|
||||||
|
end
|
||||||
|
local m = pow2[nbits]
|
||||||
|
local bits = buf_byte % m
|
||||||
|
buf_byte = (buf_byte - bits) / m
|
||||||
|
buf_nbit = buf_nbit - nbits
|
||||||
|
return bits
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
is_bitstream[o] = true
|
||||||
|
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function get_bitstream(o)
|
||||||
|
local bs
|
||||||
|
if is_bitstream[o] then
|
||||||
|
return o
|
||||||
|
elseif io.type(o) == 'file' then
|
||||||
|
bs = bitstream_from_bytestream(bytestream_from_file(o))
|
||||||
|
elseif type(o) == 'string' then
|
||||||
|
bs = bitstream_from_bytestream(bytestream_from_string(o))
|
||||||
|
elseif type(o) == 'function' then
|
||||||
|
bs = bitstream_from_bytestream(bytestream_from_function(o))
|
||||||
|
else
|
||||||
|
runtime_error 'unrecognized type'
|
||||||
|
end
|
||||||
|
return bs
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function get_obytestream(o)
|
||||||
|
local bs
|
||||||
|
if io.type(o) == 'file' then
|
||||||
|
bs = function(sbyte) o:write(string_char(sbyte)) end
|
||||||
|
elseif type(o) == 'function' then
|
||||||
|
bs = o
|
||||||
|
else
|
||||||
|
runtime_error('unrecognized type: ' .. tostring(o))
|
||||||
|
end
|
||||||
|
return bs
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function HuffmanTable(init, is_full)
|
||||||
|
local t = {}
|
||||||
|
if is_full then
|
||||||
|
for val,nbits in pairs(init) do
|
||||||
|
if nbits ~= 0 then
|
||||||
|
t[#t+1] = {val=val, nbits=nbits}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for i=1,#init-2,2 do
|
||||||
|
local firstval, nbits, nextval = init[i], init[i+1], init[i+2]
|
||||||
|
if nbits ~= 0 then
|
||||||
|
for val=firstval,nextval-1 do
|
||||||
|
t[#t+1] = {val=val, nbits=nbits}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table_sort(t, function(a,b)
|
||||||
|
return a.nbits == b.nbits and a.val < b.val or a.nbits < b.nbits
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- assign codes
|
||||||
|
local code = 1 -- leading 1 marker
|
||||||
|
local nbits = 0
|
||||||
|
for i,s in ipairs(t) do
|
||||||
|
if s.nbits ~= nbits then
|
||||||
|
code = code * pow2[s.nbits - nbits]
|
||||||
|
nbits = s.nbits
|
||||||
|
end
|
||||||
|
s.code = code
|
||||||
|
code = code + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local minbits = math.huge
|
||||||
|
local look = {}
|
||||||
|
for i,s in ipairs(t) do
|
||||||
|
minbits = math.min(minbits, s.nbits)
|
||||||
|
look[s.code] = s.val
|
||||||
|
end
|
||||||
|
|
||||||
|
local msb = NATIVE_BITOPS and function(bits, nbits)
|
||||||
|
local res = 0
|
||||||
|
for i=1,nbits do
|
||||||
|
res = lshift(res, 1) + band(bits, 1)
|
||||||
|
bits = rshift(bits, 1)
|
||||||
|
end
|
||||||
|
return res
|
||||||
|
end or function(bits, nbits)
|
||||||
|
local res = 0
|
||||||
|
for _=1,nbits do
|
||||||
|
local b = bits % 2
|
||||||
|
bits = (bits - b) / 2
|
||||||
|
res = res * 2 + b
|
||||||
|
end
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
local tfirstcode = memoize(
|
||||||
|
function(bits) return pow2[minbits] + msb(bits, minbits) end)
|
||||||
|
|
||||||
|
function t:read(bs)
|
||||||
|
local code = 1 -- leading 1 marker
|
||||||
|
local nbits = 0
|
||||||
|
while 1 do
|
||||||
|
if nbits == 0 then -- small optimization (optional)
|
||||||
|
code = tfirstcode[noeof(bs:read(minbits))]
|
||||||
|
nbits = nbits + minbits
|
||||||
|
else
|
||||||
|
local b = noeof(bs:read())
|
||||||
|
nbits = nbits + 1
|
||||||
|
code = code * 2 + b -- MSB first
|
||||||
|
end
|
||||||
|
local val = look[code]
|
||||||
|
if val then
|
||||||
|
return val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function parse_gzip_header(bs)
|
||||||
|
-- local FLG_FTEXT = 2^0
|
||||||
|
local FLG_FHCRC = 2^1
|
||||||
|
local FLG_FEXTRA = 2^2
|
||||||
|
local FLG_FNAME = 2^3
|
||||||
|
local FLG_FCOMMENT = 2^4
|
||||||
|
|
||||||
|
local id1 = bs:read(8)
|
||||||
|
local id2 = bs:read(8)
|
||||||
|
if id1 ~= 31 or id2 ~= 139 then
|
||||||
|
runtime_error 'not in gzip format'
|
||||||
|
end
|
||||||
|
local cm = bs:read(8) -- compression method
|
||||||
|
local flg = bs:read(8) -- FLaGs
|
||||||
|
local mtime = bs:read(32) -- Modification TIME
|
||||||
|
local xfl = bs:read(8) -- eXtra FLags
|
||||||
|
local os = bs:read(8) -- Operating System
|
||||||
|
|
||||||
|
if not os then runtime_error 'invalid header' end
|
||||||
|
|
||||||
|
if hasbit(flg, FLG_FEXTRA) then
|
||||||
|
local xlen = bs:read(16)
|
||||||
|
local extra = 0
|
||||||
|
for i=1,xlen do
|
||||||
|
extra = bs:read(8)
|
||||||
|
end
|
||||||
|
if not extra then runtime_error 'invalid header' end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parse_zstring(bs)
|
||||||
|
repeat
|
||||||
|
local by = bs:read(8)
|
||||||
|
if not by then runtime_error 'invalid header' end
|
||||||
|
until by == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if hasbit(flg, FLG_FNAME) then
|
||||||
|
parse_zstring(bs)
|
||||||
|
end
|
||||||
|
|
||||||
|
if hasbit(flg, FLG_FCOMMENT) then
|
||||||
|
parse_zstring(bs)
|
||||||
|
end
|
||||||
|
|
||||||
|
if hasbit(flg, FLG_FHCRC) then
|
||||||
|
local crc16 = bs:read(16)
|
||||||
|
if not crc16 then runtime_error 'invalid header' end
|
||||||
|
-- IMPROVE: check CRC. where is an example .gz file that
|
||||||
|
-- has this set?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parse_zlib_header(bs)
|
||||||
|
local cm = bs:read(4) -- Compression Method
|
||||||
|
local cinfo = bs:read(4) -- Compression info
|
||||||
|
local fcheck = bs:read(5) -- FLaGs: FCHECK (check bits for CMF and FLG)
|
||||||
|
local fdict = bs:read(1) -- FLaGs: FDICT (present dictionary)
|
||||||
|
local flevel = bs:read(2) -- FLaGs: FLEVEL (compression level)
|
||||||
|
local cmf = cinfo * 16 + cm -- CMF (Compresion Method and flags)
|
||||||
|
local flg = fcheck + fdict * 32 + flevel * 64 -- FLaGs
|
||||||
|
|
||||||
|
if cm ~= 8 then -- not "deflate"
|
||||||
|
runtime_error("unrecognized zlib compression method: " + cm)
|
||||||
|
end
|
||||||
|
if cinfo > 7 then
|
||||||
|
runtime_error("invalid zlib window size: cinfo=" + cinfo)
|
||||||
|
end
|
||||||
|
local window_size = 2^(cinfo + 8)
|
||||||
|
|
||||||
|
if (cmf*256 + flg) % 31 ~= 0 then
|
||||||
|
runtime_error("invalid zlib header (bad fcheck sum)")
|
||||||
|
end
|
||||||
|
|
||||||
|
if fdict == 1 then
|
||||||
|
runtime_error("FIX:TODO - FDICT not currently implemented")
|
||||||
|
local dictid_ = bs:read(32)
|
||||||
|
end
|
||||||
|
|
||||||
|
return window_size
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parse_huffmantables(bs)
|
||||||
|
local hlit = bs:read(5) -- # of literal/length codes - 257
|
||||||
|
local hdist = bs:read(5) -- # of distance codes - 1
|
||||||
|
local hclen = noeof(bs:read(4)) -- # of code length codes - 4
|
||||||
|
|
||||||
|
local ncodelen_codes = hclen + 4
|
||||||
|
local codelen_init = {}
|
||||||
|
local codelen_vals = {
|
||||||
|
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}
|
||||||
|
for i=1,ncodelen_codes do
|
||||||
|
local nbits = bs:read(3)
|
||||||
|
local val = codelen_vals[i]
|
||||||
|
codelen_init[val] = nbits
|
||||||
|
end
|
||||||
|
local codelentable = HuffmanTable(codelen_init, true)
|
||||||
|
|
||||||
|
local function decode(ncodes)
|
||||||
|
local init = {}
|
||||||
|
local nbits
|
||||||
|
local val = 0
|
||||||
|
while val < ncodes do
|
||||||
|
local codelen = codelentable:read(bs)
|
||||||
|
--FIX:check nil?
|
||||||
|
local nrepeat
|
||||||
|
if codelen <= 15 then
|
||||||
|
nrepeat = 1
|
||||||
|
nbits = codelen
|
||||||
|
elseif codelen == 16 then
|
||||||
|
nrepeat = 3 + noeof(bs:read(2))
|
||||||
|
-- nbits unchanged
|
||||||
|
elseif codelen == 17 then
|
||||||
|
nrepeat = 3 + noeof(bs:read(3))
|
||||||
|
nbits = 0
|
||||||
|
elseif codelen == 18 then
|
||||||
|
nrepeat = 11 + noeof(bs:read(7))
|
||||||
|
nbits = 0
|
||||||
|
else
|
||||||
|
error 'ASSERT'
|
||||||
|
end
|
||||||
|
for i=1,nrepeat do
|
||||||
|
init[val] = nbits
|
||||||
|
val = val + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local huffmantable = HuffmanTable(init, true)
|
||||||
|
return huffmantable
|
||||||
|
end
|
||||||
|
|
||||||
|
local nlit_codes = hlit + 257
|
||||||
|
local ndist_codes = hdist + 1
|
||||||
|
|
||||||
|
local littable = decode(nlit_codes)
|
||||||
|
local disttable = decode(ndist_codes)
|
||||||
|
|
||||||
|
return littable, disttable
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local tdecode_len_base
|
||||||
|
local tdecode_len_nextrabits
|
||||||
|
local tdecode_dist_base
|
||||||
|
local tdecode_dist_nextrabits
|
||||||
|
local function parse_compressed_item(bs, outstate, littable, disttable)
|
||||||
|
local val = littable:read(bs)
|
||||||
|
if val < 256 then -- literal
|
||||||
|
output(outstate, val)
|
||||||
|
elseif val == 256 then -- end of block
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
if not tdecode_len_base then
|
||||||
|
local t = {[257]=3}
|
||||||
|
local skip = 1
|
||||||
|
for i=258,285,4 do
|
||||||
|
for j=i,i+3 do t[j] = t[j-1] + skip end
|
||||||
|
if i ~= 258 then skip = skip * 2 end
|
||||||
|
end
|
||||||
|
t[285] = 258
|
||||||
|
tdecode_len_base = t
|
||||||
|
end
|
||||||
|
if not tdecode_len_nextrabits then
|
||||||
|
local t = {}
|
||||||
|
if NATIVE_BITOPS then
|
||||||
|
for i=257,285 do
|
||||||
|
local j = math_max(i - 261, 0)
|
||||||
|
t[i] = rshift(j, 2)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for i=257,285 do
|
||||||
|
local j = math_max(i - 261, 0)
|
||||||
|
t[i] = (j - (j % 4)) / 4
|
||||||
|
end
|
||||||
|
end
|
||||||
|
t[285] = 0
|
||||||
|
tdecode_len_nextrabits = t
|
||||||
|
end
|
||||||
|
local len_base = tdecode_len_base[val]
|
||||||
|
local nextrabits = tdecode_len_nextrabits[val]
|
||||||
|
local extrabits = bs:read(nextrabits)
|
||||||
|
local len = len_base + extrabits
|
||||||
|
|
||||||
|
if not tdecode_dist_base then
|
||||||
|
local t = {[0]=1}
|
||||||
|
local skip = 1
|
||||||
|
for i=1,29,2 do
|
||||||
|
for j=i,i+1 do t[j] = t[j-1] + skip end
|
||||||
|
if i ~= 1 then skip = skip * 2 end
|
||||||
|
end
|
||||||
|
tdecode_dist_base = t
|
||||||
|
end
|
||||||
|
if not tdecode_dist_nextrabits then
|
||||||
|
local t = {}
|
||||||
|
if NATIVE_BITOPS then
|
||||||
|
for i=0,29 do
|
||||||
|
local j = math_max(i - 2, 0)
|
||||||
|
t[i] = rshift(j, 1)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for i=0,29 do
|
||||||
|
local j = math_max(i - 2, 0)
|
||||||
|
t[i] = (j - (j % 2)) / 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
tdecode_dist_nextrabits = t
|
||||||
|
end
|
||||||
|
local dist_val = disttable:read(bs)
|
||||||
|
local dist_base = tdecode_dist_base[dist_val]
|
||||||
|
local dist_nextrabits = tdecode_dist_nextrabits[dist_val]
|
||||||
|
local dist_extrabits = bs:read(dist_nextrabits)
|
||||||
|
local dist = dist_base + dist_extrabits
|
||||||
|
|
||||||
|
for i=1,len do
|
||||||
|
local pos = (outstate.window_pos - 1 - dist) % 32768 + 1 -- 32K
|
||||||
|
output(outstate, assert(outstate.window[pos], 'invalid distance'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function parse_block(bs, outstate)
|
||||||
|
local bfinal = bs:read(1)
|
||||||
|
local btype = bs:read(2)
|
||||||
|
|
||||||
|
local BTYPE_NO_COMPRESSION = 0
|
||||||
|
local BTYPE_FIXED_HUFFMAN = 1
|
||||||
|
local BTYPE_DYNAMIC_HUFFMAN = 2
|
||||||
|
local BTYPE_RESERVED_ = 3
|
||||||
|
|
||||||
|
if btype == BTYPE_NO_COMPRESSION then
|
||||||
|
bs:read(bs:nbits_left_in_byte())
|
||||||
|
local len = bs:read(16)
|
||||||
|
local nlen_ = noeof(bs:read(16))
|
||||||
|
|
||||||
|
for _=1,len do
|
||||||
|
local by = noeof(bs:read(8))
|
||||||
|
output(outstate, by)
|
||||||
|
end
|
||||||
|
elseif btype == BTYPE_FIXED_HUFFMAN or btype == BTYPE_DYNAMIC_HUFFMAN then
|
||||||
|
local littable, disttable
|
||||||
|
if btype == BTYPE_DYNAMIC_HUFFMAN then
|
||||||
|
littable, disttable = parse_huffmantables(bs)
|
||||||
|
else
|
||||||
|
littable = HuffmanTable {0,8, 144,9, 256,7, 280,8, 288,nil}
|
||||||
|
disttable = HuffmanTable {0,5, 32,nil}
|
||||||
|
end
|
||||||
|
|
||||||
|
repeat
|
||||||
|
local is_done = parse_compressed_item(
|
||||||
|
bs, outstate, littable, disttable)
|
||||||
|
until is_done
|
||||||
|
else
|
||||||
|
runtime_error 'unrecognized compression type'
|
||||||
|
end
|
||||||
|
|
||||||
|
return bfinal ~= 0
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function M.inflate(t)
|
||||||
|
local bs = get_bitstream(t.input)
|
||||||
|
local outbs = get_obytestream(t.output)
|
||||||
|
local outstate = make_outstate(outbs)
|
||||||
|
|
||||||
|
repeat
|
||||||
|
local is_final = parse_block(bs, outstate)
|
||||||
|
until is_final
|
||||||
|
end
|
||||||
|
local inflate = M.inflate
|
||||||
|
|
||||||
|
|
||||||
|
function M.gunzip(t)
|
||||||
|
local bs = get_bitstream(t.input)
|
||||||
|
local outbs = get_obytestream(t.output)
|
||||||
|
local disable_crc = t.disable_crc
|
||||||
|
if disable_crc == nil then disable_crc = false end
|
||||||
|
|
||||||
|
parse_gzip_header(bs)
|
||||||
|
|
||||||
|
local data_crc32 = 0
|
||||||
|
|
||||||
|
inflate{input=bs, output=
|
||||||
|
disable_crc and outbs or
|
||||||
|
function(byte)
|
||||||
|
data_crc32 = crc32(byte, data_crc32)
|
||||||
|
outbs(byte)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
bs:read(bs:nbits_left_in_byte())
|
||||||
|
|
||||||
|
local expected_crc32 = bs:read(32)
|
||||||
|
local isize = bs:read(32) -- ignored
|
||||||
|
|
||||||
|
if not disable_crc and data_crc32 then
|
||||||
|
if data_crc32 ~= expected_crc32 then
|
||||||
|
runtime_error('invalid compressed data--crc error')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if bs:read() then
|
||||||
|
warn 'trailing garbage ignored'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function M.adler32(byte, crc)
|
||||||
|
local s1 = crc % 65536
|
||||||
|
local s2 = (crc - s1) / 65536
|
||||||
|
s1 = (s1 + byte) % 65521
|
||||||
|
s2 = (s2 + s1) % 65521
|
||||||
|
return s2*65536 + s1
|
||||||
|
end -- 65521 is the largest prime smaller than 2^16
|
||||||
|
|
||||||
|
|
||||||
|
function M.inflate_zlib(t)
|
||||||
|
local bs = get_bitstream(t.input)
|
||||||
|
local outbs = get_obytestream(t.output)
|
||||||
|
local disable_crc = t.disable_crc
|
||||||
|
if disable_crc == nil then disable_crc = false end
|
||||||
|
|
||||||
|
local window_size_ = parse_zlib_header(bs)
|
||||||
|
|
||||||
|
local data_adler32 = 1
|
||||||
|
|
||||||
|
inflate{input=bs, output=
|
||||||
|
disable_crc and outbs or
|
||||||
|
function(byte)
|
||||||
|
data_adler32 = M.adler32(byte, data_adler32)
|
||||||
|
outbs(byte)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
bs:read(bs:nbits_left_in_byte())
|
||||||
|
|
||||||
|
local b3 = bs:read(8)
|
||||||
|
local b2 = bs:read(8)
|
||||||
|
local b1 = bs:read(8)
|
||||||
|
local b0 = bs:read(8)
|
||||||
|
local expected_adler32 = ((b3*256 + b2)*256 + b1)*256 + b0
|
||||||
|
|
||||||
|
if not disable_crc then
|
||||||
|
if data_adler32 ~= expected_adler32 then
|
||||||
|
runtime_error('invalid compressed data--crc error')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if bs:read() then
|
||||||
|
warn 'trailing garbage ignored'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return M
|
||||||
1244
builder/apis/schematic.lua
Normal file
1244
builder/apis/schematic.lua
Normal file
File diff suppressed because it is too large
Load Diff
1265
builder/apis/turtle.lua
Normal file
1265
builder/apis/turtle.lua
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,19 @@
|
|||||||
requireInjector(getfenv(1))
|
local Base64 = require('builder.base64')
|
||||||
|
|
||||||
Base64 = require('base64')
|
local http = _G.http
|
||||||
|
local os = _G.os
|
||||||
|
local shell = _ENV.shell
|
||||||
|
|
||||||
local args = { ... }
|
local args = { ... }
|
||||||
|
|
||||||
if not args[2] then
|
if not args[2] then
|
||||||
error('Syntax: base64dl <file name> <url>')
|
error('Syntax: base64dl <file name> <url>')
|
||||||
end
|
end
|
||||||
|
|
||||||
local c = http.get(args[2])
|
local c = http.get(args[2])
|
||||||
|
|
||||||
if not c then
|
if not c then
|
||||||
error('unable to open url')
|
error('unable to open url')
|
||||||
end
|
end
|
||||||
|
|
||||||
local data = c.readAll()
|
local data = c.readAll()
|
||||||
@@ -23,13 +25,13 @@ print('decoded: ' .. #decoded)
|
|||||||
|
|
||||||
local file = io.open(shell.resolve(args[1]), "wb")
|
local file = io.open(shell.resolve(args[1]), "wb")
|
||||||
if not file then
|
if not file then
|
||||||
error('Unable to open ' .. args[1], 2)
|
error('Unable to open ' .. args[1], 2)
|
||||||
end
|
end
|
||||||
for k,b in ipairs(decoded) do
|
for k,b in ipairs(decoded) do
|
||||||
if (k % 1000) == 0 then
|
if (k % 1000) == 0 then
|
||||||
os.sleep(0)
|
os.sleep(0)
|
||||||
end
|
end
|
||||||
file:write(b)
|
file:write(b)
|
||||||
end
|
end
|
||||||
|
|
||||||
file:close()
|
file:close()
|
||||||
771
builder/builder.lua
Normal file
771
builder/builder.lua
Normal file
@@ -0,0 +1,771 @@
|
|||||||
|
if not _G.turtle and not _G.commands then
|
||||||
|
error('Must be run on a turtle or a command computer')
|
||||||
|
end
|
||||||
|
|
||||||
|
local Adapter = require('core.inventoryAdapter')
|
||||||
|
local Event = require('opus.event')
|
||||||
|
local GPS = require('opus.gps')
|
||||||
|
local itemDB = require('core.itemDB')
|
||||||
|
local Schematic = require('builder.schematic')
|
||||||
|
local TableDB = require('core.tableDB')
|
||||||
|
local UI = require('opus.ui')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local colors = _G.colors
|
||||||
|
local fs = _G.fs
|
||||||
|
|
||||||
|
local BUILDER_DIR = 'usr/builder'
|
||||||
|
|
||||||
|
local substitutionPage
|
||||||
|
local Builder
|
||||||
|
|
||||||
|
if _G.commands then
|
||||||
|
Builder = require('builder.commands')
|
||||||
|
else
|
||||||
|
Builder = require('builder.turtle')
|
||||||
|
end
|
||||||
|
|
||||||
|
Builder = Builder()
|
||||||
|
Builder.schematic = Schematic()
|
||||||
|
|
||||||
|
local function convertSingleBack(item)
|
||||||
|
if item then
|
||||||
|
item.id = item.name
|
||||||
|
item.dmg = item.damage
|
||||||
|
item.qty = item.count
|
||||||
|
item.max_size = item.maxCount
|
||||||
|
item.display_name = item.displayName
|
||||||
|
end
|
||||||
|
return item
|
||||||
|
end
|
||||||
|
|
||||||
|
local function convertBack(t)
|
||||||
|
for _,v in pairs(t) do
|
||||||
|
convertSingleBack(v)
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[-- SubDB --]]--
|
||||||
|
local subDB = TableDB({
|
||||||
|
fileName = fs.combine(BUILDER_DIR, 'sub.db'),
|
||||||
|
})
|
||||||
|
|
||||||
|
function subDB:load()
|
||||||
|
if fs.exists(self.fileName) then
|
||||||
|
TableDB.load(self)
|
||||||
|
elseif not Builder.isCommandComputer then
|
||||||
|
self:seedDB()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function subDB:seedDB()
|
||||||
|
self.data = {
|
||||||
|
[ "minecraft:redstone_wire:0" ] = "minecraft:redstone:0",
|
||||||
|
[ "minecraft:wall_sign:0" ] = "minecraft:sign:0",
|
||||||
|
[ "minecraft:standing_sign:0" ] = "minecraft:sign:0",
|
||||||
|
[ "minecraft:potatoes:0" ] = "minecraft:potato:0",
|
||||||
|
[ "minecraft:unlit_redstone_torch:0" ] = "minecraft:redstone_torch:0",
|
||||||
|
[ "minecraft:powered_repeater:0" ] = "minecraft:repeater:0",
|
||||||
|
[ "minecraft:unpowered_repeater:0" ] = "minecraft:repeater:0",
|
||||||
|
[ "minecraft:carrots:0" ] = "minecraft:carrot:0",
|
||||||
|
[ "minecraft:cocoa:0" ] = "minecraft:dye:3",
|
||||||
|
[ "minecraft:unpowered_comparator:0" ] = "minecraft:comparator:0",
|
||||||
|
[ "minecraft:powered_comparator:0" ] = "minecraft:comparator:0",
|
||||||
|
[ "minecraft:piston_head:0" ] = "minecraft:air:0",
|
||||||
|
[ "minecraft:piston_extension:0" ] = "minecraft:air:0",
|
||||||
|
[ "minecraft:portal:0" ] = "minecraft:air:0",
|
||||||
|
[ "minecraft:double_wooden_slab:0" ] = "minecraft:planks:0",
|
||||||
|
[ "minecraft:double_wooden_slab:1" ] = "minecraft:planks:1",
|
||||||
|
[ "minecraft:double_wooden_slab:2" ] = "minecraft:planks:2",
|
||||||
|
[ "minecraft:double_wooden_slab:3" ] = "minecraft:planks:3",
|
||||||
|
[ "minecraft:double_wooden_slab:4" ] = "minecraft:planks:4",
|
||||||
|
[ "minecraft:double_wooden_slab:5" ] = "minecraft:planks:5",
|
||||||
|
[ "minecraft:lit_redstone_lamp:0" ] = "minecraft:redstone_lamp:0",
|
||||||
|
[ "minecraft:double_stone_slab:1" ] = "minecraft:sandstone:0",
|
||||||
|
[ "minecraft:double_stone_slab:2" ] = "minecraft:planks:0",
|
||||||
|
[ "minecraft:double_stone_slab:3" ] = "minecraft:cobblestone:0",
|
||||||
|
[ "minecraft:double_stone_slab:4" ] = "minecraft:brick_block:0",
|
||||||
|
[ "minecraft:double_stone_slab:5" ] = "minecraft:stonebrick:0",
|
||||||
|
[ "minecraft:double_stone_slab:6" ] = "minecraft:nether_brick:0",
|
||||||
|
[ "minecraft:double_stone_slab:7" ] = "minecraft:quartz_block:0",
|
||||||
|
[ "minecraft:double_stone_slab:9" ] = "minecraft:sandstone:2",
|
||||||
|
[ "minecraft:double_stone_slab2:0" ] = "minecraft:sandstone:0",
|
||||||
|
[ "minecraft:stone_slab:2" ] = "minecraft:wooden_slab:0",
|
||||||
|
[ "minecraft:wheat:0" ] = "minecraft:wheat_seeds:0",
|
||||||
|
[ "minecraft:flowing_water:0" ] = "minecraft:air:0",
|
||||||
|
[ "minecraft:lit_furnace:0" ] = "minecraft:furnace:0",
|
||||||
|
[ "minecraft:wall_banner:0" ] = "minecraft:banner:0",
|
||||||
|
[ "minecraft:standing_banner:0" ] = "minecraft:banner:0",
|
||||||
|
[ "minecraft:tripwire:0" ] = "minecraft:string:0",
|
||||||
|
[ "minecraft:pumpkin_stem:0" ] = "minecraft:pumpkin_seeds:0",
|
||||||
|
}
|
||||||
|
self.dirty = true
|
||||||
|
self:flush()
|
||||||
|
end
|
||||||
|
|
||||||
|
function subDB:add(s)
|
||||||
|
TableDB.add(self, { s.id, s.dmg }, table.concat({ s.sid, s.sdmg }, ':'))
|
||||||
|
self:flush()
|
||||||
|
end
|
||||||
|
|
||||||
|
function subDB:remove(s)
|
||||||
|
-- TODO: tableDB.remove should take table key
|
||||||
|
TableDB.remove(self, s.id .. ':' .. s.dmg)
|
||||||
|
self:flush()
|
||||||
|
end
|
||||||
|
|
||||||
|
function subDB:extract(s)
|
||||||
|
local id, dmg = s:match('(.+):(%d+)')
|
||||||
|
return id, tonumber(dmg)
|
||||||
|
end
|
||||||
|
|
||||||
|
function subDB:getSubstitutedItem(id, dmg)
|
||||||
|
local sub = TableDB.get(self, { id, dmg })
|
||||||
|
if sub then
|
||||||
|
id, dmg = self:extract(sub)
|
||||||
|
end
|
||||||
|
return { id = id, dmg = dmg }
|
||||||
|
end
|
||||||
|
|
||||||
|
function subDB:lookupBlocksForSub(sid, sdmg)
|
||||||
|
local t = { }
|
||||||
|
for k,v in pairs(self.data) do
|
||||||
|
local id, dmg = self:extract(v)
|
||||||
|
if id == sid and dmg == sdmg then
|
||||||
|
id, dmg = self:extract(k)
|
||||||
|
t[k] = { id = id, dmg = dmg, sid = sid, sdmg = sdmg }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[-- blankPage --]]--
|
||||||
|
local blankPage = UI.Page()
|
||||||
|
function blankPage:draw()
|
||||||
|
self:clear(colors.black)
|
||||||
|
self:setCursorPos(1, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function blankPage:enable()
|
||||||
|
self:sync()
|
||||||
|
UI.Page.enable(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[-- selectSubstitutionPage --]]--
|
||||||
|
local selectSubstitutionPage = UI.Page({
|
||||||
|
titleBar = UI.TitleBar({
|
||||||
|
title = 'Select a substitution',
|
||||||
|
previousPage = 'listing'
|
||||||
|
}),
|
||||||
|
grid = UI.ScrollingGrid({
|
||||||
|
columns = {
|
||||||
|
{ heading = 'id', key = 'id' },
|
||||||
|
{ heading = 'dmg', key = 'dmg' },
|
||||||
|
},
|
||||||
|
sortColumn = 'id',
|
||||||
|
height = UI.term.height-1,
|
||||||
|
autospace = true,
|
||||||
|
y = 2,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
function selectSubstitutionPage:enable()
|
||||||
|
self.grid:adjustWidth()
|
||||||
|
self.grid:setIndex(1)
|
||||||
|
UI.Page.enable(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function selectSubstitutionPage:eventHandler(event)
|
||||||
|
if event.type == 'grid_select' then
|
||||||
|
substitutionPage.sub = event.selected
|
||||||
|
UI:setPage(substitutionPage)
|
||||||
|
elseif event.type == 'key' and event.key == 'q' then
|
||||||
|
UI:setPreviousPage()
|
||||||
|
else
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[-- substitutionPage --]]--
|
||||||
|
substitutionPage = UI.Page {
|
||||||
|
titleBar = UI.TitleBar {
|
||||||
|
previousPage = true,
|
||||||
|
title = 'Substitute a block'
|
||||||
|
},
|
||||||
|
menuBar = UI.MenuBar {
|
||||||
|
y = 2,
|
||||||
|
buttons = {
|
||||||
|
{ text = 'Accept', event = 'accept', help = 'Accept' },
|
||||||
|
{ text = 'Revert', event = 'revert', help = 'Restore to original' },
|
||||||
|
{ text = 'Air', event = 'air', help = 'Air' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
info = UI.Window { y = 4, width = UI.term.width, height = 3 },
|
||||||
|
grid = UI.ScrollingGrid {
|
||||||
|
columns = {
|
||||||
|
{ heading = 'Name', key = 'display_name', width = UI.term.width-9 },
|
||||||
|
{ heading = 'Qty', key = 'fQty', width = 5 },
|
||||||
|
},
|
||||||
|
sortColumn = 'display_name',
|
||||||
|
height = UI.term.height-7,
|
||||||
|
y = 7,
|
||||||
|
},
|
||||||
|
throttle = UI.Throttle { },
|
||||||
|
statusBar = UI.StatusBar { }
|
||||||
|
}
|
||||||
|
|
||||||
|
substitutionPage.menuBar:add({
|
||||||
|
filterLabel = UI.Text({
|
||||||
|
value = 'Search',
|
||||||
|
x = UI.term.width-14,
|
||||||
|
}),
|
||||||
|
filter = UI.TextEntry({
|
||||||
|
x = UI.term.width-7,
|
||||||
|
width = 7,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function substitutionPage.info:draw()
|
||||||
|
local sub = self.parent.sub
|
||||||
|
local inName = itemDB:getName({ name = sub.id, damage = sub.dmg })
|
||||||
|
local outName = ''
|
||||||
|
if sub.sid then
|
||||||
|
outName = itemDB:getName({ name = sub.sid, damage = sub.sdmg })
|
||||||
|
end
|
||||||
|
|
||||||
|
self:clear()
|
||||||
|
self:print(' Replace ' .. inName .. '\n' .. ' With ' .. outName)
|
||||||
|
end
|
||||||
|
|
||||||
|
function substitutionPage:enable()
|
||||||
|
self.allItems = convertBack(Builder.itemAdapter:refresh())
|
||||||
|
self.grid.values = self.allItems
|
||||||
|
for _,item in pairs(self.grid.values) do
|
||||||
|
item.key = item.id .. ':' .. item.dmg
|
||||||
|
item.lname = string.lower(item.display_name)
|
||||||
|
item.fQty = Util.toBytes(item.qty)
|
||||||
|
end
|
||||||
|
self.grid:update()
|
||||||
|
|
||||||
|
self.menuBar.filter:reset()
|
||||||
|
self:setFocus(self.menuBar.filter)
|
||||||
|
UI.Page.enable(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function substitutionPage:applySubstitute(id, dmg)
|
||||||
|
self.sub.sid = id
|
||||||
|
self.sub.sdmg = dmg
|
||||||
|
end
|
||||||
|
|
||||||
|
function substitutionPage:eventHandler(event)
|
||||||
|
if event.type == 'grid_focus_row' then
|
||||||
|
local s = string.format('%s:%d',
|
||||||
|
event.selected.id,
|
||||||
|
event.selected.dmg)
|
||||||
|
|
||||||
|
self.statusBar:setStatus(s)
|
||||||
|
self.statusBar:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'grid_select' then
|
||||||
|
self:applySubstitute(event.selected.id, event.selected.dmg)
|
||||||
|
self.info:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'text_change' then
|
||||||
|
local text = event.text or ''
|
||||||
|
if #text == 0 then
|
||||||
|
self.grid.values = self.allItems
|
||||||
|
else
|
||||||
|
self.grid.values = { }
|
||||||
|
for _,item in pairs(self.allItems) do
|
||||||
|
if string.find(item.lname, text) then
|
||||||
|
table.insert(self.grid.values, item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.grid:update()
|
||||||
|
self.grid:setIndex(1)
|
||||||
|
self.grid:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'accept' or event.type == 'air' or event.type == 'revert' then
|
||||||
|
self.statusBar:setStatus('Saving changes...')
|
||||||
|
self.statusBar:draw()
|
||||||
|
self:sync()
|
||||||
|
|
||||||
|
if event.type == 'air' then
|
||||||
|
self:applySubstitute('minecraft:air', 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
if event.type == 'revert' then
|
||||||
|
subDB:remove(self.sub)
|
||||||
|
elseif not self.sub.sid then
|
||||||
|
self.statusBar:setStatus('Select a substition')
|
||||||
|
self.statusBar:draw()
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
else
|
||||||
|
subDB:add(self.sub)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.throttle:enable()
|
||||||
|
Builder:reloadSchematic(function() self.throttle:update() end)
|
||||||
|
self.throttle:disable()
|
||||||
|
UI:setPage('listing')
|
||||||
|
|
||||||
|
elseif event.type == 'cancel' then
|
||||||
|
UI:setPreviousPage()
|
||||||
|
end
|
||||||
|
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[-- ListingPage --]]--
|
||||||
|
local listingPage = UI.Page({
|
||||||
|
titleBar = UI.TitleBar({
|
||||||
|
title = 'Supply List',
|
||||||
|
previousPage = 'start'
|
||||||
|
}),
|
||||||
|
menuBar = UI.MenuBar({
|
||||||
|
y = 2,
|
||||||
|
buttons = {
|
||||||
|
{ text = 'Craft', event = 'craft', help = 'Request crafting' },
|
||||||
|
{ text = 'Refresh', event = 'refresh', help = 'Refresh inventory' },
|
||||||
|
{ text = 'Toggle', event = 'toggle', help = 'Toggles needed blocks' },
|
||||||
|
{ text = 'Substitute', event = 'edit', help = 'Substitute a block' },
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
grid = UI.ScrollingGrid({
|
||||||
|
columns = {
|
||||||
|
{ heading = 'Name', key = 'display_name', width = UI.term.width - 14 },
|
||||||
|
{ heading = 'Need', key = 'need', width = 5 },
|
||||||
|
{ heading = 'Have', key = 'qty', width = 5 },
|
||||||
|
},
|
||||||
|
sortColumn = 'display_name',
|
||||||
|
y = 3,
|
||||||
|
height = UI.term.height-3,
|
||||||
|
help = 'Set a block type or pick a substitute block'
|
||||||
|
}),
|
||||||
|
accelerators = {
|
||||||
|
q = 'menu',
|
||||||
|
c = 'craft',
|
||||||
|
r = 'refresh',
|
||||||
|
t = 'toggle',
|
||||||
|
},
|
||||||
|
statusBar = UI.StatusBar(),
|
||||||
|
fullList = true
|
||||||
|
})
|
||||||
|
|
||||||
|
function listingPage:enable(throttle)
|
||||||
|
listingPage:refresh(throttle)
|
||||||
|
UI.Page.enable(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function listingPage:eventHandler(event)
|
||||||
|
if event.type == 'craft' then
|
||||||
|
local s = self.grid:getSelected()
|
||||||
|
local item = convertSingleBack(Builder.itemAdapter:getItemInfo({
|
||||||
|
name = s.id,
|
||||||
|
damage = s.dmg,
|
||||||
|
nbtHash = s.nbt_hash,
|
||||||
|
}))
|
||||||
|
if item and item.is_craftable then
|
||||||
|
local qty = math.max(0, s.need - item.qty)
|
||||||
|
|
||||||
|
if item and Builder.itemAdapter.craftItems then
|
||||||
|
Builder.itemAdapter:craftItems({{ name = s.id, damage = s.dmg, nbtHash = s.nbt_hash, count = qty }})
|
||||||
|
local name = s.display_name or s.id
|
||||||
|
self.statusBar:timedStatus('Requested ' .. qty .. ' ' .. name, 3)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self.statusBar:timedStatus('Unable to craft')
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif event.type == 'grid_focus_row' then
|
||||||
|
self.statusBar:setStatus(event.selected.id .. ':' .. event.selected.dmg)
|
||||||
|
self.statusBar:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'refresh' then
|
||||||
|
self:refresh()
|
||||||
|
self:draw()
|
||||||
|
self.statusBar:timedStatus('Refreshed ', 3)
|
||||||
|
|
||||||
|
elseif event.type == 'toggle' then
|
||||||
|
self.fullList = not self.fullList
|
||||||
|
self:refresh()
|
||||||
|
self:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'menu' then
|
||||||
|
UI:setPage('start')
|
||||||
|
|
||||||
|
elseif event.type == 'edit' or event.type == 'grid_select' then
|
||||||
|
self:manageBlock(self.grid:getSelected())
|
||||||
|
|
||||||
|
elseif event.type == 'focus_change' then
|
||||||
|
if event.focused.help then
|
||||||
|
self.statusBar:timedStatus(event.focused.help, 3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
|
||||||
|
function listingPage.grid:getDisplayValues(row)
|
||||||
|
row = Util.shallowCopy(row)
|
||||||
|
row.need = Util.toBytes(row.need)
|
||||||
|
row.qty = Util.toBytes(row.qty)
|
||||||
|
return row
|
||||||
|
end
|
||||||
|
|
||||||
|
function listingPage.grid:getRowTextColor(row, selected)
|
||||||
|
if row.is_craftable then
|
||||||
|
return colors.yellow
|
||||||
|
end
|
||||||
|
return UI.Grid:getRowTextColor(row, selected)
|
||||||
|
end
|
||||||
|
|
||||||
|
function listingPage:refresh(throttle)
|
||||||
|
local supplyList = Builder:getBlockCounts()
|
||||||
|
|
||||||
|
Builder.itemAdapter:refresh(throttle)
|
||||||
|
|
||||||
|
for _,b in pairs(supplyList) do
|
||||||
|
if b.need > 0 then
|
||||||
|
local item = convertSingleBack(Builder.itemAdapter:getItemInfo({
|
||||||
|
name = b.id,
|
||||||
|
damage = b.dmg,
|
||||||
|
nbtHash = b.nbt_hash,
|
||||||
|
}))
|
||||||
|
|
||||||
|
if item then
|
||||||
|
b.display_name = item.display_name
|
||||||
|
b.qty = item.qty
|
||||||
|
b.is_craftable = item.is_craftable
|
||||||
|
else
|
||||||
|
b.display_name = itemDB:getName({ name = b.id, damage = b.dmg })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if throttle then
|
||||||
|
throttle()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.fullList then
|
||||||
|
self.grid:setValues(supplyList)
|
||||||
|
else
|
||||||
|
local t = {}
|
||||||
|
for _,b in pairs(supplyList) do
|
||||||
|
if self.fullList or b.qty < b.need then
|
||||||
|
table.insert(t, b)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.grid:setValues(t)
|
||||||
|
end
|
||||||
|
self.grid:setIndex(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function listingPage:manageBlock(selected)
|
||||||
|
local substitutes = subDB:lookupBlocksForSub(selected.id, selected.dmg)
|
||||||
|
|
||||||
|
if Util.empty(substitutes) then
|
||||||
|
substitutionPage.sub = { id = selected.id, dmg = selected.dmg }
|
||||||
|
UI:setPage(substitutionPage)
|
||||||
|
elseif Util.size(substitutes) == 1 then
|
||||||
|
local _,sub = next(substitutes)
|
||||||
|
substitutionPage.sub = sub
|
||||||
|
UI:setPage(substitutionPage)
|
||||||
|
else
|
||||||
|
selectSubstitutionPage.selected = selected
|
||||||
|
selectSubstitutionPage.grid:setValues(substitutes)
|
||||||
|
UI:setPage(selectSubstitutionPage)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[-- startPage --]]--
|
||||||
|
local wy = 2
|
||||||
|
local my = 3
|
||||||
|
|
||||||
|
if UI.term.width < 30 then
|
||||||
|
wy = 9
|
||||||
|
my = 2
|
||||||
|
end
|
||||||
|
|
||||||
|
local startPage = UI.Page {
|
||||||
|
window = UI.Window {
|
||||||
|
x = UI.term.width-16,
|
||||||
|
y = wy,
|
||||||
|
width = 16,
|
||||||
|
height = 9,
|
||||||
|
backgroundColor = colors.gray,
|
||||||
|
grid = UI.Grid {
|
||||||
|
columns = {
|
||||||
|
{ heading = 'Name', key = 'name', width = 6 },
|
||||||
|
{ heading = 'Value', key = 'value', width = 7 },
|
||||||
|
},
|
||||||
|
disableHeader = true,
|
||||||
|
x = 1,
|
||||||
|
y = 2,
|
||||||
|
width = 16,
|
||||||
|
height = 9,
|
||||||
|
inactive = true,
|
||||||
|
backgroundColor = colors.gray
|
||||||
|
},
|
||||||
|
},
|
||||||
|
menu = UI.Menu {
|
||||||
|
x = 2,
|
||||||
|
y = my,
|
||||||
|
height = 7,
|
||||||
|
backgroundColor = UI.Page.defaults.backgroundColor,
|
||||||
|
menuItems = {
|
||||||
|
{ prompt = 'Set starting level', event = 'startLevel' },
|
||||||
|
{ prompt = 'Set starting block', event = 'startBlock' },
|
||||||
|
{ prompt = 'Set starting point', event = 'startPoint' },
|
||||||
|
{ prompt = 'Supply list', event = 'assignBlocks' },
|
||||||
|
{ prompt = 'Toggle mode', event = 'toggleMode' },
|
||||||
|
{ prompt = 'Begin', event = 'begin' },
|
||||||
|
{ prompt = 'Quit', event = 'quit' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startLevel = UI.Dialog {
|
||||||
|
title = 'Enter Starting Level',
|
||||||
|
height = 7,
|
||||||
|
form = UI.Form {
|
||||||
|
y = 3, x = 2, height = 4,
|
||||||
|
event = 'setStartLevel',
|
||||||
|
cancelEvent = 'slide_hide',
|
||||||
|
text = UI.Text {
|
||||||
|
x = 5, y = 1, width = 10,
|
||||||
|
textColor = colors.gray,
|
||||||
|
},
|
||||||
|
textEntry = UI.TextEntry {
|
||||||
|
formKey = 'level',
|
||||||
|
x = 15, y = 1, width = 7,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
statusBar = UI.StatusBar(),
|
||||||
|
},
|
||||||
|
startBlock = UI.Dialog {
|
||||||
|
title = 'Enter Block Number',
|
||||||
|
height = 7,
|
||||||
|
form = UI.Form {
|
||||||
|
y = 3, x = 2, height = 4,
|
||||||
|
event = 'setStartBlock',
|
||||||
|
cancelEvent = 'slide_hide',
|
||||||
|
text = UI.Text {
|
||||||
|
x = 2, y = 1, width = 13,
|
||||||
|
textColor = colors.gray,
|
||||||
|
},
|
||||||
|
textEntry = UI.TextEntry {
|
||||||
|
x = 16, y = 1,
|
||||||
|
width = 10, limit = 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
statusBar = UI.StatusBar(),
|
||||||
|
},
|
||||||
|
startPoint = UI.Dialog {
|
||||||
|
title = 'Set starting point',
|
||||||
|
height = 11,
|
||||||
|
form = UI.Form {
|
||||||
|
y = 2, x = 2, ey = -2,
|
||||||
|
cancelEvent = 'slide_hide',
|
||||||
|
text1 = UI.Text {
|
||||||
|
x = 1, y = 2, value = 'Turtle location' },
|
||||||
|
xLoc = UI.TextEntry {
|
||||||
|
x = 1, y = 3, formKey = 'x', width = 7, limit = 16, shadowText = 'x', required = true },
|
||||||
|
yLoc = UI.TextEntry {
|
||||||
|
x = 9, y = 3, formKey = 'y', width = 7, limit = 16, shadowText = 'y', required = true },
|
||||||
|
zLoc = UI.TextEntry {
|
||||||
|
x = 17, y = 3, formKey = 'z', width = 7, limit = 16, shadowText = 'z', required = true },
|
||||||
|
text2 = UI.Text {
|
||||||
|
x = 1, y = 5, value = 'Starting Point' },
|
||||||
|
xrLoc = UI.TextEntry {
|
||||||
|
x = 1, y = 6, formKey = 'rx', width = 7, limit = 16, shadowText = 'x', required = true },
|
||||||
|
yrLoc = UI.TextEntry {
|
||||||
|
x = 9, y = 6, formKey = 'ry', width = 7, limit = 16, shadowText = 'y', required = true },
|
||||||
|
zrLoc = UI.TextEntry {
|
||||||
|
x = 17, y = 6, formKey = 'rz', width = 7, limit = 16, shadowText = 'z', required = true },
|
||||||
|
revert = UI.Button {
|
||||||
|
x = 1, y = -2, text = 'Revert', event = 'revert' },
|
||||||
|
accelerators = {
|
||||||
|
form_cancel = 'slide_hide',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
statusBar = UI.StatusBar({ values = 'Optional start point'}),
|
||||||
|
},
|
||||||
|
throttle = UI.Throttle { },
|
||||||
|
accelerators = {
|
||||||
|
x = 'test',
|
||||||
|
[ 'control-q' ] = 'quit'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startPage:draw()
|
||||||
|
local t = {
|
||||||
|
{ name = 'mode', value = Builder.mode },
|
||||||
|
{ name = 'start', value = Builder.index },
|
||||||
|
{ name = 'blocks', value = #Builder.schematic.blocks },
|
||||||
|
{ name = 'length', value = Builder.schematic.length },
|
||||||
|
{ name = 'width', value = Builder.schematic.width },
|
||||||
|
{ name = 'height', value = Builder.schematic.height },
|
||||||
|
}
|
||||||
|
|
||||||
|
self.window.grid:setValues(t)
|
||||||
|
UI.Page.draw(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function startPage:enable()
|
||||||
|
self:setFocus(self.menu)
|
||||||
|
UI.Page.enable(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function startPage.startPoint:eventHandler(event)
|
||||||
|
if event.type == 'form_complete' then
|
||||||
|
for k,v in pairs(event.values) do
|
||||||
|
Builder.loc[k] = tonumber(v)
|
||||||
|
end
|
||||||
|
Builder:saveProgress(Builder.index)
|
||||||
|
self:hide()
|
||||||
|
elseif event.type == 'revert' then
|
||||||
|
Builder.loc = { }
|
||||||
|
Builder:saveProgress(Builder.index)
|
||||||
|
self:hide()
|
||||||
|
elseif event.type == 'form_invalid' then
|
||||||
|
self.statusBar:setStatus(event.message)
|
||||||
|
elseif event.type == 'form_cancel' or event.type == 'cancel' then
|
||||||
|
self:hide()
|
||||||
|
else
|
||||||
|
return UI.Dialog.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function startPage:eventHandler(event)
|
||||||
|
if event.type == 'startLevel' then
|
||||||
|
self.startLevel.form.text.value = '0 - ' .. Builder.schematic.height
|
||||||
|
self.startLevel:show()
|
||||||
|
|
||||||
|
elseif event.type == 'setStartLevel' then
|
||||||
|
local l = tonumber(self.startLevel.form.textEntry.value)
|
||||||
|
if l and l < Builder.schematic.height and l >= 0 then
|
||||||
|
for k,v in pairs(Builder.schematic.blocks) do
|
||||||
|
if v.y >= l then
|
||||||
|
Builder.index = k
|
||||||
|
Builder:saveProgress(Builder.index)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.startLevel:hide()
|
||||||
|
self:draw()
|
||||||
|
else
|
||||||
|
self.startLevel.statusBar:setStatus('Invalid start level')
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif event.type == 'startBlock' then
|
||||||
|
self.startBlock.form.text.value = '1 - ' .. #Builder.schematic.blocks
|
||||||
|
self.startBlock.form.textEntry.value = tostring(Builder.index)
|
||||||
|
self.startBlock:show()
|
||||||
|
|
||||||
|
elseif event.type == 'setStartBlock' then
|
||||||
|
local bn = tonumber(self.startBlock.form.textEntry.value)
|
||||||
|
if bn and bn < #Builder.schematic.blocks and bn >= 0 then
|
||||||
|
Builder.index = bn
|
||||||
|
Builder:saveProgress(Builder.index)
|
||||||
|
self.startBlock:hide()
|
||||||
|
self:draw()
|
||||||
|
else
|
||||||
|
self.startLevel.statusBar:setStatus('Invalid start block')
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif event.type == 'startPoint' then
|
||||||
|
local loc = Util.shallowCopy(Builder.loc)
|
||||||
|
if not loc.x then
|
||||||
|
if _G.turtle then
|
||||||
|
local pt = GPS.getPoint()
|
||||||
|
if pt then
|
||||||
|
loc.x = pt.x
|
||||||
|
loc.y = pt.y
|
||||||
|
loc.z = pt.z
|
||||||
|
end
|
||||||
|
elseif _G.commands then
|
||||||
|
loc.x, loc.y, loc.z = _G.commands.getBlockPosition()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self.startPoint.form:setValues(loc)
|
||||||
|
self.startPoint:show()
|
||||||
|
|
||||||
|
elseif event.type == 'assignBlocks' then
|
||||||
|
-- this might be an approximation of the blocks needed
|
||||||
|
-- as the current level's route may or may not have been
|
||||||
|
-- computed
|
||||||
|
Builder:dumpInventory()
|
||||||
|
UI:setPage('listing', function() self.throttle:update() end)
|
||||||
|
self.throttle:disable()
|
||||||
|
|
||||||
|
elseif event.type == 'toggleMode' then
|
||||||
|
if Builder.mode == 'build' then
|
||||||
|
if Builder.index == 1 then
|
||||||
|
Builder.index = #Builder.schematic.blocks
|
||||||
|
end
|
||||||
|
Builder.mode = 'destroy'
|
||||||
|
else
|
||||||
|
if Builder.index == #Builder.schematic.blocks then
|
||||||
|
Builder.index = 1
|
||||||
|
end
|
||||||
|
Builder.mode = 'build'
|
||||||
|
end
|
||||||
|
self:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'begin' then
|
||||||
|
UI:setPage('blank')
|
||||||
|
self:sync()
|
||||||
|
|
||||||
|
print('Reloading schematic')
|
||||||
|
Builder:reloadSchematic(Util.throttle())
|
||||||
|
Builder:begin()
|
||||||
|
|
||||||
|
elseif event.type == 'quit' then
|
||||||
|
UI:quit()
|
||||||
|
end
|
||||||
|
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[-- startup logic --]]--
|
||||||
|
local args = {...}
|
||||||
|
if #args < 1 then
|
||||||
|
error('supply file name or URL')
|
||||||
|
end
|
||||||
|
|
||||||
|
Builder.itemAdapter = Adapter.wrap({ side = 'bottom', direction = 'up' })
|
||||||
|
if not Builder.itemAdapter then
|
||||||
|
error('A chest or ME interface must be below turtle')
|
||||||
|
end
|
||||||
|
|
||||||
|
subDB:load()
|
||||||
|
|
||||||
|
UI.term:reset()
|
||||||
|
print('Loading schematic')
|
||||||
|
Builder.schematic:load(args[1])
|
||||||
|
print('Substituting blocks')
|
||||||
|
|
||||||
|
Builder.subDB = subDB
|
||||||
|
Builder:substituteBlocks(Util.throttle())
|
||||||
|
|
||||||
|
if not fs.exists(BUILDER_DIR) then
|
||||||
|
fs.makeDir(BUILDER_DIR)
|
||||||
|
end
|
||||||
|
|
||||||
|
Builder:loadProgress(Builder.schematic.filename .. '.progress')
|
||||||
|
|
||||||
|
Event.on('build', function()
|
||||||
|
Builder:build()
|
||||||
|
end)
|
||||||
|
|
||||||
|
UI:setPages({
|
||||||
|
listing = listingPage,
|
||||||
|
start = startPage,
|
||||||
|
blank = blankPage
|
||||||
|
})
|
||||||
|
|
||||||
|
UI:setPage('start')
|
||||||
|
UI:start()
|
||||||
74
builder/help/builder.txt
Normal file
74
builder/help/builder.txt
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
Builds structures using schematic files with a turtle or a command computer. Schematics can be downloaded from sites such as http://www.planetminecraft.com.
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
============
|
||||||
|
* Advanced Mining Turtle
|
||||||
|
* Chest (any type)
|
||||||
|
* Wrench (any mod)
|
||||||
|
|
||||||
|
Alternatively
|
||||||
|
|
||||||
|
* Command Computer
|
||||||
|
* Chest (any type)
|
||||||
|
|
||||||
|
Installation
|
||||||
|
============
|
||||||
|
> pastebin run uzghlbnc
|
||||||
|
> package install core
|
||||||
|
> package install builder
|
||||||
|
> reboot
|
||||||
|
|
||||||
|
Setup
|
||||||
|
=====
|
||||||
|
Place the turtle or command computer on top of a chest at the corner of the build location.
|
||||||
|
|
||||||
|
Copy a schematic file to the turtle/command computer (see downloading instructions if on a server)
|
||||||
|
|
||||||
|
Building
|
||||||
|
========
|
||||||
|
Run:
|
||||||
|
> builder <schematic file or url>
|
||||||
|
|
||||||
|
The first time you run the program, you must select a wrench. Place a wrench into the chest. Go to the supplies list and double-click the SelectAWrench item. Select the wrench and apply.
|
||||||
|
|
||||||
|
If interrupted during building, you can resume by placing the turtle back in the original location and restarting the program.
|
||||||
|
|
||||||
|
Wrenches
|
||||||
|
========
|
||||||
|
EnderIO, Thermal Expansion, and Applied Energistics wrenches will work (only wrenches that can spin pistons will work).
|
||||||
|
|
||||||
|
To determine if a wrench can be used, right click the side of a piston with the wrench. If the piston rotates, then the wrench can be used. Some wrenches work better than others.
|
||||||
|
|
||||||
|
Downloading schematics
|
||||||
|
======================
|
||||||
|
Single Player
|
||||||
|
-------------
|
||||||
|
Simply copy a schematic file into the computer's folder.
|
||||||
|
|
||||||
|
Multiplayer
|
||||||
|
-----------
|
||||||
|
Option 1: Pass the url of the schematic.
|
||||||
|
|
||||||
|
> builder <url>
|
||||||
|
|
||||||
|
Option 2: Use wget if the schematic is available for download (unreliable).
|
||||||
|
|
||||||
|
Option 3: Transferring via pastebin (reliable)
|
||||||
|
To create a base64 file from a command line, do:
|
||||||
|
|
||||||
|
* linux / max:
|
||||||
|
|
||||||
|
base64 -w0 NAME.schematic > NAME.schematic.base64
|
||||||
|
|
||||||
|
* windows (I haven't tried this - but google said this will work):
|
||||||
|
|
||||||
|
certutil -encode NAME.schematic tmp.b64 && findstr /v /c:- tmp.b64 > NAME.schematic.base64
|
||||||
|
Upload the base64 file to pastebin.
|
||||||
|
|
||||||
|
To download and convert the base64 file back into a schematic file, do:
|
||||||
|
|
||||||
|
> base64dl.lua <filename> <url>
|
||||||
|
|
||||||
|
You can download a simple test schematic using:
|
||||||
|
|
||||||
|
> base64dl.lua chicken.schematic https://pastebin.com/raw/vxpHBUky
|
||||||
430
builder/supplier.lua
Normal file
430
builder/supplier.lua
Normal file
@@ -0,0 +1,430 @@
|
|||||||
|
local Event = require('opus.event')
|
||||||
|
local MEProvider = require('core.meProvider')
|
||||||
|
local Message = require('core.message')
|
||||||
|
local Point = require('opus.point')
|
||||||
|
local TableDB = require('core.tableDB')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local device = _G.device
|
||||||
|
local os = _G.os
|
||||||
|
local turtle = _G.turtle
|
||||||
|
|
||||||
|
--[[
|
||||||
|
A supplier turtle for the builder turtle. For larger builds, use
|
||||||
|
ender modems.
|
||||||
|
|
||||||
|
Setup:
|
||||||
|
|
||||||
|
1. chest or ME interface at level 0 (bottom of build area)
|
||||||
|
2. builder turtle on top facing the build area
|
||||||
|
3. If facing the build turtle, the supplier turtle is to the right
|
||||||
|
pointing at the chest/interface
|
||||||
|
]]--
|
||||||
|
|
||||||
|
local ChestProvider = require('core.chestProvider')
|
||||||
|
if Util.getVersion() == 1.8 then
|
||||||
|
ChestProvider = require('core.chestProvider18')
|
||||||
|
end
|
||||||
|
|
||||||
|
if not device.wireless_modem then
|
||||||
|
error('No wireless modem detected')
|
||||||
|
end
|
||||||
|
|
||||||
|
local __BUILDER_ID = 6
|
||||||
|
local itemInfoDB
|
||||||
|
|
||||||
|
local Builder = {
|
||||||
|
version = '1.70',
|
||||||
|
ccVersion = nil,
|
||||||
|
slots = { },
|
||||||
|
index = 1,
|
||||||
|
fuelItem = { id = 'minecraft:coal', dmg = 0 },
|
||||||
|
resupplying = true,
|
||||||
|
ready = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
--[[-- maxStackDB --]]--
|
||||||
|
local maxStackDB = TableDB({
|
||||||
|
fileName = 'maxstack.db',
|
||||||
|
tabledef = {
|
||||||
|
autokeys = false,
|
||||||
|
type = 'simple',
|
||||||
|
columns = {
|
||||||
|
{ label = 'Key', type = 'key', length = 8 },
|
||||||
|
{ label = 'Quantity', type = 'number', length = 2 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function maxStackDB:get(id, dmg)
|
||||||
|
return self.data[id .. ':' .. dmg] or 64
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:dumpInventory()
|
||||||
|
|
||||||
|
local success = true
|
||||||
|
|
||||||
|
for i = 1, 16 do
|
||||||
|
local qty = turtle.getItemCount(i)
|
||||||
|
if qty > 0 then
|
||||||
|
self.itemProvider:insert(i, qty)
|
||||||
|
end
|
||||||
|
if turtle.getItemCount(i) ~= 0 then
|
||||||
|
success = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
turtle.select(1)
|
||||||
|
|
||||||
|
return success
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:dumpInventoryWithCheck()
|
||||||
|
while not self:dumpInventory() do
|
||||||
|
Builder:log('Unable to dump inventory')
|
||||||
|
print('Provider is full or missing - make space or replace')
|
||||||
|
print('Press enter to continue')
|
||||||
|
--turtle.setHeading(0)
|
||||||
|
self.ready = false
|
||||||
|
_G.read()
|
||||||
|
end
|
||||||
|
self.ready = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:autocraft(supplies)
|
||||||
|
local t = { }
|
||||||
|
|
||||||
|
for _,s in pairs(supplies) do
|
||||||
|
local key = s.id .. ':' .. s.dmg
|
||||||
|
local item = t[key]
|
||||||
|
if not item then
|
||||||
|
item = {
|
||||||
|
id = s.id,
|
||||||
|
dmg = s.dmg,
|
||||||
|
qty = 0,
|
||||||
|
}
|
||||||
|
t[key] = item
|
||||||
|
end
|
||||||
|
item.qty = item.qty + (s.need-s.qty)
|
||||||
|
end
|
||||||
|
|
||||||
|
Builder.itemProvider:craftItems(t)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:refuel()
|
||||||
|
while turtle.getFuelLevel() < 4000 and self.fuelItem do
|
||||||
|
Builder:log('Refueling')
|
||||||
|
turtle.select(1)
|
||||||
|
self.itemProvider:provide(self.fuelItem, 64, 1)
|
||||||
|
if turtle.getItemCount(1) == 0 then
|
||||||
|
Builder:log('Out of fuel, add coal to chest/ME system')
|
||||||
|
--turtle.setHeading(0)
|
||||||
|
os.sleep(5)
|
||||||
|
else
|
||||||
|
turtle.refuel(64)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:log(...)
|
||||||
|
Util.print(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:getSupplies()
|
||||||
|
|
||||||
|
Builder.itemProvider:refresh()
|
||||||
|
|
||||||
|
local t = { }
|
||||||
|
for _,s in ipairs(self.slots) do
|
||||||
|
if s.need > 0 then
|
||||||
|
local item = Builder.itemProvider:getItemInfo(s)
|
||||||
|
if item then
|
||||||
|
if item.name then
|
||||||
|
s.name = item.name
|
||||||
|
end
|
||||||
|
|
||||||
|
local qty = math.min(s.need-s.qty, item.qty)
|
||||||
|
|
||||||
|
if qty + s.qty > item.max_size then
|
||||||
|
maxStackDB:add({ s.id, s.dmg }, item.max_size)
|
||||||
|
maxStackDB.dirty = true
|
||||||
|
maxStackDB:flush()
|
||||||
|
qty = item.max_size
|
||||||
|
s.need = qty
|
||||||
|
end
|
||||||
|
if qty > 0 then
|
||||||
|
self.itemProvider:provide(item, qty, s.index)
|
||||||
|
s.qty = turtle.getItemCount(s.index)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if s.qty < s.need then
|
||||||
|
table.insert(t, s)
|
||||||
|
local name = s.name or s.id .. ':' .. s.dmg
|
||||||
|
local item = itemInfoDB:get({ s.id, s.dmg })
|
||||||
|
if item then
|
||||||
|
name = item.displayName
|
||||||
|
end
|
||||||
|
|
||||||
|
Builder:log('Need %d %s', s.need - s.qty, name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
local function moveTowardsX(dx)
|
||||||
|
|
||||||
|
local direction = dx - turtle.point.x
|
||||||
|
local move
|
||||||
|
|
||||||
|
if direction == 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if direction > 0 and turtle.point.heading == 0 or
|
||||||
|
direction < 0 and turtle.point.heading == 2 then
|
||||||
|
move = turtle.forward
|
||||||
|
else
|
||||||
|
move = turtle.back
|
||||||
|
end
|
||||||
|
|
||||||
|
return move()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function moveTowardsZ(dz)
|
||||||
|
|
||||||
|
local direction = dz - turtle.point.z
|
||||||
|
local move
|
||||||
|
|
||||||
|
if direction == 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if direction > 0 and turtle.point.heading == 1 or
|
||||||
|
direction < 0 and turtle.point.heading == 3 then
|
||||||
|
move = turtle.forward
|
||||||
|
else
|
||||||
|
move = turtle.back
|
||||||
|
end
|
||||||
|
|
||||||
|
return move()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:finish()
|
||||||
|
Builder.resupplying = true
|
||||||
|
Builder.ready = false
|
||||||
|
if turtle.gotoLocation('supplies') then
|
||||||
|
turtle.setHeading(1)
|
||||||
|
os.sleep(.1) -- random 'Computer is not connected' error...
|
||||||
|
Builder:dumpInventory()
|
||||||
|
Event.exitPullEvents()
|
||||||
|
print('Finished')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Builder:gotoBuilder()
|
||||||
|
if Builder.lastPoint then
|
||||||
|
turtle.setStatus('tracking')
|
||||||
|
while true do
|
||||||
|
local pt = Point.copy(Builder.lastPoint)
|
||||||
|
pt.y = pt.y + 3
|
||||||
|
if turtle.point.y ~= pt.y then
|
||||||
|
turtle.gotoY(pt.y)
|
||||||
|
else
|
||||||
|
local distance = Point.turtleDistance(turtle.point, pt)
|
||||||
|
if distance <= 3 then
|
||||||
|
Builder:log('Synchronized')
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
if turtle.point.heading % 2 == 0 then
|
||||||
|
if turtle.point.x == pt.x then
|
||||||
|
turtle.headTowardsZ(pt.z)
|
||||||
|
moveTowardsZ(pt.z)
|
||||||
|
else
|
||||||
|
moveTowardsX(pt.x)
|
||||||
|
end
|
||||||
|
elseif turtle.point.z ~= pt.z then
|
||||||
|
moveTowardsZ(pt.z)
|
||||||
|
else
|
||||||
|
turtle.headTowardsX(pt.x)
|
||||||
|
moveTowardsX(pt.x)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Message.addHandler('builder',
|
||||||
|
function(_, id, msg)
|
||||||
|
if not id or id ~= __BUILDER_ID then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not Builder.resupplying then
|
||||||
|
local pt = msg.contents
|
||||||
|
pt.y = pt.y + 3
|
||||||
|
|
||||||
|
turtle.setStatus('supervising')
|
||||||
|
turtle.gotoYfirst(pt)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
Message.addHandler('supplyList',
|
||||||
|
function(_, id, msg)
|
||||||
|
if not id or id ~= __BUILDER_ID then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
turtle.setStatus('resupplying')
|
||||||
|
Builder.resupplying = true
|
||||||
|
Builder.slots = msg.contents.slots
|
||||||
|
Builder.slotUid = msg.contents.uid
|
||||||
|
|
||||||
|
Builder:log('Received supply list ' .. Builder.slotUid)
|
||||||
|
|
||||||
|
os.sleep(0)
|
||||||
|
if not turtle.gotoLocation('supplies') then
|
||||||
|
Builder:log('Failed to go to supply location')
|
||||||
|
Builder.ready = false
|
||||||
|
Event.exitPullEvents()
|
||||||
|
end
|
||||||
|
turtle.setHeading(1)
|
||||||
|
os.sleep(.2) -- random 'Computer is not connected' error...
|
||||||
|
Builder:dumpInventoryWithCheck()
|
||||||
|
Builder:refuel()
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local supplies = Builder:getSupplies()
|
||||||
|
if #supplies == 0 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
Builder:autocraft(supplies)
|
||||||
|
turtle.setStatus('waiting')
|
||||||
|
os.sleep(5)
|
||||||
|
end
|
||||||
|
Builder:log('Got all supplies')
|
||||||
|
os.sleep(0)
|
||||||
|
Builder:gotoBuilder()
|
||||||
|
Builder.resupplying = false
|
||||||
|
end)
|
||||||
|
|
||||||
|
Message.addHandler('needSupplies',
|
||||||
|
function(_, id, msg)
|
||||||
|
if not id or id ~= __BUILDER_ID then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if Builder.resupplying or msg.contents.uid ~= Builder.slotUid then
|
||||||
|
Builder:log('No supplies ready')
|
||||||
|
Message.send(__BUILDER_ID, 'gotSupplies')
|
||||||
|
else
|
||||||
|
turtle.setStatus('supplying')
|
||||||
|
Builder:log('Supplying')
|
||||||
|
os.sleep(0)
|
||||||
|
|
||||||
|
local pt = msg.contents.point
|
||||||
|
pt.y = turtle.getPoint().y
|
||||||
|
pt.heading = nil
|
||||||
|
if not turtle.gotoYfirst(pt) then -- location of builder
|
||||||
|
Builder.resupplying = true
|
||||||
|
Message.send(__BUILDER_ID, 'gotSupplies')
|
||||||
|
os.sleep(0)
|
||||||
|
if not turtle.gotoLocation('supplies') then
|
||||||
|
Builder:log('failed to go to supply location')
|
||||||
|
Event.exitPullEvents()
|
||||||
|
end
|
||||||
|
turtle.setHeading(1)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
pt.y = pt.y - 2 -- location where builder should go for the chest to be above
|
||||||
|
|
||||||
|
turtle.select(15)
|
||||||
|
turtle.placeDown()
|
||||||
|
os.sleep(.1) -- random computer not connected error
|
||||||
|
local p = ChestProvider({ direction = 'up', wrapSide = 'bottom' })
|
||||||
|
for i = 1, 16 do
|
||||||
|
p:insert(i, 64)
|
||||||
|
end
|
||||||
|
|
||||||
|
Message.send(__BUILDER_ID, 'gotSupplies', { supplies = true, point = pt })
|
||||||
|
|
||||||
|
Message.waitForMessage('thanks', 5, __BUILDER_ID)
|
||||||
|
--os.sleep(0)
|
||||||
|
|
||||||
|
--p.condenseItems()
|
||||||
|
for i = 1, 16 do
|
||||||
|
p:extract(i, 64)
|
||||||
|
end
|
||||||
|
turtle.digDown()
|
||||||
|
turtle.setStatus('waiting')
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
Message.addHandler('finished',
|
||||||
|
function(_, id)
|
||||||
|
if not id or id ~= __BUILDER_ID then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
Builder:finish()
|
||||||
|
end)
|
||||||
|
|
||||||
|
Event.on('turtle_abort',
|
||||||
|
function()
|
||||||
|
turtle.abort(false)
|
||||||
|
turtle.setStatus('aborting')
|
||||||
|
Builder:finish()
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function onTheWay() -- parallel routine
|
||||||
|
while true do
|
||||||
|
local _, _, _, id, msg, _ = os.pullEvent('modem_message')
|
||||||
|
if Builder.ready then
|
||||||
|
if id == __BUILDER_ID and msg and msg.type then
|
||||||
|
if msg.type == 'needSupplies' then
|
||||||
|
Message.send(__BUILDER_ID, 'gotSupplies', { supplies = true })
|
||||||
|
elseif msg.type == 'builder' then
|
||||||
|
Builder.lastPoint = msg.contents
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local args = {...}
|
||||||
|
if #args < 2 then
|
||||||
|
error('syntax: <builder id> <facing>')
|
||||||
|
end
|
||||||
|
|
||||||
|
__BUILDER_ID = tonumber(args[1])
|
||||||
|
|
||||||
|
maxStackDB:load()
|
||||||
|
|
||||||
|
itemInfoDB = TableDB({
|
||||||
|
fileName = 'items.db'
|
||||||
|
})
|
||||||
|
|
||||||
|
itemInfoDB:load()
|
||||||
|
|
||||||
|
Builder.itemProvider = MEProvider({ direction = args[2] })
|
||||||
|
if not Builder.itemProvider:isValid() then
|
||||||
|
local sides = {
|
||||||
|
east = 'west',
|
||||||
|
west = 'east',
|
||||||
|
north = 'south',
|
||||||
|
south = 'north',
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.itemProvider = ChestProvider({ direction = sides[args[2]], wrapSide = 'front' })
|
||||||
|
if not Builder.itemProvider:isValid() then
|
||||||
|
error('A chest or ME interface must be in front of turtle')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
turtle.run(function()
|
||||||
|
turtle.setPoint({ x = -1, z = -2, y = -1, heading = 1 })
|
||||||
|
|
||||||
|
turtle.saveLocation('supplies')
|
||||||
|
|
||||||
|
Event.pullEvents(onTheWay)
|
||||||
|
end)
|
||||||
160
builder/viewer.lua
Normal file
160
builder/viewer.lua
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
local Builder = require('builder.builder')
|
||||||
|
local Schematic = require('builder.schematic')
|
||||||
|
local TableDB = require('core.tableDB')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local device = _G.device
|
||||||
|
local fs = _G.fs
|
||||||
|
|
||||||
|
local function Syntax(msg)
|
||||||
|
print([[Required:
|
||||||
|
* Neural Interface
|
||||||
|
* Overlay glasses
|
||||||
|
* Entity sensor
|
||||||
|
* Introspection module
|
||||||
|
]])
|
||||||
|
error(msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
local neural = device['neuralInterface'] or Syntax('Must be run on a neural interface')
|
||||||
|
|
||||||
|
local function assertModule(module, name)
|
||||||
|
if not neural.hasModule(module) then
|
||||||
|
Syntax('Missing: ' .. name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assertModule('plethora:glasses', 'Overlay glasses')
|
||||||
|
assertModule('plethora:sensor', 'Entity sensor')
|
||||||
|
assertModule('plethora:introspection', 'Introspection module')
|
||||||
|
|
||||||
|
local BUILDER_DIR = 'usr/builder'
|
||||||
|
|
||||||
|
--[[-- SubDB --]]--
|
||||||
|
local subDB = TableDB({
|
||||||
|
fileName = fs.combine(BUILDER_DIR, 'sub.db'),
|
||||||
|
})
|
||||||
|
|
||||||
|
function subDB:load()
|
||||||
|
if fs.exists(self.fileName) then
|
||||||
|
TableDB.load(self)
|
||||||
|
elseif not Builder.isCommandComputer then
|
||||||
|
self:seedDB()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function subDB:seedDB()
|
||||||
|
self.data = {
|
||||||
|
[ "minecraft:redstone_wire:0" ] = "minecraft:redstone:0",
|
||||||
|
[ "minecraft:wall_sign:0" ] = "minecraft:sign:0",
|
||||||
|
[ "minecraft:standing_sign:0" ] = "minecraft:sign:0",
|
||||||
|
[ "minecraft:potatoes:0" ] = "minecraft:potato:0",
|
||||||
|
[ "minecraft:unlit_redstone_torch:0" ] = "minecraft:redstone_torch:0",
|
||||||
|
[ "minecraft:powered_repeater:0" ] = "minecraft:repeater:0",
|
||||||
|
[ "minecraft:unpowered_repeater:0" ] = "minecraft:repeater:0",
|
||||||
|
[ "minecraft:carrots:0" ] = "minecraft:carrot:0",
|
||||||
|
[ "minecraft:cocoa:0" ] = "minecraft:dye:3",
|
||||||
|
[ "minecraft:unpowered_comparator:0" ] = "minecraft:comparator:0",
|
||||||
|
[ "minecraft:powered_comparator:0" ] = "minecraft:comparator:0",
|
||||||
|
[ "minecraft:piston_head:0" ] = "minecraft:air:0",
|
||||||
|
[ "minecraft:piston_extension:0" ] = "minecraft:air:0",
|
||||||
|
[ "minecraft:portal:0" ] = "minecraft:air:0",
|
||||||
|
[ "minecraft:double_wooden_slab:0" ] = "minecraft:planks:0",
|
||||||
|
[ "minecraft:double_wooden_slab:1" ] = "minecraft:planks:1",
|
||||||
|
[ "minecraft:double_wooden_slab:2" ] = "minecraft:planks:2",
|
||||||
|
[ "minecraft:double_wooden_slab:3" ] = "minecraft:planks:3",
|
||||||
|
[ "minecraft:double_wooden_slab:4" ] = "minecraft:planks:4",
|
||||||
|
[ "minecraft:double_wooden_slab:5" ] = "minecraft:planks:5",
|
||||||
|
[ "minecraft:lit_redstone_lamp:0" ] = "minecraft:redstone_lamp:0",
|
||||||
|
[ "minecraft:double_stone_slab:1" ] = "minecraft:sandstone:0",
|
||||||
|
[ "minecraft:double_stone_slab:2" ] = "minecraft:planks:0",
|
||||||
|
[ "minecraft:double_stone_slab:3" ] = "minecraft:cobblestone:0",
|
||||||
|
[ "minecraft:double_stone_slab:4" ] = "minecraft:brick_block:0",
|
||||||
|
[ "minecraft:double_stone_slab:5" ] = "minecraft:stonebrick:0",
|
||||||
|
[ "minecraft:double_stone_slab:6" ] = "minecraft:nether_brick:0",
|
||||||
|
[ "minecraft:double_stone_slab:7" ] = "minecraft:quartz_block:0",
|
||||||
|
[ "minecraft:double_stone_slab:9" ] = "minecraft:sandstone:2",
|
||||||
|
[ "minecraft:double_stone_slab2:0" ] = "minecraft:sandstone:0",
|
||||||
|
[ "minecraft:stone_slab:2" ] = "minecraft:wooden_slab:0",
|
||||||
|
[ "minecraft:wheat:0" ] = "minecraft:wheat_seeds:0",
|
||||||
|
[ "minecraft:flowing_water:0" ] = "minecraft:air:0",
|
||||||
|
[ "minecraft:lit_furnace:0" ] = "minecraft:furnace:0",
|
||||||
|
[ "minecraft:wall_banner:0" ] = "minecraft:banner:0",
|
||||||
|
[ "minecraft:standing_banner:0" ] = "minecraft:banner:0",
|
||||||
|
[ "minecraft:tripwire:0" ] = "minecraft:string:0",
|
||||||
|
[ "minecraft:pumpkin_stem:0" ] = "minecraft:pumpkin_seeds:0",
|
||||||
|
}
|
||||||
|
self.dirty = true
|
||||||
|
self:flush()
|
||||||
|
end
|
||||||
|
|
||||||
|
function subDB:add(s)
|
||||||
|
TableDB.add(self, { s.id, s.dmg }, table.concat({ s.sid, s.sdmg }, ':'))
|
||||||
|
self:flush()
|
||||||
|
end
|
||||||
|
|
||||||
|
function subDB:remove(s)
|
||||||
|
-- TODO: tableDB.remove should take table key
|
||||||
|
TableDB.remove(self, s.id .. ':' .. s.dmg)
|
||||||
|
self:flush()
|
||||||
|
end
|
||||||
|
|
||||||
|
function subDB:extract(s)
|
||||||
|
local id, dmg = s:match('(.+):(%d+)')
|
||||||
|
return id, tonumber(dmg)
|
||||||
|
end
|
||||||
|
|
||||||
|
function subDB:getSubstitutedItem(id, dmg)
|
||||||
|
local sub = TableDB.get(self, { id, dmg })
|
||||||
|
if sub then
|
||||||
|
id, dmg = self:extract(sub)
|
||||||
|
end
|
||||||
|
return { id = id, dmg = dmg }
|
||||||
|
end
|
||||||
|
|
||||||
|
function subDB:lookupBlocksForSub(sid, sdmg)
|
||||||
|
local t = { }
|
||||||
|
for k,v in pairs(self.data) do
|
||||||
|
local id, dmg = self:extract(v)
|
||||||
|
if id == sid and dmg == sdmg then
|
||||||
|
id, dmg = self:extract(k)
|
||||||
|
t[k] = { id = id, dmg = dmg, sid = sid, sdmg = sdmg }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[-- startup logic --]]--
|
||||||
|
local args = {...}
|
||||||
|
if #args < 1 then
|
||||||
|
error('supply file name')
|
||||||
|
end
|
||||||
|
|
||||||
|
subDB:load()
|
||||||
|
|
||||||
|
print('Loading schematic')
|
||||||
|
Builder.schematic = Schematic()
|
||||||
|
Builder.schematic:load(args[1])
|
||||||
|
print('Substituting blocks')
|
||||||
|
|
||||||
|
Builder.subDB = subDB
|
||||||
|
Builder:substituteBlocks(Util.throttle())
|
||||||
|
|
||||||
|
local cn = neural.canvas3d().create()
|
||||||
|
local pos = neural.getMetaOwner().withinBlock
|
||||||
|
|
||||||
|
cn.recenter({-pos.x + .5, -(pos.y + 2) + .5, -pos.z + .5 })
|
||||||
|
|
||||||
|
for i = 1, #Builder.schematic.blocks do
|
||||||
|
local b = Builder.schematic:getComputedBlock(i)
|
||||||
|
if b.id ~= "minecraft:air" and b.id ~= 'minecraft:water' then
|
||||||
|
local s, m = pcall(function()
|
||||||
|
cn.addItem({ b.x, b.y, b.z }, b.id, b.dmg)
|
||||||
|
end)
|
||||||
|
if not s and m then
|
||||||
|
_G.printError(m)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
pcall(_G.read)
|
||||||
|
cn.clear()
|
||||||
9
busted/.package
Normal file
9
busted/.package
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
title = 'busted',
|
||||||
|
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/moonscript',
|
||||||
|
description = [[WIP]],
|
||||||
|
license = 'MIT',
|
||||||
|
required = {
|
||||||
|
'penlight',
|
||||||
|
},
|
||||||
|
}
|
||||||
16
busted/depend/system.lua
Normal file
16
busted/depend/system.lua
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
return {
|
||||||
|
-- Returns the monotonic time the system has been up, in secconds.
|
||||||
|
monotime = function()
|
||||||
|
return os.clock()
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Sleep for n seconds.
|
||||||
|
sleep = function(n)
|
||||||
|
os.sleep(n)
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Returns the current system time, 1970 (UTC), in secconds.
|
||||||
|
gettime = function()
|
||||||
|
return os.epoch('utc') / 1000
|
||||||
|
end,
|
||||||
|
}
|
||||||
5
busted/depend/term.lua
Normal file
5
busted/depend/term.lua
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
return {
|
||||||
|
isatty = function()
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
}
|
||||||
8
busted/etc/fstab
Normal file
8
busted/etc/fstab
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
packages/busted/busted urlfs https://raw.githubusercontent.com/Olivine-Labs/busted/master/bin/busted
|
||||||
|
rom/modules/main/busted gitfs Olivine-Labs/busted/master/busted
|
||||||
|
rom/modules/main/mediator.lua urlfs https://raw.githubusercontent.com/Olivine-Labs/mediator_lua/master/src/mediator.lua
|
||||||
|
rom/modules/main/cliargs gitfs amireh/lua_cliargs/master/src/cliargs
|
||||||
|
rom/modules/main/luassert gitfs Olivine-Labs/luassert/master/src
|
||||||
|
rom/modules/main/say.lua urlfs https://raw.githubusercontent.com/Olivine-Labs/say/master/src/init.lua
|
||||||
|
rom/modules/main/term.lua linkfs packages/busted/depend/term.lua
|
||||||
|
rom/modules/main/system.lua linkfs packages/busted/depend/system.lua
|
||||||
24
cash/.package
Normal file
24
cash/.package
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
title = 'ComputerCraft Advanced Shell',
|
||||||
|
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/cash',
|
||||||
|
description = [[A Bourne-compatible shell for ComputerCraft.
|
||||||
|
|
||||||
|
Features
|
||||||
|
Bash/sh-style command line
|
||||||
|
Tab completion (defaulting to file names where not supported)
|
||||||
|
Customizable prompts (including ANSI support)
|
||||||
|
Local & environment variables
|
||||||
|
Argument quoting
|
||||||
|
Multiple commands on one line with semicolons
|
||||||
|
Many built-in functions (including in-line Lua commands)
|
||||||
|
Arithmetic expansion
|
||||||
|
If, while, for statements
|
||||||
|
Function support
|
||||||
|
Shell scripting/shebangs
|
||||||
|
Background jobs
|
||||||
|
rc files
|
||||||
|
Restorable history
|
||||||
|
Partial CCKernel2 support
|
||||||
|
Full compatibility with CraftOS shell.lua]],
|
||||||
|
license = 'MIT',
|
||||||
|
}
|
||||||
7
cash/etc/apps.db
Normal file
7
cash/etc/apps.db
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
[ "16323efd4cb1f1f639d67b94161c2ef37a905517e" ] = {
|
||||||
|
title = "Cash",
|
||||||
|
category = "Apps",
|
||||||
|
run = "packages/cash/cash.lua",
|
||||||
|
},
|
||||||
|
}
|
||||||
1
cash/etc/fstab
Normal file
1
cash/etc/fstab
Normal file
@@ -0,0 +1 @@
|
|||||||
|
packages/cash/cash.lua urlfs https://raw.githubusercontent.com/MCJack123/cash/master/cash.lua
|
||||||
8
ccemux/.package
Normal file
8
ccemux/.package
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
title = 'CCEmuX peripheral management',
|
||||||
|
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/ccemux',
|
||||||
|
description = [[Peripheral management for CCEmuX
|
||||||
|
|
||||||
|
Adds a tab in the System application for configuring peripherals.]],
|
||||||
|
license = 'MIT',
|
||||||
|
}
|
||||||
20
ccemux/autorun/startup.lua
Normal file
20
ccemux/autorun/startup.lua
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
local ccemux = _G.ccemux
|
||||||
|
local fs = _G.fs
|
||||||
|
local textutils = _G.textutils
|
||||||
|
|
||||||
|
if ccemux then
|
||||||
|
-- add a System setup tab
|
||||||
|
fs.mount('sys/apps/system/ccemux.lua', 'linkfs', 'packages/ccemux/system/ccemux.lua')
|
||||||
|
|
||||||
|
_G.kernel.hook('clipboard_copy', function(_, args)
|
||||||
|
local data = args[1]
|
||||||
|
if type(data) == 'table' then
|
||||||
|
local s, m = pcall(textutils.serialize, data)
|
||||||
|
data = s and m or tostring(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
if data then
|
||||||
|
ccemux.setClipboard(data)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
20
ccemux/bin/emustartup.lua
Normal file
20
ccemux/bin/emustartup.lua
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
local ccemux
|
||||||
|
ccemux = _G.ccemux
|
||||||
|
local fs
|
||||||
|
fs = _G.fs
|
||||||
|
local peripheral
|
||||||
|
peripheral = _G.peripheral
|
||||||
|
local unserialize
|
||||||
|
unserialize = _G.textutils.unserialize
|
||||||
|
local CONFIG = 'usr/config/ccemux'
|
||||||
|
if ccemux and fs.exists(CONFIG) then
|
||||||
|
local f = fs.open(CONFIG, 'r')
|
||||||
|
local c = unserialize(f.readAll())
|
||||||
|
f.close()
|
||||||
|
for k, v in pairs(c) do
|
||||||
|
if not peripheral.getType(k) then
|
||||||
|
ccemux.attach(k, v.type, v.args)
|
||||||
|
print(k)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
16
ccemux/bin/emustartup.moon
Normal file
16
ccemux/bin/emustartup.moon
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import ccemux from _G
|
||||||
|
import fs from _G
|
||||||
|
import peripheral from _G
|
||||||
|
import unserialize from _G.textutils
|
||||||
|
|
||||||
|
CONFIG = 'usr/config/ccemux'
|
||||||
|
|
||||||
|
if ccemux and fs.exists CONFIG
|
||||||
|
f = fs.open(CONFIG, 'r')
|
||||||
|
c = unserialize(f.readAll())
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
for k,v in pairs c
|
||||||
|
if not peripheral.getType(k)
|
||||||
|
ccemux.attach(k, v.type, v.args)
|
||||||
|
print k
|
||||||
12
ccemux/etc/apps.db
Normal file
12
ccemux/etc/apps.db
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
[ "87e89abb4c1c551fe08d355d097f18b8de78edca5f556997085681662fce8eed" ] = {
|
||||||
|
title = "Config",
|
||||||
|
category = "CCEmuX",
|
||||||
|
run = "emu config",
|
||||||
|
},
|
||||||
|
[ "cec3a9b89b2e391393d0f68e4bc12a9fa6cf358b3cdf79496dc442d52b8dd528" ] = {
|
||||||
|
title = "Data",
|
||||||
|
category = "CCEmuX",
|
||||||
|
run = "emu data",
|
||||||
|
},
|
||||||
|
}
|
||||||
142
ccemux/system/ccemux.lua
Normal file
142
ccemux/system/ccemux.lua
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
local Config = require('opus.config')
|
||||||
|
local UI = require('opus.ui')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local ccemux = _G.ccemux
|
||||||
|
|
||||||
|
local sides = { 'bottom', 'top', 'back', 'front', 'right', 'left' }
|
||||||
|
|
||||||
|
local tab = UI.Tab {
|
||||||
|
title = 'CCEmuX',
|
||||||
|
description = 'CCEmuX peripherals',
|
||||||
|
form = UI.Form {
|
||||||
|
x = 2, ex = -2, y = 2, ey = 5,
|
||||||
|
values = {
|
||||||
|
side = 'bottom',
|
||||||
|
type = 'wireless_modem',
|
||||||
|
},
|
||||||
|
manualControls = true,
|
||||||
|
side = UI.Chooser {
|
||||||
|
formLabel = 'Side', formKey = 'side',
|
||||||
|
width = 10,
|
||||||
|
},
|
||||||
|
ptype = UI.Chooser {
|
||||||
|
formLabel = 'Type', formKey = 'type',
|
||||||
|
width = 10,
|
||||||
|
choices = {
|
||||||
|
{ name = 'Modem', value = 'wireless_modem' },
|
||||||
|
{ name = 'Drive', value = 'disk_drive' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
drive_id = UI.TextEntry {
|
||||||
|
x = 19, y = 3,
|
||||||
|
formKey = 'drive_id',
|
||||||
|
shadowText = 'id',
|
||||||
|
width = 5,
|
||||||
|
limit = 3,
|
||||||
|
transform = 'number',
|
||||||
|
},
|
||||||
|
add = UI.Button {
|
||||||
|
x = -6, y = 3, width = 5,
|
||||||
|
text = 'Add', event = 'form_ok',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid = UI.Grid {
|
||||||
|
x = 2, ex = -2, y = 7, ey = -2,
|
||||||
|
columns = {
|
||||||
|
{ heading = 'Side', key = 'side', width = 8 },
|
||||||
|
{ heading = 'Type', key = 'type' },
|
||||||
|
{ heading = 'ID', key = 'args', width = 4 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function tab:updatePeripherals(config)
|
||||||
|
self.grid.values = { }
|
||||||
|
for k,v in pairs(config) do
|
||||||
|
table.insert(self.grid.values, {
|
||||||
|
side = k,
|
||||||
|
type = v.type,
|
||||||
|
args = v.args and v.args.id,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
self.grid:update()
|
||||||
|
end
|
||||||
|
|
||||||
|
function tab.bootCheck()
|
||||||
|
local startupFile = 'packages/ccemux/bin/emustartup.lua'
|
||||||
|
|
||||||
|
local c = Util.readTable('.startup.boot')
|
||||||
|
if c then
|
||||||
|
for _,v in pairs(c.preload) do
|
||||||
|
if v == startupFile then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(c.preload, startupFile)
|
||||||
|
Util.writeTable('.startup.boot', c)
|
||||||
|
end
|
||||||
|
|
||||||
|
function tab:enable()
|
||||||
|
self:bootCheck()
|
||||||
|
local config = Config.load('ccemux')
|
||||||
|
|
||||||
|
local choices = { }
|
||||||
|
for _,k in pairs(sides) do
|
||||||
|
table.insert(choices, { name = k, value = k })
|
||||||
|
end
|
||||||
|
self.form.side.choices = choices
|
||||||
|
|
||||||
|
self:updatePeripherals(config)
|
||||||
|
UI.Tab.enable(self)
|
||||||
|
|
||||||
|
self.form.drive_id.enabled = false
|
||||||
|
end
|
||||||
|
|
||||||
|
function tab:eventHandler(event)
|
||||||
|
if event.type == 'form_complete' then
|
||||||
|
if event.values.type == 'disk_drive' and not event.values.drive_id then
|
||||||
|
self:emit({ type = 'error_message', message = 'Invalid drive ID' })
|
||||||
|
else
|
||||||
|
ccemux.detach(event.values.side)
|
||||||
|
|
||||||
|
local config = Config.load('ccemux')
|
||||||
|
config[event.values.side] = {
|
||||||
|
type = event.values.type
|
||||||
|
}
|
||||||
|
if event.values.type == 'disk_drive' then
|
||||||
|
config[event.values.side].args = {
|
||||||
|
id = event.values.drive_id
|
||||||
|
}
|
||||||
|
ccemux.attach(event.values.side, event.values.type, { id = event.values.drive_id })
|
||||||
|
else
|
||||||
|
ccemux.attach(event.values.side, event.values.type)
|
||||||
|
end
|
||||||
|
Config.update('ccemux', config)
|
||||||
|
self:updatePeripherals(config)
|
||||||
|
self.grid:draw()
|
||||||
|
|
||||||
|
self:emit({ type = 'success_message', message = 'Attached' })
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif event.type == 'choice_change' then
|
||||||
|
if event.element == self.form.ptype then
|
||||||
|
self.form.drive_id.enabled = event.value == 'disk_drive'
|
||||||
|
self.form:draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif event.type == 'grid_select' then
|
||||||
|
local config = Config.load('ccemux')
|
||||||
|
config[event.selected.side] = nil
|
||||||
|
Config.update('ccemux', config)
|
||||||
|
self:updatePeripherals(config)
|
||||||
|
self.grid:draw()
|
||||||
|
|
||||||
|
self:emit({ type = 'success_message', message = 'Detached' })
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return tab
|
||||||
9
collections/.package
Normal file
9
collections/.package
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
title = 'Collections',
|
||||||
|
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/collections',
|
||||||
|
description = [[Collections (rework)
|
||||||
|
See: https://github.com/imliam/Lua-Collections
|
||||||
|
|
||||||
|
Collections are like tables on steroids. They are designed to act as a fluent wrapper when working with structured data, offering the developer convenience for common tasks.]],
|
||||||
|
license = 'MIT',
|
||||||
|
}
|
||||||
1257
collections/apis/init.lua
Normal file
1257
collections/apis/init.lua
Normal file
File diff suppressed because it is too large
Load Diff
1
collections/etc/fstab
Normal file
1
collections/etc/fstab
Normal file
@@ -0,0 +1 @@
|
|||||||
|
packages/collections/tests/tests.lua urlfs https://raw.githubusercontent.com/imliam/Lua-Collections/master/tests.lua
|
||||||
1030
collections/tests/tests.lua
Normal file
1030
collections/tests/tests.lua
Normal file
File diff suppressed because it is too large
Load Diff
19
common/.package
Normal file
19
common/.package
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
title = 'Useful applications',
|
||||||
|
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/common',
|
||||||
|
description = [[Various applications for Opus.
|
||||||
|
|
||||||
|
* App Store
|
||||||
|
* Peripheral API viewer
|
||||||
|
* Disk / Computer copier
|
||||||
|
* A better editor (copy/paste/undo)
|
||||||
|
* Turtle swarm miner
|
||||||
|
* Turtle follow
|
||||||
|
* Screen recorder
|
||||||
|
* and more...
|
||||||
|
]],
|
||||||
|
license = 'MIT',
|
||||||
|
required = {
|
||||||
|
'core',
|
||||||
|
},
|
||||||
|
}
|
||||||
365
common/Appstore.lua
Normal file
365
common/Appstore.lua
Normal file
@@ -0,0 +1,365 @@
|
|||||||
|
local Ansi = require('opus.ansi')
|
||||||
|
local SHA = require('opus.crypto.sha2')
|
||||||
|
local UI = require('opus.ui')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local fs = _G.fs
|
||||||
|
local http = _G.http
|
||||||
|
local multishell = _ENV.multishell
|
||||||
|
local os = _G.os
|
||||||
|
local shell = _ENV.shell
|
||||||
|
local colors = _G.colors
|
||||||
|
|
||||||
|
local REGISTRY_DIR = 'usr/.registry'
|
||||||
|
|
||||||
|
-- FIX SOMEDAY
|
||||||
|
local function registerApp(app, key)
|
||||||
|
app.key = SHA.compute(key)
|
||||||
|
Util.writeTable(fs.combine(REGISTRY_DIR, app.key), app)
|
||||||
|
os.queueEvent('os_register_app')
|
||||||
|
end
|
||||||
|
|
||||||
|
local function unregisterApp(key)
|
||||||
|
local filename = fs.combine(REGISTRY_DIR, SHA.compute(key))
|
||||||
|
if fs.exists(filename) then
|
||||||
|
fs.delete(filename)
|
||||||
|
os.queueEvent('os_register_app')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
multishell.setTitle(multishell.getCurrent(), 'App Store')
|
||||||
|
UI:configure('Appstore', ...)
|
||||||
|
|
||||||
|
local APP_DIR = 'usr/apps'
|
||||||
|
|
||||||
|
local source = {
|
||||||
|
text = "STD Default",
|
||||||
|
event = 'source',
|
||||||
|
url = "https://github.com/LDDestroier/STD-GUI/raw/master/list.lua",
|
||||||
|
}
|
||||||
|
|
||||||
|
shell.setDir(APP_DIR)
|
||||||
|
|
||||||
|
local function downloadApp(app)
|
||||||
|
local h
|
||||||
|
|
||||||
|
if type(app.url) == "table" then
|
||||||
|
h = contextualGet(app.url[1])
|
||||||
|
else
|
||||||
|
h = http.get(app.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
if h then
|
||||||
|
local contents = h.readAll()
|
||||||
|
h:close()
|
||||||
|
return contents
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function runApp(app, checkExists, ...)
|
||||||
|
local env = shell.makeEnv(_ENV)
|
||||||
|
local path, fn
|
||||||
|
local args = { ... }
|
||||||
|
|
||||||
|
if checkExists and fs.exists(fs.combine(APP_DIR, app.name)) then
|
||||||
|
path = fs.combine(APP_DIR, app.name)
|
||||||
|
else
|
||||||
|
local program = downloadApp(app)
|
||||||
|
|
||||||
|
fn = function()
|
||||||
|
|
||||||
|
if not program then
|
||||||
|
error('Failed to download')
|
||||||
|
end
|
||||||
|
|
||||||
|
fn = _G.loadstring(program, app.name)
|
||||||
|
|
||||||
|
if not fn then
|
||||||
|
error('Failed to download')
|
||||||
|
end
|
||||||
|
|
||||||
|
_G.setfenv(fn, env)
|
||||||
|
fn(table.unpack(args))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
multishell.openTab(_ENV, {
|
||||||
|
title = app.name,
|
||||||
|
path = path,
|
||||||
|
fn = fn,
|
||||||
|
focused = true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return true, 'Running program'
|
||||||
|
end
|
||||||
|
|
||||||
|
local installApp = function(app)
|
||||||
|
|
||||||
|
local program = downloadApp(app)
|
||||||
|
if not program then
|
||||||
|
return false, "Failed to download"
|
||||||
|
end
|
||||||
|
|
||||||
|
local fullPath = fs.combine(APP_DIR, app.name)
|
||||||
|
Util.writeFile(fullPath, program)
|
||||||
|
return true, 'Installed as ' .. fullPath
|
||||||
|
end
|
||||||
|
|
||||||
|
local viewApp = function(app)
|
||||||
|
|
||||||
|
local program = downloadApp(app)
|
||||||
|
if not program then
|
||||||
|
return false, "Failed to download"
|
||||||
|
end
|
||||||
|
|
||||||
|
Util.writeFile('/.source', program)
|
||||||
|
shell.openForegroundTab('edit /.source')
|
||||||
|
fs.delete('/.source')
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local getSourceListing = function()
|
||||||
|
local contents = http.get(source.url)
|
||||||
|
if contents then
|
||||||
|
|
||||||
|
local fn = _G.loadstring(contents.readAll(), source.text)
|
||||||
|
contents.close()
|
||||||
|
|
||||||
|
local env = { std = { } }
|
||||||
|
setmetatable(env, { __index = _G })
|
||||||
|
_G.setfenv(fn, env)
|
||||||
|
fn()
|
||||||
|
|
||||||
|
if env.contextualGet then
|
||||||
|
contextualGet = env.contextualGet
|
||||||
|
end
|
||||||
|
|
||||||
|
source.storeURLs = env.std.storeURLs
|
||||||
|
source.storeCatagoryNames = env.std.storeCatagoryNames
|
||||||
|
|
||||||
|
if source.storeURLs and source.storeCatagoryNames then
|
||||||
|
for k,v in pairs(source.storeURLs) do
|
||||||
|
if source.generateName then
|
||||||
|
v.name = v.title:match('(%w+)')
|
||||||
|
if not v.name or #v.name == 0 then
|
||||||
|
v.name = tostring(k)
|
||||||
|
else
|
||||||
|
v.name = v.name:lower()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
v.name = k
|
||||||
|
end
|
||||||
|
v.categoryName = source.storeCatagoryNames[v.catagory]
|
||||||
|
v.ltitle = v.title:lower()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
getSourceListing()
|
||||||
|
|
||||||
|
if not source.storeURLs then
|
||||||
|
error('Unable to download application list')
|
||||||
|
end
|
||||||
|
|
||||||
|
local buttons = { }
|
||||||
|
for k,v in Util.spairs(source.storeCatagoryNames,
|
||||||
|
function(a, b) return a:lower() < b:lower() end) do
|
||||||
|
|
||||||
|
if v ~= 'Operating System' then
|
||||||
|
table.insert(buttons, {
|
||||||
|
text = v,
|
||||||
|
event = 'category',
|
||||||
|
index = k,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
source.index, source.name = Util.first(source.storeCatagoryNames)
|
||||||
|
|
||||||
|
local appPage = UI.Page {
|
||||||
|
menuBar = UI.MenuBar {
|
||||||
|
buttons = {
|
||||||
|
{ text = '\027', event = 'back' },
|
||||||
|
{ text = 'Install', event = 'install' },
|
||||||
|
{ text = 'Run', event = 'run' },
|
||||||
|
{ text = 'View', event = 'view' },
|
||||||
|
{ text = 'Remove', event = 'uninstall', name = 'removeButton' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
container = UI.Window {
|
||||||
|
x = 2, y = 3, ex = -2, ey = -3,
|
||||||
|
viewport = UI.Viewport(),
|
||||||
|
},
|
||||||
|
notification = UI.Notification(),
|
||||||
|
accelerators = {
|
||||||
|
[ 'control-q' ] = 'back',
|
||||||
|
backspace = 'back',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function appPage.container.viewport:draw()
|
||||||
|
local app = self.parent.parent.app
|
||||||
|
local str = string.format(
|
||||||
|
'%s \nBy: %s \nCategory: %s\nFile name: %s\n\n%s',
|
||||||
|
Ansi.yellow .. app.title .. Ansi.reset,
|
||||||
|
app.creator,
|
||||||
|
app.categoryName, app.name,
|
||||||
|
Ansi.yellow .. app.description .. Ansi.reset)
|
||||||
|
|
||||||
|
self:clear()
|
||||||
|
self:print(str)
|
||||||
|
|
||||||
|
if appPage.notification.enabled then
|
||||||
|
appPage.notification:draw()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function appPage:enable(app)
|
||||||
|
self.source = source
|
||||||
|
self.app = app
|
||||||
|
UI.Page.enable(self)
|
||||||
|
|
||||||
|
self.container.viewport:setScrollPosition(0)
|
||||||
|
if fs.exists(fs.combine(APP_DIR, app.name)) then
|
||||||
|
self.menuBar.removeButton:enable('Remove')
|
||||||
|
else
|
||||||
|
self.menuBar.removeButton:disable('Remove')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function appPage:eventHandler(event)
|
||||||
|
if event.type == 'back' then
|
||||||
|
UI:setPreviousPage()
|
||||||
|
|
||||||
|
elseif event.type == 'run' then
|
||||||
|
self.notification:info('Running program', 3)
|
||||||
|
self:sync()
|
||||||
|
runApp(self.app, true)
|
||||||
|
|
||||||
|
elseif event.type == 'view' then
|
||||||
|
self.notification:info('Downloading program', 3)
|
||||||
|
self:sync()
|
||||||
|
viewApp(self.app)
|
||||||
|
|
||||||
|
elseif event.type == 'uninstall' then
|
||||||
|
if self.app.runOnly then
|
||||||
|
runApp(self.app, false, 'uninstall')
|
||||||
|
else
|
||||||
|
fs.delete(fs.combine(APP_DIR, self.app.name))
|
||||||
|
self.notification:success("Uninstalled " .. self.app.name, 3)
|
||||||
|
self:focusFirst(self)
|
||||||
|
self.menuBar.removeButton:disable('Remove')
|
||||||
|
self.menuBar:draw()
|
||||||
|
|
||||||
|
unregisterApp(self.app.creator .. '.' .. self.app.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif event.type == 'install' then
|
||||||
|
self.notification:info("Installing", 3)
|
||||||
|
self:sync()
|
||||||
|
local s, m
|
||||||
|
if self.app.runOnly then
|
||||||
|
s,m = runApp(self.app, false)
|
||||||
|
else
|
||||||
|
s,m = installApp(self.app)
|
||||||
|
end
|
||||||
|
if s then
|
||||||
|
self.notification:success(m, 3)
|
||||||
|
|
||||||
|
if not self.app.runOnly then
|
||||||
|
self.menuBar.removeButton:enable('Remove')
|
||||||
|
self.menuBar:draw()
|
||||||
|
|
||||||
|
local category = 'Apps'
|
||||||
|
if self.app.catagoryName == 'Game' then
|
||||||
|
category = 'Games'
|
||||||
|
end
|
||||||
|
|
||||||
|
registerApp({
|
||||||
|
run = fs.combine(APP_DIR, self.app.name),
|
||||||
|
title = self.app.title,
|
||||||
|
category = category,
|
||||||
|
icon = self.app.icon,
|
||||||
|
}, self.app.creator .. '.' .. self.app.name)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self.notification:error(m, 3)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local categoryPage = UI.Page {
|
||||||
|
menuBar = UI.MenuBar {
|
||||||
|
buttons = {
|
||||||
|
{ text = 'Category', name = 'categoryButton', dropdown = buttons },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid = UI.ScrollingGrid {
|
||||||
|
y = 2, ey = -2,
|
||||||
|
columns = {
|
||||||
|
{ heading = 'Title', key = 'title' },
|
||||||
|
},
|
||||||
|
sortColumn = 'title',
|
||||||
|
},
|
||||||
|
statusBar = UI.StatusBar(),
|
||||||
|
accelerators = {
|
||||||
|
l = 'lua',
|
||||||
|
[ 'control-q' ] = 'quit',
|
||||||
|
},
|
||||||
|
source = source,
|
||||||
|
}
|
||||||
|
|
||||||
|
function categoryPage:setCategory(name, index)
|
||||||
|
self.grid.values = { }
|
||||||
|
for _,v in pairs(source.storeURLs) do
|
||||||
|
if index == 0 or index == v.catagory then
|
||||||
|
table.insert(self.grid.values, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.statusBar:setStatus(string.format('%s: %s', self.source.text or '', name))
|
||||||
|
self.grid:update()
|
||||||
|
self.grid:setIndex(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function categoryPage.grid:sortCompare(a, b)
|
||||||
|
return a.ltitle < b.ltitle
|
||||||
|
end
|
||||||
|
|
||||||
|
function categoryPage.grid:getRowTextColor(row, selected)
|
||||||
|
if fs.exists(fs.combine(APP_DIR, row.name)) then
|
||||||
|
return colors.orange
|
||||||
|
end
|
||||||
|
return UI.Grid:getRowTextColor(row, selected)
|
||||||
|
end
|
||||||
|
|
||||||
|
function categoryPage:eventHandler(event)
|
||||||
|
if event.type == 'grid_select' or event.type == 'select' then
|
||||||
|
UI:setPage(appPage, self.grid:getSelected())
|
||||||
|
|
||||||
|
elseif event.type == 'category' then
|
||||||
|
self:setCategory(event.button.text, event.button.index)
|
||||||
|
self:setFocus(self.grid)
|
||||||
|
self:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'source' then
|
||||||
|
self:setFocus(self.grid)
|
||||||
|
self:setSource(event.button)
|
||||||
|
self:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'quit' then
|
||||||
|
UI:quit()
|
||||||
|
|
||||||
|
else
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
print("Retrieving catalog list")
|
||||||
|
categoryPage:setCategory(source.name, source.index)
|
||||||
|
|
||||||
|
UI:setPage(categoryPage)
|
||||||
|
UI:start()
|
||||||
198
common/Devices.lua
Normal file
198
common/Devices.lua
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
local Ansi = require('opus.ansi')
|
||||||
|
local Event = require('opus.event')
|
||||||
|
local UI = require('opus.ui')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local device = _G.device
|
||||||
|
|
||||||
|
--[[ -- PeripheralsPage -- ]] --
|
||||||
|
local peripheralsPage = UI.Page {
|
||||||
|
grid = UI.ScrollingGrid {
|
||||||
|
ey = -2,
|
||||||
|
columns = {
|
||||||
|
--{ heading = 'Name', key = 'name' },
|
||||||
|
{ heading = 'Type', key = 'type' },
|
||||||
|
{ heading = 'Side', key = 'side' },
|
||||||
|
},
|
||||||
|
sortColumn = 'type',
|
||||||
|
autospace = true,
|
||||||
|
enable = function(self)
|
||||||
|
Util.clear(self.values)
|
||||||
|
for _,v in pairs(device) do
|
||||||
|
table.insert(self.values, {
|
||||||
|
type = v.type,
|
||||||
|
side = v.side,
|
||||||
|
name = v.name,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
self:update()
|
||||||
|
self:adjustWidth()
|
||||||
|
UI.Grid.enable(self)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
statusBar = UI.StatusBar {
|
||||||
|
values = 'Select peripheral',
|
||||||
|
},
|
||||||
|
accelerators = {
|
||||||
|
[ 'control-q' ] = 'quit',
|
||||||
|
},
|
||||||
|
updatePeripherals = function(self)
|
||||||
|
if UI:getCurrentPage() == self then
|
||||||
|
self.grid:draw()
|
||||||
|
self:sync()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
eventHandler = function(self, event)
|
||||||
|
if event.type == 'quit' then
|
||||||
|
UI:quit()
|
||||||
|
|
||||||
|
elseif event.type == 'grid_select' then
|
||||||
|
UI:setPage('methods', event.selected)
|
||||||
|
|
||||||
|
end
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
--[[ -- MethodsPage -- ]] --
|
||||||
|
local methodsPage = UI.Page {
|
||||||
|
doc = UI.TextArea {
|
||||||
|
backgroundColor = 'black',
|
||||||
|
ey = -7,
|
||||||
|
marginLeft = 1, marginTop = 1,
|
||||||
|
},
|
||||||
|
grid = UI.ScrollingGrid {
|
||||||
|
y = -6, ey = -2,
|
||||||
|
columns = {
|
||||||
|
{ heading = 'Name', key = 'name' }
|
||||||
|
},
|
||||||
|
sortColumn = 'name',
|
||||||
|
},
|
||||||
|
statusBar = UI.StatusBar {
|
||||||
|
status = 'q to return',
|
||||||
|
},
|
||||||
|
accelerators = {
|
||||||
|
[ 'control-q' ] = 'back',
|
||||||
|
backspace = 'back',
|
||||||
|
},
|
||||||
|
enable = function(self, p)
|
||||||
|
self.peripheral = p or self.peripheral
|
||||||
|
|
||||||
|
p = device[self.peripheral.name]
|
||||||
|
if p.getDocs then
|
||||||
|
-- plethora
|
||||||
|
self.grid.values = { }
|
||||||
|
for k,v in pairs(p.getDocs()) do
|
||||||
|
table.insert(self.grid.values, {
|
||||||
|
name = k,
|
||||||
|
doc = v,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
elseif not p.getAdvancedMethodsData then
|
||||||
|
-- computercraft
|
||||||
|
self.grid.values = { }
|
||||||
|
for k,v in pairs(p) do
|
||||||
|
if type(v) == 'function' then
|
||||||
|
table.insert(self.grid.values, {
|
||||||
|
name = k,
|
||||||
|
noext = true,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- open peripherals
|
||||||
|
self.grid.values = p.getAdvancedMethodsData()
|
||||||
|
for name,f in pairs(self.grid.values) do
|
||||||
|
f.name = name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self.grid:update()
|
||||||
|
self.grid:setIndex(1)
|
||||||
|
|
||||||
|
self.doc:setText(self:getDocumentation())
|
||||||
|
|
||||||
|
self.statusBar:setStatus(self.peripheral.type)
|
||||||
|
UI.Page.enable(self)
|
||||||
|
|
||||||
|
self:setFocus(self.grid)
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
function methodsPage:eventHandler(event)
|
||||||
|
if event.type == 'back' then
|
||||||
|
UI:setPage(peripheralsPage)
|
||||||
|
return true
|
||||||
|
elseif event.type == 'grid_focus_row' then
|
||||||
|
self.doc:setText(self:getDocumentation())
|
||||||
|
end
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
|
||||||
|
function methodsPage:getDocumentation()
|
||||||
|
local method = self.grid:getSelected()
|
||||||
|
|
||||||
|
if not method or method.noext then -- computercraft docs
|
||||||
|
return 'No documentation'
|
||||||
|
end
|
||||||
|
|
||||||
|
if method.doc then -- plethora docs
|
||||||
|
return Ansi.yellow .. method.doc
|
||||||
|
end
|
||||||
|
|
||||||
|
-- open peripherals docs
|
||||||
|
local sb = { }
|
||||||
|
if method.description then
|
||||||
|
table.insert(sb, method.description .. '\n\n')
|
||||||
|
end
|
||||||
|
|
||||||
|
if method.returnTypes ~= '()' then
|
||||||
|
table.insert(sb, Ansi.yellow .. method.returnTypes .. ' ')
|
||||||
|
end
|
||||||
|
table.insert(sb, Ansi.blue .. method.name .. Ansi.reset .. '(')
|
||||||
|
|
||||||
|
for k,arg in ipairs(method.args) do
|
||||||
|
if arg.optional then
|
||||||
|
table.insert(sb, Ansi.orange .. string.format('[%s]', arg.name))
|
||||||
|
else
|
||||||
|
table.insert(sb, Ansi.green .. arg.name)
|
||||||
|
end
|
||||||
|
if k < #method.args then
|
||||||
|
table.insert(sb, ',')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(sb, Ansi.reset .. ')')
|
||||||
|
|
||||||
|
Util.filterInplace(method.args, function(a) return #a.description > 0 end)
|
||||||
|
if #method.args > 0 then
|
||||||
|
table.insert(sb, '\n\n')
|
||||||
|
for k,arg in ipairs(method.args) do
|
||||||
|
if arg.optional then
|
||||||
|
table.insert(sb, Ansi.orange)
|
||||||
|
else
|
||||||
|
table.insert(sb, Ansi.green)
|
||||||
|
end
|
||||||
|
table.insert(sb, arg.name .. Ansi.reset .. ': ' .. arg.description)
|
||||||
|
if k ~= #method.args then
|
||||||
|
table.insert(sb, '\n\n')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return table.concat(sb)
|
||||||
|
end
|
||||||
|
|
||||||
|
Event.on('peripheral', function()
|
||||||
|
peripheralsPage:updatePeripherals()
|
||||||
|
end)
|
||||||
|
|
||||||
|
Event.on('peripheral_detach', function()
|
||||||
|
peripheralsPage:updatePeripherals()
|
||||||
|
end)
|
||||||
|
|
||||||
|
UI:setPage(peripheralsPage)
|
||||||
|
|
||||||
|
UI:setPages({
|
||||||
|
methods = methodsPage,
|
||||||
|
})
|
||||||
|
|
||||||
|
UI:start()
|
||||||
276
common/DiskCopy.lua
Normal file
276
common/DiskCopy.lua
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
local Ansi = require('opus.ansi')
|
||||||
|
local Config = require('opus.config')
|
||||||
|
local Event = require('opus.event')
|
||||||
|
local UI = require('opus.ui')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local colors = _G.colors
|
||||||
|
local fs = _G.fs
|
||||||
|
local peripheral = _G.peripheral
|
||||||
|
|
||||||
|
local drives = { }
|
||||||
|
if peripheral.getType('left') == 'drive' then
|
||||||
|
drives.left = Util.shallowCopy(peripheral.wrap('left'))
|
||||||
|
drives.left.name = 'left'
|
||||||
|
end
|
||||||
|
if peripheral.getType('right') == 'drive' then
|
||||||
|
drives.right = Util.shallowCopy(peripheral.wrap('right'))
|
||||||
|
drives.right.name = 'right'
|
||||||
|
end
|
||||||
|
|
||||||
|
peripheral.find('drive', function(n, v)
|
||||||
|
if not drives.left then
|
||||||
|
drives.left = Util.shallowCopy(v)
|
||||||
|
drives.left.name = n
|
||||||
|
elseif not drives.right then
|
||||||
|
drives.right = Util.shallowCopy(v)
|
||||||
|
drives.right.name = n
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
if not (drives.left and drives.right) then
|
||||||
|
error('Two drives are required')
|
||||||
|
end
|
||||||
|
|
||||||
|
local COPY_LEFT = 1
|
||||||
|
local COPY_RIGHT = 2
|
||||||
|
local directions = {
|
||||||
|
[ COPY_LEFT ] = { text = '-->>' },
|
||||||
|
[ COPY_RIGHT ] = { text = '<<--' },
|
||||||
|
}
|
||||||
|
|
||||||
|
local config = Config.load('DiskCopy', {
|
||||||
|
eject = true,
|
||||||
|
automatic = false,
|
||||||
|
copyDir = COPY_LEFT
|
||||||
|
})
|
||||||
|
|
||||||
|
local page = UI.Page {
|
||||||
|
linfo = UI.Window {
|
||||||
|
x = 2, y = 2, ey = 5, width = 18,
|
||||||
|
},
|
||||||
|
rinfo = UI.Window {
|
||||||
|
x = -19, y = 2, ey = 5, width = 18,
|
||||||
|
},
|
||||||
|
dir = UI.Button {
|
||||||
|
x = 17, y = 6, width = 6,
|
||||||
|
event = 'change_dir',
|
||||||
|
},
|
||||||
|
progress = UI.ProgressBar {
|
||||||
|
x = 2, ex = -2, y = -4,
|
||||||
|
backgroundColor = colors.black,
|
||||||
|
},
|
||||||
|
ejectText = UI.Text {
|
||||||
|
x = 2, y = -2,
|
||||||
|
value = 'Eject'
|
||||||
|
},
|
||||||
|
eject = UI.Checkbox {
|
||||||
|
x = 8, y = -2,
|
||||||
|
},
|
||||||
|
automaticText = UI.Text {
|
||||||
|
x = 12, y = -2,
|
||||||
|
value = 'Copy automatically'
|
||||||
|
},
|
||||||
|
automatic = UI.Checkbox {
|
||||||
|
x = 31, y = -2,
|
||||||
|
},
|
||||||
|
copyButton = UI.Button {
|
||||||
|
x = -7, y = -2,
|
||||||
|
text = 'Copy',
|
||||||
|
event = 'copy',
|
||||||
|
inactive = true,
|
||||||
|
},
|
||||||
|
warning = UI.Text {
|
||||||
|
x = 2, ex = -2, y = -1,
|
||||||
|
align = 'center',
|
||||||
|
textColor = colors.orange,
|
||||||
|
},
|
||||||
|
notification = UI.Notification { },
|
||||||
|
}
|
||||||
|
|
||||||
|
function page:enable()
|
||||||
|
Util.merge(self.dir, directions[config.copyDir])
|
||||||
|
|
||||||
|
self.eject.value = config.eject
|
||||||
|
self.automatic.value = config.automatic
|
||||||
|
|
||||||
|
self.dir:move(math.floor((self.width / 2) - 3) + 1, self.dir.y)
|
||||||
|
|
||||||
|
UI.Page.enable(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isValid(drive)
|
||||||
|
return drive.isDiskPresent() and drive.getMountPath()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function needsLabel(drive)
|
||||||
|
return drive.isDiskPresent() and not drive.getMountPath() and not drive.getAudioTitle()
|
||||||
|
end
|
||||||
|
|
||||||
|
function page:drawInfo(drive, textArea)
|
||||||
|
local function getLabel()
|
||||||
|
return not drive.isDiskPresent() and 'empty' or
|
||||||
|
not drive.getMountPath() and 'invalid' or
|
||||||
|
drive.getDiskLabel() or 'unlabeled'
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getUsed()
|
||||||
|
return isValid(drive) and fs.getSize(drive.getMountPath(), true) or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getFree()
|
||||||
|
return isValid(drive) and fs.getFreeSpace(drive.getMountPath()) or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
textArea:print(string.format('Drive: %s%s%s\nLabel: %s%s%s\nUsed: %s%s%s\nFree: %s%s%s',
|
||||||
|
Ansi.yellow, drive.name, Ansi.reset,
|
||||||
|
isValid(drive) and Ansi.yellow or Ansi.orange, getLabel():sub(1, 10), Ansi.reset,
|
||||||
|
Ansi.yellow, Util.toBytes(getUsed()), Ansi.reset,
|
||||||
|
Ansi.yellow, Util.toBytes(getFree()), Ansi.reset))
|
||||||
|
end
|
||||||
|
|
||||||
|
function page:scan()
|
||||||
|
local showWarning = needsLabel(drives.left) or needsLabel(drives.right)
|
||||||
|
local valid = isValid(drives.left) and isValid(drives.right)
|
||||||
|
|
||||||
|
self.warning.value = showWarning and 'Computers must be labeled'
|
||||||
|
self.copyButton.inactive = not valid
|
||||||
|
|
||||||
|
self:draw()
|
||||||
|
self.progress:clear()
|
||||||
|
self.progress:centeredWrite(1, 'Analyzing Disks..')
|
||||||
|
self.progress:sync()
|
||||||
|
|
||||||
|
self:drawInfo(drives.left, self.linfo)
|
||||||
|
self:drawInfo(drives.right, self.rinfo)
|
||||||
|
|
||||||
|
self.progress:clear()
|
||||||
|
end
|
||||||
|
|
||||||
|
function page:copy()
|
||||||
|
local sdrive = config.copyDir == COPY_LEFT and drives.left or drives.right
|
||||||
|
local tdrive = config.copyDir == COPY_LEFT and drives.right or drives.left
|
||||||
|
|
||||||
|
local throttle = Util.throttle()
|
||||||
|
local sourceFiles, targetFiles = { }, { }
|
||||||
|
|
||||||
|
local function getListing(mountPath, path, files)
|
||||||
|
for _,f in pairs(fs.list(path)) do
|
||||||
|
local file = fs.combine(path, f)
|
||||||
|
if not fs.isReadOnly(file) then
|
||||||
|
files[string.sub(file, #mountPath + 1)] = true
|
||||||
|
if fs.isDir(file) then
|
||||||
|
getListing(mountPath, file, files)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
throttle()
|
||||||
|
end
|
||||||
|
|
||||||
|
self.progress:clear()
|
||||||
|
self.progress:centeredWrite(1, 'Computing..')
|
||||||
|
self.progress:sync()
|
||||||
|
|
||||||
|
getListing(sdrive.getMountPath(), sdrive.getMountPath(), sourceFiles)
|
||||||
|
getListing(tdrive.getMountPath(), tdrive.getMountPath(), targetFiles)
|
||||||
|
|
||||||
|
local copied = 0
|
||||||
|
local totalFiles = Util.size(sourceFiles)
|
||||||
|
|
||||||
|
local function rawCopy(source, target)
|
||||||
|
if fs.isDir(source) then
|
||||||
|
copied = copied + 1
|
||||||
|
if not fs.exists(target) then
|
||||||
|
fs.makeDir(target)
|
||||||
|
end
|
||||||
|
for _,f in pairs(fs.list(source)) do
|
||||||
|
rawCopy(fs.combine(source, f), fs.combine(target, f))
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
if fs.exists(target) then
|
||||||
|
fs.delete(target)
|
||||||
|
end
|
||||||
|
|
||||||
|
fs.copy(source, target)
|
||||||
|
copied = copied + 1
|
||||||
|
self.progress.value = copied * 100 / totalFiles
|
||||||
|
self.progress:draw()
|
||||||
|
self.progress:sync()
|
||||||
|
end
|
||||||
|
throttle()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function cleanup()
|
||||||
|
for k in pairs(targetFiles) do
|
||||||
|
if not sourceFiles[k] then
|
||||||
|
fs.delete(fs.combine(tdrive.getMountPath(), k))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self.progress:clear()
|
||||||
|
rawCopy(sdrive.getMountPath(), tdrive.getMountPath())
|
||||||
|
cleanup()
|
||||||
|
|
||||||
|
self.progress:clear()
|
||||||
|
self.progress:centeredWrite(1, 'Copy Complete', colors.lime, colors.black)
|
||||||
|
self.progress:sync()
|
||||||
|
|
||||||
|
self.progress.value = 0
|
||||||
|
-- self.progress:clear()
|
||||||
|
|
||||||
|
self:scan()
|
||||||
|
|
||||||
|
if config.eject then
|
||||||
|
tdrive.ejectDisk()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function page:eventHandler(event)
|
||||||
|
if event.type == 'change_dir' then
|
||||||
|
config.copyDir = (config.copyDir) % 2 + 1
|
||||||
|
Util.merge(self.dir, directions[config.copyDir])
|
||||||
|
Config.update('DiskCopy', config)
|
||||||
|
self.dir:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'copy' then
|
||||||
|
self:copy()
|
||||||
|
|
||||||
|
elseif event.type == 'checkbox_change' then
|
||||||
|
if event.element == self.eject then
|
||||||
|
config.eject = not not event.checked
|
||||||
|
elseif event.element == self.automatic then
|
||||||
|
config.automatic = not not event.checked
|
||||||
|
end
|
||||||
|
|
||||||
|
Config.update('DiskCopy', config)
|
||||||
|
event.element:draw()
|
||||||
|
|
||||||
|
else
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
Event.on("disk", function()
|
||||||
|
page:scan()
|
||||||
|
page:sync()
|
||||||
|
|
||||||
|
if config.automatic and not page.copyButton.inactive then
|
||||||
|
page:copy()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
Event.on("disk_eject", function()
|
||||||
|
page:scan()
|
||||||
|
page:sync()
|
||||||
|
end)
|
||||||
|
|
||||||
|
Event.onTimeout(.2, function()
|
||||||
|
page:scan()
|
||||||
|
page:sync()
|
||||||
|
end)
|
||||||
|
|
||||||
|
UI:setPage(page)
|
||||||
|
UI:start()
|
||||||
127
common/Events.lua
Normal file
127
common/Events.lua
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
local Event = require('opus.event')
|
||||||
|
local UI = require('opus.ui')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local multishell = _ENV.multishell
|
||||||
|
local kernel = _G.kernel
|
||||||
|
|
||||||
|
UI:configure('Events', ...)
|
||||||
|
|
||||||
|
local page = UI.Page {
|
||||||
|
menuBar = UI.MenuBar {
|
||||||
|
buttons = {
|
||||||
|
{ text = 'Filter', event = 'filter' },
|
||||||
|
{ text = 'Reset', event = 'reset' },
|
||||||
|
{ text = 'Pause ', event = 'toggle', name = 'pauseButton' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid = UI.ScrollingGrid {
|
||||||
|
y = 2,
|
||||||
|
columns = {
|
||||||
|
{ key = 'event' },
|
||||||
|
{ key = 'p1' },
|
||||||
|
{ key = 'p2' },
|
||||||
|
{ key = 'p3' },
|
||||||
|
{ key = 'p4' },
|
||||||
|
{ key = 'p5' },
|
||||||
|
},
|
||||||
|
autospace = true,
|
||||||
|
disableHeader = true,
|
||||||
|
getDisplayValues = function(_, row)
|
||||||
|
row = Util.shallowCopy(row)
|
||||||
|
|
||||||
|
for k,v in pairs(row) do
|
||||||
|
row[k] = type(v) == 'table' and 'table' or v
|
||||||
|
end
|
||||||
|
|
||||||
|
return row
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
accelerators = {
|
||||||
|
f = 'filter',
|
||||||
|
p = 'toggle',
|
||||||
|
r = 'reset',
|
||||||
|
c = 'clear',
|
||||||
|
[ 'control-q' ] = 'quit',
|
||||||
|
},
|
||||||
|
filtered = { },
|
||||||
|
eventHandler = function(self, event)
|
||||||
|
if event.type == 'filter' then
|
||||||
|
local entry = self.grid:getSelected()
|
||||||
|
self.filtered[entry.event] = true
|
||||||
|
|
||||||
|
elseif event.type == 'toggle' then
|
||||||
|
self.paused = not self.paused
|
||||||
|
if self.paused then
|
||||||
|
self.menuBar.pauseButton.text = 'Resume'
|
||||||
|
else
|
||||||
|
self.menuBar.pauseButton.text = 'Pause '
|
||||||
|
end
|
||||||
|
self.menuBar:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'grid_select' then
|
||||||
|
multishell.openTab(_ENV, {
|
||||||
|
path = 'sys/apps/Lua.lua',
|
||||||
|
args = { event.selected },
|
||||||
|
focused = true,
|
||||||
|
})
|
||||||
|
|
||||||
|
elseif event.type == 'reset' then
|
||||||
|
self.filtered = { }
|
||||||
|
self.grid:setValues({ })
|
||||||
|
self.grid:draw()
|
||||||
|
if self.paused then
|
||||||
|
self:emit({ type = 'toggle' })
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif event.type == 'clear' then
|
||||||
|
self.grid:setValues({ })
|
||||||
|
self.grid:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'quit' then
|
||||||
|
UI:quit()
|
||||||
|
|
||||||
|
else
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
local timerId = os.startTimer(1)
|
||||||
|
|
||||||
|
Event.addRoutine(function()
|
||||||
|
while true do
|
||||||
|
local _, id = os.pullEvent('timer')
|
||||||
|
if id == timerId then
|
||||||
|
while #page.grid.values > 100 do
|
||||||
|
table.remove(page.grid.values)
|
||||||
|
end
|
||||||
|
timerId = nil
|
||||||
|
page.grid:update()
|
||||||
|
page.grid:draw()
|
||||||
|
page:sync()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local hookFunction = function(event, e)
|
||||||
|
if not page.filtered[event] and not page.paused and not (event == 'timer' and e[1] == timerId) then
|
||||||
|
table.insert(page.grid.values, 1, {
|
||||||
|
event = event,
|
||||||
|
p1 = e[1],
|
||||||
|
p2 = e[2],
|
||||||
|
p3 = e[3],
|
||||||
|
p4 = e[4],
|
||||||
|
p5 = e[5],
|
||||||
|
})
|
||||||
|
timerId = timerId or os.startTimer(.1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
kernel.hook('*', hookFunction)
|
||||||
|
|
||||||
|
UI:setPage(page)
|
||||||
|
UI:start()
|
||||||
|
|
||||||
|
kernel.unhook('*', hookFunction)
|
||||||
243
common/Follow.lua
Normal file
243
common/Follow.lua
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
local Event = require('opus.event')
|
||||||
|
local GPS = require('opus.gps')
|
||||||
|
local Point = require('opus.point')
|
||||||
|
local Socket = require('opus.socket')
|
||||||
|
local Swarm = require('core.swarm')
|
||||||
|
local UI = require('opus.ui')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local colors = _G.colors
|
||||||
|
local network = _G.network
|
||||||
|
local os = _G.os
|
||||||
|
|
||||||
|
local swarm = Swarm()
|
||||||
|
local gpt = GPS.getPoint() or error('GPS not found')
|
||||||
|
local pts, blocks
|
||||||
|
|
||||||
|
local page = UI.Page {
|
||||||
|
menuBar = UI.MenuBar {
|
||||||
|
buttons = {
|
||||||
|
{ text = 'Range', event = 'range' },
|
||||||
|
{ text = 'Stop', event = 'stop' },
|
||||||
|
},
|
||||||
|
mode = UI.Chooser {
|
||||||
|
x = -16,
|
||||||
|
choices = {
|
||||||
|
{ name = 'No breaking', value = 'digNone' },
|
||||||
|
{ name = 'Destructive', value = 'turtleSafe' },
|
||||||
|
},
|
||||||
|
value = 'digNone',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid = UI.ScrollingGrid {
|
||||||
|
y = 2, ey = -2,
|
||||||
|
columns = {
|
||||||
|
{ heading = 'Label', key = 'label' },
|
||||||
|
{ heading = 'Dist', key = 'distance' },
|
||||||
|
{ heading = 'Status', key = 'status' },
|
||||||
|
{ heading = 'Fuel', key = 'fuel' },
|
||||||
|
},
|
||||||
|
sortColumn = 'distance',
|
||||||
|
autospace = true,
|
||||||
|
},
|
||||||
|
range = UI.SlideOut {
|
||||||
|
y = -7, height = 7,
|
||||||
|
titleBar = UI.TitleBar {
|
||||||
|
event = 'cancel',
|
||||||
|
title = 'Enter range',
|
||||||
|
},
|
||||||
|
notice = UI.TextArea {
|
||||||
|
x = 2, ex = -2, y = 3, ey = 4,
|
||||||
|
value =
|
||||||
|
[[Select all turtles within a specified range]],
|
||||||
|
},
|
||||||
|
entry = UI.TextEntry {
|
||||||
|
y = 6, x = 2, ex = 10,
|
||||||
|
limit = 4,
|
||||||
|
shadowText = 'range',
|
||||||
|
accelerators = {
|
||||||
|
enter = 'select_range',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
button = UI.Button {
|
||||||
|
x = 12, y = 6,
|
||||||
|
text = 'Apply',
|
||||||
|
event = 'select_range',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function page.grid:getRowTextColor(row, selected)
|
||||||
|
if swarm.pool[row.id] then
|
||||||
|
return colors.yellow
|
||||||
|
end
|
||||||
|
return UI.ScrollingGrid.getRowTextColor(self, row, selected)
|
||||||
|
end
|
||||||
|
|
||||||
|
function page.grid:getDisplayValues(row)
|
||||||
|
row = Util.shallowCopy(row)
|
||||||
|
if row.fuel then
|
||||||
|
row.fuel = row.fuel > 0 and Util.toBytes(row.fuel) or ''
|
||||||
|
end
|
||||||
|
if row.distance then
|
||||||
|
row.distance = Util.round(row.distance, 1)
|
||||||
|
end
|
||||||
|
return row
|
||||||
|
end
|
||||||
|
|
||||||
|
function page:enable()
|
||||||
|
local function update()
|
||||||
|
local t = { }
|
||||||
|
for _,v in pairs(network) do
|
||||||
|
if v.fuel and v.active and v.fuel > 0 and v.distance then
|
||||||
|
table.insert(t, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.grid:setValues(t)
|
||||||
|
end
|
||||||
|
|
||||||
|
Event.onInterval(3, function()
|
||||||
|
update()
|
||||||
|
self.grid:draw()
|
||||||
|
self:sync()
|
||||||
|
end)
|
||||||
|
|
||||||
|
update()
|
||||||
|
|
||||||
|
UI.Page.enable(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function follow(member)
|
||||||
|
local turtle = member.turtle
|
||||||
|
turtle.reset()
|
||||||
|
turtle.set({
|
||||||
|
digPolicy = page.menuBar.mode.value,
|
||||||
|
status = 'Following',
|
||||||
|
})
|
||||||
|
|
||||||
|
if not turtle.enableGPS(nil, true) then
|
||||||
|
error('turtle: No GPS found')
|
||||||
|
end
|
||||||
|
|
||||||
|
member.snmp = Socket.connect(member.id, 161)
|
||||||
|
member.snmp.co = coroutine.running()
|
||||||
|
|
||||||
|
local pt
|
||||||
|
|
||||||
|
while true do
|
||||||
|
while pt and Point.same(gpt, pt) do
|
||||||
|
os.sleep(.5)
|
||||||
|
end
|
||||||
|
pt = Point.copy(gpt)
|
||||||
|
|
||||||
|
local cpt = Point.closest(turtle.getPoint(), pts)
|
||||||
|
|
||||||
|
turtle.abort(false)
|
||||||
|
if turtle.pathfind(cpt, { blocks = blocks }) then
|
||||||
|
turtle.headTowards(pt)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function swarm:onRemove(member, status, message)
|
||||||
|
if member.socket then
|
||||||
|
pcall(function()
|
||||||
|
member.turtle.set({ status = 'idle' })
|
||||||
|
member.turtle.abort(true)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
if member.snmp then
|
||||||
|
member.snmp:close()
|
||||||
|
member.snmp = nil
|
||||||
|
end
|
||||||
|
if not status then
|
||||||
|
_G._syslog(message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function page:eventHandler(event)
|
||||||
|
if event.type == 'grid_select' then
|
||||||
|
if not swarm.pool[event.selected.id] then
|
||||||
|
swarm:add(event.selected.id)
|
||||||
|
swarm:run(follow)
|
||||||
|
else
|
||||||
|
swarm:remove(event.selected.id)
|
||||||
|
end
|
||||||
|
self.grid:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'choice_change' then
|
||||||
|
local script = string.format('turtle.set({ digPolicy = "%s"})', event.value)
|
||||||
|
for _, member in pairs(swarm.pool) do
|
||||||
|
member.snmp:write({ type = 'scriptEx', args = script })
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif event.type == 'stop' then
|
||||||
|
for id in pairs(swarm.pool) do
|
||||||
|
swarm:remove(id)
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif event.type == 'range' then
|
||||||
|
self.range:show()
|
||||||
|
|
||||||
|
elseif event.type == 'cancel' then
|
||||||
|
self.range:hide()
|
||||||
|
|
||||||
|
elseif event.type == 'select_range' then
|
||||||
|
local range = tonumber(self.range.entry.value)
|
||||||
|
|
||||||
|
if range and range > 0 then
|
||||||
|
for id, v in pairs(network) do
|
||||||
|
if not swarm.pool[id] then
|
||||||
|
if v.fuel and v.active and v.fuel > 0 and v.distance and v.distance <= range then
|
||||||
|
swarm:add(id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
swarm:run(follow)
|
||||||
|
self.range:hide()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
Event.addRoutine(function()
|
||||||
|
while true do
|
||||||
|
local pt = GPS.getPoint()
|
||||||
|
if not pts or (pt and not Point.same(pt, gpt)) then
|
||||||
|
gpt = pt
|
||||||
|
pts = {
|
||||||
|
{ x = pt.x + 2, z = pt.z, y = pt.y },
|
||||||
|
{ x = pt.x - 2, z = pt.z, y = pt.y },
|
||||||
|
{ x = pt.x, z = pt.z + 2, y = pt.y },
|
||||||
|
{ x = pt.x, z = pt.z - 2, y = pt.y },
|
||||||
|
}
|
||||||
|
blocks = { }
|
||||||
|
|
||||||
|
local function addBlocks(tpt)
|
||||||
|
table.insert(blocks, tpt)
|
||||||
|
local apts = Point.adjacentPoints(tpt)
|
||||||
|
for _,apt in pairs(apts) do
|
||||||
|
table.insert(blocks, apt)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- don't run into player
|
||||||
|
addBlocks(pt)
|
||||||
|
addBlocks(Point.above(pt))
|
||||||
|
|
||||||
|
for _, member in pairs(swarm.pool) do
|
||||||
|
if member.snmp then
|
||||||
|
member.snmp:write({ type = 'scriptEx', args = 'turtle.abort(true)' })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
os.sleep(1)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
UI:setPage(page)
|
||||||
|
UI:start()
|
||||||
|
|
||||||
|
swarm:stop()
|
||||||
63
common/SoundPlayer.lua
Normal file
63
common/SoundPlayer.lua
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
local Sound = require('opus.sound')
|
||||||
|
local UI = require('opus.ui')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local peripheral = _G.peripheral
|
||||||
|
|
||||||
|
if not peripheral.find('speaker') then
|
||||||
|
error('No speaker attached')
|
||||||
|
end
|
||||||
|
|
||||||
|
local rawSounds = Util.readLines('packages/common/etc/sounds.txt') or error('Unable to read sounds file')
|
||||||
|
local sounds = { }
|
||||||
|
for _, s in pairs(rawSounds) do
|
||||||
|
table.insert(sounds, { name = s })
|
||||||
|
end
|
||||||
|
|
||||||
|
UI:configure('SoundPlayer', ...)
|
||||||
|
|
||||||
|
local page = UI.Page {
|
||||||
|
labelText = UI.Text {
|
||||||
|
x = 3, y = 2,
|
||||||
|
value = 'Search',
|
||||||
|
},
|
||||||
|
filter = UI.TextEntry {
|
||||||
|
x = 10, y = 2, ex = -3,
|
||||||
|
limit = 32,
|
||||||
|
},
|
||||||
|
grid = UI.ScrollingGrid {
|
||||||
|
y = 4,
|
||||||
|
columns = {
|
||||||
|
{ heading = 'Name', key = 'name' },
|
||||||
|
},
|
||||||
|
values = sounds,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function page:eventHandler(event)
|
||||||
|
if event.type == 'grid_select' then
|
||||||
|
Sound.play(event.selected.name)
|
||||||
|
|
||||||
|
elseif event.type == 'text_change' then
|
||||||
|
if not event.text then
|
||||||
|
self.grid.values = sounds
|
||||||
|
else
|
||||||
|
self.grid.values = { }
|
||||||
|
for _,f in pairs(sounds) do
|
||||||
|
if string.find(f.name, event.text) then
|
||||||
|
table.insert(self.grid.values, f)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.grid:update()
|
||||||
|
self.grid:setIndex(1)
|
||||||
|
self.grid:draw()
|
||||||
|
|
||||||
|
else
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
UI:setPage(page)
|
||||||
|
UI:start()
|
||||||
397
common/Turtles.lua
Normal file
397
common/Turtles.lua
Normal file
@@ -0,0 +1,397 @@
|
|||||||
|
local Config = require('opus.config')
|
||||||
|
local Event = require('opus.event')
|
||||||
|
local itemDB = require('core.itemDB')
|
||||||
|
local Socket = require('opus.socket')
|
||||||
|
local UI = require('opus.ui')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local fs = _G.fs
|
||||||
|
local multishell = _ENV.multishell
|
||||||
|
local network = _G.network
|
||||||
|
local os = _G.os
|
||||||
|
|
||||||
|
UI:configure('Turtles', ...)
|
||||||
|
|
||||||
|
local config = { }
|
||||||
|
Config.load('Turtles', config)
|
||||||
|
|
||||||
|
local options = {
|
||||||
|
turtle = { arg = 'i', type = 'number', value = config.id or -1,
|
||||||
|
desc = 'Turtle ID' },
|
||||||
|
tab = { arg = 's', type = 'string', value = config.tab or 'Sel',
|
||||||
|
desc = 'Selected tab to display' },
|
||||||
|
help = { arg = 'h', type = 'flag', value = false,
|
||||||
|
desc = 'Displays the options' },
|
||||||
|
}
|
||||||
|
|
||||||
|
local SCRIPTS_PATH = 'packages/common/etc/scripts'
|
||||||
|
|
||||||
|
local socket, turtle, page
|
||||||
|
|
||||||
|
page = UI.Page {
|
||||||
|
coords = UI.Window {
|
||||||
|
backgroundColor = 'black',
|
||||||
|
height = 3,
|
||||||
|
marginTop = 1, marginLeft = 1,
|
||||||
|
draw = function(self)
|
||||||
|
local t = turtle
|
||||||
|
self:clear()
|
||||||
|
if t then
|
||||||
|
self:setCursorPos(2, 2)
|
||||||
|
local ind = 'GPS'
|
||||||
|
if not t.point.gps then
|
||||||
|
ind = 'REL'
|
||||||
|
end
|
||||||
|
self:print(string.format('%s : %d,%d,%d',
|
||||||
|
ind, t.point.x, t.point.y, t.point.z))
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
tabs = UI.Tabs {
|
||||||
|
x = 1, y = 4, ey = -2,
|
||||||
|
UI.Tab {
|
||||||
|
title = 'Run',
|
||||||
|
scripts = UI.ScrollingGrid {
|
||||||
|
backgroundColor = 'primary',
|
||||||
|
columns = {
|
||||||
|
{ heading = '', key = 'label' },
|
||||||
|
},
|
||||||
|
disableHeader = true,
|
||||||
|
sortColumn = 'label',
|
||||||
|
autospace = true,
|
||||||
|
draw = function(self)
|
||||||
|
Util.clear(self.values)
|
||||||
|
local files = fs.list(SCRIPTS_PATH)
|
||||||
|
for _,path in pairs(files) do
|
||||||
|
table.insert(self.values, { label = path, path = fs.combine(SCRIPTS_PATH, path) })
|
||||||
|
end
|
||||||
|
self:update()
|
||||||
|
UI.ScrollingGrid.draw(self)
|
||||||
|
end,
|
||||||
|
eventHandler = function(self, event)
|
||||||
|
if event.type == 'grid_select' then
|
||||||
|
page:runScript(event.selected.label)
|
||||||
|
else
|
||||||
|
return UI.ScrollingGrid.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UI.Tab {
|
||||||
|
title = 'Select',
|
||||||
|
turtles = UI.ScrollingGrid {
|
||||||
|
backgroundColor = 'primary',
|
||||||
|
columns = {
|
||||||
|
{ heading = 'label', key = 'label' },
|
||||||
|
{ heading = 'Dist', key = 'distance' },
|
||||||
|
{ heading = 'Status', key = 'status' },
|
||||||
|
{ heading = 'Fuel', key = 'fuel' },
|
||||||
|
},
|
||||||
|
disableHeader = true,
|
||||||
|
sortColumn = 'label',
|
||||||
|
autospace = true,
|
||||||
|
getDisplayValues = function(_, row)
|
||||||
|
row = Util.shallowCopy(row)
|
||||||
|
if row.fuel then
|
||||||
|
row.fuel = Util.toBytes(row.fuel)
|
||||||
|
end
|
||||||
|
if row.distance then
|
||||||
|
row.distance = Util.round(row.distance, 1)
|
||||||
|
end
|
||||||
|
return row
|
||||||
|
end,
|
||||||
|
draw = function(self)
|
||||||
|
Util.clear(self.values)
|
||||||
|
for _,v in pairs(network) do
|
||||||
|
if v.fuel then
|
||||||
|
table.insert(self.values, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:update()
|
||||||
|
UI.ScrollingGrid.draw(self)
|
||||||
|
end,
|
||||||
|
eventHandler = function(self, event)
|
||||||
|
if event.type == 'grid_select' then
|
||||||
|
turtle = event.selected
|
||||||
|
config.id = event.selected.id
|
||||||
|
Config.update('Turtles', config)
|
||||||
|
multishell.setTitle(multishell.getCurrent(), turtle.label)
|
||||||
|
if socket then
|
||||||
|
socket:close()
|
||||||
|
socket = nil
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return UI.ScrollingGrid.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UI.Tab {
|
||||||
|
title = 'Inv',
|
||||||
|
inventory = UI.ScrollingGrid {
|
||||||
|
backgroundColor = 'primary',
|
||||||
|
columns = {
|
||||||
|
{ heading = '', key = 'index', width = 2 },
|
||||||
|
{ heading = '', key = 'count', width = 2 },
|
||||||
|
{ heading = 'Inventory', key = 'key' },
|
||||||
|
},
|
||||||
|
disableHeader = true,
|
||||||
|
sortColumn = 'index',
|
||||||
|
getRowTextColor = function(self, row, selected)
|
||||||
|
if turtle and row.selected then
|
||||||
|
return 'yellow'
|
||||||
|
end
|
||||||
|
return UI.ScrollingGrid.getRowTextColor(self, row, selected)
|
||||||
|
end,
|
||||||
|
draw = function(self)
|
||||||
|
local t = turtle
|
||||||
|
Util.clear(self.values)
|
||||||
|
if t then
|
||||||
|
for k,v in pairs(t.inv or { }) do -- new method (less data)
|
||||||
|
local index, count = k:match('(%d+),(%d+)')
|
||||||
|
v = {
|
||||||
|
index = tonumber(index),
|
||||||
|
key = v,
|
||||||
|
count = tonumber(count),
|
||||||
|
}
|
||||||
|
table.insert(self.values, v)
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,v in pairs(t.inventory or { }) do
|
||||||
|
if v.count > 0 then
|
||||||
|
table.insert(self.values, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,v in pairs(self.values) do
|
||||||
|
if v.index == t.slotIndex then
|
||||||
|
v.selected = true
|
||||||
|
end
|
||||||
|
if v.key then
|
||||||
|
v.key = itemDB:getName(v.key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:adjustWidth()
|
||||||
|
self:update()
|
||||||
|
UI.ScrollingGrid.draw(self)
|
||||||
|
end,
|
||||||
|
eventHandler = function(self, event)
|
||||||
|
if event.type == 'grid_select' then
|
||||||
|
local fn = string.format('turtle.select(%d)', event.selected.index)
|
||||||
|
page:runFunction(fn)
|
||||||
|
else
|
||||||
|
return UI.ScrollingGrid.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UI.Tab {
|
||||||
|
title = 'Action',
|
||||||
|
backgroundColor = 'primary',
|
||||||
|
moveUp = UI.Button {
|
||||||
|
x = 5, y = 2,
|
||||||
|
text = 'up',
|
||||||
|
fn = 'turtle.up',
|
||||||
|
},
|
||||||
|
moveDown = UI.Button {
|
||||||
|
x = 5, y = 4,
|
||||||
|
text = 'dn',
|
||||||
|
fn = 'turtle.down',
|
||||||
|
},
|
||||||
|
moveForward = UI.Button {
|
||||||
|
x = 9, y = 3,
|
||||||
|
text = 'f',
|
||||||
|
fn = 'turtle.forward',
|
||||||
|
},
|
||||||
|
moveBack = UI.Button {
|
||||||
|
x = 2, y = 3,
|
||||||
|
text = 'b',
|
||||||
|
fn = 'turtle.back',
|
||||||
|
},
|
||||||
|
turnLeft = UI.Button {
|
||||||
|
x = 2, y = 6,
|
||||||
|
text = 'lt',
|
||||||
|
fn = 'turtle.turnLeft',
|
||||||
|
},
|
||||||
|
turnRight = UI.Button {
|
||||||
|
x = 8, y = 6,
|
||||||
|
text = 'rt',
|
||||||
|
fn = 'turtle.turnRight',
|
||||||
|
},
|
||||||
|
info = UI.TextArea {
|
||||||
|
x = 15, y = 1,
|
||||||
|
inactive = true,
|
||||||
|
},
|
||||||
|
showBlocks = function(self)
|
||||||
|
local script = [[
|
||||||
|
local function inspect(direction)
|
||||||
|
local s,b = turtle['inspect' .. (direction or '')]()
|
||||||
|
if not s then
|
||||||
|
return 'minecraft:air:0'
|
||||||
|
end
|
||||||
|
return string.format('%s:%d', b.name, b.metadata)
|
||||||
|
end
|
||||||
|
|
||||||
|
local bu, bf, bd = inspect('Up'), inspect(), inspect('Down')
|
||||||
|
return string.format('%s\n%s\n%s', bu, bf, bd)
|
||||||
|
]]
|
||||||
|
|
||||||
|
local s, m = page:runFunction(script, true)
|
||||||
|
self.info:setText(s or m)
|
||||||
|
end,
|
||||||
|
eventHandler = function(self, event)
|
||||||
|
if event.type == 'button_press' then
|
||||||
|
if event.button.fn then
|
||||||
|
page:runFunction(event.button.fn, event.button.nowrap)
|
||||||
|
self:showBlocks()
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return UI.Tab.eventHandler(self, event)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
enable = function(self)
|
||||||
|
if config.tab then
|
||||||
|
self:selectTab(Util.find(self, 'title', config.tab))
|
||||||
|
end
|
||||||
|
UI.Tabs.enable(self)
|
||||||
|
end
|
||||||
|
},
|
||||||
|
statusBar = UI.StatusBar {
|
||||||
|
values = { },
|
||||||
|
columns = {
|
||||||
|
{ key = 'status' },
|
||||||
|
{ key = 'distance', width = 6 },
|
||||||
|
{ key = 'fuel', width = 6 },
|
||||||
|
},
|
||||||
|
draw = function(self)
|
||||||
|
local t = turtle
|
||||||
|
if t then
|
||||||
|
self.values.status = t.status
|
||||||
|
self.values.distance = t.distance and Util.round(t.distance, 2)
|
||||||
|
self.values.fuel = Util.toBytes(t.fuel)
|
||||||
|
end
|
||||||
|
UI.StatusBar.draw(self)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
notification = UI.Notification(),
|
||||||
|
accelerators = {
|
||||||
|
[ 'control-q' ] = 'quit',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function page:runFunction(script, nowrap)
|
||||||
|
for _ = 1, 2 do
|
||||||
|
if not socket then
|
||||||
|
socket = Socket.connect(turtle.id, 161)
|
||||||
|
end
|
||||||
|
|
||||||
|
if socket then
|
||||||
|
if not nowrap then
|
||||||
|
script = 'turtle.run(' .. script .. ')'
|
||||||
|
end
|
||||||
|
if socket:write({ type = 'scriptEx', args = script }) then
|
||||||
|
local t = socket:read(3)
|
||||||
|
if t then
|
||||||
|
return table.unpack(t)
|
||||||
|
end
|
||||||
|
return false, 'Socket timeout'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
socket = nil
|
||||||
|
end
|
||||||
|
self.notification:error('Unable to connect')
|
||||||
|
end
|
||||||
|
|
||||||
|
function page:runScript(scriptName)
|
||||||
|
if turtle then
|
||||||
|
self.notification:info('Connecting')
|
||||||
|
self:sync()
|
||||||
|
|
||||||
|
local script = Util.readFile(fs.combine(SCRIPTS_PATH, scriptName))
|
||||||
|
if not script then
|
||||||
|
print('Unable to read script file')
|
||||||
|
end
|
||||||
|
|
||||||
|
local function processVariables()
|
||||||
|
local variables = {
|
||||||
|
COMPUTER_ID = os.getComputerID,
|
||||||
|
GPS = function()
|
||||||
|
local pt = require('opus.gps').getPoint()
|
||||||
|
if not pt then
|
||||||
|
error('Unable to determine location')
|
||||||
|
end
|
||||||
|
return _G.textutils.serialize(pt)
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
for k,v in pairs(variables) do
|
||||||
|
local token = string.format('{%s}', k)
|
||||||
|
if script:find(token, 1, true) then
|
||||||
|
local s, m = pcall(v)
|
||||||
|
if not s then
|
||||||
|
self.notification:error(m)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
script = script:gsub(token, m)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if processVariables(script) then
|
||||||
|
local socket = Socket.connect(turtle.id, 161)
|
||||||
|
if not socket then
|
||||||
|
self.notification:error('Unable to connect')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
socket:write({ type = 'script', args = script })
|
||||||
|
socket:close()
|
||||||
|
|
||||||
|
self.notification:success('Sent')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function page:eventHandler(event)
|
||||||
|
if event.type == 'quit' then
|
||||||
|
UI:quit()
|
||||||
|
|
||||||
|
elseif event.type == 'tab_select' then
|
||||||
|
config.tab = event.button.text
|
||||||
|
Config.update('Turtles', config)
|
||||||
|
|
||||||
|
else
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if not Util.getOptions(options, { ... }, true) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.turtle.value >= 0 then
|
||||||
|
for _ = 1, 10 do
|
||||||
|
turtle = _G.network[options.turtle.value]
|
||||||
|
if turtle then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
os.sleep(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Event.onInterval(1, function()
|
||||||
|
if turtle then
|
||||||
|
--local t = _G.network[turtle.id]
|
||||||
|
--turtle = t
|
||||||
|
page:draw()
|
||||||
|
page:sync()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
UI:setPage(page)
|
||||||
|
UI:start()
|
||||||
54
common/autorun/common.lua
Normal file
54
common/autorun/common.lua
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
local c = function(shell, nIndex, sText)
|
||||||
|
if nIndex == 1 then
|
||||||
|
return _G.fs.complete(sText, shell.dir(), true, false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
_ENV.shell.setCompletionFunction("packages/common/edit.lua", c)
|
||||||
|
_ENV.shell.setCompletionFunction("packages/common/hexedit.lua", c)
|
||||||
|
|
||||||
|
_ENV.shell.registerHandler(function(env, command, args)
|
||||||
|
if command:match('^!') then
|
||||||
|
return {
|
||||||
|
title = 'lua',
|
||||||
|
path = table.concat({ command:match('^!(.+)'), table.unpack(args) }, ' '),
|
||||||
|
args = args,
|
||||||
|
load = function(s)
|
||||||
|
return function()
|
||||||
|
local fn, m
|
||||||
|
local wrapped
|
||||||
|
|
||||||
|
fn = load('return (' ..s.. ')', 'lua', nil, env)
|
||||||
|
|
||||||
|
if fn then
|
||||||
|
fn = load('return {' ..s.. '}', 'lua', nil, env)
|
||||||
|
wrapped = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if fn then
|
||||||
|
fn, m = pcall(fn)
|
||||||
|
if #m <= 1 and wrapped then
|
||||||
|
m = m[1]
|
||||||
|
end
|
||||||
|
else
|
||||||
|
fn, m = load(s, 'lua', nil, env)
|
||||||
|
if fn then
|
||||||
|
fn, m = pcall(fn)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if fn then
|
||||||
|
if m or wrapped then
|
||||||
|
require('opus.util').print(m or 'nil')
|
||||||
|
else
|
||||||
|
print()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
_G.printError(m)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
env = env,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end)
|
||||||
41
common/debugMonitor.lua
Normal file
41
common/debugMonitor.lua
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local device = _G.device
|
||||||
|
local os = _G.os
|
||||||
|
local peripheral = _G.peripheral
|
||||||
|
local term = _G.term
|
||||||
|
|
||||||
|
local args = { ... }
|
||||||
|
local mon = not args[1] and term.current() or
|
||||||
|
device[args[1]] or
|
||||||
|
peripheral.wrap(args[1]) or
|
||||||
|
peripheral.find('monitor') or
|
||||||
|
error('Syntax: debug <monitor>')
|
||||||
|
|
||||||
|
mon.clear()
|
||||||
|
if mon.setTextScale then
|
||||||
|
mon.setTextScale(.5)
|
||||||
|
end
|
||||||
|
mon.setCursorPos(1, 1)
|
||||||
|
|
||||||
|
local oldDebug = _G._syslog
|
||||||
|
|
||||||
|
_G._syslog = function(...)
|
||||||
|
local oldTerm = term.redirect(mon)
|
||||||
|
Util.print(...)
|
||||||
|
term.redirect(oldTerm)
|
||||||
|
oldDebug(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
repeat
|
||||||
|
local e, side = os.pullEventRaw('monitor_touch')
|
||||||
|
if e == 'monitor_touch' and side == mon.side then
|
||||||
|
mon.clear()
|
||||||
|
if mon.setTextScale then
|
||||||
|
mon.setTextScale(.5)
|
||||||
|
end
|
||||||
|
mon.setCursorPos(1, 1)
|
||||||
|
end
|
||||||
|
until e == 'terminate'
|
||||||
|
|
||||||
|
_G._syslog = oldDebug
|
||||||
1484
common/edit.lua
Normal file
1484
common/edit.lua
Normal file
File diff suppressed because it is too large
Load Diff
86
common/etc/apps.db
Normal file
86
common/etc/apps.db
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
{
|
||||||
|
[ "c5497bca58468ae64aed6c0fd921109217988db3" ] = {
|
||||||
|
title = "Events",
|
||||||
|
category = "System",
|
||||||
|
iconExt = "\0300\031 \159\135\030 \0310\156\0301\031 \159\030 \0311\144\0300\031 \147\139\030 \0310\144\010\0300\128\128\030 \149\0311\157\142\0300\031 \149\0310\128\128\010\030 \130\139\141\0311\130\131\0310\142\135\129",
|
||||||
|
run = "Events.lua",
|
||||||
|
},
|
||||||
|
[ "f1e14c30480042e8c71b5482d92ef161815b915e" ] = {
|
||||||
|
title = "DiskCopy",
|
||||||
|
category = "System",
|
||||||
|
run = "DiskCopy",
|
||||||
|
iconExt = "\0305\0318\138\0303\159\0305\0317\133\030 \0315\148\031 \128\0318\151\129\010\0300\0315\149\030b\0318\138\143\0317\133\030 \031b\148\0308\031 \140\030 \0318\145\010\030 \031 \128\0300\031b\149\0310\128\128\030 \031b\149\0318\157\133",
|
||||||
|
},
|
||||||
|
[ "7ef35cac539f84722b0a988caee03b2df734c56a" ] = {
|
||||||
|
title = "AppStore",
|
||||||
|
category = "System",
|
||||||
|
icon = "\030 \0310=\0300 \030 XX\0300\031f \030 \010\030 \031f \0300 \030 \010\030 \031f \0310o \031f \0310o\031f ",
|
||||||
|
iconExt = "\030 \031e\139\0318\151\151\151\151\151\149\010\030 \031 \128\0308\136\136\136\136\030 \0318\135\031 \128\010\030 \031 \128\0317\130\031 \128\0317\130\031 \128\128\128",
|
||||||
|
run = "Appstore.lua",
|
||||||
|
},
|
||||||
|
[ "90ef98d4b6fd15466f0a1f212ec1db8d9ebe018c" ] = {
|
||||||
|
title = "Turtles",
|
||||||
|
category = "Apps",
|
||||||
|
icon = " \0305 \030c \0305 \030 \010\030d \030c \0305 \030c \0308 \030d\031f\"\010 \0308\031f.\030 \031 \0308\031f.\030 \031 ",
|
||||||
|
iconExt = "\030 \031 \128\0305\135\030c\031c\128\128\0305\031 \139\0307\149\0308\0317\143\0307\128\010\030c\031 \145\031c\128\030d\132\136\030c\128\0307\149\0318\143\133\010\030 \031 \128\0317\143\031 \128\128\0317\143\031 \128\128\128",
|
||||||
|
run = "Turtles.lua",
|
||||||
|
},
|
||||||
|
[ "66587a7a62a90fc91c4c88f1872bedaa52d23a35" ] = {
|
||||||
|
title = "Follow",
|
||||||
|
category = "Turtle",
|
||||||
|
run = "Follow.lua",
|
||||||
|
iconExt = "\030 \031 \128\128\128\128\128\128\0314\144\031 \128\010\030 \031 \128\128\128\128\0304\139\133\0310\130\030 \0311\156\010\0307\0318\136\030 \0317\149\0307\0318\136\030 \0317\149\0304\031 \144\0314\128\030 \159\031 \128",
|
||||||
|
},
|
||||||
|
[ "df485c871329671f46570634d63216761441bcd6" ] = {
|
||||||
|
title = "Devices",
|
||||||
|
category = "System",
|
||||||
|
icon = "\0304 \030 \010\030f \0304 \0307 \030 \031 \031f_\010\030f \0304 \0307 \030 \031f/",
|
||||||
|
iconExt = "\030 \031 \128\128\128\0308\159\143\0300\0317\151\0307\0310\140\148\010\0314\151\131\0304\031f\148\030 \0318\138\148\0307\0310\138\131\129\010\0304\031f\138\143\133\030 \0318\131\129\031 \128\128\128",
|
||||||
|
run = "Devices.lua",
|
||||||
|
},
|
||||||
|
[ "114edfc04a1ab03541bdc80ce064f66a7cfcedbb" ] = {
|
||||||
|
title = "Recorder",
|
||||||
|
category = "Apps",
|
||||||
|
icon = "\030 \031f \031b \031foo \010\030 \031f \030e\031b \030 \031f/\010\030 \031b \030e \030 \031f\\",
|
||||||
|
iconExt = "\030 \031 \128\030e\143\030 \031e\144\031 \128\0304\149\0307\0314\131\131\030 \149\010\030e\031 \129\031e\128\128\030 \148\0304\031 \149\0307\0318\140\140\030 \0314\149\010\030 \031e\139\030e\128\030 \159\129\0314\130\131\131\129",
|
||||||
|
run = "recorder.lua",
|
||||||
|
},
|
||||||
|
[ "5c7e3aec1f9179ce3325a4f7a101dca65ac905f3" ] = {
|
||||||
|
title = "Swarm",
|
||||||
|
category = "Turtle",
|
||||||
|
requires = "neuralInterface",
|
||||||
|
icon = "\030 \0315\\\030 \031 \010\030 \0304\031f _ \030 \031c/\0315\\\010\030 \0304 ",
|
||||||
|
run = "multiMiner.lua",
|
||||||
|
},
|
||||||
|
[ "783ecb3650eabd68f3caadc387abd23018132967" ] = {
|
||||||
|
title = "HexEdit",
|
||||||
|
category = "Apps",
|
||||||
|
requires = "advancedComputer",
|
||||||
|
iconExt = "\030 \031 \128\030d\159\030 \031d\140\030d\031 \155\030 \0315\140\0305\031 \155\030 \128\010\030 \031d\136\145\0315\136\145\031d\153\031 \128\0315\153\010\030 \031 \128\031d\130\140\134\0315\140\134\031 \128",
|
||||||
|
run = "fileui --exec=hexedit.lua --title=hexedit",
|
||||||
|
},
|
||||||
|
[ "fb1c39e9f4f3c2628ad173ab401a6e4e4baf783d" ] = {
|
||||||
|
title = "Sounds",
|
||||||
|
category = "System",
|
||||||
|
run = "SoundPlayer",
|
||||||
|
iconExt = "\030 \031 \128\0307\159\129\030 \0317\149\0310\144\0300\031 \155\030 \0310\137\144\010\0307\0317\128\128\128\030 \149\0300\031 \149\030 \128\0310\149\0300\031 \149\010\030 \031 \128\0317\130\0307\031 \144\030 \0317\149\0310\129\134\152\129",
|
||||||
|
},
|
||||||
|
[ "464c4ffd019e1e9691dcf0537c797353ef2b1c1d4833d3d463e5b74ae4547344" ] = {
|
||||||
|
title = "Editor",
|
||||||
|
category = "Apps",
|
||||||
|
run = "edit",
|
||||||
|
iconExt = "7\
|
||||||
|
¨¨¨¨f0¨\
|
||||||
|
¨¨f0¨¨f",
|
||||||
|
},
|
||||||
|
[ "3f00927a719345edd4a8316599d3b328857987547f8884306861161ffa09647e" ] = {
|
||||||
|
title = "Write",
|
||||||
|
category = "Apps",
|
||||||
|
run = "write",
|
||||||
|
},
|
||||||
|
[ "c543ece81605c7d202121c62080a0db4020fc2c75bfac35d101d7f3e93c93949" ] = {
|
||||||
|
category = "Apps",
|
||||||
|
run = "ascii",
|
||||||
|
title = "Ascii",
|
||||||
|
},
|
||||||
|
}
|
||||||
8
common/etc/fstab
Normal file
8
common/etc/fstab
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
packages/common/ascii.lua urlfs https://pastebin.com/raw/U3KcNyJd
|
||||||
|
packages/common/hexedit.lua urlfs https://pastebin.com/raw/Ds9ajsp4
|
||||||
|
packages/common/colors.lua urlfs https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/ignore/colors.lua
|
||||||
|
packages/common/cowsay.lua urlfs https://pastebin.com/raw/n00VQJsw
|
||||||
|
packages/common/calc.lua urlfs https://pastebin.com/raw/nAinUn1h
|
||||||
|
packages/common/write.lua urlfs https://pastebin.com/raw/RSyhCjqv
|
||||||
|
# pretty output
|
||||||
|
rom/modules/main/inspect.lua urlfs https://raw.githubusercontent.com/kikito/inspect.lua/master/inspect.lua
|
||||||
1
common/etc/scripts/abort
Normal file
1
common/etc/scripts/abort
Normal file
@@ -0,0 +1 @@
|
|||||||
|
turtle.abort(true)
|
||||||
6
common/etc/scripts/goHome
Normal file
6
common/etc/scripts/goHome
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
local config = require('opus.config').load('gps')
|
||||||
|
if config.home then
|
||||||
|
if turtle.enableGPS() then
|
||||||
|
return turtle.pathfind(config.home)
|
||||||
|
end
|
||||||
|
end
|
||||||
15
common/etc/scripts/moveTo.lua
Normal file
15
common/etc/scripts/moveTo.lua
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
local turtle = _G.turtle
|
||||||
|
|
||||||
|
turtle.run(function()
|
||||||
|
local GPS = require('opus.gps')
|
||||||
|
|
||||||
|
if not turtle.enableGPS() then
|
||||||
|
error('turtle: No GPS found')
|
||||||
|
end
|
||||||
|
|
||||||
|
local pt = {GPS}
|
||||||
|
|
||||||
|
if not turtle.pathfind(pt) then
|
||||||
|
error('Unable to go to location')
|
||||||
|
end
|
||||||
|
end)
|
||||||
7
common/etc/scripts/setHome
Normal file
7
common/etc/scripts/setHome
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
local Config = require('opus.config')
|
||||||
|
local pt = turtle.enableGPS()
|
||||||
|
if pt then
|
||||||
|
local config = Config.load('gps', { })
|
||||||
|
config.home = pt
|
||||||
|
Config.update('gps', config)
|
||||||
|
end
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
local function summon(id)
|
local function summon(id)
|
||||||
|
local GPS = require('opus.gps')
|
||||||
|
local Point = require('opus.point')
|
||||||
|
local Socket = require('opus.socket')
|
||||||
|
|
||||||
requireInjector(getfenv(1))
|
turtle.setStatus('GPSing')
|
||||||
|
|
||||||
local GPS = require('gps')
|
|
||||||
local Point = require('point')
|
|
||||||
local Socket = require('socket')
|
|
||||||
|
|
||||||
turtle.status = 'GPSing'
|
|
||||||
turtle.setPoint({ x = 0, y = 0, z = 0, heading = 0 })
|
turtle.setPoint({ x = 0, y = 0, z = 0, heading = 0 })
|
||||||
|
|
||||||
local pts = {
|
local pts = {
|
||||||
@@ -32,7 +29,7 @@ local function summon(id)
|
|||||||
local function doGPS()
|
local function doGPS()
|
||||||
tFixes = { }
|
tFixes = { }
|
||||||
for i = 1, 4 do
|
for i = 1, 4 do
|
||||||
if not turtle.gotoPoint(pts[i]) then
|
if not turtle.go(pts[i]) then
|
||||||
error('turtle: Unable to perform GPS maneuver')
|
error('turtle: Unable to perform GPS maneuver')
|
||||||
end
|
end
|
||||||
local distance = getDistance()
|
local distance = getDistance()
|
||||||
@@ -64,7 +61,7 @@ local function summon(id)
|
|||||||
local pt = { x = pos.x, y = pos.y, z = pos.z }
|
local pt = { x = pos.x, y = pos.y, z = pos.z }
|
||||||
local _, h = Point.calculateMoves(turtle.getPoint(), pt)
|
local _, h = Point.calculateMoves(turtle.getPoint(), pt)
|
||||||
local hi = turtle.getHeadingInfo(h)
|
local hi = turtle.getHeadingInfo(h)
|
||||||
turtle.status = 'recalling'
|
turtle.setStatus('recalling')
|
||||||
turtle.pathfind({ x = pt.x - hi.xd, z = pt.z - hi.zd, y = pt.y - hi.yd, heading = h })
|
turtle.pathfind({ x = pt.x - hi.xd, z = pt.z - hi.zd, y = pt.y - hi.yd, heading = h })
|
||||||
else
|
else
|
||||||
error("turtle: Could not determine position")
|
error("turtle: Could not determine position")
|
||||||
498
common/etc/sounds.txt
Normal file
498
common/etc/sounds.txt
Normal file
@@ -0,0 +1,498 @@
|
|||||||
|
block.anvil.break
|
||||||
|
block.anvil.destroy
|
||||||
|
block.anvil.fall
|
||||||
|
block.anvil.hit
|
||||||
|
block.anvil.land
|
||||||
|
block.anvil.place
|
||||||
|
block.anvil.step
|
||||||
|
block.anvil.use
|
||||||
|
block.boat.place
|
||||||
|
entity.boat.paddle_land
|
||||||
|
entity.boat.paddle_water
|
||||||
|
block.brewing_stand.brew
|
||||||
|
block.chest.close
|
||||||
|
block.chest.locked
|
||||||
|
block.chest.open
|
||||||
|
block.cloth.break
|
||||||
|
block.cloth.fall
|
||||||
|
block.cloth.hit
|
||||||
|
block.cloth.place
|
||||||
|
block.cloth.step
|
||||||
|
block.comparator.click
|
||||||
|
block.dispenser.dispense
|
||||||
|
block.dispenser.fail
|
||||||
|
block.dispenser.launch
|
||||||
|
block.enchantment_table.use
|
||||||
|
block.end_gateway.spawn
|
||||||
|
block.end_portal.spawn
|
||||||
|
block.end_portal_frame.fill
|
||||||
|
block.enderchest.close
|
||||||
|
block.enderchest.open
|
||||||
|
block.fence_gate.close
|
||||||
|
block.fence_gate.open
|
||||||
|
block.fire.ambient
|
||||||
|
block.fire.extinguish
|
||||||
|
block.furnace.fire_crackle
|
||||||
|
block.glass.break
|
||||||
|
block.glass.fall
|
||||||
|
block.glass.hit
|
||||||
|
block.glass.place
|
||||||
|
block.glass.step
|
||||||
|
block.grass.break
|
||||||
|
block.grass.fall
|
||||||
|
block.grass.hit
|
||||||
|
block.grass.place
|
||||||
|
block.grass.step
|
||||||
|
block.gravel.break
|
||||||
|
block.gravel.fall
|
||||||
|
block.gravel.hit
|
||||||
|
block.gravel.place
|
||||||
|
block.gravel.step
|
||||||
|
block.iron_door.close
|
||||||
|
block.iron_door.open
|
||||||
|
block.ladder.break
|
||||||
|
block.ladder.fall
|
||||||
|
block.ladder.hit
|
||||||
|
block.ladder.place
|
||||||
|
block.ladder.step
|
||||||
|
block.lava.ambient
|
||||||
|
block.lava.extinguish
|
||||||
|
block.lava.pop
|
||||||
|
block.lever.click
|
||||||
|
block.metal.break
|
||||||
|
block.metal.fall
|
||||||
|
block.metal.hit
|
||||||
|
block.metal.place
|
||||||
|
block.metal.step
|
||||||
|
block.metal_pressureplate.click_off
|
||||||
|
block.metal_pressureplate.click_on
|
||||||
|
block.note.basedrum
|
||||||
|
block.note.bass
|
||||||
|
block.note.bell
|
||||||
|
block.note.chime
|
||||||
|
block.note.flute
|
||||||
|
block.note.guitar
|
||||||
|
block.note.harp
|
||||||
|
block.note.hat
|
||||||
|
block.note.pling
|
||||||
|
block.note.snare
|
||||||
|
block.note.xylophone
|
||||||
|
block.piston.contract
|
||||||
|
block.piston.extend
|
||||||
|
block.portal.ambient
|
||||||
|
block.portal.travel
|
||||||
|
block.portal.trigger
|
||||||
|
block.redstone_torch.burnout
|
||||||
|
block.sand.break
|
||||||
|
block.sand.fall
|
||||||
|
block.sand.hit
|
||||||
|
block.sand.place
|
||||||
|
block.sand.step
|
||||||
|
block.shulker_box.close
|
||||||
|
block.shulker_box.open
|
||||||
|
block.slime.break
|
||||||
|
block.slime.fall
|
||||||
|
block.slime.hit
|
||||||
|
block.slime.place
|
||||||
|
block.slime.step
|
||||||
|
block.snow.break
|
||||||
|
block.snow.fall
|
||||||
|
block.snow.hit
|
||||||
|
block.snow.place
|
||||||
|
block.snow.step
|
||||||
|
block.stone.break
|
||||||
|
block.stone.fall
|
||||||
|
block.stone.hit
|
||||||
|
block.stone.place
|
||||||
|
block.stone.step
|
||||||
|
block.stone_button.click_off
|
||||||
|
block.stone_button.click_on
|
||||||
|
block.stone_pressureplate.click_off
|
||||||
|
block.stone_pressureplate.click_on
|
||||||
|
block.wooden_trapdoor.close
|
||||||
|
block.wooden_trapdoor.open
|
||||||
|
block.iron_trapdoor.close
|
||||||
|
block.iron_trapdoor.open
|
||||||
|
block.tripwire.attach
|
||||||
|
block.tripwire.click_off
|
||||||
|
block.tripwire.click_on
|
||||||
|
block.tripwire.detach
|
||||||
|
block.water.ambient
|
||||||
|
block.waterlily.place
|
||||||
|
block.wood.break
|
||||||
|
block.wood.fall
|
||||||
|
block.wood.hit
|
||||||
|
block.wood.place
|
||||||
|
block.wood.step
|
||||||
|
block.wood_button.click_off
|
||||||
|
block.wood_button.click_on
|
||||||
|
block.wood_pressureplate.click_off
|
||||||
|
block.wood_pressureplate.click_on
|
||||||
|
block.wooden_door.close
|
||||||
|
block.wooden_door.open
|
||||||
|
entity.arrow.hit
|
||||||
|
entity.arrow.shoot
|
||||||
|
entity.arrow.successful_hit
|
||||||
|
entity.bat.ambient
|
||||||
|
entity.armorstand.break
|
||||||
|
entity.armorstand.fall
|
||||||
|
entity.armorstand.hit
|
||||||
|
entity.armorstand.place
|
||||||
|
entity.bat.death
|
||||||
|
entity.bat.hurt
|
||||||
|
entity.bat.takeoff
|
||||||
|
entity.blaze.ambient
|
||||||
|
entity.blaze.burn
|
||||||
|
entity.blaze.death
|
||||||
|
entity.blaze.hurt
|
||||||
|
entity.blaze.shoot
|
||||||
|
entity.bobber.splash
|
||||||
|
entity.bobber.throw
|
||||||
|
entity.bobber.retrieve
|
||||||
|
entity.cat.ambient
|
||||||
|
entity.cat.death
|
||||||
|
entity.cat.hiss
|
||||||
|
entity.cat.hurt
|
||||||
|
entity.cat.purr
|
||||||
|
entity.cat.purreow
|
||||||
|
entity.chicken.ambient
|
||||||
|
entity.chicken.death
|
||||||
|
entity.chicken.egg
|
||||||
|
entity.chicken.hurt
|
||||||
|
entity.chicken.step
|
||||||
|
entity.cow.ambient
|
||||||
|
entity.cow.death
|
||||||
|
entity.cow.hurt
|
||||||
|
entity.cow.milk
|
||||||
|
entity.cow.step
|
||||||
|
entity.creeper.death
|
||||||
|
entity.creeper.hurt
|
||||||
|
entity.creeper.primed
|
||||||
|
entity.donkey.ambient
|
||||||
|
entity.donkey.angry
|
||||||
|
entity.donkey.chest
|
||||||
|
entity.donkey.death
|
||||||
|
entity.donkey.hurt
|
||||||
|
entity.egg.throw
|
||||||
|
entity.elder_guardian.ambient
|
||||||
|
entity.elder_guardian.ambient_land
|
||||||
|
entity.elder_guardian.curse
|
||||||
|
entity.elder_guardian.death
|
||||||
|
entity.elder_guardian.death_land
|
||||||
|
entity.elder_guardian.hurt
|
||||||
|
entity.elder_guardian.hurt_land
|
||||||
|
entity.enderdragon.ambient
|
||||||
|
entity.enderdragon.death
|
||||||
|
entity.enderdragon.flap
|
||||||
|
entity.enderdragon.growl
|
||||||
|
entity.enderdragon.hurt
|
||||||
|
entity.enderdragon.shoot
|
||||||
|
entity.enderdragon_fireball.explode
|
||||||
|
entity.endereye.launch
|
||||||
|
entity.endereye.death
|
||||||
|
entity.endermen.death
|
||||||
|
entity.endermen.hurt
|
||||||
|
entity.endermen.scream
|
||||||
|
entity.endermen.stare
|
||||||
|
entity.endermen.teleport
|
||||||
|
entity.endermite.ambient
|
||||||
|
entity.endermite.death
|
||||||
|
entity.endermite.hurt
|
||||||
|
entity.endermite.step
|
||||||
|
entity.enderpearl.throw
|
||||||
|
entity.evokation_illager.ambient
|
||||||
|
entity.evokation_illager.hurt
|
||||||
|
entity.evokation_illager.death
|
||||||
|
entity.evokation_illager.cast_spell
|
||||||
|
entity.evokation_illager.prepare_attack
|
||||||
|
entity.evokation_illager.prepare_summon
|
||||||
|
entity.evokation_illager.cast_spell
|
||||||
|
entity.evokation_fangs.attack
|
||||||
|
entity.experience_bottle.throw
|
||||||
|
entity.experience_orb.pickup
|
||||||
|
entity.firework.blast
|
||||||
|
entity.firework.blast_far
|
||||||
|
entity.firework.large_blast
|
||||||
|
entity.firework.large_blast_far
|
||||||
|
entity.firework.launch
|
||||||
|
entity.firework.shoot
|
||||||
|
entity.firework.twinkle
|
||||||
|
entity.firework.twinkle_far
|
||||||
|
entity.generic.big_fall
|
||||||
|
entity.generic.burn
|
||||||
|
entity.generic.death
|
||||||
|
entity.generic.drink
|
||||||
|
entity.generic.eat
|
||||||
|
entity.generic.explode
|
||||||
|
entity.generic.extinguish_fire
|
||||||
|
entity.generic.hurt
|
||||||
|
entity.generic.small_fall
|
||||||
|
entity.generic.splash
|
||||||
|
entity.generic.swim
|
||||||
|
entity.ghast.ambient
|
||||||
|
entity.ghast.death
|
||||||
|
entity.ghast.hurt
|
||||||
|
entity.ghast.shoot
|
||||||
|
entity.ghast.warn
|
||||||
|
entity.guardian.ambient
|
||||||
|
entity.guardian.ambient_land
|
||||||
|
entity.guardian.attack
|
||||||
|
entity.guardian.death
|
||||||
|
entity.guardian.death_land
|
||||||
|
entity.guardian.flop
|
||||||
|
entity.guardian.hurt
|
||||||
|
entity.guardian.hurt_land
|
||||||
|
entity.horse.ambient
|
||||||
|
entity.horse.angry
|
||||||
|
entity.horse.armor
|
||||||
|
entity.horse.breathe
|
||||||
|
entity.horse.death
|
||||||
|
entity.horse.eat
|
||||||
|
entity.horse.gallop
|
||||||
|
entity.horse.hurt
|
||||||
|
entity.horse.jump
|
||||||
|
entity.horse.land
|
||||||
|
entity.horse.saddle
|
||||||
|
entity.horse.step
|
||||||
|
entity.horse.step_wood
|
||||||
|
entity.hostile.big_fall
|
||||||
|
entity.hostile.death
|
||||||
|
entity.hostile.hurt
|
||||||
|
entity.hostile.hurt
|
||||||
|
entity.hostile.splash
|
||||||
|
entity.hostile.swim
|
||||||
|
entity.husk.ambient
|
||||||
|
entity.husk.death
|
||||||
|
entity.husk.hurt
|
||||||
|
entity.husk.step
|
||||||
|
entity.illusion_illager.ambient
|
||||||
|
entity.illusion_illager.cast_spell
|
||||||
|
entity.illusion_illager.death
|
||||||
|
entity.illusion_illager.hurt
|
||||||
|
entity.illusion_illager.mirror_moveentity.illusion_illager.prepare_blindness
|
||||||
|
entity.illusion_illager.prepare_mirror
|
||||||
|
entity.irongolem.attack
|
||||||
|
entity.irongolem.death
|
||||||
|
entity.irongolem.hurt
|
||||||
|
entity.irongolem.step
|
||||||
|
entity.item.break
|
||||||
|
entity.item.pickup
|
||||||
|
entity.itemframe.add_item
|
||||||
|
entity.itemframe.break
|
||||||
|
entity.itemframe.place
|
||||||
|
entity.itemframe.remove_item
|
||||||
|
entity.itemframe.rotate_item
|
||||||
|
entity.llama.ambient
|
||||||
|
entity.llama.angry
|
||||||
|
entity.llama.death
|
||||||
|
entity.llama.eat
|
||||||
|
entity.llama.hurt
|
||||||
|
entity.llama.spit
|
||||||
|
entity.llama.step
|
||||||
|
entity.llama.swag
|
||||||
|
entity.leashknot.break
|
||||||
|
entity.leashknot.place
|
||||||
|
entity.lightning.impact
|
||||||
|
entity.lightning.thunder
|
||||||
|
entity.lingeringpotion.throw
|
||||||
|
entity.magmacube.death
|
||||||
|
entity.magmacube.hurt
|
||||||
|
entity.magmacube.jump
|
||||||
|
entity.magmacube.squish
|
||||||
|
entity.minecart.inside
|
||||||
|
entity.minecart.riding
|
||||||
|
entity.mooshroom.shear
|
||||||
|
entity.mule.ambient
|
||||||
|
entity.mule.death
|
||||||
|
entity.mule.hurt
|
||||||
|
entity.painting.break
|
||||||
|
entity.painting.place
|
||||||
|
entity.parrot.ambient
|
||||||
|
entity.parrot.death
|
||||||
|
entity.parrot.eat
|
||||||
|
entity.parrot.fly
|
||||||
|
entity.parrot.hurt
|
||||||
|
entity.parrot.step
|
||||||
|
entity.pig.ambient
|
||||||
|
entity.pig.death
|
||||||
|
entity.pig.hurt
|
||||||
|
entity.pig.saddle
|
||||||
|
entity.pig.step
|
||||||
|
entity.player.attack.crit
|
||||||
|
entity.player.attack.knockback
|
||||||
|
entity.player.attack.nodamage
|
||||||
|
entity.player.attack.strong
|
||||||
|
entity.player.attack.sweep
|
||||||
|
entity.player.attack.weak
|
||||||
|
entity.player.big_fall
|
||||||
|
entity.player.burp
|
||||||
|
entity.player.death
|
||||||
|
entity.player.hurt
|
||||||
|
entity.player.levelup
|
||||||
|
entity.player.small_fall
|
||||||
|
entity.player.splash
|
||||||
|
entity.player.swim
|
||||||
|
entity.polar_bear.ambient
|
||||||
|
entity.polar_bear.baby_ambient
|
||||||
|
entity.polar_bear.death
|
||||||
|
entity.polar_bear.hurt
|
||||||
|
entity.polar_bear.step
|
||||||
|
entity.polar_bear.warning
|
||||||
|
entity.rabbit.attack
|
||||||
|
entity.rabbit.ambient
|
||||||
|
entity.rabbit.death
|
||||||
|
entity.rabbit.hurt
|
||||||
|
entity.rabbit.jump
|
||||||
|
entity.sheep.death
|
||||||
|
entity.sheep.hurt
|
||||||
|
entity.sheep.shear
|
||||||
|
entity.sheep.step
|
||||||
|
entity.shield.break
|
||||||
|
entity.shield.block
|
||||||
|
entity.shulker.ambient
|
||||||
|
entity.shulker_bullet.hit
|
||||||
|
entity.shulker_bullet.hurt
|
||||||
|
entity.shulker.death
|
||||||
|
entity.shulker.close
|
||||||
|
entity.shulker.hit
|
||||||
|
entity.shulker.hurt
|
||||||
|
entity.shulker.hurt_closed
|
||||||
|
entity.shulker.shoot
|
||||||
|
entity.shulker.teleport
|
||||||
|
entity.silverfish.ambient
|
||||||
|
entity.silverfish.death
|
||||||
|
entity.silverfish.hurt
|
||||||
|
entity.silverfish.step
|
||||||
|
entity.skeleton.ambient
|
||||||
|
entity.skeleton.death
|
||||||
|
entity.skeleton.hurt
|
||||||
|
entity.skeleton.shoot
|
||||||
|
entity.skeleton.step
|
||||||
|
entity.skeleton_horse.ambient
|
||||||
|
entity.skeleton_horse.death
|
||||||
|
entity.skeleton_horse.hurt
|
||||||
|
entity.slime.attack
|
||||||
|
entity.slime.death
|
||||||
|
entity.slime.hurt
|
||||||
|
entity.slime.jump
|
||||||
|
entity.slime.squish
|
||||||
|
entity.small_magmacube.death
|
||||||
|
entity.small_magmacube.hurt
|
||||||
|
entity.small_magmacube.squish
|
||||||
|
entity.small_slime.death
|
||||||
|
entity.small_slime.hurt
|
||||||
|
entity.small_slime.squish
|
||||||
|
entity.snowball.throw
|
||||||
|
entity.snowman.ambient
|
||||||
|
entity.snowman.death
|
||||||
|
entity.snowman.hurt
|
||||||
|
entity.snowman.shoot
|
||||||
|
entity.spider.ambient
|
||||||
|
entity.spider.death
|
||||||
|
entity.spider.hurt
|
||||||
|
entity.spider.step
|
||||||
|
entity.splash_potion.break
|
||||||
|
entity.splash_potion.throw
|
||||||
|
entity.squid.ambient
|
||||||
|
entity.squid.death
|
||||||
|
entity.squid.hurt
|
||||||
|
entity.stray.ambient
|
||||||
|
entity.stray.death
|
||||||
|
entity.stray.hurt
|
||||||
|
entity.stray.step
|
||||||
|
entity.tnt.primed
|
||||||
|
entity.vex.ambient
|
||||||
|
entity.vex.charge
|
||||||
|
entity.vex.hurt
|
||||||
|
entity.vex.death
|
||||||
|
entity.vindication_illager.ambient
|
||||||
|
entity.vindication_illager.hurt
|
||||||
|
entity.vindication_illager.death
|
||||||
|
entity.villager.ambient
|
||||||
|
entity.villager.death
|
||||||
|
entity.villager.hurt
|
||||||
|
entity.villager.no
|
||||||
|
entity.villager.trading
|
||||||
|
entity.villager.yes
|
||||||
|
entity.witch.ambient
|
||||||
|
entity.witch.death
|
||||||
|
entity.witch.drink
|
||||||
|
entity.witch.hurt
|
||||||
|
entity.witch.throw
|
||||||
|
entity.wither.ambient
|
||||||
|
entity.wither.break_block
|
||||||
|
entity.wither.death
|
||||||
|
entity.wither.hurt
|
||||||
|
entity.wither.shoot
|
||||||
|
entity.wither.spawn
|
||||||
|
entity.wither_skeleton.ambient
|
||||||
|
entity.wither_skeleton.death
|
||||||
|
entity.wither_skeleton.hurt
|
||||||
|
entity.wither_skeleton.step
|
||||||
|
entity.wolf.ambient
|
||||||
|
entity.wolf.death
|
||||||
|
entity.wolf.growl
|
||||||
|
entity.wolf.hurt
|
||||||
|
entity.wolf.pant
|
||||||
|
entity.wolf.shake
|
||||||
|
entity.wolf.step
|
||||||
|
entity.wolf.whine
|
||||||
|
entity.zombie.ambient
|
||||||
|
entity.zombie.attack_door_wood
|
||||||
|
entity.zombie.attack_iron_door
|
||||||
|
entity.zombie.break_door_wood
|
||||||
|
entity.zombie.cure
|
||||||
|
entity.zombie.death
|
||||||
|
entity.zombie.hurt
|
||||||
|
entity.zombie.step
|
||||||
|
entity.zombie_horse.ambient
|
||||||
|
entity.zombie_horse.death
|
||||||
|
entity.zombie_horse.hurt
|
||||||
|
entity.zombie.infect
|
||||||
|
entity.zombie_pig.ambient
|
||||||
|
entity.zombie_pig.angry
|
||||||
|
entity.zombie_pig.death
|
||||||
|
entity.zombie_pig.hurt
|
||||||
|
enchant.thorns.hit
|
||||||
|
music.creative
|
||||||
|
music.credits
|
||||||
|
music.dragon
|
||||||
|
music.end
|
||||||
|
music.game
|
||||||
|
music.menu
|
||||||
|
music.nether
|
||||||
|
record.11
|
||||||
|
record.13
|
||||||
|
record.blocks
|
||||||
|
record.cat
|
||||||
|
record.chirp
|
||||||
|
record.far
|
||||||
|
record.mall
|
||||||
|
record.mellohi
|
||||||
|
record.stal
|
||||||
|
record.strad
|
||||||
|
record.wait
|
||||||
|
record.ward
|
||||||
|
item.armor.equip_chain
|
||||||
|
item.armor.equip_diamond
|
||||||
|
item.armor.equip_generic
|
||||||
|
item.armor.equip_gold
|
||||||
|
item.armor.equip_iron
|
||||||
|
item.armor.equip_leather
|
||||||
|
item.bottle.fill
|
||||||
|
item.bottle.fill_dragonbreath
|
||||||
|
item.bucket.empty
|
||||||
|
item.bucket.empty_lava
|
||||||
|
item.bucket.fill
|
||||||
|
item.bucket.fill_lava
|
||||||
|
item.chorus_fruit.teleport
|
||||||
|
item.elytra.flying
|
||||||
|
item.firecharge.use
|
||||||
|
item.flintandsteel.use
|
||||||
|
item.hoe.till
|
||||||
|
item.shovel.flatten
|
||||||
|
item.totem.use
|
||||||
|
weather.rain
|
||||||
|
weather.rain.above
|
||||||
|
ambient.cave
|
||||||
|
ui.button.click
|
||||||
617
common/multiMiner.lua
Normal file
617
common/multiMiner.lua
Normal file
@@ -0,0 +1,617 @@
|
|||||||
|
local Event = require('opus.event')
|
||||||
|
local GPS = require('opus.gps')
|
||||||
|
local itemDB = require('core.itemDB')
|
||||||
|
local Point = require('opus.point')
|
||||||
|
local Socket = require('opus.socket')
|
||||||
|
local Sound = require('opus.sound')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
local UI = require('opus.ui')
|
||||||
|
|
||||||
|
local colors = _G.colors
|
||||||
|
local device = _G.device
|
||||||
|
local gps = _G.gps
|
||||||
|
local network = _G.network
|
||||||
|
local os = _G.os
|
||||||
|
|
||||||
|
UI:configure('multiMiner', ...)
|
||||||
|
|
||||||
|
local glasses = device['plethora:glasses']
|
||||||
|
local scanner = device['plethora:scanner'] or
|
||||||
|
error('Plethora scanner must be equipped')
|
||||||
|
|
||||||
|
-- hud
|
||||||
|
local canvas = glasses and glasses.canvas()
|
||||||
|
if canvas then
|
||||||
|
local lh
|
||||||
|
|
||||||
|
local function addText(x, y, text, color)
|
||||||
|
local th = canvas.group.addText({ x, y }, text, color or 0xa0a0a0FF)
|
||||||
|
lh = lh or th.getLineHeight()
|
||||||
|
th.setShadow(true)
|
||||||
|
th.setScale(.75)
|
||||||
|
return th
|
||||||
|
end
|
||||||
|
|
||||||
|
canvas.group = canvas.addGroup({ 4, 90 })
|
||||||
|
canvas.group.bg = canvas.group.addRectangle(0, 0, 80, 10, 0x40404080)
|
||||||
|
canvas.group.addLines(
|
||||||
|
{ 0, 0 },
|
||||||
|
{ 80, 0 },
|
||||||
|
{ 80, 10 },
|
||||||
|
{ 0, 10 },
|
||||||
|
{ 0, 0 },
|
||||||
|
0x202020FF,
|
||||||
|
2)
|
||||||
|
addText(20, 2, 'Swarm Miner', 0xc0c0c0FF)
|
||||||
|
|
||||||
|
local y = 15
|
||||||
|
addText(3, y, 'Turtles')
|
||||||
|
canvas.turtles = addText(60, y, '')
|
||||||
|
canvas.group.addLine({ 0, y + lh - 2 }, { 80, y + lh - 2 }, 0x404040FF, 4)
|
||||||
|
|
||||||
|
y = y + lh + 5
|
||||||
|
addText(3, y, 'Queue')
|
||||||
|
canvas.queue = addText(60, y, '')
|
||||||
|
canvas.group.addLine({ 0, y + lh - 2 }, { 80, y + lh - 2 }, 0x404040FF, 4)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- container
|
||||||
|
local canvas3d = glasses and glasses.canvas3d().create()
|
||||||
|
local box, offset
|
||||||
|
|
||||||
|
local paused = false
|
||||||
|
|
||||||
|
local function inBox(pt)
|
||||||
|
if not box or not box.ex then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return Point.inBox(pt, box)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function locate()
|
||||||
|
for _ = 1, 3 do
|
||||||
|
local pt = GPS.getPoint()
|
||||||
|
if pt then
|
||||||
|
return pt
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local spt = GPS.getPoint() or error('GPS failure')
|
||||||
|
local chestPoint -- location of chest
|
||||||
|
local blockTypes = { } -- blocks types requested to mine
|
||||||
|
local turtles = { } -- active turtles
|
||||||
|
local pool = { } -- all turtles
|
||||||
|
local queue = { } -- actual blocks to mine
|
||||||
|
local abort
|
||||||
|
|
||||||
|
local function hijackTurtle(remoteId)
|
||||||
|
local socket, msg = Socket.connect(remoteId, 188)
|
||||||
|
|
||||||
|
if not socket then
|
||||||
|
error(msg, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
socket:write('turtle')
|
||||||
|
local methods = socket:read()
|
||||||
|
|
||||||
|
local hijack = { }
|
||||||
|
for _,method in pairs(methods) do
|
||||||
|
hijack[method] = function(...)
|
||||||
|
socket:write({ method, ... })
|
||||||
|
local resp = socket:read()
|
||||||
|
if not resp then
|
||||||
|
error('T/O: ' .. method, 0)
|
||||||
|
end
|
||||||
|
return table.unpack(resp)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return hijack, socket
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getNextPoint(turtle)
|
||||||
|
if not paused then
|
||||||
|
local pt = Point.closest(turtle.getPoint(), queue)
|
||||||
|
if pt then
|
||||||
|
turtle.pt = pt
|
||||||
|
queue[pt.pkey] = nil
|
||||||
|
return pt
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function run(member, point)
|
||||||
|
Event.addRoutine(function()
|
||||||
|
local turtle, socket
|
||||||
|
local _, m = pcall(function()
|
||||||
|
member.active = true
|
||||||
|
turtle, socket = hijackTurtle(member.id)
|
||||||
|
|
||||||
|
local function emptySlots(retain, pt)
|
||||||
|
local slots = turtle.getFilledSlots()
|
||||||
|
for _,slot in pairs(slots) do
|
||||||
|
if not retain[slot.key] and not slot.name:find('turtle') then
|
||||||
|
turtle.select(slot.index)
|
||||||
|
if pt then
|
||||||
|
turtle.dropAt(pt, 64)
|
||||||
|
else
|
||||||
|
turtle.dropUp(64)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function dropOff()
|
||||||
|
-- go to 2 above chest
|
||||||
|
local topPoint = Point.copy(chestPoint)
|
||||||
|
topPoint.y = topPoint.y + 2
|
||||||
|
turtle.gotoY(topPoint.y)
|
||||||
|
while not turtle.go(topPoint) do
|
||||||
|
os.sleep(.5)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- path to chest
|
||||||
|
local box = Point.makeBox(
|
||||||
|
{ x = chestPoint.x - 3, y = chestPoint.y + 3, z = chestPoint.z - 3 },
|
||||||
|
{ x = chestPoint.x + 3, y = chestPoint.y, z = chestPoint.z + 3 }
|
||||||
|
)
|
||||||
|
turtle.set({
|
||||||
|
movementStrategy = 'pathing',
|
||||||
|
pathingBox = Point.normalizeBox(box),
|
||||||
|
digPolicy = 'digNone',
|
||||||
|
})
|
||||||
|
while not turtle.moveAgainst(chestPoint) do
|
||||||
|
os.sleep(.5)
|
||||||
|
end
|
||||||
|
emptySlots({ }, chestPoint)
|
||||||
|
|
||||||
|
-- path to 3 above chest
|
||||||
|
turtle.pathfind(Point.above(topPoint))
|
||||||
|
turtle.set({
|
||||||
|
movementStrategy = 'goto',
|
||||||
|
digPolicy = 'blacklist',
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
if turtle then
|
||||||
|
turtles[member.id] = turtle
|
||||||
|
|
||||||
|
turtle.reset()
|
||||||
|
turtle.set({
|
||||||
|
attackPolicy = 'attack',
|
||||||
|
digPolicy = 'blacklist',
|
||||||
|
blacklist = {
|
||||||
|
'turtle',
|
||||||
|
'chest',
|
||||||
|
'shulker',
|
||||||
|
},
|
||||||
|
movementStrategy = 'goto',
|
||||||
|
point = point,
|
||||||
|
})
|
||||||
|
turtle.select(1)
|
||||||
|
|
||||||
|
repeat
|
||||||
|
local pt = getNextPoint(turtle)
|
||||||
|
if pt then
|
||||||
|
member.status = 'digging'
|
||||||
|
|
||||||
|
if blockTypes[pt.key] == true then
|
||||||
|
if turtle.moveAgainst(pt) then
|
||||||
|
local index = turtle.selectOpenSlot()
|
||||||
|
if turtle.digAt(pt, pt.name) then
|
||||||
|
local slot = turtle.getSlot(index)
|
||||||
|
if slot.count > 0 then
|
||||||
|
blockTypes[pt.key] = slot.key
|
||||||
|
if slot.key ~= pt.key then
|
||||||
|
blockTypes[slot.key] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
turtle.select(1)
|
||||||
|
else
|
||||||
|
turtle.digAt(pt, pt.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
if turtle.getItemCount(15) > 0 then
|
||||||
|
member.status = 'ejecting trash'
|
||||||
|
emptySlots(blockTypes)
|
||||||
|
turtle.condense()
|
||||||
|
if turtle.getItemCount(15) > 0 then
|
||||||
|
member.status = 'dropping off'
|
||||||
|
if not chestPoint then
|
||||||
|
member.abort = true
|
||||||
|
member.status = 'full'
|
||||||
|
else
|
||||||
|
dropOff()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
turtle.select(1)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
member.status = 'waiting'
|
||||||
|
os.sleep(1)
|
||||||
|
end
|
||||||
|
if member.fuel < 100 then
|
||||||
|
member.status = 'out of fuel'
|
||||||
|
break
|
||||||
|
end
|
||||||
|
until member.abort
|
||||||
|
end
|
||||||
|
|
||||||
|
emptySlots(blockTypes)
|
||||||
|
|
||||||
|
if chestPoint then
|
||||||
|
dropOff()
|
||||||
|
while not turtle.go(Point.above(spt)) do
|
||||||
|
os.sleep(.5)
|
||||||
|
end
|
||||||
|
--if turtle.selectSlotWithQuantity(0) then
|
||||||
|
--turtle.set({ digPolicy = 'dig' })
|
||||||
|
--end
|
||||||
|
while not turtle.go(spt) do
|
||||||
|
os.sleep(.5)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
turtle.gotoY(spt.y)
|
||||||
|
while not turtle.go(spt) do
|
||||||
|
os.sleep(.5)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
turtles[member.id] = nil
|
||||||
|
member.status = m
|
||||||
|
member.active = false
|
||||||
|
if socket then
|
||||||
|
socket:close()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function drawContainer(pos)
|
||||||
|
if canvas3d then
|
||||||
|
canvas3d.clear()
|
||||||
|
|
||||||
|
local function addBox(b)
|
||||||
|
canvas3d.addBox(
|
||||||
|
b.x - offset.x + .25,
|
||||||
|
b.y - offset.y + .25 ,
|
||||||
|
b.z - offset.z + .25 ,
|
||||||
|
.5, .5, .5).setDepthTested(false)
|
||||||
|
end
|
||||||
|
if box and box.ex then
|
||||||
|
addBox({ x = box.x, y = box.y, z = box.z })
|
||||||
|
addBox({ x = box.x, y = box.y, z = box.ez })
|
||||||
|
addBox({ x = box.ex, y = box.y, z = box.z })
|
||||||
|
addBox({ x = box.ex, y = box.y, z = box.ez })
|
||||||
|
addBox({ x = box.x, y = box.ey, z = box.z })
|
||||||
|
addBox({ x = box.x, y = box.ey, z = box.ez })
|
||||||
|
addBox({ x = box.ex, y = box.ey, z = box.z })
|
||||||
|
addBox({ x = box.ex, y = box.ey, z = box.ez })
|
||||||
|
elseif box then
|
||||||
|
canvas3d.recenter({ -(pos.x % 1), -(pos.y % 1), -(pos.z % 1) })
|
||||||
|
addBox(box)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local pauseResume = {
|
||||||
|
{ text = 'Pause', event = 'pause' },
|
||||||
|
{ text = 'Resume', event = 'resume' },
|
||||||
|
}
|
||||||
|
local containerText = {
|
||||||
|
[[Set a corner to contain mining area]],
|
||||||
|
[[Set ending corner]],
|
||||||
|
[[Set again to clear]],
|
||||||
|
}
|
||||||
|
|
||||||
|
local containTab = UI.Tab {
|
||||||
|
title = 'Contain',
|
||||||
|
button = UI.Button {
|
||||||
|
x = 2, y = 2,
|
||||||
|
text = 'Set corner',
|
||||||
|
event = 'contain'
|
||||||
|
},
|
||||||
|
textArea = UI.TextArea {
|
||||||
|
x = 2, y = 4,
|
||||||
|
value = containerText[1],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
local blocksTab = UI.Tab {
|
||||||
|
title = 'Blocks',
|
||||||
|
grid = UI.ScrollingGrid {
|
||||||
|
y = 1,
|
||||||
|
columns = {
|
||||||
|
{ heading = 'Count', key = 'count', width = 6, align = 'right' },
|
||||||
|
{ heading = 'Name', key = 'displayName' },
|
||||||
|
},
|
||||||
|
sortColumn = 'displayName',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
local turtlesTab = UI.Tab {
|
||||||
|
title = 'Turtles',
|
||||||
|
grid = UI.ScrollingGrid {
|
||||||
|
y = 1,
|
||||||
|
values = pool,
|
||||||
|
columns = {
|
||||||
|
{ heading = 'ID', key = 'id', width = 5, },
|
||||||
|
{ heading = ' Fuel', key = 'fuel', width = 5, align = 'right' },
|
||||||
|
{ heading = ' Dist', key = 'distance', width = 5, align = 'right' },
|
||||||
|
{ heading = 'Status', key = 'status' },
|
||||||
|
},
|
||||||
|
sortColumn = 'label',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
local page = UI.Page {
|
||||||
|
menuBar = UI.MenuBar {
|
||||||
|
buttons = {
|
||||||
|
{ text = 'Scan', event = 'scan' },
|
||||||
|
pauseResume[1],
|
||||||
|
{ text = 'Abort', event = 'abort', x = -7 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tabs = UI.Tabs {
|
||||||
|
y = 2, ey = -2,
|
||||||
|
[1] = blocksTab,
|
||||||
|
[2] = turtlesTab,
|
||||||
|
[3] = containTab,
|
||||||
|
},
|
||||||
|
info = UI.Window {
|
||||||
|
y = -1,
|
||||||
|
backgroundColor = colors.blue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function page.info:draw()
|
||||||
|
self:clear()
|
||||||
|
self:write(2, 1, 'Turtles: ' .. Util.size(turtles))
|
||||||
|
if not chestPoint then
|
||||||
|
self:write(16, 1, 'No chest')
|
||||||
|
end
|
||||||
|
self:write(28, 1, 'Queue: ' .. Util.size(queue))
|
||||||
|
end
|
||||||
|
|
||||||
|
function turtlesTab.grid:getDisplayValues(row)
|
||||||
|
row = Util.shallowCopy(row)
|
||||||
|
row.distance = row.distance and Util.round(row.distance, 1)
|
||||||
|
row.fuel = row.fuel and row.fuel > 0 and Util.toBytes(row.fuel) or ''
|
||||||
|
return row
|
||||||
|
end
|
||||||
|
|
||||||
|
function page:scan()
|
||||||
|
local gpt = GPS.getPoint()
|
||||||
|
if not gpt then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local rawBlocks = scanner:scan()
|
||||||
|
local candidates = { }
|
||||||
|
|
||||||
|
self.totals = Util.reduce(rawBlocks,
|
||||||
|
function(acc, b)
|
||||||
|
b.key = table.concat({ b.name, b.metadata }, ':')
|
||||||
|
local entry = acc[b.key]
|
||||||
|
if not entry then
|
||||||
|
b.displayName = itemDB:getName(b.key)
|
||||||
|
b.count = 1
|
||||||
|
acc[b.key] = b
|
||||||
|
else
|
||||||
|
entry.count = entry.count + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if b.name == 'computercraft:turtle_advanced' or
|
||||||
|
b.name == 'computercraft:turtle_expanded' or
|
||||||
|
b.name == 'computercraft:turtle' then
|
||||||
|
table.insert(candidates, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
if b.name == 'minecraft:chest' or b.name:find('shulker') then
|
||||||
|
chestPoint = b
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add relevant blocks to queue
|
||||||
|
b.x = gpt.x + b.x
|
||||||
|
b.y = gpt.y + b.y
|
||||||
|
b.z = gpt.z + b.z
|
||||||
|
b.pkey = table.concat({ b.x, b.y, b.z }, ':')
|
||||||
|
if blockTypes[b.key] and inBox(b) then
|
||||||
|
if not Util.any(turtles, function(t)
|
||||||
|
return t.pt and t.pt.pkey == b.pkey
|
||||||
|
end) then
|
||||||
|
queue[b.pkey] = b
|
||||||
|
end
|
||||||
|
else
|
||||||
|
queue[b.pkey] = nil
|
||||||
|
end
|
||||||
|
return acc
|
||||||
|
end,
|
||||||
|
{ })
|
||||||
|
|
||||||
|
for _, b in pairs(candidates) do
|
||||||
|
local v = scanner.getBlockMeta(b.x - gpt.x, b.y - gpt.y, b.z - gpt.z)
|
||||||
|
if v and v.computer then
|
||||||
|
local member = pool[v.computer.id]
|
||||||
|
if not member then
|
||||||
|
member = {
|
||||||
|
id = v.computer.id,
|
||||||
|
label = v.computer.label,
|
||||||
|
}
|
||||||
|
pool[v.computer.id] = member
|
||||||
|
end
|
||||||
|
|
||||||
|
member.fuel = v.turtle.fuel
|
||||||
|
member.distance = 0
|
||||||
|
|
||||||
|
if not v.computer.isOn then
|
||||||
|
member.status = 'Powered off'
|
||||||
|
elseif v.turtle.fuel < 100 and not member.active then
|
||||||
|
member.status = 'Not enough fuel'
|
||||||
|
elseif not member.active and not member.abort then
|
||||||
|
local pt = Point.copy(b)
|
||||||
|
pt.heading = Point.facings[v.state.facing].heading
|
||||||
|
run(member, pt)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function blocksTab.grid:getDisplayValues(row)
|
||||||
|
row = Util.shallowCopy(row)
|
||||||
|
row.count = Util.toBytes(row.count) .. ' '
|
||||||
|
return row
|
||||||
|
end
|
||||||
|
|
||||||
|
function blocksTab.grid:getRowTextColor(row, selected)
|
||||||
|
return blockTypes[row.key] and
|
||||||
|
colors.yellow or
|
||||||
|
UI.Grid.getRowTextColor(self, row, selected)
|
||||||
|
end
|
||||||
|
|
||||||
|
function blocksTab:eventHandler(event)
|
||||||
|
if event.type == 'grid_select' then
|
||||||
|
local key = event.selected.key
|
||||||
|
if blockTypes[key] then
|
||||||
|
for k,v in pairs(queue) do
|
||||||
|
if v.key == key then
|
||||||
|
queue[k] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
blockTypes[key] = nil
|
||||||
|
else
|
||||||
|
blockTypes[key] = true
|
||||||
|
end
|
||||||
|
self.grid:draw()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function page:eventHandler(event)
|
||||||
|
if event.type == 'scan' then
|
||||||
|
blocksTab.grid:setValues(self.totals)
|
||||||
|
blocksTab.grid:draw()
|
||||||
|
self.tabs:selectTab(blocksTab)
|
||||||
|
|
||||||
|
elseif event.type == 'pause' then
|
||||||
|
paused = true
|
||||||
|
Util.merge(event.button, pauseResume[2])
|
||||||
|
event.button:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'resume' then
|
||||||
|
paused = false
|
||||||
|
Util.merge(event.button, pauseResume[1])
|
||||||
|
event.button:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'contain' then
|
||||||
|
local pt = { gps.locate() }
|
||||||
|
local pos = {
|
||||||
|
x = pt[1],
|
||||||
|
y = pt[2],
|
||||||
|
z = pt[3],
|
||||||
|
}
|
||||||
|
|
||||||
|
if not box then
|
||||||
|
offset = {
|
||||||
|
x = math.floor(pos.x),
|
||||||
|
y = math.floor(pos.y),
|
||||||
|
z = math.floor(pos.z),
|
||||||
|
}
|
||||||
|
box = {
|
||||||
|
x = math.floor(pos.x),
|
||||||
|
y = math.floor(pos.y) - 1,
|
||||||
|
z = math.floor(pos.z),
|
||||||
|
}
|
||||||
|
containTab.textArea.value = containerText[2]
|
||||||
|
elseif not box.ex then
|
||||||
|
box.ex = math.floor(pos.x)
|
||||||
|
box.ey = math.floor(pos.y) - 1
|
||||||
|
box.ez = math.floor(pos.z)
|
||||||
|
box = Point.normalizeBox(box)
|
||||||
|
containTab.textArea.value = containerText[3]
|
||||||
|
else
|
||||||
|
box = nil
|
||||||
|
containTab.textArea.value = containerText[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
containTab.textArea:draw()
|
||||||
|
drawContainer(pos)
|
||||||
|
|
||||||
|
elseif event.type == 'abort' then
|
||||||
|
for _, v in pairs(pool) do
|
||||||
|
v.abort = true
|
||||||
|
v.status = 'aborting'
|
||||||
|
end
|
||||||
|
spt = Point.above(locate())
|
||||||
|
abort = true
|
||||||
|
end
|
||||||
|
|
||||||
|
UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
|
||||||
|
Event.onInterval(5, function()
|
||||||
|
if not abort and not paused then
|
||||||
|
|
||||||
|
--local meta = scanner.getMetaOwner()
|
||||||
|
--if meta.isSneaking then
|
||||||
|
page:scan()
|
||||||
|
-- Sound.play('entity.bobber.throw', .6)
|
||||||
|
--end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
Event.onInterval(1, function()
|
||||||
|
for id,v in pairs(network) do
|
||||||
|
if v.fuel then
|
||||||
|
if pool[id] then
|
||||||
|
pool[id].fuel = v.fuel
|
||||||
|
pool[id].distance = v.distance
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if abort and Util.size(turtles) == 0 then
|
||||||
|
Event.exitPullEvents()
|
||||||
|
end
|
||||||
|
|
||||||
|
if turtlesTab.enabled then
|
||||||
|
turtlesTab.grid:update()
|
||||||
|
turtlesTab.grid:draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
page.info:draw()
|
||||||
|
page.info:sync()
|
||||||
|
|
||||||
|
if canvas then
|
||||||
|
canvas.turtles.setText(tostring(Util.size(turtles)))
|
||||||
|
canvas.queue.setText(tostring(Util.size(queue)))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
Event.onTimeout(.1, function()
|
||||||
|
page:scan()
|
||||||
|
blocksTab.grid:setValues(page.totals)
|
||||||
|
blocksTab.grid:draw()
|
||||||
|
page:sync()
|
||||||
|
end)
|
||||||
|
|
||||||
|
UI:setPage(page)
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Event.onTerminate(function()
|
||||||
|
spt = Point.above(locate())
|
||||||
|
for _, v in pairs(pool) do
|
||||||
|
v.status = 'aborting'
|
||||||
|
v.abort = true
|
||||||
|
end
|
||||||
|
abort = true
|
||||||
|
end)
|
||||||
|
]]
|
||||||
|
|
||||||
|
Event.pullEvents()
|
||||||
|
|
||||||
|
if canvas then
|
||||||
|
canvas3d.clear()
|
||||||
|
canvas.group.remove()
|
||||||
|
end
|
||||||
@@ -14,15 +14,19 @@ local version = "Version 1.1.6"
|
|||||||
-- Original code by Bomb Bloke
|
-- Original code by Bomb Bloke
|
||||||
-- Modified to integrate with opus os
|
-- Modified to integrate with opus os
|
||||||
|
|
||||||
requireInjector(getfenv(1))
|
local Util = require('opus.util')
|
||||||
|
|
||||||
local Util = require('util')
|
local multishell = _ENV.multishell
|
||||||
|
local os = _G.os
|
||||||
|
|
||||||
local recTerm, oldTerm, arg, showInput, skipLast, lastDelay, curInput = {}, Util.shallowCopy(multishell.term), {...}, false, false, 2, ""
|
local colours = _G.colors
|
||||||
|
|
||||||
|
local args, options = Util.parse(...)
|
||||||
|
|
||||||
|
local oldTerm, showInput, skipLast, lastDelay, curInput = Util.shallowCopy(_G.device.terminal), false, false, 2, ""
|
||||||
local curBlink, oldBlink, tTerm, buffer, colourNum, xPos, yPos, oldXPos, oldYPos, tCol, bCol, xSize, ySize = false, false, {}, {}, {}, 1, 1, 1, 1, colours.white, colours.black, oldTerm.getSize()
|
local curBlink, oldBlink, tTerm, buffer, colourNum, xPos, yPos, oldXPos, oldYPos, tCol, bCol, xSize, ySize = false, false, {}, {}, {}, 1, 1, 1, 1, colours.white, colours.black, oldTerm.getSize()
|
||||||
local greys, buttons = {["0"] = true, ["7"] = true, ["8"] = true, ["f"] = true}, {"l", "r", "m"}
|
local greys, buttons = {["0"] = true, ["7"] = true, ["8"] = true, ["f"] = true}, {"l", "r", "m"}
|
||||||
local charW, charH, chars, resp
|
local charW, charH, chars
|
||||||
local filename
|
|
||||||
|
|
||||||
local calls = { }
|
local calls = { }
|
||||||
local curCalls = { delay = 0 }
|
local curCalls = { delay = 0 }
|
||||||
@@ -32,37 +36,44 @@ local callCount = 0
|
|||||||
local function showSyntax()
|
local function showSyntax()
|
||||||
print('Gif Recorder by Bomb Bloke\n')
|
print('Gif Recorder by Bomb Bloke\n')
|
||||||
print('Syntax: recGif [-i] [-s] [-ld:<delay>] filename')
|
print('Syntax: recGif [-i] [-s] [-ld:<delay>] filename')
|
||||||
print(' -i : show input')
|
print(' --showInput : show input')
|
||||||
print(' -s : skip last')
|
print(' --skipLast : skip last')
|
||||||
print(' -ld : last delay')
|
print(' --lastDelay : last delay')
|
||||||
|
print(' --noResize : dont resize')
|
||||||
end
|
end
|
||||||
|
|
||||||
for i = #arg, 1, -1 do
|
if options.showInput then
|
||||||
local curArg = arg[i]:lower()
|
showInput, ySize = true, ySize + 1
|
||||||
|
|
||||||
if curArg == "-i" then
|
|
||||||
showInput, ySize = true, ySize + 1
|
|
||||||
table.remove(arg, i)
|
|
||||||
elseif curArg == "-s" then
|
|
||||||
skipLast = true
|
|
||||||
table.remove(arg, i)
|
|
||||||
elseif curArg:sub(1, 4) == "-ld:" then
|
|
||||||
curArg = tonumber(curArg:sub(5))
|
|
||||||
if curArg then lastDelay = curArg end
|
|
||||||
table.remove(arg, i)
|
|
||||||
elseif curArg == '-?' then
|
|
||||||
showSyntax()
|
|
||||||
return
|
|
||||||
elseif i ~= #arg then
|
|
||||||
showSyntax()
|
|
||||||
printError('\nInvalid argument')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
print('Press control-p to stop recording')
|
if options.skipLast then
|
||||||
|
skipLast = true
|
||||||
|
end
|
||||||
|
|
||||||
local filename = arg[#arg]
|
if options.lastDelay then
|
||||||
|
lastDelay = options.lastDelay
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.help then
|
||||||
|
showSyntax()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.daemon then
|
||||||
|
_G.device.keyboard.addHotkey('control-P', function()
|
||||||
|
multishell.openTab(_ENV, {
|
||||||
|
path = 'sys/apps/shell.lua',
|
||||||
|
args = { arg[0], '--noResize', '--rawOutput', 'recorder.gif' },
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
print('Gif Recorder by Bomb Bloke')
|
||||||
|
print(version)
|
||||||
|
print('\nPress control-p to stop recording')
|
||||||
|
|
||||||
|
local filename = args[1]
|
||||||
if not filename then
|
if not filename then
|
||||||
print('Enter file name:')
|
print('Enter file name:')
|
||||||
filename = read()
|
filename = read()
|
||||||
@@ -83,15 +94,21 @@ local function loadAPI(url, env)
|
|||||||
apiEnv.shell = nil
|
apiEnv.shell = nil
|
||||||
apiEnv.multishell = nil
|
apiEnv.multishell = nil
|
||||||
setmetatable(apiEnv, { __index = _G })
|
setmetatable(apiEnv, { __index = _G })
|
||||||
local fn = Util.loadUrl(url, apiEnv)
|
local fn, m = Util.loadUrl(url, apiEnv)
|
||||||
|
if not fn then
|
||||||
|
error(m)
|
||||||
|
end
|
||||||
fn()
|
fn()
|
||||||
return apiEnv
|
return apiEnv
|
||||||
end
|
end
|
||||||
|
|
||||||
bbpack = loadAPI('http://pastebin.com/raw/PdrJjb5S', getfenv(1))
|
bbpack = loadAPI('http://pastebin.com/raw/cUYTGbpb', getfenv(1))
|
||||||
GIF = loadAPI('http://pastebin.com/raw/5uk9uRjC', getfenv(1))
|
GIF = loadAPI('http://pastebin.com/raw/5uk9uRjC', getfenv(1))
|
||||||
|
|
||||||
Util.runUrl(getfenv(1), 'http://pastebin.com/raw/cUYTGbpb', 'get', 'CnLzL5fg')
|
local s, m = Util.runUrl(getfenv(1), 'http://pastebin.com/raw/cUYTGbpb', 'get', 'CnLzL5fg')
|
||||||
|
if not s then
|
||||||
|
error(m)
|
||||||
|
end
|
||||||
-- 'Y0eLUPtr')
|
-- 'Y0eLUPtr')
|
||||||
-- CnLzL5fg
|
-- CnLzL5fg
|
||||||
local function snooze()
|
local function snooze()
|
||||||
@@ -124,37 +141,34 @@ end
|
|||||||
|
|
||||||
-- Build a terminal that records stuff:
|
-- Build a terminal that records stuff:
|
||||||
|
|
||||||
recTerm = multishell.term
|
local recTerm = _G.device.terminal
|
||||||
|
|
||||||
for key, func in pairs(oldTerm) do
|
for key, func in pairs(oldTerm) do
|
||||||
recTerm[key] = function(...)
|
if type(func) == 'function' then
|
||||||
local result = { func(...) }
|
recTerm[key] = function(...)
|
||||||
|
local result = { func(...) }
|
||||||
|
|
||||||
if callCount == 0 then
|
if callCount == 0 then
|
||||||
os.queueEvent('capture_frame')
|
os.queueEvent('capture_frame')
|
||||||
|
end
|
||||||
|
callCount = callCount + 1
|
||||||
|
curCalls[callCount] = { key, ... }
|
||||||
|
return table.unpack(result)
|
||||||
end
|
end
|
||||||
callCount = callCount + 1
|
|
||||||
curCalls[callCount] = { key, ... }
|
|
||||||
return unpack(result)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local tabId = multishell.getCurrent()
|
local tabId = multishell.getCurrent()
|
||||||
|
multishell.hideTab(tabId)
|
||||||
|
|
||||||
multishell.addHotkey(25, function()
|
if not options.noResize then
|
||||||
os.queueEvent('recorder_stop')
|
os.queueEvent('term_resize')
|
||||||
end)
|
|
||||||
|
|
||||||
local tabs = multishell.getTabs()
|
|
||||||
for _,tab in pairs(tabs) do
|
|
||||||
if tab.isOverview then
|
|
||||||
multishell.hideTab(tabId)
|
|
||||||
multishell.setFocus(tab.tabId)
|
|
||||||
os.queueEvent('term_resize')
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
_G.device.keyboard.addHotkey('control-p', function()
|
||||||
|
os.queueEvent('recorder_stop')
|
||||||
|
end)
|
||||||
|
|
||||||
local curTime = os.clock() - 1
|
local curTime = os.clock() - 1
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
@@ -179,10 +193,10 @@ while true do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
multishell.removeHotkey(25)
|
_G.device.keyboard.removeHotkey('control-p')
|
||||||
|
|
||||||
for k,fn in pairs(oldTerm) do
|
for k,fn in pairs(oldTerm) do
|
||||||
multishell.term[k] = fn
|
_G.device.terminal[k] = fn
|
||||||
end
|
end
|
||||||
|
|
||||||
multishell.unhideTab(tabId)
|
multishell.unhideTab(tabId)
|
||||||
@@ -193,8 +207,12 @@ if skipLast and #calls > 1 then calls[#calls] = nil end
|
|||||||
|
|
||||||
calls[#calls].delay = lastDelay
|
calls[#calls].delay = lastDelay
|
||||||
|
|
||||||
|
if options.rawOutput then
|
||||||
|
Util.writeTable('tmp/raw.txt', calls)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
print(string.format("Encoding %d frames...", #calls))
|
print(string.format("Encoding %d frames...", #calls))
|
||||||
--Util.writeTable('tmp/raw.txt', calls)
|
|
||||||
|
|
||||||
-- Perform a quick re-parse of the recorded data (adding frames for when the cursor blinks):
|
-- Perform a quick re-parse of the recorded data (adding frames for when the cursor blinks):
|
||||||
|
|
||||||
6
compress/.package
Normal file
6
compress/.package
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
title = 'Compress',
|
||||||
|
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/compress',
|
||||||
|
description = [[untar / gunzip]],
|
||||||
|
license = 'MIT',
|
||||||
|
}
|
||||||
530
compress/apis/deflatelua.lua
Normal file
530
compress/apis/deflatelua.lua
Normal file
@@ -0,0 +1,530 @@
|
|||||||
|
--[[
|
||||||
|
see: https://github.com/davidm/lua-compress-deflatelua/
|
||||||
|
for licensing / details
|
||||||
|
--]]
|
||||||
|
|
||||||
|
local M = {_TYPE='module', _NAME='compress.deflatelua', _VERSION='0.3.20111128'}
|
||||||
|
|
||||||
|
local assert = assert
|
||||||
|
local error = error
|
||||||
|
local ipairs = ipairs
|
||||||
|
local pairs = pairs
|
||||||
|
local tostring = tostring
|
||||||
|
local type = type
|
||||||
|
local setmetatable = setmetatable
|
||||||
|
local io = io
|
||||||
|
local math = math
|
||||||
|
local table_sort = table.sort
|
||||||
|
local math_max = math.max
|
||||||
|
local string_char = string.char
|
||||||
|
local band = bit32.band
|
||||||
|
local lshift = bit32.lshift
|
||||||
|
local rshift = bit32.rshift
|
||||||
|
|
||||||
|
local function runtime_error(s, level)
|
||||||
|
level = level or 1
|
||||||
|
error(s, level+1)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function make_outstate(outbs)
|
||||||
|
local outstate = {}
|
||||||
|
outstate.outbs = outbs
|
||||||
|
outstate.window = {}
|
||||||
|
outstate.window_pos = 1
|
||||||
|
return outstate
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function output(outstate, byte)
|
||||||
|
local window_pos = outstate.window_pos
|
||||||
|
outstate.outbs(byte)
|
||||||
|
outstate.window[window_pos] = byte
|
||||||
|
outstate.window_pos = window_pos % 32768 + 1 -- 32K
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function noeof(val)
|
||||||
|
return assert(val, 'unexpected end of file')
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function hasbit(bits, bit)
|
||||||
|
return bits % (bit + bit) >= bit
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function memoize(f)
|
||||||
|
local mt = {}
|
||||||
|
local t = setmetatable({}, mt)
|
||||||
|
function mt:__index(k)
|
||||||
|
local v = f(k)
|
||||||
|
t[k] = v
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- small optimization (lookup table for powers of 2)
|
||||||
|
local pow2 = memoize(function(n) return 2^n end)
|
||||||
|
|
||||||
|
--local tbits = memoize(
|
||||||
|
-- function(bits)
|
||||||
|
-- return memoize( function(bit) return getbit(bits, bit) end )
|
||||||
|
-- end )
|
||||||
|
|
||||||
|
|
||||||
|
-- weak metatable marking objects as bitstream type
|
||||||
|
local is_bitstream = setmetatable({}, {__mode='k'})
|
||||||
|
|
||||||
|
local function bytestream_from_file(fh)
|
||||||
|
local o = {}
|
||||||
|
function o.read()
|
||||||
|
local sb = fh:read(1)
|
||||||
|
if sb then return sb:byte() end
|
||||||
|
end
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function bytestream_from_string(s)
|
||||||
|
local i = 1
|
||||||
|
local o = {}
|
||||||
|
function o.read()
|
||||||
|
local by
|
||||||
|
if i <= #s then
|
||||||
|
by = s:byte(i)
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
return by
|
||||||
|
end
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function bytestream_from_function(f)
|
||||||
|
local o = {}
|
||||||
|
function o.read()
|
||||||
|
return f()
|
||||||
|
end
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function bitstream_from_bytestream(bys)
|
||||||
|
local buf_byte = 0
|
||||||
|
local buf_nbit = 0
|
||||||
|
local o = {}
|
||||||
|
|
||||||
|
function o.nbits_left_in_byte()
|
||||||
|
return buf_nbit
|
||||||
|
end
|
||||||
|
|
||||||
|
function o:read(nbits)
|
||||||
|
nbits = nbits or 1
|
||||||
|
while buf_nbit < nbits do
|
||||||
|
local byte = bys:read()
|
||||||
|
if not byte then return end -- note: more calls also return nil
|
||||||
|
buf_byte = buf_byte + lshift(byte, buf_nbit)
|
||||||
|
buf_nbit = buf_nbit + 8
|
||||||
|
end
|
||||||
|
local bits
|
||||||
|
if nbits == 0 then
|
||||||
|
bits = 0
|
||||||
|
elseif nbits == 32 then
|
||||||
|
bits = buf_byte
|
||||||
|
buf_byte = 0
|
||||||
|
else
|
||||||
|
bits = band(buf_byte, rshift(0xffffffff, 32 - nbits))
|
||||||
|
buf_byte = rshift(buf_byte, nbits)
|
||||||
|
end
|
||||||
|
buf_nbit = buf_nbit - nbits
|
||||||
|
return bits
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
is_bitstream[o] = true
|
||||||
|
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function get_bitstream(o)
|
||||||
|
local bs
|
||||||
|
if is_bitstream[o] then
|
||||||
|
return o
|
||||||
|
elseif io.type(o) == 'file' then
|
||||||
|
bs = bitstream_from_bytestream(bytestream_from_file(o))
|
||||||
|
elseif type(o) == 'string' then
|
||||||
|
bs = bitstream_from_bytestream(bytestream_from_string(o))
|
||||||
|
elseif type(o) == 'function' then
|
||||||
|
bs = bitstream_from_bytestream(bytestream_from_function(o))
|
||||||
|
else
|
||||||
|
runtime_error 'unrecognized type'
|
||||||
|
end
|
||||||
|
return bs
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function get_obytestream(o)
|
||||||
|
local bs
|
||||||
|
if io.type(o) == 'file' then
|
||||||
|
bs = function(sbyte) o:write(string_char(sbyte)) end
|
||||||
|
elseif type(o) == 'function' then
|
||||||
|
bs = o
|
||||||
|
else
|
||||||
|
runtime_error('unrecognized type: ' .. tostring(o))
|
||||||
|
end
|
||||||
|
return bs
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function HuffmanTable(init, is_full)
|
||||||
|
local t = {}
|
||||||
|
if is_full then
|
||||||
|
for val,nbits in pairs(init) do
|
||||||
|
if nbits ~= 0 then
|
||||||
|
t[#t+1] = {val=val, nbits=nbits}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for i=1,#init-2,2 do
|
||||||
|
local firstval, nbits, nextval = init[i], init[i+1], init[i+2]
|
||||||
|
if nbits ~= 0 then
|
||||||
|
for val=firstval,nextval-1 do
|
||||||
|
t[#t+1] = {val=val, nbits=nbits}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table_sort(t, function(a,b)
|
||||||
|
return a.nbits == b.nbits and a.val < b.val or a.nbits < b.nbits
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- assign codes
|
||||||
|
local code = 1 -- leading 1 marker
|
||||||
|
local nbits = 0
|
||||||
|
for _,s in ipairs(t) do
|
||||||
|
if s.nbits ~= nbits then
|
||||||
|
code = code * pow2[s.nbits - nbits]
|
||||||
|
nbits = s.nbits
|
||||||
|
end
|
||||||
|
s.code = code
|
||||||
|
code = code + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local minbits = math.huge
|
||||||
|
local look = {}
|
||||||
|
for _,s in ipairs(t) do
|
||||||
|
minbits = math.min(minbits, s.nbits)
|
||||||
|
look[s.code] = s.val
|
||||||
|
end
|
||||||
|
|
||||||
|
local msb = function(bits, nbits)
|
||||||
|
local res = 0
|
||||||
|
for _=1,nbits do
|
||||||
|
res = lshift(res, 1) + band(bits, 1)
|
||||||
|
bits = rshift(bits, 1)
|
||||||
|
end
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
local tfirstcode = memoize(
|
||||||
|
function(bits) return pow2[minbits] + msb(bits, minbits) end)
|
||||||
|
|
||||||
|
function t:read(bs)
|
||||||
|
local code = 1 -- leading 1 marker
|
||||||
|
local nbits = 0
|
||||||
|
while 1 do
|
||||||
|
if nbits == 0 then -- small optimization (optional)
|
||||||
|
code = tfirstcode[noeof(bs:read(minbits))]
|
||||||
|
nbits = nbits + minbits
|
||||||
|
else
|
||||||
|
local b = noeof(bs:read())
|
||||||
|
nbits = nbits + 1
|
||||||
|
code = code * 2 + b -- MSB first
|
||||||
|
end
|
||||||
|
local val = look[code]
|
||||||
|
if val then
|
||||||
|
return val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function parse_gzip_header(bs)
|
||||||
|
-- local FLG_FTEXT = 2^0
|
||||||
|
local FLG_FHCRC = 2^1
|
||||||
|
local FLG_FEXTRA = 2^2
|
||||||
|
local FLG_FNAME = 2^3
|
||||||
|
local FLG_FCOMMENT = 2^4
|
||||||
|
|
||||||
|
local id1 = bs:read(8)
|
||||||
|
local id2 = bs:read(8)
|
||||||
|
if id1 ~= 31 or id2 ~= 139 then
|
||||||
|
runtime_error 'not in gzip format'
|
||||||
|
end
|
||||||
|
bs:read(8) -- compression method
|
||||||
|
local flg = bs:read(8) -- FLaGs
|
||||||
|
local mtime = bs:read(32) -- Modification TIME
|
||||||
|
local xfl = bs:read(8) -- eXtra FLags
|
||||||
|
local os = bs:read(8) -- Operating System
|
||||||
|
|
||||||
|
if not os then runtime_error 'invalid header' end
|
||||||
|
|
||||||
|
if hasbit(flg, FLG_FEXTRA) then
|
||||||
|
local xlen = bs:read(16)
|
||||||
|
local extra = 0
|
||||||
|
for i=1,xlen do
|
||||||
|
extra = bs:read(8)
|
||||||
|
end
|
||||||
|
if not extra then runtime_error 'invalid header' end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parse_zstring(bs)
|
||||||
|
repeat
|
||||||
|
local by = bs:read(8)
|
||||||
|
if not by then runtime_error 'invalid header' end
|
||||||
|
until by == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if hasbit(flg, FLG_FNAME) then
|
||||||
|
parse_zstring(bs)
|
||||||
|
end
|
||||||
|
|
||||||
|
if hasbit(flg, FLG_FCOMMENT) then
|
||||||
|
parse_zstring(bs)
|
||||||
|
end
|
||||||
|
|
||||||
|
if hasbit(flg, FLG_FHCRC) then
|
||||||
|
local crc16 = bs:read(16)
|
||||||
|
if not crc16 then runtime_error 'invalid header' end
|
||||||
|
-- IMPROVE: check CRC. where is an example .gz file that
|
||||||
|
-- has this set?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parse_zlib_header(bs)
|
||||||
|
local cm = bs:read(4) -- Compression Method
|
||||||
|
local cinfo = bs:read(4) -- Compression info
|
||||||
|
local fcheck = bs:read(5) -- FLaGs: FCHECK (check bits for CMF and FLG)
|
||||||
|
local fdict = bs:read(1) -- FLaGs: FDICT (present dictionary)
|
||||||
|
local flevel = bs:read(2) -- FLaGs: FLEVEL (compression level)
|
||||||
|
local cmf = cinfo * 16 + cm -- CMF (Compresion Method and flags)
|
||||||
|
local flg = fcheck + fdict * 32 + flevel * 64 -- FLaGs
|
||||||
|
|
||||||
|
if cm ~= 8 then -- not "deflate"
|
||||||
|
runtime_error("unrecognized zlib compression method: " + cm)
|
||||||
|
end
|
||||||
|
if cinfo > 7 then
|
||||||
|
runtime_error("invalid zlib window size: cinfo=" + cinfo)
|
||||||
|
end
|
||||||
|
local window_size = 2^(cinfo + 8)
|
||||||
|
|
||||||
|
if (cmf*256 + flg) % 31 ~= 0 then
|
||||||
|
runtime_error("invalid zlib header (bad fcheck sum)")
|
||||||
|
end
|
||||||
|
|
||||||
|
if fdict == 1 then
|
||||||
|
runtime_error("FIX:TODO - FDICT not currently implemented")
|
||||||
|
local dictid_ = bs:read(32)
|
||||||
|
end
|
||||||
|
|
||||||
|
return window_size
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parse_huffmantables(bs)
|
||||||
|
local hlit = bs:read(5) -- # of literal/length codes - 257
|
||||||
|
local hdist = bs:read(5) -- # of distance codes - 1
|
||||||
|
local hclen = noeof(bs:read(4)) -- # of code length codes - 4
|
||||||
|
|
||||||
|
local ncodelen_codes = hclen + 4
|
||||||
|
local codelen_init = {}
|
||||||
|
local codelen_vals = {
|
||||||
|
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}
|
||||||
|
for i=1,ncodelen_codes do
|
||||||
|
local nbits = bs:read(3)
|
||||||
|
local val = codelen_vals[i]
|
||||||
|
codelen_init[val] = nbits
|
||||||
|
end
|
||||||
|
local codelentable = HuffmanTable(codelen_init, true)
|
||||||
|
|
||||||
|
local function decode(ncodes)
|
||||||
|
local init = {}
|
||||||
|
local nbits
|
||||||
|
local val = 0
|
||||||
|
while val < ncodes do
|
||||||
|
local codelen = codelentable:read(bs)
|
||||||
|
--FIX:check nil?
|
||||||
|
local nrepeat
|
||||||
|
if codelen <= 15 then
|
||||||
|
nrepeat = 1
|
||||||
|
nbits = codelen
|
||||||
|
elseif codelen == 16 then
|
||||||
|
nrepeat = 3 + noeof(bs:read(2))
|
||||||
|
-- nbits unchanged
|
||||||
|
elseif codelen == 17 then
|
||||||
|
nrepeat = 3 + noeof(bs:read(3))
|
||||||
|
nbits = 0
|
||||||
|
elseif codelen == 18 then
|
||||||
|
nrepeat = 11 + noeof(bs:read(7))
|
||||||
|
nbits = 0
|
||||||
|
else
|
||||||
|
error 'ASSERT'
|
||||||
|
end
|
||||||
|
for i=1,nrepeat do
|
||||||
|
init[val] = nbits
|
||||||
|
val = val + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local huffmantable = HuffmanTable(init, true)
|
||||||
|
return huffmantable
|
||||||
|
end
|
||||||
|
|
||||||
|
local nlit_codes = hlit + 257
|
||||||
|
local ndist_codes = hdist + 1
|
||||||
|
|
||||||
|
local littable = decode(nlit_codes)
|
||||||
|
local disttable = decode(ndist_codes)
|
||||||
|
|
||||||
|
return littable, disttable
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local tdecode_len_base
|
||||||
|
local tdecode_len_nextrabits
|
||||||
|
local tdecode_dist_base
|
||||||
|
local tdecode_dist_nextrabits
|
||||||
|
local function parse_compressed_item(bs, outstate, littable, disttable)
|
||||||
|
local val = littable:read(bs)
|
||||||
|
if val < 256 then -- literal
|
||||||
|
output(outstate, val)
|
||||||
|
elseif val == 256 then -- end of block
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
if not tdecode_len_base then
|
||||||
|
local t = {[257]=3}
|
||||||
|
local skip = 1
|
||||||
|
for i=258,285,4 do
|
||||||
|
for j=i,i+3 do t[j] = t[j-1] + skip end
|
||||||
|
if i ~= 258 then skip = skip * 2 end
|
||||||
|
end
|
||||||
|
t[285] = 258
|
||||||
|
tdecode_len_base = t
|
||||||
|
end
|
||||||
|
if not tdecode_len_nextrabits then
|
||||||
|
local t = {}
|
||||||
|
|
||||||
|
for i=257,285 do
|
||||||
|
local j = math_max(i - 261, 0)
|
||||||
|
t[i] = rshift(j, 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
t[285] = 0
|
||||||
|
tdecode_len_nextrabits = t
|
||||||
|
end
|
||||||
|
local len_base = tdecode_len_base[val]
|
||||||
|
local nextrabits = tdecode_len_nextrabits[val]
|
||||||
|
local extrabits = bs:read(nextrabits)
|
||||||
|
local len = len_base + extrabits
|
||||||
|
|
||||||
|
if not tdecode_dist_base then
|
||||||
|
local t = {[0]=1}
|
||||||
|
local skip = 1
|
||||||
|
for i=1,29,2 do
|
||||||
|
for j=i,i+1 do t[j] = t[j-1] + skip end
|
||||||
|
if i ~= 1 then skip = skip * 2 end
|
||||||
|
end
|
||||||
|
tdecode_dist_base = t
|
||||||
|
end
|
||||||
|
if not tdecode_dist_nextrabits then
|
||||||
|
local t = {}
|
||||||
|
|
||||||
|
for i=0,29 do
|
||||||
|
local j = math_max(i - 2, 0)
|
||||||
|
t[i] = rshift(j, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
tdecode_dist_nextrabits = t
|
||||||
|
end
|
||||||
|
local dist_val = disttable:read(bs)
|
||||||
|
local dist_base = tdecode_dist_base[dist_val]
|
||||||
|
local dist_nextrabits = tdecode_dist_nextrabits[dist_val]
|
||||||
|
local dist_extrabits = bs:read(dist_nextrabits)
|
||||||
|
local dist = dist_base + dist_extrabits
|
||||||
|
|
||||||
|
for i=1,len do
|
||||||
|
local pos = (outstate.window_pos - 1 - dist) % 32768 + 1 -- 32K
|
||||||
|
output(outstate, assert(outstate.window[pos], 'invalid distance'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function parse_block(bs, outstate, throttle)
|
||||||
|
local bfinal = bs:read(1)
|
||||||
|
local btype = bs:read(2)
|
||||||
|
|
||||||
|
local BTYPE_NO_COMPRESSION = 0
|
||||||
|
local BTYPE_FIXED_HUFFMAN = 1
|
||||||
|
local BTYPE_DYNAMIC_HUFFMAN = 2
|
||||||
|
local BTYPE_RESERVED_ = 3
|
||||||
|
|
||||||
|
if btype == BTYPE_NO_COMPRESSION then
|
||||||
|
bs:read(bs:nbits_left_in_byte())
|
||||||
|
local len = bs:read(16)
|
||||||
|
local nlen_ = noeof(bs:read(16))
|
||||||
|
|
||||||
|
for _=1,len do
|
||||||
|
local by = noeof(bs:read(8))
|
||||||
|
output(outstate, by)
|
||||||
|
end
|
||||||
|
elseif btype == BTYPE_FIXED_HUFFMAN or btype == BTYPE_DYNAMIC_HUFFMAN then
|
||||||
|
local littable, disttable
|
||||||
|
if btype == BTYPE_DYNAMIC_HUFFMAN then
|
||||||
|
littable, disttable = parse_huffmantables(bs)
|
||||||
|
else
|
||||||
|
littable = HuffmanTable {0,8, 144,9, 256,7, 280,8, 288,nil}
|
||||||
|
disttable = HuffmanTable {0,5, 32,nil}
|
||||||
|
end
|
||||||
|
|
||||||
|
repeat
|
||||||
|
local is_done = parse_compressed_item(
|
||||||
|
bs, outstate, littable, disttable)
|
||||||
|
throttle()
|
||||||
|
until is_done
|
||||||
|
else
|
||||||
|
runtime_error 'unrecognized compression type'
|
||||||
|
end
|
||||||
|
|
||||||
|
return bfinal ~= 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.inflate(t)
|
||||||
|
local bs = get_bitstream(t.input)
|
||||||
|
local outbs = get_obytestream(t.output)
|
||||||
|
local outstate = make_outstate(outbs)
|
||||||
|
|
||||||
|
repeat
|
||||||
|
local is_final = parse_block(bs, outstate, t.throttle)
|
||||||
|
until is_final
|
||||||
|
end
|
||||||
|
local inflate = M.inflate
|
||||||
|
|
||||||
|
function M.gunzip(t)
|
||||||
|
local bs = get_bitstream(t.input)
|
||||||
|
local outbs = get_obytestream(t.output)
|
||||||
|
|
||||||
|
parse_gzip_header(bs)
|
||||||
|
|
||||||
|
inflate{input=bs, output=outbs, throttle=t.throttle or function() end}
|
||||||
|
|
||||||
|
bs:read(bs:nbits_left_in_byte())
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
3525
compress/apis/libdeflate.lua
Normal file
3525
compress/apis/libdeflate.lua
Normal file
File diff suppressed because it is too large
Load Diff
29
compress/compress.lua
Normal file
29
compress/compress.lua
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
local LZW = require('opus.compress.lzw')
|
||||||
|
local Tar = require('opus.compress.tar')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local shell = _ENV.shell
|
||||||
|
|
||||||
|
local args = { ... }
|
||||||
|
|
||||||
|
if not args[2] then
|
||||||
|
error('Syntax: tar OUTFILE DIR')
|
||||||
|
end
|
||||||
|
|
||||||
|
local file = shell.resolve(args[1])
|
||||||
|
local dir = shell.resolve(args[2])
|
||||||
|
|
||||||
|
local filetype = 'tar'
|
||||||
|
if file:match('(.+)%.tar$') then
|
||||||
|
filetype = 'tar'
|
||||||
|
elseif file:match('(.+)%.lzw$') then
|
||||||
|
filetype = 'lzw'
|
||||||
|
end
|
||||||
|
|
||||||
|
if filetype == 'tar' then
|
||||||
|
Tar.tar(file, dir)
|
||||||
|
|
||||||
|
elseif filetype == 'lzw' then
|
||||||
|
local c = Tar.tar_string(dir)
|
||||||
|
Util.writeFile(file, LZW.compress(c), 'wb')
|
||||||
|
end
|
||||||
54
compress/uncompress.lua
Normal file
54
compress/uncompress.lua
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
local DEFLATE = require('compress.deflatelua')
|
||||||
|
local LZW = require('opus.compress.lzw')
|
||||||
|
local Tar = require('opus.compress.tar')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local io = _G.io
|
||||||
|
local shell = _ENV.shell
|
||||||
|
|
||||||
|
local args = { ... }
|
||||||
|
|
||||||
|
if not args[2] then
|
||||||
|
error('Syntax: tar FILE DESTDIR')
|
||||||
|
end
|
||||||
|
|
||||||
|
local inFile = shell.resolve(args[1])
|
||||||
|
local outDir = shell.resolve(args[2])
|
||||||
|
|
||||||
|
if inFile:match('(.+)%.[gG][zZ]$') then
|
||||||
|
-- uncompress a file created with: tar czf ...
|
||||||
|
local fh = io.open(inFile, 'rb') or error('Error opening ' .. inFile)
|
||||||
|
|
||||||
|
local t = { }
|
||||||
|
local function writer(b)
|
||||||
|
table.insert(t, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
DEFLATE.gunzip {input=fh, output=writer, disable_crc=true}
|
||||||
|
|
||||||
|
fh:close()
|
||||||
|
|
||||||
|
local s, m = Tar.untar_string(string.char(table.unpack(t)), outDir, true)
|
||||||
|
|
||||||
|
if not s then
|
||||||
|
error(m)
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif inFile:match('(.+)%.tar%.lzw$') then
|
||||||
|
local c = Util.readFile(inFile, 'rb')
|
||||||
|
if not c then
|
||||||
|
error('Unable to open ' .. inFile)
|
||||||
|
end
|
||||||
|
|
||||||
|
local s, m = Tar.untar_string(LZW.decompress(c), outDir, true)
|
||||||
|
|
||||||
|
if not s then
|
||||||
|
error(m)
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
local s, m = Tar.untar(inFile, outDir, true)
|
||||||
|
if not s then
|
||||||
|
error(m)
|
||||||
|
end
|
||||||
|
end
|
||||||
6
core/.package
Normal file
6
core/.package
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
title = 'APIs used by various programs',
|
||||||
|
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/core',
|
||||||
|
description = [[Provides APIs used by Opus applications.]],
|
||||||
|
license = 'MIT',
|
||||||
|
}
|
||||||
161
core/apis/chestAdapter.lua
Normal file
161
core/apis/chestAdapter.lua
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
local class = require('opus.class')
|
||||||
|
local itemDB = require('core.itemDB')
|
||||||
|
local Peripheral = require('opus.peripheral')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local os = _G.os
|
||||||
|
|
||||||
|
local ChestAdapter = class()
|
||||||
|
|
||||||
|
local convertNames = {
|
||||||
|
name = 'id',
|
||||||
|
damage = 'dmg',
|
||||||
|
maxCount = 'max_size',
|
||||||
|
count = 'qty',
|
||||||
|
displayName = 'display_name',
|
||||||
|
maxDamage = 'max_dmg',
|
||||||
|
nbtHash = 'nbt_hash',
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Strip off color prefix
|
||||||
|
local function safeString(text)
|
||||||
|
|
||||||
|
local val = text:byte(1)
|
||||||
|
|
||||||
|
if val < 32 or val > 128 then
|
||||||
|
|
||||||
|
local newText = {}
|
||||||
|
for i = 4, #text do
|
||||||
|
val = text:byte(i)
|
||||||
|
newText[i - 3] = (val > 31 and val < 127) and val or 63
|
||||||
|
end
|
||||||
|
return string.char(unpack(newText))
|
||||||
|
end
|
||||||
|
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
|
||||||
|
local function convertItem(item)
|
||||||
|
for k,v in pairs(convertNames) do
|
||||||
|
item[k] = item[v]
|
||||||
|
item[v] = nil
|
||||||
|
end
|
||||||
|
item.displayName = safeString(item.displayName)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:init(args)
|
||||||
|
local defaults = {
|
||||||
|
name = 'chest',
|
||||||
|
}
|
||||||
|
Util.merge(self, defaults)
|
||||||
|
Util.merge(self, args)
|
||||||
|
|
||||||
|
local chest
|
||||||
|
if not self.side then
|
||||||
|
chest = Peripheral.getByMethod('getAllStacks')
|
||||||
|
else
|
||||||
|
chest = Peripheral.getBySide(self.side)
|
||||||
|
if chest and not chest.getAllStacks then
|
||||||
|
chest = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if chest then
|
||||||
|
Util.merge(self, chest)
|
||||||
|
|
||||||
|
if chest.listAvailableItems then
|
||||||
|
self.list = chest.listAvailableItems
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:isValid()
|
||||||
|
return not not self.getAllStacks
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:refresh(throttle)
|
||||||
|
return self:listItems(throttle)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- provide a consolidated list of items
|
||||||
|
function ChestAdapter:listItems(throttle)
|
||||||
|
local cache = { }
|
||||||
|
local items = { }
|
||||||
|
throttle = throttle or Util.throttle()
|
||||||
|
|
||||||
|
-- getAllStacks sometimes fails
|
||||||
|
local s, m = pcall(function()
|
||||||
|
for _,v in pairs(self.getAllStacks(false)) do
|
||||||
|
if v.qty > 0 then
|
||||||
|
convertItem(v)
|
||||||
|
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')
|
||||||
|
|
||||||
|
local entry = cache[key]
|
||||||
|
if not entry then
|
||||||
|
entry = itemDB:get(v) or itemDB:add(v)
|
||||||
|
entry = Util.shallowCopy(entry)
|
||||||
|
entry.count = 0
|
||||||
|
cache[key] = entry
|
||||||
|
table.insert(items, entry)
|
||||||
|
end
|
||||||
|
entry.count = entry.count + v.count
|
||||||
|
throttle()
|
||||||
|
end
|
||||||
|
itemDB:flush()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
if s then
|
||||||
|
if not Util.empty(items) then
|
||||||
|
self.cache = cache
|
||||||
|
return items
|
||||||
|
end
|
||||||
|
else
|
||||||
|
_G._syslog(m)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:getItemInfo(item)
|
||||||
|
if not self.cache then
|
||||||
|
self:listItems()
|
||||||
|
end
|
||||||
|
local key = table.concat({ item.name, item.damage, item.nbtHash }, ':')
|
||||||
|
return self.cache[key]
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:provide(item, qty, slot, direction)
|
||||||
|
pcall(function()
|
||||||
|
for key,stack in Util.rpairs(self.getAllStacks(false)) do
|
||||||
|
if stack.id == item.name and
|
||||||
|
(not item.damage or stack.dmg == item.damage) and
|
||||||
|
(not item.nbtHash or stack.nbt_hash == item.nbtHash) then
|
||||||
|
local amount = math.min(qty, stack.qty)
|
||||||
|
if amount > 0 then
|
||||||
|
self.pushItemIntoSlot(direction or self.direction, key, amount, slot)
|
||||||
|
end
|
||||||
|
qty = qty - amount
|
||||||
|
if qty <= 0 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:extract(slot, qty, toSlot)
|
||||||
|
if toSlot then
|
||||||
|
self.pushItemIntoSlot(self.direction, slot, qty, toSlot)
|
||||||
|
else
|
||||||
|
self.pushItem(self.direction, slot, qty)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:insert(slot, qty, toSlot)
|
||||||
|
-- toSlot not tested ...
|
||||||
|
local s, m = pcall(self.pullItem, self.direction, slot, qty, toSlot)
|
||||||
|
if not s and m then
|
||||||
|
os.sleep(1)
|
||||||
|
pcall(self.pullItem, self.direction, slot, qty, toSlot)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return ChestAdapter
|
||||||
164
core/apis/chestAdapter18.lua
Normal file
164
core/apis/chestAdapter18.lua
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
local class = require('opus.class')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
local itemDB = require('core.itemDB')
|
||||||
|
local Peripheral = require('opus.peripheral')
|
||||||
|
|
||||||
|
local ChestAdapter = class()
|
||||||
|
|
||||||
|
function ChestAdapter:init(args)
|
||||||
|
local defaults = {
|
||||||
|
name = 'chest',
|
||||||
|
adapter = 'ChestAdapter18'
|
||||||
|
}
|
||||||
|
Util.merge(self, defaults)
|
||||||
|
Util.merge(self, args)
|
||||||
|
|
||||||
|
local chest
|
||||||
|
if not self.side then
|
||||||
|
chest = Peripheral.getByMethod('list') or
|
||||||
|
Peripheral.getByMethod('listAvailableItems')
|
||||||
|
else
|
||||||
|
chest = Peripheral.getBySide(self.side)
|
||||||
|
if chest and not chest.list and not chest.listAvailableItems then
|
||||||
|
chest = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if chest then
|
||||||
|
Util.merge(self, chest)
|
||||||
|
|
||||||
|
if chest.listAvailableItems then
|
||||||
|
self.list = chest.listAvailableItems
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:isValid()
|
||||||
|
return not not self.list
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle both AE/RS and generic inventory
|
||||||
|
function ChestAdapter:getItemDetails(index, item)
|
||||||
|
if self.getItemMeta then
|
||||||
|
local s, detail = pcall(self.getItemMeta, index)
|
||||||
|
if not s or not detail or detail.name ~= item.name then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
return detail
|
||||||
|
else
|
||||||
|
local detail = self.findItems(item)
|
||||||
|
if detail and #detail > 0 then
|
||||||
|
return detail[1].getMetadata()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:getCachedItemDetails(item, k)
|
||||||
|
local cached = itemDB:get(item)
|
||||||
|
if cached then
|
||||||
|
return cached
|
||||||
|
end
|
||||||
|
|
||||||
|
local detail = self:getItemDetails(k, item)
|
||||||
|
if detail then
|
||||||
|
return itemDB:add(detail)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:refresh(throttle)
|
||||||
|
return self:listItems(throttle)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- provide a consolidated list of items
|
||||||
|
function ChestAdapter:listItems(throttle)
|
||||||
|
for _ = 1, 5 do
|
||||||
|
local list = self:listItemsInternal(throttle)
|
||||||
|
if list then
|
||||||
|
return list
|
||||||
|
end
|
||||||
|
end
|
||||||
|
error('Error accessing inventory: ' .. self.direction)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:listItemsInternal(throttle)
|
||||||
|
local cache = { }
|
||||||
|
local items = { }
|
||||||
|
throttle = throttle or Util.throttle()
|
||||||
|
|
||||||
|
for k,v in pairs(self.list()) do
|
||||||
|
if v.count > 0 then
|
||||||
|
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')
|
||||||
|
|
||||||
|
local entry = cache[key]
|
||||||
|
if not entry then
|
||||||
|
entry = self:getCachedItemDetails(v, k)
|
||||||
|
if not entry then
|
||||||
|
return -- Inventory has changed
|
||||||
|
end
|
||||||
|
entry = Util.shallowCopy(entry)
|
||||||
|
entry.count = 0
|
||||||
|
cache[key] = entry
|
||||||
|
table.insert(items, entry)
|
||||||
|
end
|
||||||
|
|
||||||
|
if entry then
|
||||||
|
entry.count = entry.count + v.count
|
||||||
|
end
|
||||||
|
throttle()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
itemDB:flush()
|
||||||
|
|
||||||
|
self.cache = cache
|
||||||
|
return items
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:getItemInfo(item)
|
||||||
|
if not self.cache then
|
||||||
|
self:listItems()
|
||||||
|
end
|
||||||
|
local key = table.concat({ item.name, item.damage, item.nbtHash }, ':')
|
||||||
|
local items = self.cache or { }
|
||||||
|
return items[key]
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:getPercentUsed()
|
||||||
|
if self.cache and self.getDrawerCount then
|
||||||
|
return math.floor(Util.size(self.cache) / self.getDrawerCount() * 100)
|
||||||
|
end
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:provide(item, qty, slot, direction)
|
||||||
|
local total = 0
|
||||||
|
|
||||||
|
local _, m = pcall(function()
|
||||||
|
local stacks = self.list()
|
||||||
|
for key,stack in Util.rpairs(stacks) do
|
||||||
|
if stack.name == item.name and
|
||||||
|
stack.damage == item.damage and
|
||||||
|
stack.nbtHash == item.nbtHash then
|
||||||
|
local amount = math.min(qty, stack.count)
|
||||||
|
if amount > 0 then
|
||||||
|
amount = self.pushItems(direction or self.direction, key, amount, slot)
|
||||||
|
end
|
||||||
|
qty = qty - amount
|
||||||
|
total = total + amount
|
||||||
|
if qty <= 0 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
return total, m
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:extract(slot, qty, toSlot, direction)
|
||||||
|
return self.pushItems(direction or self.direction, slot, qty, toSlot)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ChestAdapter:insert(slot, qty, toSlot, direction)
|
||||||
|
return self.pullItems(direction or self.direction, slot, qty, toSlot)
|
||||||
|
end
|
||||||
|
|
||||||
|
return ChestAdapter
|
||||||
54
core/apis/inventoryAdapter.lua
Normal file
54
core/apis/inventoryAdapter.lua
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
local Adapter = { }
|
||||||
|
|
||||||
|
function Adapter.wrap(args)
|
||||||
|
local adapters = {
|
||||||
|
'core.refinedAdapter',
|
||||||
|
'core.meAdapter18',
|
||||||
|
'core.chestAdapter18',
|
||||||
|
|
||||||
|
-- adapters for version 1.7
|
||||||
|
'core.meAdapter',
|
||||||
|
'core.chestAdapter',
|
||||||
|
}
|
||||||
|
|
||||||
|
for _,adapterType in ipairs(adapters) do
|
||||||
|
local adapter = require(adapterType)(args)
|
||||||
|
|
||||||
|
if adapter:isValid() then
|
||||||
|
|
||||||
|
-- figure out which direction to push/pull items from an inventory
|
||||||
|
-- based on the side the inventory is attached and which way the
|
||||||
|
-- turtle/computer is facing
|
||||||
|
if args and args.facing and adapter.side and not adapter.direction then
|
||||||
|
local horz = { top = 'down', bottom = 'up' }
|
||||||
|
adapter.direction = horz[adapter.side]
|
||||||
|
|
||||||
|
if not adapter.direction then
|
||||||
|
local sides = {
|
||||||
|
front = 0,
|
||||||
|
right = 1,
|
||||||
|
back = 2,
|
||||||
|
left = 3,
|
||||||
|
}
|
||||||
|
-- pretty sure computer/turtle have sides reversed
|
||||||
|
local cards = {
|
||||||
|
east = 0,
|
||||||
|
south = 1,
|
||||||
|
west = 2,
|
||||||
|
north = 3,
|
||||||
|
}
|
||||||
|
local icards = {
|
||||||
|
[ 0 ] = 'west',
|
||||||
|
[ 1 ] = 'north',
|
||||||
|
[ 2 ] = 'east',
|
||||||
|
[ 3 ] = 'south',
|
||||||
|
}
|
||||||
|
adapter.direction = icards[(cards[args.facing] + sides[adapter.side]) % 4]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return adapter
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return Adapter
|
||||||
284
core/apis/itemDB.lua
Normal file
284
core/apis/itemDB.lua
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
local Map = require('opus.map')
|
||||||
|
local nameDB = require('core.nameDB')
|
||||||
|
local TableDB = require('core.tableDB')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local itemDB = TableDB({ fileName = 'usr/config/items.db' })
|
||||||
|
|
||||||
|
local function safeString(text)
|
||||||
|
local val = text:byte(1)
|
||||||
|
|
||||||
|
if val < 32 or val > 128 then
|
||||||
|
|
||||||
|
local newText = { }
|
||||||
|
local skip = 0
|
||||||
|
for i = 1, #text do
|
||||||
|
val = text:byte(i)
|
||||||
|
if val == 167 then
|
||||||
|
skip = 2
|
||||||
|
end
|
||||||
|
if skip > 0 then
|
||||||
|
skip = skip - 1
|
||||||
|
else
|
||||||
|
if val >= 32 and val <= 128 then
|
||||||
|
newText[#newText + 1] = val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return string.char(unpack(newText))
|
||||||
|
end
|
||||||
|
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
|
||||||
|
function itemDB:makeKey(item)
|
||||||
|
if not item then error('itemDB:makeKey: item is required', 2) end
|
||||||
|
return table.concat({ item.name, item.damage or '*', item.nbtHash }, ':')
|
||||||
|
end
|
||||||
|
|
||||||
|
function itemDB:splitKey(key, item)
|
||||||
|
item = item or { }
|
||||||
|
|
||||||
|
local t = Util.split(key, '(.-):')
|
||||||
|
if #t[#t] > 8 then
|
||||||
|
item.nbtHash = table.remove(t)
|
||||||
|
end
|
||||||
|
local damage = table.remove(t)
|
||||||
|
if damage ~= '*' then
|
||||||
|
item.damage = tonumber(damage)
|
||||||
|
end
|
||||||
|
item.name = table.concat(t, ':')
|
||||||
|
|
||||||
|
return item
|
||||||
|
end
|
||||||
|
|
||||||
|
function itemDB:get(key, populateFn)
|
||||||
|
if not key then error('itemDB:get: key is required', 2) end
|
||||||
|
if type(key) == 'string' then
|
||||||
|
key = self:splitKey(key)
|
||||||
|
else
|
||||||
|
key = Util.shallowCopy(key)
|
||||||
|
end
|
||||||
|
|
||||||
|
local item = self:_get(key)
|
||||||
|
if not item and populateFn then
|
||||||
|
item = populateFn()
|
||||||
|
if item then
|
||||||
|
item = self:add(item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return item and Util.merge(key, item)
|
||||||
|
end
|
||||||
|
|
||||||
|
function itemDB:_get(key)
|
||||||
|
if not key then error('itemDB:get: key is required', 2) end
|
||||||
|
if type(key) == 'string' then
|
||||||
|
key = self:splitKey(key)
|
||||||
|
end
|
||||||
|
|
||||||
|
local item = TableDB.get(self, self:makeKey(key))
|
||||||
|
if item then
|
||||||
|
return item
|
||||||
|
end
|
||||||
|
|
||||||
|
-- try finding an item that has damage values
|
||||||
|
if type(key.damage) == 'number' then
|
||||||
|
item = TableDB.get(self, self:makeKey({ name = key.name, nbtHash = key.nbtHash }))
|
||||||
|
if item and item.maxDamage then
|
||||||
|
item = Util.shallowCopy(item)
|
||||||
|
item.damage = key.damage
|
||||||
|
if item.maxDamage > 0 and type(item.damage) == 'number' and item.damage > 0 then
|
||||||
|
item.displayName = string.format('%s (damage: %s)', item.displayName, item.damage)
|
||||||
|
end
|
||||||
|
return item
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for k,item in pairs(self.data) do
|
||||||
|
if key.name == item.name and
|
||||||
|
key.nbtHash == key.nbtHash and
|
||||||
|
item.maxDamage > 0 then
|
||||||
|
item = Util.shallowCopy(item)
|
||||||
|
item.nbtHash = key.nbtHash
|
||||||
|
return item
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if key.nbtHash then
|
||||||
|
item = self:get({ name = key.name, damage = key.damage })
|
||||||
|
|
||||||
|
if item and item.ignoreNBT then
|
||||||
|
item = Util.shallowCopy(item)
|
||||||
|
item.nbtHash = key.nbtHash
|
||||||
|
item.damage = key.damage
|
||||||
|
return item
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function formatTime(t)
|
||||||
|
local m = math.floor(t/60)
|
||||||
|
local s = t % 60
|
||||||
|
if s < 10 then
|
||||||
|
s = '0' .. s
|
||||||
|
end
|
||||||
|
|
||||||
|
return m .. ':' .. s
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
If the base item contains an NBT hash, then the NBT hash uniquely
|
||||||
|
identifies this item.
|
||||||
|
]]--
|
||||||
|
function itemDB:add(baseItem)
|
||||||
|
local nItem = {
|
||||||
|
name = baseItem.name,
|
||||||
|
damage = baseItem.damage,
|
||||||
|
nbtHash = baseItem.nbtHash,
|
||||||
|
}
|
||||||
|
-- if detail.maxDamage > 0 then
|
||||||
|
-- nItem.damage = '*'
|
||||||
|
-- end
|
||||||
|
|
||||||
|
nItem.displayName = safeString(baseItem.displayName)
|
||||||
|
nItem.maxCount = baseItem.maxCount
|
||||||
|
nItem.maxDamage = baseItem.maxDamage
|
||||||
|
|
||||||
|
-- enchanted items
|
||||||
|
if baseItem.enchantments then
|
||||||
|
if nItem.name == 'minecraft:enchanted_book' then
|
||||||
|
nItem.displayName = 'Book: '
|
||||||
|
else
|
||||||
|
nItem.displayName = nItem.displayName .. ': '
|
||||||
|
end
|
||||||
|
for k, v in ipairs(baseItem.enchantments) do
|
||||||
|
if k > 1 then
|
||||||
|
nItem.displayName = nItem.displayName .. ', '
|
||||||
|
end
|
||||||
|
nItem.displayName = nItem.displayName .. v.fullName
|
||||||
|
end
|
||||||
|
|
||||||
|
-- turtles / computers / etc
|
||||||
|
elseif baseItem.computer then
|
||||||
|
-- a turtle's NBT is updated constantly
|
||||||
|
-- update the cache with the new NBT
|
||||||
|
if baseItem.computer.id then
|
||||||
|
Map.removeMatches(self.data, { name = nItem.name, displayName = nItem.displayName })
|
||||||
|
end
|
||||||
|
nItem.displayName = baseItem.computer.label or baseItem.displayName
|
||||||
|
|
||||||
|
-- disks
|
||||||
|
elseif baseItem.media then
|
||||||
|
-- don't ignore nbt... as disks can be labeled
|
||||||
|
if baseItem.media.recordTitle then
|
||||||
|
nItem.displayName = nItem.displayName .. ': ' .. baseItem.media.recordTitle
|
||||||
|
end
|
||||||
|
|
||||||
|
-- potions
|
||||||
|
elseif nItem.name == 'minecraft:potion' or nItem.name == 'minecraft:lingering_potion' then
|
||||||
|
if baseItem.effects then
|
||||||
|
local effect = baseItem.effects[1]
|
||||||
|
if effect.amplifier == 1 then
|
||||||
|
nItem.displayName = nItem.displayName .. ' II'
|
||||||
|
end
|
||||||
|
if effect.duration and effect.duration > 0 then
|
||||||
|
nItem.displayName = string.format('%s (%s)', nItem.displayName, formatTime(effect.duration))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
for k,item in pairs(self.data) do
|
||||||
|
if nItem.name == item.name and
|
||||||
|
nItem.displayName == item.displayName then
|
||||||
|
|
||||||
|
if nItem.nbtHash ~= item.nbtHash and nItem.damage ~= item.damage then
|
||||||
|
nItem.damage = '*'
|
||||||
|
nItem.nbtHash = nil
|
||||||
|
nItem.ignoreNBT = true
|
||||||
|
self.data[k] = nil
|
||||||
|
break
|
||||||
|
elseif nItem.damage ~= item.damage then
|
||||||
|
nItem.damage = '*'
|
||||||
|
self.data[k] = nil
|
||||||
|
break
|
||||||
|
elseif nItem.nbtHash ~= item.nbtHash then
|
||||||
|
nItem.nbtHash = nil
|
||||||
|
nItem.ignoreNBT = true
|
||||||
|
self.data[k] = nil
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
TableDB.add(self, self:makeKey(nItem), nItem)
|
||||||
|
nItem = Util.shallowCopy(nItem)
|
||||||
|
nItem.damage = baseItem.damage
|
||||||
|
nItem.nbtHash = baseItem.nbtHash
|
||||||
|
|
||||||
|
return nItem
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Accepts: "minecraft:stick:0" or { name = 'minecraft:stick', damage = 0 }
|
||||||
|
function itemDB:getName(item)
|
||||||
|
if type(item) == 'string' then
|
||||||
|
item = self:splitKey(item)
|
||||||
|
end
|
||||||
|
|
||||||
|
local detail = self:get(item)
|
||||||
|
if detail then
|
||||||
|
return detail.displayName
|
||||||
|
end
|
||||||
|
|
||||||
|
-- fallback to nameDB
|
||||||
|
local strId = self:makeKey(item)
|
||||||
|
local name = nameDB.data[strId]
|
||||||
|
if not name and not item.damage then
|
||||||
|
name = nameDB.data[self:makeKey({ name = item.name, damage = 0, nbtHash = item.nbtHash })]
|
||||||
|
end
|
||||||
|
return name or strId
|
||||||
|
end
|
||||||
|
|
||||||
|
function itemDB:getMaxCount(item)
|
||||||
|
local detail = self:get(item)
|
||||||
|
return detail and detail.maxCount or 64
|
||||||
|
end
|
||||||
|
|
||||||
|
function itemDB:load()
|
||||||
|
TableDB.load(self)
|
||||||
|
|
||||||
|
for key,item in pairs(self.data) do
|
||||||
|
self:splitKey(key, item)
|
||||||
|
item.maxDamage = item.maxDamage or 0
|
||||||
|
item.maxCount = item.maxCount or 64
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function itemDB:flush()
|
||||||
|
if self.dirty then
|
||||||
|
|
||||||
|
local t = { }
|
||||||
|
for k,v in pairs(self.data) do
|
||||||
|
v = Util.shallowCopy(v)
|
||||||
|
v.name = nil
|
||||||
|
v.damage = nil
|
||||||
|
v.nbtHash = nil
|
||||||
|
v.count = nil -- wipe out previously saved counts - temporary
|
||||||
|
if v.maxDamage == 0 then
|
||||||
|
v.maxDamage = nil
|
||||||
|
end
|
||||||
|
if v.maxCount == 64 then
|
||||||
|
v.maxCount = nil
|
||||||
|
end
|
||||||
|
t[k] = v
|
||||||
|
end
|
||||||
|
|
||||||
|
Util.writeTable(self.fileName, t)
|
||||||
|
self.dirty = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
itemDB:load()
|
||||||
|
|
||||||
|
return itemDB
|
||||||
254
core/apis/meAdapter.lua
Normal file
254
core/apis/meAdapter.lua
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
local class = require('opus.class')
|
||||||
|
local itemDB = require('core.itemDB')
|
||||||
|
local Peripheral = require('opus.peripheral')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local os = _G.os
|
||||||
|
|
||||||
|
local convertNames = {
|
||||||
|
name = 'id',
|
||||||
|
damage = 'dmg',
|
||||||
|
maxCount = 'max_size',
|
||||||
|
count = 'qty',
|
||||||
|
displayName = 'display_name',
|
||||||
|
maxDamage = 'max_dmg',
|
||||||
|
nbtHash = 'nbt_hash',
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Strip off color prefix
|
||||||
|
local function safeString(text)
|
||||||
|
|
||||||
|
local val = text:byte(1)
|
||||||
|
|
||||||
|
if val < 32 or val > 128 then
|
||||||
|
|
||||||
|
local newText = {}
|
||||||
|
for i = 4, #text do
|
||||||
|
val = text:byte(i)
|
||||||
|
newText[i - 3] = (val > 31 and val < 127) and val or 63
|
||||||
|
end
|
||||||
|
return string.char(unpack(newText))
|
||||||
|
end
|
||||||
|
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
|
||||||
|
local function convertItem(item)
|
||||||
|
for k,v in pairs(convertNames) do
|
||||||
|
item[k] = item[v]
|
||||||
|
item[v] = nil
|
||||||
|
end
|
||||||
|
item.displayName = safeString(item.displayName)
|
||||||
|
end
|
||||||
|
|
||||||
|
local MEAdapter = class()
|
||||||
|
|
||||||
|
function MEAdapter:init(args)
|
||||||
|
local defaults = {
|
||||||
|
items = { },
|
||||||
|
name = 'ME',
|
||||||
|
jobList = { },
|
||||||
|
}
|
||||||
|
Util.merge(self, defaults)
|
||||||
|
Util.merge(self, args)
|
||||||
|
|
||||||
|
local chest
|
||||||
|
|
||||||
|
if not self.side then
|
||||||
|
chest = Peripheral.getByMethod('getAvailableItems')
|
||||||
|
else
|
||||||
|
chest = Peripheral.getBySide(self.side)
|
||||||
|
if chest and not chest.getAvailableItems then
|
||||||
|
chest = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if chest then
|
||||||
|
Util.merge(self, chest)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEAdapter:isValid()
|
||||||
|
return self.getAvailableItems and self.getAvailableItems()
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEAdapter:refresh()
|
||||||
|
self.items = nil
|
||||||
|
local hasItems, failed
|
||||||
|
|
||||||
|
local s, m = pcall(function()
|
||||||
|
self.items = self.getAvailableItems('all')
|
||||||
|
for _,v in pairs(self.items) do
|
||||||
|
Util.merge(v, v.item)
|
||||||
|
convertItem(v)
|
||||||
|
|
||||||
|
-- if power has been interrupted, the list will still be returned
|
||||||
|
-- but all items will have a 0 quantity
|
||||||
|
-- ensure that the list is valid
|
||||||
|
if not hasItems then
|
||||||
|
hasItems = v.count > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if not v.fingerprint then
|
||||||
|
failed = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
if not itemDB:get(v) then
|
||||||
|
itemDB:add(v, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
itemDB:flush()
|
||||||
|
|
||||||
|
if not s and m then
|
||||||
|
_G._syslog(m)
|
||||||
|
end
|
||||||
|
|
||||||
|
if s and not failed and hasItems and self.items and not Util.empty(self.items) then
|
||||||
|
return self.items
|
||||||
|
end
|
||||||
|
self.items = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEAdapter:listItems()
|
||||||
|
self:refresh()
|
||||||
|
return self.items
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEAdapter:getItemInfo(item)
|
||||||
|
for _,i in pairs(self.items) do
|
||||||
|
if item.name == i.name and
|
||||||
|
item.damage == i.damage and
|
||||||
|
item.nbtHash == i.nbtHash then
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEAdapter:isCPUAvailable()
|
||||||
|
local cpus = self.getCraftingCPUs() or { }
|
||||||
|
local available = false
|
||||||
|
|
||||||
|
for cpu,v in pairs(cpus) do
|
||||||
|
if not v.busy then
|
||||||
|
available = true
|
||||||
|
elseif not self.jobList[cpu] then -- something else is crafting something (don't know what)
|
||||||
|
return false -- return false since we are in an unknown state
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return available
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEAdapter:craft(item, count)
|
||||||
|
if not self:isCPUAvailable() then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
self:refresh()
|
||||||
|
|
||||||
|
item = self:getItemInfo(item)
|
||||||
|
if item and item.is_craftable then
|
||||||
|
|
||||||
|
local cpus = self.getCraftingCPUs() or { }
|
||||||
|
for cpu,v in pairs(cpus) do
|
||||||
|
if not v.busy then
|
||||||
|
self.requestCrafting({
|
||||||
|
id = item.name,
|
||||||
|
dmg = item.damage,
|
||||||
|
nbt_hash = item.nbtHash,
|
||||||
|
},
|
||||||
|
count or 1,
|
||||||
|
v.name -- CPUs must be named ! use anvil
|
||||||
|
)
|
||||||
|
|
||||||
|
os.sleep(0) -- needed ?
|
||||||
|
cpus = self.getCraftingCPUs() or { }
|
||||||
|
|
||||||
|
if cpus[cpu].busy then
|
||||||
|
self.jobList[cpu] = {
|
||||||
|
name = item.name,
|
||||||
|
damage = item.damage,
|
||||||
|
nbtHash = item.nbtHash,
|
||||||
|
count = count,
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
break -- only need to try the first available cpu
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEAdapter:getJobList()
|
||||||
|
local cpus = self.getCraftingCPUs() or { }
|
||||||
|
for cpu,v in pairs(cpus) do
|
||||||
|
if not v.busy then
|
||||||
|
self.jobList[cpu] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return self.jobList
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEAdapter:isCrafting(item)
|
||||||
|
for _,v in pairs(self:getJobList()) do
|
||||||
|
if v.name == item.name and
|
||||||
|
v.damage == item.damage and
|
||||||
|
v.nbtHash == item.nbtHash then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEAdapter:craftItems(items)
|
||||||
|
local cpus = self.getCraftingCPUs() or { }
|
||||||
|
local count = 0
|
||||||
|
|
||||||
|
for _,cpu in pairs(cpus) do
|
||||||
|
if cpu.busy then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,item in pairs(items) do
|
||||||
|
if count >= #cpus then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
if not self:isCrafting(item) then
|
||||||
|
if self:craft(item, item.count) then
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEAdapter:provide(item, qty, slot, direction)
|
||||||
|
return pcall(function()
|
||||||
|
for _,stack in pairs(self.getAvailableItems('all')) do
|
||||||
|
if stack.item.id == item.name and
|
||||||
|
(not item.damage or stack.item.dmg == item.damage) and
|
||||||
|
(not item.nbtHash or stack.item.nbt_hash == item.nbtHash) then
|
||||||
|
local amount = math.min(qty, stack.item.qty)
|
||||||
|
if amount > 0 then
|
||||||
|
self.exportItem(stack.item, direction or self.direction, amount, slot)
|
||||||
|
end
|
||||||
|
qty = qty - amount
|
||||||
|
if qty <= 0 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEAdapter:insert(slot, qty, toSlot)
|
||||||
|
local s, m = pcall(self.pullItem, self.direction, slot, qty, toSlot)
|
||||||
|
if not s and m then
|
||||||
|
os.sleep(1)
|
||||||
|
pcall(self.pullItem, self.direction, slot, qty, toSlot)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return MEAdapter
|
||||||
90
core/apis/meAdapter18.lua
Normal file
90
core/apis/meAdapter18.lua
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
local class = require('opus.class')
|
||||||
|
local RSAdapter = require('core.refinedAdapter')
|
||||||
|
local Peripheral = require('opus.peripheral')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local MEAdapter = class(RSAdapter)
|
||||||
|
|
||||||
|
local DEVICE_TYPE = 'appliedenergistics2:interface'
|
||||||
|
|
||||||
|
function MEAdapter:init(args)
|
||||||
|
local defaults = {
|
||||||
|
name = 'appliedEnergistics',
|
||||||
|
jobList = { },
|
||||||
|
}
|
||||||
|
Util.merge(self, defaults)
|
||||||
|
Util.merge(self, args)
|
||||||
|
|
||||||
|
local controller
|
||||||
|
if not self.side then
|
||||||
|
controller = Peripheral.getByType(DEVICE_TYPE)
|
||||||
|
else
|
||||||
|
controller = Peripheral.getBySide(self.side)
|
||||||
|
end
|
||||||
|
|
||||||
|
if controller then
|
||||||
|
Util.merge(self, controller)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEAdapter:isValid()
|
||||||
|
return self.type == DEVICE_TYPE and not not self.findItems
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEAdapter:clearFinished()
|
||||||
|
for _,key in pairs(Util.keys(self.jobList)) do
|
||||||
|
local job = self.jobList[key]
|
||||||
|
if job.info.status() == 'finished' then
|
||||||
|
self.jobList[key] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEAdapter:isCPUAvailable()
|
||||||
|
local cpus = self.getCraftingCPUs() or { }
|
||||||
|
local busy = 0
|
||||||
|
|
||||||
|
for _,cpu in pairs(cpus) do
|
||||||
|
if cpu.busy then
|
||||||
|
busy = busy + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:clearFinished()
|
||||||
|
return busy == Util.size(self.jobList) and busy < #cpus
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEAdapter:craft(item, count)
|
||||||
|
if not self:isCPUAvailable() then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local detail = self.findItem(item)
|
||||||
|
if detail and detail.craft then
|
||||||
|
local info = detail.craft(count or 1)
|
||||||
|
if info.status() == 'unknown' then
|
||||||
|
self.jobList[info.getId()] = {
|
||||||
|
name = item.name,
|
||||||
|
damage = item.damage,
|
||||||
|
nbtHash = item.nbtHash,
|
||||||
|
info = info,
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function MEAdapter:isCrafting(item)
|
||||||
|
self:clearFinished()
|
||||||
|
|
||||||
|
for _,job in pairs(self.jobList) do
|
||||||
|
if job.name == item.name and
|
||||||
|
job.damage == item.damage and
|
||||||
|
job.nbtHash == item.nbtHash then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return MEAdapter
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user