708 Commits

Author SHA1 Message Date
MayaTheShy
51b1009099 feat: add inventory-manager (stable) and inventory-manager-unstable package listings 2026-03-29 15:50:43 -04:00
MayaTheShy
6d66344feb feat: register cc-platform-core in packages.list
Add platform package entry pointing to Gitea repo for Opus
package manager installation.
2026-03-26 16:25:51 -04:00
xAnavrins
2461d060e0 Updates to swshop part 1
Updated URL to new krist node
Form now validates if address own provided domain
Support for third-party sync nodes
Update k.lua and w.lua

Closes #60
2022-07-18 00:07:55 -04:00
xAnavrins
aa3cd6d08d Better error visibility in multiMiner 2022-07-17 19:53:06 -04:00
Kan18
cba2f7013f fix jobMonitor.lua (#61) 2022-07-17 19:44:54 -04:00
EmmaKnijn
b3052fe57b Adress a couple of issues (#58) 2022-07-17 17:43:14 -04:00
Kan18
af570c7769 Fix storageGen.lua (#59) 2022-07-01 03:02:56 -04:00
Anavrins
1d2187b957 Closes #56 2022-06-19 20:00:56 -04:00
RubenHetKonijn
c85bb53062 Make the block scanner requirement more clear (#49) 2021-01-02 19:41:42 -07:00
Anavrins
0272d2a303 fix: missing damage value in tl3 2020-12-26 19:22:46 -05:00
Drew Lemmy
5e275e36ad fix: parallel fix 2 (#47) 2020-09-29 17:18:57 -04:00
Drew Lemmy
13601c97a9 fix: too many milo parallel tasks hurting server (#46) 2020-09-29 13:51:35 -04:00
Anavrins
c7a347f723 Cleanup 2020-09-20 23:08:18 -04:00
Anavrins
88cacb4823 More customization on canvasServer 2020-08-24 23:11:25 -04:00
Anavrins
64a218810a Fix GPS server
Was sending 4 replies per nodes and caused resolving to fail
2020-08-22 20:18:07 -04:00
Anavrins
7f880b1563 Typos and broken links fix 2020-08-20 03:23:59 -04:00
Anavrins
86db60ba97 Fix Ores crashing near water
Fixes https://github.com/kepler155c/opus/issues/45
2020-08-15 02:28:54 -04:00
Anavrins
db007af3ea Add config path args to MiloRemote
Allowing to connect to multiple Milo system from one NI
Example:
> MiloRemote base
> MiloRemote shop
2020-08-14 01:51:45 -04:00
Anavrins
153c1ac4a2 UI-ification of the ores/xray app 2020-08-13 21:18:49 -04:00
Anavrins
287b362765 Fix Shoplogs menu not working 2020-08-07 20:50:53 -04:00
Anavrins
dcdd126bdf Fix handling of invalid items 2020-08-07 20:21:25 -04:00
Drew Lemmy
31a5d6c2d0 Update swshop package description (#45)
Because the package says 'by Lemmmy', people get confused and think that I made the swshop package, so I keep getting questions about how to operate it etc. This should clear up the confusion.
2020-08-07 16:51:16 -06:00
kepler155c@gmail.com
d6f61acace run lua commands from shell and other tweaks 2020-07-25 18:52:13 -06:00
devomaa
cb1126e216 Duplicate "stdin" (#40) 2020-06-30 22:40:31 -06:00
kepler155c@gmail.com
cd0e6af55f remove alternates - add ccemux peripherals upon system startup 2020-06-15 19:55:56 -06:00
kepler155c@gmail.com
27c7d2dd18 moonscript support fixes 2020-06-13 12:19:13 -06:00
kepler155c@gmail.com
1a166bdb22 compability fixes 2020-06-11 13:29:02 -06:00
kepler155c@gmail.com
e9f9999f41 better file select for debugger - native support for moonscript 2020-06-10 19:47:42 -06:00
kepler155c@gmail.com
fae3b6a654 package list updated 2020-06-09 18:18:52 -06:00
kepler155c@gmail.com
0f7534d12c moonscript, busted, penlight packages + debugger speed improvements 2020-06-09 18:17:21 -06:00
kepler155c@gmail.com
de3d73de70 update package list 2020-06-05 21:44:41 -06:00
kepler155c@gmail.com
4f74ab2840 added moonscript package 2020-06-05 21:38:45 -06:00
kepler155c@gmail.com
b289594c7f compression package - debugger fixes 2020-06-01 16:54:28 -06:00
kepler155c@gmail.com
fb5e69f703 compression package wip + debugger bugfixes 2020-05-31 23:51:04 -06:00
kepler155c@gmail.com
d203510527 debugger refactor 2020-05-30 20:06:30 -06:00
kepler155c@gmail.com
6394a48766 support multiple simultaneous breakpoints in coroutines 2020-05-28 22:04:20 -06:00
kepler155c@gmail.com
8fef5d3580 debugger cleanup + ls now supports date 2020-05-27 14:49:51 -06:00
kepler155c@gmail.com
70001196cb debugger - error handling, ui rework 2020-05-25 21:49:17 -06:00
kepler155c@gmail.com
26b693d609 debugger part 2 2020-05-24 15:18:18 -06:00
cynagen
f07302588c Add furniController.lua (#37)
* Update exportTask.lua

Cheap fix to stop cascading storage updates from eating up all the working time available after failing exports, this needs to be more fleshed out better, if the chest is full, scan the slots for any that match and are not full, stop blindly exporting

* Create furniController.lua

(Super)MultiFurnace Controller (did I mention, it's SUPER!?)
This app is designed to wrap up to 4 of kepler155c's MultiFurnace (multiFurni) arrays and divide up workload, it functions as a straight drop-in for furni.lua and no other adjustments are required to make it work. Additionally, you get the ability to target any one multiFurni array for smelting when creating the machine recipe in Milo. Further uses for this direct targeting can be limited-scope auto-smelting, using anywhere from 1/2 to 1/4 of your total capacity to auto-smelt, leaving the rest of the cluster available for you when you want it, great for multi-user bases!

Setup: Plug this turtle into a modem on your Milo network, and another modem leading to 2-4 turtles running furni.lua (or even furniController.lua for chained setups), configure useSlot4 on line 20, map in Milo and go!

* Update furniController.lua

Cleaned up bad reference naming to avoid confusion. Additionally moved relevant variables to local scope, cleaned up a few other minor things.

* Update exportTask.lua

Fleshed out the exportItems function which was blindly firing items into full chests. Made it a little more sensitive, slight increase in average processing time, but no more erroneous hits to export causing perf issues.

* Update exportTask.lua

Clean up cached .list() after done, accidentally left it in, bloating storage config sizes

* Update exportTask.lua

Fixing some logic, make less plethora calls

* Update exportTask.lua

* Update exportTask.lua

Last fix, was making a call to Util when it wasn't defined, and left the export function completely dead. Defined correctly, now exporting as expected, whoops.

* Update exportTask.lua

So uhh, yeah, the looped getItemMeta calls cost me a LOT of time. Figured out most of the relevant data I wanted was already being provided elsewhere, no extra calls to make, it's as fast as I can make it now. Now it's dependent on the storage optimizations as they stand, this does not speed anything up, just makes it more accurate.

* Sync with kepler155c's branch

* Another resync
2020-05-23 22:09:22 -06:00
kepler155c@gmail.com
cb58a553f5 Lua debugger part 1 2020-05-23 21:44:55 -06:00
kepler155c@gmail.com
2c27787f27 better fuzzy searching 2020-05-19 17:06:30 -06:00
kepler155c@gmail.com
3d5f665b59 oops 2020-05-18 22:01:21 -06:00
kepler155c@gmail.com
503a340035 more rework on exportTask 2020-05-18 21:57:04 -06:00
kepler155c@gmail.com
72a85f1a4a revert perf change 2020-05-18 21:20:35 -06:00
kepler155c@gmail.com
46c1e3f7e5 storage export perf fix 2020-05-18 11:38:32 -06:00
kepler155c@gmail.com
249414e027 change editors default file to untitled.lua - add package for LuaFileSystem port 2020-05-17 19:38:29 -06:00
kepler155c@gmail.com
0619eee41c milo storage perf fix - nwm update 2020-05-16 10:40:49 -06:00
kepler155c@gmail.com
bc0cf883b4 Improved error messages + nwm opacity/resizing 2020-05-12 21:26:02 -06:00
kepler155c@gmail.com
ad32dcc2df updates for environment handling changes 2020-05-11 17:26:43 -06:00
kepler155c@gmail.com
759e4e2b95 cleanup 2020-05-10 14:04:42 -06:00
Luca S
d902acacf4 Add a Defragment button to Milo (#36) 2020-05-10 01:48:39 -04:00
kepler155c@gmail.com
ad4cc5884f editor/nwm bug fixes 2020-05-08 22:31:06 -06:00
kepler155c@gmail.com
1fc2d08c18 some documentation and some bad icons 2020-05-07 17:15:30 -06:00
kepler155c@gmail.com
94b743bfc0 remove border around windows in mwm 2020-05-06 18:49:28 -06:00
kepler155c@gmail.com
9eff14f3ba titlebar/close/moving of nwm windows 2020-05-06 18:02:01 -06:00
Anavrins
428477bdec Whoops 2020-05-06 19:02:44 -04:00
Anavrins
bc3a48f30f Swshop update
- Toggle lamp based on keepalive packets from the node
- Add functionality to split profits of a bought item
(Will document later)
2020-05-06 03:03:49 -04:00
Anavrins
59de8e7f63 Update k.lua and others 2020-05-06 02:54:15 -04:00
kepler155c@gmail.com
8db9a89f68 nwm opacity / improvements - env cleanup 2020-05-05 17:26:44 -06:00
Anavrins
87a3f9fa96 Cleanup 2020-05-04 21:21:43 -04:00
kepler155c@gmail.com
8f13a0932e neural window manager 2020-05-04 16:45:31 -06:00
Anavrins
caa525e31d Swshop: Add option to show out of stock items
- Make ShopView use colors from ui.theme
2020-05-04 04:09:13 -04:00
Anavrins
39fb43d6a3 Update storageGen to a more user-friendly UI
- Able to merge into current Milo configs
- Moved it to the Milo package
2020-05-04 02:53:17 -04:00
kepler155c@gmail.com
38b5c4a5ed support mouse clicks when using plethora keyboard on overlay 2020-05-02 23:45:58 -06:00
kepler155c@gmail.com
9bf017fe27 rename help files with .txt - overlay.lua creates a terminal compatible window on the overlay glasses - you can now run any program on the glasses overlay 2020-05-02 22:22:30 -06:00
kepler155c@gmail.com
8a9878b8e5 minor bug fixes 2020-04-27 22:31:57 -06:00
kepler155c@gmail.com
ef0886ec85 wizard and tab rework 2020-04-26 19:39:28 -06:00
kepler155c@gmail.com
8d014c0098 remove a few screen savers - some bugfixes 2020-04-22 23:36:03 -06:00
kepler155c
4576969739 Ui enhancements 2.0 (#29)
* canvas overhaul

* editor 2.0

* more tweaks

* more editor work

* completions + refactor

* cleanup + editor additions

* cleanup + undo overhaul

* editor recent/peripherals/redo + cleanup

* editor path issues

* cleanup

* changes for deprecated ui methods - recolor milo - make turtle scripts run again - mob rancher improvements

* can now use named colors
2020-04-21 22:40:47 -06:00
Anavrins
47e0a90116 gpsServer: Reduce amount of peripheral calls
Redraw screen every seconds instead of every packets received, yeah that was a bad idea.
2020-04-21 20:56:21 -04:00
Anavrins
d287e3fe91 Merge pull request #27 from Lemmmy/patch-1
Temporary patch for #20
2020-03-27 15:11:20 -04:00
Drew Lemmy
b08a0f225e Temporary patch for #20
A more comprehensive fix should be found in the future
2020-03-27 17:09:04 +00:00
Anavrins
408601f0d2 Swshop: whoops 2020-03-24 00:17:35 -04:00
Anavrins
996b8c1cd3 Swshop: Configurable lamp side 2020-03-24 00:09:51 -04:00
Anavrins
2dc74d80ea Milo: Bump max request amount to 9999 2020-03-23 23:41:16 -04:00
Anavrins
afcbfd1b04 Added a "Level Emitter" to Milo 2020-02-24 21:10:30 -05:00
Anavrins
301f531a4a Fix getFuelLimit and getFuelLevel 2020-02-14 01:13:43 -05:00
Anavrins
d971448c9a Fix swshop
Fixes a number transform issue which caused swshop to crash without giving the bought item.
2020-02-08 00:01:36 -05:00
Anavrins
48f142accc swshop helpfile and updates 2020-01-29 23:14:38 -05:00
Anavrins
a9c613164c Merge pull request #23 from LDDestroier/patch-2
Updated list URL
2020-01-19 21:24:17 -05:00
LDDestroier
62951067ae Updated list URL 2020-01-19 20:48:41 -05:00
Anavrins
3f19c94e0f GPS overhaul 2019-12-27 01:18:09 -05:00
kepler155c@gmail.com
79c8c4beae builder help update 2019-12-16 18:02:25 -07:00
kepler155c@gmail.com
83452a2be0 experimental terminal window manager 2019-12-15 19:12:36 -07:00
kepler155c@gmail.com
ddb699db7e Merge branch 'develop-1.8' of https://github.com/kepler155c/opus-apps into develop-1.8 2019-12-08 13:50:26 -07:00
kepler155c@gmail.com
14e6fd57a7 cash tweaks 2019-12-08 13:50:22 -07:00
Anavrins
a85e14e94d ores.lua: Load ore lists from config file and damage value support 2019-12-07 22:32:40 -05:00
kepler155c@gmail.com
7b45348710 oops 2019-12-05 15:34:20 -07:00
kepler155c@gmail.com
6831de37a3 compression read missing 2019-12-05 13:27:14 -07:00
kepler155c@gmail.com
e0960bd930 cash alternative shell added 2019-12-05 11:39:55 -07:00
kepler155c@gmail.com
8a826543d0 Merge branch 'develop-1.8' of https://github.com/kepler155c/opus-apps into develop-1.8 2019-12-05 11:37:37 -07:00
kepler155c@gmail.com
a2c8f0c655 cash alternative shell added 2019-12-05 11:37:31 -07:00
Anavrins
824685fc07 speakerView fix 2019-12-05 00:18:55 -05:00
kepler155c@gmail.com
aeec3c688e move scripts back for turtles program 2019-11-25 15:29:25 -07:00
kepler155c@gmail.com
df8c80e4db crafting fix 2019-11-25 15:24:11 -07:00
Anavrins
d8f33b9cb7 multiMiner.lua Move abort button to avoid accidental clicking 2019-11-18 15:03:02 -05:00
kepler155c@gmail.com
922c391bf2 cleanup + example start 2019-11-17 18:21:48 -07:00
kepler155c@gmail.com
64ec8c82d3 properly handle empty text entry fields (including transformations) 2019-11-13 14:24:54 -07:00
kepler155c@gmail.com
7e520744a2 lzwfs oops 2019-11-10 16:38:22 -07:00
kepler155c@gmail.com
1be7494982 mbs install fixes 2019-11-10 15:58:53 -07:00
kepler155c@gmail.com
c873a2e588 tweaks 2019-11-09 23:43:36 -07:00
kepler155c@gmail.com
957ac4f95f set mbs shell as default when installed 2019-11-09 22:26:27 -07:00
kepler155c@gmail.com
7773ecd55d package manager wip 2019-11-09 22:01:40 -07:00
kepler155c@gmail.com
b1a24235ea package manager wip 2019-11-09 21:17:07 -07:00
kepler155c@gmail.com
3255a5a670 minify + fix lzwfs ui bug 2019-11-07 22:49:59 -07:00
kepler155c@gmail.com
db31a0e5de Events shows all + tweaks 2019-11-07 18:01:20 -07:00
kepler155c@gmail.com
a7cc764c53 lzwfs final tweaks 2019-11-06 19:43:14 -07:00
kepler155c@gmail.com
be2bf9fcaf lzwfs wip 2019-11-05 14:10:53 -07:00
kepler155c@gmail.com
18441ffaef lzwfs wip 2019-11-05 12:14:01 -07:00
kepler155c@gmail.com
cecce156d7 lzwfs 2019-11-05 11:20:09 -07:00
kepler155c@gmail.com
636deed29f lzwfs 2019-11-05 11:18:49 -07:00
Anavrins
61755fb024 miloApps paths fix 2019-11-03 01:11:56 -04:00
kepler155c@gmail.com
06c340c064 add turtle back as a milo dependency - sigh 2019-11-02 22:21:29 -06:00
kepler155c@gmail.com
7423176c52 tweaks + new screen savers 2019-11-02 14:15:23 -06:00
kepler155c@gmail.com
4ce86a9e5a screen saver(s) 2019-11-02 12:25:00 -06:00
kepler155c@gmail.com
e1d05da20f screen locking 2019-11-01 22:39:57 -06:00
kepler155c@gmail.com
5c0ea3a2a2 typo + lock screen 2019-11-01 16:36:47 -06:00
kepler155c@gmail.com
a36d54a372 restructure 2019-10-30 22:50:01 -06:00
kepler155c
e5866b415e Merge pull request #17 from Kan18/patch-2
fix milo setup crash
2019-08-03 08:11:01 -06:00
Kan18
119d952860 fix milo setup crash
I've applied to this to my milo because I thought it was a one-off thing, however when setting up a new milo turtle I noticed that the crash also happened, so this fixes it
2019-08-02 16:24:15 -04:00
kepler155c@gmail.com
0803b69bbe Merge branch 'develop-1.8' of https://github.com/kepler155c/opus-apps into develop-1.8 2019-07-23 13:30:20 -06:00
kepler155c@gmail.com
10df63ae9e consistent command line processing-usage 2019-07-23 13:30:13 -06:00
xAnavrins
5616ef0007 shop password tweak 2019-07-23 00:29:41 -04:00
kepler155c
3665bfb26a Merge pull request #13 from Kan18/patch-1
add supertreefarm to overview
2019-07-22 13:04:42 -06:00
Kan18
3bd391a173 add supertreefarm to overview 2019-07-21 21:11:44 -04:00
kepler155c@gmail.com
50b68c312e shop password config 2019-07-21 16:49:48 -06:00
kepler155c@gmail.com
267ee439b5 oopses 2019-07-21 14:49:03 -06:00
kepler155c@gmail.com
e68c9e938f shop refactor 2019-07-21 14:38:24 -06:00
kepler155c@gmail.com
e73ce3875b milo shop updates 2019-07-21 13:58:25 -06:00
kepler155c@gmail.com
96e8d7e7cd lint warnings 2019-07-21 10:17:30 -06:00
xAnavrins
a48bd364fd swshop log viewer app 2019-07-21 03:11:32 -04:00
xAnavrins
86e6155cde swshop fixes
and gpsServer
2019-07-21 03:11:02 -04:00
xAnavrins
edb29f5875 gpsServer final tweaks 2019-07-19 22:37:11 -04:00
xAnavrins
7b91ddefe9 Yes, more gpsServer stuff...
Heartbeat monitor
Gray out inactive nodes after 60 seconds
2019-07-19 00:57:27 -04:00
kepler155c@gmail.com
b4f51df463 nothing to see here 2019-07-18 17:25:07 -06:00
kepler155c
e36f1bf364 Merge pull request #12 from Kan18/develop-1.8
Fix swshop breaking with other websockets
2019-07-18 17:16:15 -06:00
Kan18
89a6c19bdf Fix swshop breaking with other websockets
w.lua does not like websockets not opened with w.lua. this fixes that. this should fix not being able to use cloudcatcher/chatbox and operate the shop at the same time.
2019-07-18 16:43:56 -04:00
xAnavrins
da00b5149f gpsServer again 2019-07-18 01:21:57 -04:00
xAnavrins
628eb56613 Fix apps.db and legacy icon transparency 2019-07-16 22:29:10 -04:00
xAnavrins
60484feb8e Evener more gpsServer tweaks
- colors
- label
- GPS and SNMP modes
2019-07-16 19:56:07 -04:00
xAnavrins
327b40a414 Even more gpsServer tweaks 2019-07-16 17:56:09 -04:00
kepler155c@gmail.com
e4b1b50117 manipulator modules working after detach/reattach 2019-07-15 20:10:06 -06:00
xAnavrins
ae20a8ada7 Change mirrorHost port number
to avoid conflict with SVNC
2019-07-14 03:56:35 -04:00
xAnavrins
b16c6ec4d3 more gpsServer tweaks
Highlight change in position
Added distance from turtle
2019-07-14 02:38:53 -04:00
xAnavrins
f51b286f2b Use ScrollingGrid component for gpsServer 2019-07-14 01:38:07 -04:00
kepler155c@gmail.com
a79e79db28 minor tweaks 2019-07-12 16:03:55 -06:00
kepler155c@gmail.com
d1bac2b309 replace volume setting with slider control 2019-07-12 12:48:36 -06:00
kepler155c@gmail.com
a421c78a0c swshop accepts uppercase names for items #11 2019-07-11 10:33:54 -06:00
kepler155c@gmail.com
dc6e6c546b icon transparency 2019-07-11 00:43:15 -06:00
kepler155c@gmail.com
6ddeda308e milo: update cache when using transfer from storage 2019-07-10 11:07:48 -06:00
kepler155c@gmail.com
0144c61b7f app tweaks 2019-07-09 14:06:15 -04:00
kepler155c@gmail.com
8067f3bd99 reduce net traffic 2019-07-08 13:53:22 -04:00
kepler155c@gmail.com
d2e25cb951 minor tweaks 2019-07-08 00:15:50 -04:00
kepler155c@gmail.com
b86f32b83a package manager list in repo + cleanup 2019-07-06 23:53:47 -04:00
kepler155c@gmail.com
ed99703fda control-q instead of q for exitting apps 2019-07-02 14:12:41 -04:00
kepler155c@gmail.com
6fa7779d7d shuffled some files around 2019-07-01 17:25:21 -04:00
kepler155c@gmail.com
fdf6590dc7 better info on upgrade 2019-06-29 08:50:40 -04:00
kepler155c@gmail.com
b8544644b7 move apis into rom/modules/main for shell compatibility 2019-06-28 13:50:34 -04:00
kepler155c@gmail.com
d4e379af2e milo: oops 2019-06-28 08:47:31 -04:00
kepler155c@gmail.com
b5014729e7 sha conversion 2019-06-28 06:34:14 -04:00
kepler155c@gmail.com
03643bcd34 milo crafting updates 2019-06-27 16:29:37 -04:00
kepler155c@gmail.com
bbd36ec96a milo crafting improvements 2019-06-26 12:10:34 -04:00
kepler155c@gmail.com
8a88df815f milo: crafting fixes 2019-06-20 10:02:40 -04:00
kepler155c@gmail.com
045b32884f spaces->tab, equipper improvements, supertreefarm rewrite, follow improvements, sensor cleanup, milo multiple items allowed in recipes, remote canvas access 2019-06-18 15:23:20 -04:00
kepler155c@gmail.com
3b9b509429 swarm: show miner ids greater than 9999 2019-06-05 19:14:45 -04:00
kepler155c@gmail.com
fcc4faefaa shop monitor selection 2019-06-02 22:44:27 -04:00
kepler155c@gmail.com
4a6fa305f4 shellex: fix ls on netfs dirs 2019-05-14 05:58:07 -04:00
kepler155c@gmail.com
180f0f9b93 shellex: fix aliasing and colorize grep 2019-05-13 09:30:27 -04:00
kepler155c@gmail.com
9f3070bbdc shell tools: globbing 2019-05-10 08:13:02 -04:00
kepler155c@gmail.com
86cba3b585 shell tools: globbing 2019-05-10 08:08:49 -04:00
kepler155c@gmail.com
8ea2598254 milo: unassign machine 2019-05-07 07:14:30 -04:00
kepler155c@gmail.com
e61940a7aa farmer: allow chest to be above as well as below 2019-05-04 00:43:25 -04:00
kepler155c@gmail.com
731fc2c761 change _debug to _syslog 2019-05-03 15:30:37 -04:00
kepler155c@gmail.com
074e0742d5 neural cleanup 2019-05-01 00:17:37 -04:00
kepler155c@gmail.com
0ec0c63ba6 neural cleanup 2019-04-30 23:02:49 -04:00
kepler155c@gmail.com
7549c4e620 neural refactor 2019-04-30 12:20:57 -04:00
kepler155c@gmail.com
ab6e3c8d6b ores: change to show ore instead of ingot 2019-04-30 09:50:25 -04:00
kepler155c@gmail.com
ec11ae0b50 ores: change to show ore instead of ingot 2019-04-30 09:19:26 -04:00
kepler155c@gmail.com
e3a3ab004c mobRancher 2019-04-29 21:50:52 -04:00
kepler155c@gmail.com
23c81cd546 mwm bug fixes 2019-04-27 19:33:51 -04:00
kepler155c@gmail.com
f77f7cf499 app tweaks 2019-04-25 14:54:36 -04:00
kepler155c@gmail.com
b03b0d366b app tweaks 2019-04-25 14:43:55 -04:00
kepler155c@gmail.com
c486301116 better bulk handling 2019-04-25 12:55:32 -04:00
kepler155c@gmail.com
c3ee734c19 chat errors if not online 2019-04-25 12:39:52 -04:00
kepler155c@gmail.com
ddf0bd4727 better bulk handling 2019-04-25 12:37:20 -04:00
kepler155c@gmail.com
da160be233 better bulk handling 2019-04-25 11:29:18 -04:00
kepler155c@gmail.com
54bba18522 hexEdit 2019-04-25 01:44:35 -04:00
kepler155c@gmail.com
fff680d202 blacklist chests 2019-04-24 22:10:05 -04:00
kepler155c@gmail.com
5892da5d1b remove sneak on multiminer-temp 2019-04-24 01:12:47 -04:00
kepler155c@gmail.com
36c75981d6 better support for mass furnaces 2019-04-23 20:21:25 -04:00
kepler155c@gmail.com
b3a8432f86 milo: big oops 2019-04-22 16:12:36 -04:00
kepler155c@gmail.com
9cf630000b milo: better perf when storage is full 2019-04-22 15:15:27 -04:00
kepler155c@gmail.com
7e092b66ee altgr combos 2019-04-22 02:06:30 -04:00
kepler155c@gmail.com
320714c2d0 foreign keyboards 2019-04-21 23:46:04 -04:00
kepler155c@gmail.com
60efe15fd9 keys for foreign keyboards 2019-04-21 23:40:21 -04:00
kepler155c
8b1d1c12e3 Merge pull request #8 from Lemmmy/diskcopy-enhancements
Enhancements to DiskCopy.
2019-04-21 15:25:19 -04:00
Drew Lemmy
826e30c129 Enhancements to DiskCopy.
- Persist copy direction in config
- Remove unused 'clone' checkbox
- Add auto-eject and auto-copy functions
2019-04-21 20:16:47 +01:00
kepler155c@gmail.com
93b08eb4f3 cap boost to 4 2019-04-20 14:22:49 -04:00
kepler155c@gmail.com
7d8edefe66 show hud when flying + crouching 2019-04-20 12:48:37 -04:00
kepler155c@gmail.com
340759f95d show hud when flying + crouching 2019-04-20 12:42:52 -04:00
kepler155c@gmail.com
78603b1736 show hud when flying + crouching 2019-04-20 12:38:48 -04:00
kepler155c@gmail.com
954711c5fe read schematic files from web 2019-04-19 19:00:06 -04:00
kepler155c@gmail.com
a6ddc77b4e cleanup 2019-04-16 00:23:37 -04:00
kepler155c@gmail.com
f13e56327e updated to use new canvas features 2019-04-15 20:40:58 -04:00
kepler155c@gmail.com
1d6839a499 fisher using state machine 2019-04-15 19:01:41 -04:00
kepler155c@gmail.com
61e2a2e8bf visual tweaks 2019-04-15 11:49:43 -04:00
kepler155c@gmail.com
b116e56676 ores: show items instead of colors 2019-04-14 17:04:28 -04:00
kepler155c@gmail.com
4733116476 app tweaks 2019-04-13 21:38:57 -04:00
kepler155c@gmail.com
d45249e022 app tweaks 2019-04-13 12:47:20 -04:00
kepler155c@gmail.com
ade43e90e8 auto fisher 2019-04-09 14:38:19 -04:00
kepler155c@gmail.com
c3b06e4854 builder bug 2019-04-08 12:56:04 -04:00
kepler155c@gmail.com
a16510e254 shell utils fixes 2019-04-08 09:31:13 -04:00
kepler155c@gmail.com
fba1546213 tweaks + gps server fix 2019-04-07 21:20:42 -04:00
kepler155c@gmail.com
8f2c97d269 openOS command line programs 2019-04-07 10:10:30 -04:00
kepler155c@gmail.com
5f2f33f1f2 coroutine pool in parallel code 2019-04-05 21:47:35 -04:00
kepler155c@gmail.com
426c856dfb refactor parallel code 2019-04-05 17:32:22 -04:00
kepler155c@gmail.com
d5eb07c3b9 multiMiner improvements 2019-04-04 13:41:31 -04:00
kepler155c@gmail.com
1c30ccda29 milo: improve export speed 2019-04-03 17:13:20 -04:00
kepler155c@gmail.com
ad165825bd milo use parallel during crafting for ingredients (holy shit thats fast) 2019-04-02 16:45:11 -04:00
kepler155c@gmail.com
8f432c4638 milo: crafting recursion fix 2019-04-02 14:28:09 -04:00
kepler155c@gmail.com
54ed32ecd9 sensor rewrite 2019-04-02 03:42:08 -04:00
kepler155c@gmail.com
71b343cf86 better armor stand intelligence 2019-04-01 08:51:53 -04:00
kepler155c@gmail.com
e51a4108ad fix menu separators 2019-03-31 15:20:47 -04:00
kepler155c@gmail.com
5fb22a2637 milo: override control-a for stack 2019-03-30 21:43:50 -04:00
kepler155c@gmail.com
e9faa553c3 better text entry 2019-03-29 09:57:16 -04:00
kepler155c@gmail.com
6f3606de20 edit: fix sticky control + autorun in shell mode 2019-03-27 15:22:13 -04:00
kepler155c@gmail.com
a181a58420 tweaks 2019-03-27 07:42:33 -04:00
kepler155c@gmail.com
4d06a9a738 milo better notifications 2019-03-26 02:10:20 -04:00
kepler155c@gmail.com
f8eb2965ae edit: dont generate control-char combos 2019-03-24 14:21:26 -04:00
kepler155c@gmail.com
a4162e546b milo: fix cached transfer locations not getting uncached 2019-03-24 12:30:36 -04:00
kepler155c@gmail.com
1961882327 CCEmuX integration 2019-03-24 03:45:02 -04:00
kepler155c@gmail.com
81abec75f5 cleanup 2019-03-23 12:21:10 -04:00
kepler155c@gmail.com
82d355cdf0 name change for turtles (turtle_expanded) 2019-03-23 06:13:05 -04:00
kepler155c@gmail.com
d25e160452 shooting gallery 2019-03-23 02:17:04 -04:00
kepler155c@gmail.com
8ca0071786 milo save status tab + update record for new pastebin link 2019-03-21 20:28:51 -04:00
kepler155c@gmail.com
3d815c6332 a couple oops 2019-03-20 18:40:07 -04:00
kepler155c@gmail.com
dabd64046e update scanner 2019-03-20 10:27:45 -04:00
kepler155c@gmail.com
f6d077bf08 overlay glasses 2019-03-19 09:59:28 -04:00
kepler155c@gmail.com
2cd1ec27e0 overlay glasses 2019-03-19 07:34:03 -04:00
kepler155c@gmail.com
048fbc41c5 overlay glasses 2019-03-19 06:23:12 -04:00
kepler155c@gmail.com
6bf7ca4985 overlay glasses 2019-03-19 06:04:10 -04:00
kepler155c@gmail.com
b1c709795f neural cleanup 2019-03-19 01:41:32 -04:00
kepler155c@gmail.com
b4c894b8a1 milo auto-feeder 2019-03-17 00:30:07 -04:00
kepler155c@gmail.com
4362a92193 milo logging 2019-03-16 21:30:24 -04:00
kepler155c@gmail.com
ebd0896ee9 milo logging 2019-03-16 21:02:49 -04:00
kepler155c@gmail.com
ed0dfcd473 fix copy/paste in editor 2019-03-14 09:48:22 -04:00
kepler155c@gmail.com
a0a92818d6 neural look 2019-03-13 17:53:49 -04:00
kepler155c@gmail.com
81a7894037 refactor common apps into its own package 2019-03-12 20:05:04 -04:00
kepler155c@gmail.com
d8e0a669af milo config backups 2019-03-12 10:20:02 -04:00
kepler155c@gmail.com
7ac10948df milo config backups 2019-03-12 09:58:02 -04:00
kepler155c@gmail.com
b43761faf1 remove dependency on device global + milo backup config 2019-03-11 23:48:53 -04:00
kepler155c@gmail.com
f2a8a7e639 sounds 2019-03-11 19:23:06 -04:00
kepler155c@gmail.com
1e5014492c icon update - thx LDD 2019-03-10 19:37:14 -04:00
kepler155c@gmail.com
3192dc52d7 phase out peripheral.lookup 2019-03-08 16:26:18 -05:00
kepler155c@gmail.com
9392fe47f8 turtle equip round 2 2019-03-07 18:14:19 -05:00
kepler155c@gmail.com
7d83e66e4a turtle equip round 2 2019-03-07 17:49:06 -05:00
kepler155c@gmail.com
fd5343c9c2 better turtle.equip 2019-03-07 13:27:34 -05:00
kepler155c@gmail.com
d3e8a158b4 better turtle.equip 2019-03-07 13:14:47 -05:00
kepler155c@gmail.com
e5f1374325 milo performance update 2019-03-06 13:15:36 -05:00
kepler155c@gmail.com
5a55c584b6 milo performance update 2019-03-06 12:32:11 -05:00
kepler155c@gmail.com
76367eb8c9 milo task status 2019-03-06 11:48:45 -05:00
kepler155c@gmail.com
fcb6914276 shop to accept private key 2019-03-03 14:05:18 -05:00
kepler155c@gmail.com
29bcb1bcc6 elytraFly hud 2019-03-01 21:11:14 -05:00
kepler155c@gmail.com
838b7d5d69 small updates + elytraFly hud 2019-02-28 22:34:02 -05:00
kepler155c@gmail.com
389038dc77 oops 2019-02-28 10:44:00 -05:00
kepler155c@gmail.com
d5d05b9690 milo status panel 2019-02-27 21:39:59 -05:00
kepler155c@gmail.com
10787dd2b3 milo status panel 2019-02-27 19:35:26 -05:00
kepler155c@gmail.com
177533b760 rename justify property to align 2019-02-27 17:15:44 -05:00
kepler155c@gmail.com
5af2bb1e91 shop fix dupe sell names + disk copy cloning 2019-02-27 08:10:49 -05:00
kepler155c@gmail.com
116e39034c milo style 2019-02-26 20:29:59 -05:00
kepler155c@gmail.com
e0846ec91c milo status panel + system tabs 2019-02-26 18:03:39 -05:00
kepler155c@gmail.com
442f1c4d47 util refactor + milo bulk crafting 2019-02-26 09:47:44 -05:00
kepler155c@gmail.com
4303aeb1f8 cleanup itemdb for ever changing nbt hashes for turtles 2019-02-25 09:02:01 -05:00
kepler155c@gmail.com
d62d9fe729 oops 2019-02-24 16:43:10 -05:00
kepler155c@gmail.com
b8919851e6 finish recipe books 2019-02-24 06:59:30 -05:00
kepler155c@gmail.com
5dc87ef8d1 network sniffer 2019-02-23 18:36:42 -05:00
kepler155c@gmail.com
3d875890b0 cleanup 2019-02-23 08:17:04 -05:00
kepler155c@gmail.com
b580bab10b cleanup 2019-02-23 08:03:57 -05:00
kepler155c@gmail.com
40f80809c1 cleanup 2019-02-23 07:36:44 -05:00
kepler155c@gmail.com
6bd90ef46e cleanup 2019-02-23 07:26:57 -05:00
kepler155c@gmail.com
9db6d22ab5 turtle gps rework 2019-02-22 05:18:13 -05:00
kepler155c@gmail.com
f37dac0c79 turtle gps rework 2019-02-22 03:53:40 -05:00
kepler155c@gmail.com
70a3bd1166 turtle status 2019-02-21 02:27:28 -05:00
kepler155c@gmail.com
10e7c1c233 turtle status 2019-02-21 02:05:35 -05:00
kepler155c@gmail.com
1643145cf0 turtle follow 2019-02-20 08:53:12 -05:00
kepler155c@gmail.com
9f009c164a launch control for elytraFly + mirror fixes 2019-02-20 01:26:08 -05:00
kepler155c@gmail.com
07b7151fd2 finder 2019-02-18 06:25:45 -05:00
kepler155c@gmail.com
0775a67cfe finder 2019-02-18 03:17:47 -05:00
kepler155c@gmail.com
3564dff4fd begin moving turtle specific code from opus into turtle package 2019-02-18 02:57:01 -05:00
kepler155c@gmail.com
20bb3d831f tweaks 2019-02-17 06:27:43 -05:00
kepler155c@gmail.com
aabfea6e19 finder 2019-02-17 00:10:17 -05:00
kepler155c@gmail.com
1d4b9c9b24 finder 2019-02-16 20:26:12 -05:00
kepler155c@gmail.com
8d3dfa165b finder 2019-02-16 20:18:43 -05:00
kepler155c@gmail.com
4342326c62 finder 2019-02-16 07:29:53 -05:00
kepler155c@gmail.com
e2c3fb6e42 finder 2019-02-16 05:47:14 -05:00
kepler155c@gmail.com
f3e131ec00 finder 2019-02-16 05:23:12 -05:00
kepler155c@gmail.com
f5d2c0e527 finder 2019-02-16 05:01:17 -05:00
kepler155c@gmail.com
d81ca1c10d finder 2019-02-16 04:48:20 -05:00
kepler155c@gmail.com
1a887e9dbe finder 2019-02-16 04:43:21 -05:00
kepler155c@gmail.com
d50749a753 finder 2019-02-16 04:35:36 -05:00
kepler155c@gmail.com
0ca89a3a7f milo: stats 2019-02-15 21:45:56 -05:00
kepler155c@gmail.com
cbfc5901f3 elytra fly tweak 2019-02-15 06:30:13 -05:00
kepler155c@gmail.com
88c9d33758 elytra fly tweak 2019-02-15 00:25:29 -05:00
kepler155c@gmail.com
c09e0a99e4 elytra fly tweak 2019-02-14 23:23:30 -05:00
kepler155c@gmail.com
152bb35cde better flying 2019-02-14 11:06:54 -05:00
kepler155c
68997b28a0 Merge pull request #4 from jakedacatman/patch-2
Update brewArray.lua
2019-02-14 00:00:07 -05:00
kepler155c@gmail.com
d26092baa6 milo stats 2019-02-13 23:45:39 -05:00
kepler155c@gmail.com
e1b6d46d9d milo stats 2019-02-13 23:17:07 -05:00
jakedacatman
23715f827c Update brewArray.lua 2019-02-13 21:13:56 -05:00
kepler155c@gmail.com
eee431ce26 oops 2019-02-13 20:49:58 -05:00
kepler155c@gmail.com
f30b1b19f9 elytra fly + milo stats 2019-02-13 20:17:39 -05:00
kepler155c@gmail.com
68c1e8ccde neural apps 2019-02-12 18:10:01 -05:00
kepler155c@gmail.com
d98ba35a21 improve follow 2019-02-12 17:04:14 -05:00
kepler155c@gmail.com
e0bad8edaf fallout from require path changes 2019-02-10 13:10:04 -05:00
kepler155c@gmail.com
3d0dc8528d bug due to api rework 2019-02-09 05:29:45 -05:00
kepler155c@gmail.com
8a0104585c bug due to api rework 2019-02-09 04:57:00 -05:00
kepler155c@gmail.com
b31d027516 milo: protect against crashed in callbacks 2019-02-09 00:19:21 -05:00
kepler155c@gmail.com
a541aee3f3 oops 2019-02-09 00:03:50 -05:00
kepler155c@gmail.com
a12b25cda8 edit autocomplete 2019-02-08 21:03:00 -05:00
kepler155c@gmail.com
8a0cf10498 milo name clash breaks require on Windows 2019-02-08 19:07:40 -05:00
kepler155c@gmail.com
af85d0aafb fix neural api location 2019-02-07 22:14:32 -05:00
kepler155c@gmail.com
e537922e46 mwm nearly there 2019-02-07 10:11:00 -05:00
kepler155c@gmail.com
3f2f8afaad fix mwm wip 2019-02-07 03:10:40 -05:00
kepler155c@gmail.com
b07343a530 ui overhaul 2019-02-05 23:04:22 -05:00
kepler155c@gmail.com
ab22ae8224 canvas use in UI overhaul 2019-01-30 16:27:23 -05:00
kepler155c@gmail.com
c180387438 canvas use in UI overhaul 2019-01-30 15:11:21 -05:00
kepler155c@gmail.com
328df55a19 log shop transactions 2019-01-28 23:07:10 -05:00
kepler155c@gmail.com
eb4740df0a log shop transactions 2019-01-28 22:56:11 -05:00
kepler155c@gmail.com
06106049f8 log shop transactions 2019-01-28 22:41:47 -05:00
kepler155c@gmail.com
9de52daa11 multiMiner tweak 2019-01-28 22:04:39 -05:00
kepler155c@gmail.com
e264d98739 cleanup 2019-01-27 00:27:05 -05:00
kepler155c@gmail.com
04815b2d09 require rework round 3 2019-01-26 22:58:21 -05:00
kepler155c@gmail.com
bae28521db milo perf + cleanup 2019-01-26 00:28:46 -05:00
kepler155c@gmail.com
779d58cd7e milo: better matching 2019-01-25 19:00:30 -05:00
kepler155c@gmail.com
408687e4f9 miner tweaks 2019-01-25 16:16:21 -05:00
kepler155c@gmail.com
574316d519 milo perf fixes 2019-01-25 12:26:31 -05:00
kepler155c@gmail.com
1de92f153f milo tweaks + cloud catcher 2019-01-23 15:30:14 -05:00
kepler155c@gmail.com
4276ea533f Milo: remote learn + remove logger 2019-01-22 15:50:37 -05:00
kepler155c@gmail.com
e4cfba5750 disk copy app 2019-01-20 19:20:52 -05:00
kepler155c@gmail.com
1de46ed867 use mounts for web apps 2019-01-20 01:29:14 -05:00
kepler155c@gmail.com
15943bc191 milo shop example 2019-01-19 22:04:02 -05:00
kepler155c
2e898a32b4 Merge pull request #2 from Lemmmy/deposit-all
Add depositAll plugin for MiloRemote
2019-01-19 18:05:04 -05:00
Drew Lemmy
ce5bb368ad Add depositAll plugin for MiloRemote 2019-01-19 22:56:04 +00:00
kepler155c@gmail.com
5acf27cdb8 multiMiner 2019-01-18 23:46:00 -05:00
kepler155c@gmail.com
3241c9d83b multiMiner 2019-01-18 21:49:07 -05:00
kepler155c@gmail.com
2cfc715aad multiMiner 2019-01-18 21:14:45 -05:00
kepler155c@gmail.com
b66e214dbf SoundPlayer 2019-01-18 08:47:49 -05:00
kepler155c@gmail.com
8fed8618d3 app changes 2019-01-17 23:33:43 -05:00
kepler155c@gmail.com
fc4514b6e2 cleanup + games 2019-01-17 22:03:12 -05:00
kepler155c@gmail.com
ef5eab65f9 cleanup + games 2019-01-17 22:01:40 -05:00
kepler155c@gmail.com
6416e67130 rework turtle policies 2019-01-17 13:31:43 -05:00
kepler155c@gmail.com
7914990d6c multiMiner 2019-01-17 11:22:10 -05:00
kepler155c@gmail.com
ad671621e8 multiMiner 2019-01-16 11:33:15 -05:00
kepler155c@gmail.com
be405fac23 multiMiner 2019-01-16 11:08:42 -05:00
kepler155c@gmail.com
dc200f95f5 multiMiner 2019-01-16 10:26:33 -05:00
kepler155c@gmail.com
f2f03b4cd3 tweaks 2019-01-15 18:21:24 -05:00
kepler155c@gmail.com
f82ebabc24 tweaks 2019-01-15 18:06:15 -05:00
kepler155c@gmail.com
b674c6ab90 rework plugin management 2019-01-14 10:03:53 -05:00
kepler155c@gmail.com
1cc9829eed milo shop tweaks 2019-01-13 18:27:44 -05:00
kepler155c@gmail.com
905783ad7a milo shop tweaks 2019-01-13 16:25:16 -05:00
kepler155c@gmail.com
376be37256 milo monitor button sizes 2019-01-13 15:17:54 -05:00
kepler155c@gmail.com
dee2b05cfb multiMiner + cloud catcher 2019-01-13 13:26:19 -05:00
kepler155c@gmail.com
91d434f93d milo shop updates 2019-01-12 10:06:48 -05:00
kepler155c@gmail.com
9921ede8fa shop nil checks 2019-01-12 09:12:52 -05:00
kepler155c@gmail.com
9cb3a084db milo shop updates 2019-01-11 20:13:50 -05:00
kepler155c@gmail.com
11da26e078 milo shop updates 2019-01-11 16:14:10 -05:00
kepler155c@gmail.com
17912f6054 milo shop updates 2019-01-11 14:59:33 -05:00
kepler155c@gmail.com
bfa528756e milo cleanup + shop 2019-01-11 10:01:37 -05:00
kepler155c@gmail.com
42e72cf3c8 milo: storefront wip 2019-01-11 03:11:17 -05:00
kepler155c@gmail.com
21b7c7c3af milo: storefront wip 2019-01-11 02:25:31 -05:00
kepler155c@gmail.com
6a37e47086 milo: storefront wip 2019-01-10 12:27:08 -05:00
kepler155c@gmail.com
1dbbe17ea0 spawner tweaks 2019-01-09 17:11:57 -05:00
kepler155c@gmail.com
00d0c0a507 spawner tweaks 2019-01-09 06:35:08 -05:00
kepler155c@gmail.com
8dcab201dc spawner tweaks 2019-01-09 06:26:27 -05:00
kepler155c@gmail.com
df2f2dcb59 spawner tweaks 2019-01-09 06:23:06 -05:00
kepler155c@gmail.com
4311c982cd spawner tweaks 2019-01-09 05:41:33 -05:00
kepler155c@gmail.com
df8855acd5 recipe books overhaul 2019-01-08 18:24:01 -05:00
kepler155c@gmail.com
c5c79e6de3 mass storage not ready yet 2019-01-08 11:39:05 -05:00
kepler155c@gmail.com
004dac65ce milo: mass storage support (ae/rs) 2019-01-08 04:42:43 -05:00
kepler155c@gmail.com
e07298a9c4 tweaks + simplify rancher (no xp collection) 2019-01-07 13:56:51 -05:00
kepler155c@gmail.com
b0042eaca2 spawner v1 2019-01-07 08:27:03 -05:00
kepler155c@gmail.com
4686804f3c app tweaks 2019-01-07 03:41:35 -05:00
kepler155c@gmail.com
6ac9816189 fix crafting bug - missing ingredients 2019-01-06 04:15:36 -05:00
kepler155c@gmail.com
1164bec812 better gps setup 2019-01-05 11:30:12 -05:00
kepler155c@gmail.com
e7692a282a better gps setup + option icon change 2019-01-05 10:36:24 -05:00
kepler155c@gmail.com
aa525dbd9c gps server record positions 2019-01-05 01:50:58 -05:00
kepler155c@gmail.com
f148bee303 gps server record positions 2019-01-05 01:01:23 -05:00
kepler155c@gmail.com
2539fce815 gps server record positions 2019-01-05 00:40:43 -05:00
kepler155c@gmail.com
b05e614130 superTreefarm: now with more trees 2019-01-04 14:06:12 -05:00
kepler155c@gmail.com
3d27808ce7 single turtle gps 2019-01-04 06:57:57 -05:00
kepler155c@gmail.com
57aecae996 milo: cleanup 2019-01-04 04:13:00 -05:00
kepler155c@gmail.com
1f00136f42 item get/add helper 2019-01-04 01:32:11 -05:00
kepler155c@gmail.com
6606975f5b recipe nbts + item get/add helper 2019-01-04 00:29:03 -05:00
kepler155c@gmail.com
d0a7a6e488 milo: fix export when diff item in slot 2019-01-03 22:29:50 -05:00
kepler155c@gmail.com
706e1351ae fix ui setTab 2019-01-03 12:55:27 -05:00
kepler155c@gmail.com
dd0f37d9dd milo: refactor item page 2019-01-03 05:24:16 -05:00
kepler155c@gmail.com
00e698eb18 milo bugfixes + enderchest app 2019-01-02 23:57:06 -05:00
kepler155c@gmail.com
859977f75c multi-device input support in UI 2019-01-02 10:34:31 -05:00
kepler155c@gmail.com
1aa5dfdeaf better auto deposit 2019-01-02 06:03:42 -05:00
kepler155c@gmail.com
759f7b0fa6 mob warning in Sensor 2019-01-02 03:03:35 -05:00
kepler155c@gmail.com
89b6f15785 farmer autorefuel 2019-01-01 21:15:58 -05:00
kepler155c@gmail.com
96fc44473d milo: auto-send select items to server 2019-01-01 07:09:02 -05:00
kepler155c@gmail.com
46be7de2bd milo: auto-send select items to server 2019-01-01 06:59:25 -05:00
kepler155c@gmail.com
9cd480f188 milo: item rewrite + ability to remove nbts from recipes 2018-12-31 11:39:24 -05:00
kepler155c@gmail.com
b726e5011f ignore nbt for crafting 2018-12-30 06:16:09 -05:00
kepler155c@gmail.com
d4b8ffb978 sensor update 2018-12-29 23:32:20 -05:00
kepler155c@gmail.com
d2deddcf99 fix equip 2018-12-27 00:42:49 -05:00
kepler155c@gmail.com
615e2a2202 revert peripheral changes 2018-12-26 14:35:16 -05:00
kepler155c@gmail.com
0679147a22 revert peripheral change 2018-12-26 14:21:28 -05:00
kepler155c@gmail.com
03b7e9602b Entities app 2018-12-26 05:17:14 -05:00
kepler155c@gmail.com
0a2a3c06dc reorg app db 2018-12-26 04:01:11 -05:00
kepler155c@gmail.com
8b354549cc app tweaks 2018-12-25 03:00:24 -05:00
kepler155c@gmail.com
32a831862b peripheral updates 2018-12-25 02:33:14 -05:00
kepler155c@gmail.com
ffab2372de tweaks for superTreefarm 2018-12-24 00:19:08 -05:00
kepler155c@gmail.com
297b0bdd9f tweaks for superTreefarm 2018-12-24 00:12:00 -05:00
kepler155c@gmail.com
931e368357 better pathing in superTreefarm 2018-12-23 22:26:09 -05:00
kepler155c@gmail.com
10b9772130 better chopping in superTreefarm 2018-12-23 21:09:15 -05:00
kepler155c@gmail.com
61fbbf5e3d better chopping in superTreefarm 2018-12-23 19:50:09 -05:00
kepler155c@gmail.com
83a9aa46ab require overhaul part 2 2018-12-23 17:32:38 -05:00
kepler155c@gmail.com
29af471374 app tweaks 2018-12-23 02:17:59 -05:00
kepler155c@gmail.com
e69d68c7b4 tweaks 2018-12-22 22:09:59 -05:00
kepler155c@gmail.com
9104b915af Entities 2018-12-22 15:07:51 -05:00
kepler155c@gmail.com
3123478d0b superTreefarm 2018-12-22 02:59:16 -05:00
kepler155c@gmail.com
8aa9a63da6 superTreefarm 2018-12-21 21:42:21 -05:00
kepler155c@gmail.com
89b05f1fae superTreefarm 2018-12-21 20:08:44 -05:00
kepler155c@gmail.com
fdf9c70338 superTreefarm 2018-12-21 19:48:40 -05:00
kepler155c@gmail.com
12aebb0b88 scanner 2018-12-18 01:20:23 -05:00
kepler155c@gmail.com
4bcc851f22 entities - display on overlay 2018-12-17 01:04:15 -05:00
kepler155c@gmail.com
eb8d6f0ebb entities - display on overlay 2018-12-17 00:37:39 -05:00
kepler155c@gmail.com
7f458484b6 entities - display on overlay 2018-12-17 00:30:38 -05:00
kepler155c@gmail.com
400ebabed3 entities - display on overlay 2018-12-16 23:27:47 -05:00
kepler155c@gmail.com
c08e2a560a entities - display on overlay 2018-12-16 23:27:31 -05:00
kepler155c@gmail.com
e0c45bd87c attack dropping modules 2018-12-16 22:38:25 -05:00
kepler155c@gmail.com
82a82fbcab entities - display on overlay 2018-12-16 22:09:14 -05:00
kepler155c@gmail.com
073c55314f various tweaks 2018-12-16 15:00:01 -05:00
kepler155c@gmail.com
ecd3d52797 various tweaks 2018-12-16 14:53:54 -05:00
kepler155c@gmail.com
bf3983a7e2 package cleanup 2018-12-10 10:33:10 -05:00
kepler155c@gmail.com
37ffae42bd miner improvements 2018-12-09 13:33:55 -05:00
kepler155c@gmail.com
c814d178e6 merge from master-1.8 2018-12-08 13:30:18 -05:00
kepler155c@gmail.com
c0092ba52c new release 2018-12-08 13:29:26 -05:00
kepler155c@gmail.com
d0d825dd9a treefarm doc 2018-12-08 13:27:26 -05:00
kepler155c@gmail.com
5c28bc736f milo: tweaks 2018-12-07 13:43:33 -05:00
kepler155c@gmail.com
11c4670213 milo: single wired modem only 2018-12-05 21:20:50 -05:00
kepler155c@gmail.com
f209719c9d reassign machines 2018-12-05 12:04:04 -05:00
kepler155c@gmail.com
0041ffaa5e milo tweaks 2018-12-05 01:28:42 -05:00
kepler155c@gmail.com
74aa9829c3 milo tweaks 2018-12-05 01:16:42 -05:00
kepler155c@gmail.com
c79c93e4d7 milo tweaks 2018-12-05 00:55:49 -05:00
kepler155c@gmail.com
aa3e3e615c learn 2018-12-04 21:21:32 -05:00
kepler155c@gmail.com
fff04ec01a milo: simplify learn types + monitor resize 2018-12-04 18:49:01 -05:00
kepler155c@gmail.com
e90e6cebdd floppy disk name fix 2018-12-04 02:14:31 -05:00
kepler155c@gmail.com
0d13af3827 milo: speaker setup 2018-12-04 01:33:17 -05:00
kepler155c@gmail.com
21d182fd55 milo: improve list perf 2018-12-03 20:04:43 -05:00
kepler155c@gmail.com
1421d3ed1b milo: improve list perf 2018-12-03 19:22:06 -05:00
kepler155c@gmail.com
9c81386a8e milo: improve list perf 2018-12-03 18:55:20 -05:00
kepler155c@gmail.com
a49d2d820d app tweaks 2018-12-03 15:40:51 -05:00
kepler155c@gmail.com
93d2aa331c better wireless 2018-12-02 18:42:49 -05:00
kepler155c@gmail.com
ef0cf88387 manipulator :( 2018-12-02 15:11:10 -05:00
kepler155c@gmail.com
4b229b868e milo: text scaling + manipulator fixes 2018-12-02 13:16:00 -05:00
kepler155c@gmail.com
27c455ba16 milo: smart transfer 2018-12-01 19:46:12 -05:00
kepler155c@gmail.com
4a844eebfe milo: smart transfer 2018-12-01 19:08:57 -05:00
kepler155c@gmail.com
0853a16b59 milo: categories 2018-12-01 01:44:47 -05:00
kepler155c@gmail.com
e1084422e5 milo: categories, better listing page separation 2018-12-01 00:52:39 -05:00
kepler155c
c5d209161e autoruns 2018-11-30 19:26:56 -05:00
kepler155c
53c11db773 farms - improvements 2018-11-30 13:15:40 -05:00
kepler155c
e9d9025ea7 remove rancher dispenser requirement 2018-11-30 11:53:18 -05:00
kepler155c
05cbb72c1d milo: improved sorting 2018-11-30 04:55:19 -05:00
kepler155c
fcbdda953c farm nether wart 2018-11-30 00:34:45 -05:00
kepler155c
995f635807 farm chorus fruit 2018-11-29 16:18:09 -05:00
kepler155c
7b17e756f9 farm chorus fruit 2018-11-29 15:13:36 -05:00
kepler155c
a2a87db49c milo: cobblegen 2018-11-29 02:07:11 -05:00
kepler155c
ccec3ceaf4 milo: edit name bug + furni 2018-11-29 00:42:17 -05:00
kepler155c
f0b058959a attack chests 2018-11-28 16:03:59 -05:00
kepler155c
b6bad398ca attack chests 2018-11-28 16:03:19 -05:00
kepler155c
c70a22964a milo: refactor eject 2018-11-28 14:26:46 -05:00
kepler155c
d4bba2ce4f milo: quantity update bug in listing 2018-11-28 01:04:55 -05:00
kepler155c
f3240affc9 milo: rework potion naming + storage cache updating 2018-11-28 00:20:15 -05:00
kepler155c
f3411f0ba3 enchanted items 2018-11-27 14:17:20 -05:00
kepler155c
94e160f738 milo: sounds 2018-11-26 20:52:38 -05:00
kepler155c
6138ebcf7c milo: sounds 2018-11-26 18:44:51 -05:00
kepler155c
6f60d91e52 milo: sounds 2018-11-26 18:32:07 -05:00
kepler155c
4833f7f12b milo: sounds 2018-11-26 17:10:39 -05:00
kepler155c
427006d2cb milo: reduce modem traffic 2018-11-25 21:08:27 -05:00
kepler155c
c24def5d2e milo: reduce modem traffic 2018-11-25 12:40:21 -05:00
kepler155c
bf4f45f5e5 milo: reduce modem traffic 2018-11-25 12:33:22 -05:00
kepler155c
3830a02c34 milo: mitigate manipulator bound bug 2018-11-24 22:14:23 -05:00
kepler155c
8c2674e5dd milo: slow down retry time 2018-11-24 17:00:23 -05:00
kepler155c
537db89a82 milo autorun programs 2018-11-24 14:16:44 -05:00
kepler155c
dff7f4bdab milo: quad cooker 2018-11-24 13:12:31 -05:00
kepler155c
cbee122165 attack program - new 2018-11-23 20:12:12 -05:00
kepler155c
8b439baf30 attack program - new 2018-11-23 18:57:19 -05:00
kepler155c
c8f3ad3b65 fix potion recipe - revert maxCount in craft 2018-11-23 18:26:27 -05:00
kepler155c
8ff38e52b7 attack program - new 2018-11-23 17:26:26 -05:00
kepler155c
d426b80475 attack program - new 2018-11-23 17:03:33 -05:00
kepler155c
4774b6b504 attack program - new 2018-11-23 16:19:40 -05:00
kepler155c
cf39d1d74c attack program - new 2018-11-23 16:13:53 -05:00
kepler155c
dc61f89739 attack program - new 2018-11-23 15:13:56 -05:00
kepler155c
252fdfb138 attack program - new 2018-11-23 13:55:21 -05:00
kepler155c
fa43186d52 milo: cleanup 2018-11-23 11:39:39 -05:00
kepler155c
7498276573 milo: refresh on storage config change + rabbit + auto detect player name 2018-11-21 22:05:04 -05:00
kepler155c
0787cc7913 milo: peripheral improvements 2018-11-21 17:28:50 -05:00
kepler155c
a5d62c3271 new release prep 2018-11-21 12:10:53 -05:00
kepler155c
d076326273 prep for release 2018-11-20 20:54:25 -05:00
kepler155c
d6b0fef5d4 milo fixes 2018-11-19 19:36:08 -05:00
kepler155c
4df1b30098 milo fixes 2018-11-19 17:18:02 -05:00
kepler155c
cc913bc10c milo fixes 2018-11-19 16:10:24 -05:00
kepler155c
6b11b29bba recipe reorg + milo fixes 2018-11-19 13:52:32 -05:00
kepler155c
2605410c6d milo: better monitor handling + assigning machines to items 2018-11-17 21:27:45 -05:00
kepler155c
939ec9516d milo: save state 2018-11-17 18:03:03 -05:00
kepler155c
39edf76c6b deposit toggle 2018-11-17 17:42:09 -05:00
kepler155c
72f23d4814 deposit toggle 2018-11-17 17:34:26 -05:00
kepler155c
b73775ad93 milo wip 2018-11-16 10:26:41 -05:00
kepler155c
3e756abe42 milo wip 2018-11-15 12:17:50 -05:00
kepler155c
560b9e42c9 new icons - thx LDD 2018-11-14 21:24:14 -05:00
kepler155c
2abbece8c3 neural flying 2018-11-14 19:12:29 -05:00
kepler155c
a460bc31f3 itemdb potion hack 2018-11-14 14:22:43 -05:00
kepler155c
1328fc233a milo performance 2018-11-14 13:56:53 -05:00
kepler155c
364ae04ceb milo performance 2018-11-14 13:22:31 -05:00
kepler155c
8e9ca385d9 milo: UI cleanup 2018-11-13 21:31:27 -05:00
kepler155c
38777041d1 milo: drop from trashcans 2018-11-13 19:06:49 -05:00
kepler155c
f6c05dba56 milo: drop from trashcans 2018-11-13 18:56:29 -05:00
kepler155c
16c56aa092 milo: lock chest with multiple items 2018-11-13 17:18:55 -05:00
kepler155c
6870378422 milo: lock chest with multiple items 2018-11-13 16:07:28 -05:00
kepler155c
2af29c5d93 milo: refresh interval 2018-11-13 14:32:37 -05:00
kepler155c
986fe160c7 cleanup + adv brewing 2018-11-13 13:43:30 -05:00
kepler155c
c50de60f43 treefarm bug fixes 2018-11-13 00:03:36 -05:00
kepler155c
1eaaca2cc3 milo better user experience 2018-11-12 20:22:14 -05:00
kepler155c
52ea4e039c milo manipulator + crafting fixes 2018-11-12 11:44:24 -05:00
kepler155c
3f095ff522 milo logging 2018-11-12 02:03:37 -05:00
kepler155c
9ee8ce9f33 milo node rework 2018-11-11 21:59:13 -05:00
kepler155c
fe1fc6ea93 milo cleanup 2018-11-11 14:45:39 -05:00
kepler155c
a00a66f4e6 milo cleanup 2018-11-11 01:04:24 -05:00
kepler155c
bc9028f0c7 rework milo crafting 2018-11-10 21:01:53 -05:00
kepler155c
aff05f8587 rework milo crafting 2018-11-10 13:04:23 -05:00
kepler155c
7d09073703 app tweaks 2018-11-09 15:07:12 -05:00
kepler155c
e59a7a59a4 app tweaks 2018-11-09 11:35:27 -05:00
kepler155c
5e744be252 app tweaks 2018-11-08 22:38:04 -05:00
kepler155c
85b1d1d52e app tweaks 2018-11-08 15:21:57 -05:00
kepler155c
0893164868 refuel with lava program 2018-11-08 12:43:00 -05:00
kepler155c
a0dd3a7b6d farm cocoa beans 2018-11-08 11:50:58 -05:00
kepler155c
6db34bc4f9 scanning miner fixes 2018-11-08 10:26:20 -05:00
kepler155c
3098cd86bf fix neural autorun 2018-11-06 17:28:41 -05:00
kepler155c
01452ced72 package manager + tweaks to milo 2018-11-06 16:21:31 -05:00
kepler155c
d113b1da1a milo wip 2018-11-05 00:46:07 -05:00
kepler155c
6de154dc48 milo wip 2018-11-05 00:26:28 -05:00
kepler155c
ed6f95ef94 package manager 2018-11-04 13:01:10 -05:00
kepler155c
4d65ebe24c remove hardcoded devices from cows.lua 2018-11-03 19:45:30 -04:00
kepler155c
1f7ef4a483 package management 2018-11-03 18:14:11 -04:00
kepler155c
aa66b1c663 tweaks and shatter 2018-11-03 04:47:52 -04:00
kepler155c
047c2cdddb milo wip 2018-11-02 21:42:47 -04:00
kepler155c
10bbb5ebf4 scanning miner tweak 2018-11-01 21:15:18 -04:00
kepler155c
fa109c40ed scanning miner tweaks 2018-11-01 17:13:27 -04:00
kepler155c
93a31994b7 scanning miner tweaks 2018-11-01 15:28:43 -04:00
kepler155c
44f8e61f2f milo wip 2018-10-31 20:10:27 -04:00
kepler155c
865d642e5c milo wip 2018-10-31 19:38:54 -04:00
kepler155c
4d3a896d6b rename debug function + milo wip 2018-10-31 00:05:56 -04:00
kepler155c
871f44a170 milo wip 2018-10-29 22:00:59 -04:00
kepler155c
f24aeb135d milo wip 2018-10-29 17:15:07 -04:00
kepler155c
46ed01630d milo perf 2018-10-29 16:04:41 -04:00
kepler155c
ff892dfac2 milo wip 2018-10-29 02:45:13 -04:00
kepler155c
906d26112b milo wip 2018-10-28 23:37:44 -04:00
kepler155c
ca22b49aaf milo wip 2018-10-28 20:36:40 -04:00
kepler155c
1c70085450 milo storage filter + crafting fixes 2018-10-28 15:24:12 -04:00
kepler155c
9d45460a3b brewing station auto learn 2018-10-28 04:23:24 -04:00
kepler155c
b96c064431 brewing station auto learn 2018-10-28 03:24:32 -04:00
kepler155c
763f6bb27a milo wip 2018-10-27 23:47:28 -04:00
kepler155c
dc6af1d0c3 milo wip 2018-10-27 23:16:16 -04:00
kepler155c
57e7a574c4 milo wip 2018-10-27 21:35:21 -04:00
kepler155c
d227d6ebb7 milo wip 2018-10-26 23:08:44 -04:00
kepler155c
87da95ca05 milo wip 2018-10-26 18:44:42 -04:00
kepler155c
f9ae197cfc storage refactor - void option 2018-10-26 16:08:07 -04:00
kepler155c
6dde2245a6 milo wip 2018-10-26 06:19:34 -04:00
kepler155c
2be14cdd43 milo wip 2018-10-26 01:24:29 -04:00
kepler155c
9ee67c1fad milo wip 2018-10-26 01:03:31 -04:00
kepler155c
6d3389c9c6 milo wip 2018-10-26 00:30:37 -04:00
kepler155c
eb6ab3ed08 milo wip 2018-10-25 17:24:50 -04:00
kepler155c
ceb558dcff milo wip 2018-10-25 17:12:37 -04:00
kepler155c
9f9dcafc60 milo wip 2018-10-25 05:51:46 -04:00
kepler155c
6ce4039e8a milo wip 2018-10-25 00:18:38 -04:00
kepler155c
d5b0ad73b9 milo wip 2018-10-24 19:52:27 -04:00
kepler155c
3687df3d36 milo wip 2018-10-24 19:12:03 -04:00
kepler155c
55653aa494 milo wip 2018-10-24 09:00:06 -04:00
kepler155c
eb3cdad008 milo wip 2018-10-24 08:24:44 -04:00
kepler155c
27f38032ca milo wip 2018-10-24 06:52:46 -04:00
kepler155c
e45aad7ed0 milo wip 2018-10-24 06:51:05 -04:00
kepler155c
9664077f2e plethora crafter rename to milo - wip 2018-10-23 22:32:25 -04:00
kepler155c
a19960959b lora wip 2018-10-23 03:04:34 -04:00
kepler155c
f40f7ae7a8 lora wip 2018-10-22 01:24:36 -04:00
kepler155c
9e5487b100 Lora major refactor 2018-10-21 18:43:04 -04:00
kepler155c
f56611d957 plethora autocrafting wip 2018-10-21 04:44:10 -04:00
kepler155c
782850b94d plethora autocrafting wip 2018-10-21 00:38:24 -04:00
kepler155c
d5c102054a plethora networked autocrafting 2018-10-20 23:53:49 -04:00
kepler155c
0db3ad47bb plethora networked autocrafting 2018-10-20 20:36:16 -04:00
kepler155c
1290ca5486 scanner app improvements 2018-10-19 19:34:29 -04:00
kepler155c
51e73e1c98 new cow breeder app 2018-10-17 20:16:01 -04:00
kepler155c
72bf4d89ba new cow breeder app 2018-10-17 20:13:32 -04:00
kepler155c
21c1827682 new cow breeder app 2018-10-17 19:27:00 -04:00
kepler155c
e8b69254fd plethora app fixes 2018-10-17 01:15:04 -04:00
kepler155c
93b1635de2 farmer drop items into chest 2018-10-16 22:26:05 -04:00
kepler155c
d4296d2b8b farmer drop items into chest 2018-10-16 22:18:15 -04:00
kepler155c
b4f7e3a69c farmer drop items into chest 2018-10-16 22:17:08 -04:00
kepler155c
a4121c50af farmer drop items into chest 2018-10-16 21:37:48 -04:00
kepler155c
1da12b8342 plethora app fixes 2018-10-16 19:10:11 -04:00
kepler155c
611e4676a4 use scanner for farm heading + scanning miner fixes 2018-10-15 21:14:37 -04:00
kepler155c
77aada762f new icon set + scanning miner 2018-10-15 16:07:03 -04:00
kepler155c
755c37bc9b new icon set + scanning miner 2018-10-14 15:37:54 -04:00
kepler155c
708476b734 builder error on resupply page 2018-04-03 17:16:21 -04:00
kepler155c
0bda31a574 builder error on resupply page 2018-04-03 13:11:38 -04:00
kepler155c
b712accfc0 1.12 updates 2018-04-01 18:51:47 -04:00
kepler155c
23368d1112 1.12 updates 2018-04-01 18:28:46 -04:00
kepler155c
b0d5738a1f 1.12 fixes 2018-03-30 23:11:33 -04:00
kepler155c
dc67a4e9ac plethora import/export support 2018-03-29 21:50:27 -04:00
kepler155c
0441662514 wireless crafting 2018-03-29 21:43:16 -04:00
kepler155c
5a332d8c35 rttp initial version -- insecure 2018-03-29 21:23:12 -04:00
kepler155c
d82cef0546 plethora import/export support 2018-03-29 17:47:16 -04:00
kepler155c
1bf79de220 plethora import/export support 2018-03-29 14:22:02 -04:00
kepler155c
3d1ee74f0f plethora import/export support + introspection module 2018-03-29 07:04:56 -04:00
kepler155c
08bc0d578e plethora import/export support + introspection module 2018-03-29 05:04:34 -04:00
kepler155c
55f49a1127 1.7 compability 2018-03-28 18:37:38 -04:00
kepler155c
767efbae7d builder debug 2018-03-26 19:37:34 -04:00
kepler155c
8590f867bd refined storage initial update - wip 2018-03-26 17:16:46 -04:00
kepler155c
76590d6638 oops 2018-03-26 15:23:35 -04:00
kepler155c
e532ee60f3 refined storage initial update - wip 2018-03-26 15:13:12 -04:00
kepler155c
f62b760174 bug due to adapter rework 2018-03-23 13:06:21 -04:00
kepler155c
14a62585d8 1.7.10 compatibility 2018-03-21 13:33:41 -04:00
kepler155c
8b2620d4aa 1.7.10 compatibility 2018-03-21 12:34:47 -04:00
kepler155c
9986857ef1 1.7.10 compatibility 2018-03-21 11:34:28 -04:00
kepler155c
dce51a0903 1.7.10 compatibility 2018-03-20 05:12:54 -04:00
kepler155c
b1afacfbb1 1.7.10 compatibility 2018-03-20 05:00:52 -04:00
kepler155c
a9c7672fbf 1.7.10 compatibility 2018-03-20 04:36:47 -04:00
kepler155c
2f8cb22105 1.7.10 compatibility 2018-03-16 15:22:14 -04:00
kepler155c
df72e11a45 1.7.10 compatibility 2018-03-14 09:36:27 -04:00
kepler155c
2743792ef3 1.7.10 compatibility 2018-03-14 08:45:43 -04:00
kepler155c
b798f392ad 1.7.10 compatibility 2018-03-13 12:27:24 -04:00
kepler155c
2e72b1884d 1.7.10 compatibility 2018-03-11 17:33:30 -04:00
kepler155c
f170a00f02 1.7.10 compatibility 2018-03-11 17:28:46 -04:00
kepler155c
c1f6ac11e1 1.7.10 compatibility 2018-03-11 17:24:12 -04:00
kepler155c
1e59c9301f 1.7.10 compatibility 2018-03-08 11:46:05 -05:00
kepler155c
d905e97aec 1.7.10 compatibility 2018-03-08 11:43:34 -05:00
kepler155c
8096a208e1 1.7.10 compatibility 2018-03-08 10:27:14 -05:00
kepler155c
715fc1c2c6 1.7.10 compatibility 2018-03-08 10:13:28 -05:00
kepler155c
71dfb8af4b 1.7.10 compatibility 2018-03-08 10:04:08 -05:00
kepler155c
37d3264999 1.7.10 compatibility 2018-03-07 22:43:50 -05:00
kepler155c
b970aa047e 1.7.10 compatibility 2018-03-07 22:39:32 -05:00
kepler155c
2caa51db7c ingredient max count fix 2018-03-06 18:46:33 -05:00
kepler155c
f08794079e ingredient max count fix 2018-03-06 18:09:03 -05:00
kepler155c
b2ba4b272d autocrafting count fix 2018-03-04 13:12:14 -05:00
kepler155c
a77c2f6ea0 autocrafting tools fix 2018-03-01 18:25:34 -05:00
kepler155c
3c73567c62 1.7.10 compatibility 2018-02-27 06:54:31 -05:00
kepler155c
b1696bd141 1.7.10 compatibility 2018-02-26 05:31:44 -05:00
kepler155c
ea3258ed7e crafter bug fixes 2018-02-26 05:12:17 -05:00
kepler155c
1eaeed35c7 1.7.10 compatibility 2018-02-25 11:35:54 -05:00
kepler155c
959058b5fb 1.7.10 compatibility 2018-02-25 07:05:19 -05:00
kepler155c
2bf2e41ee6 1.7.10 compatibility 2018-02-24 15:02:56 -05:00
kepler155c
3926ee1705 1.7.10 compatibility 2018-02-24 10:43:15 -05:00
kepler155c
759ed6bba7 1.7.10 compatibility 2018-02-24 09:38:25 -05:00
kepler155c
f38c968656 1.7.10 compatibility 2018-02-24 04:47:47 -05:00
kepler155c
00ab55be72 1.7.10 compatibility 2018-02-24 04:22:28 -05:00
kepler155c
1ad06ee70e 1.7.10 compatibility 2018-02-24 03:42:13 -05:00
kepler155c
0524f35e57 1.7.10 compatibility 2018-02-24 03:25:01 -05:00
kepler155c
af6435682b 1.7.10 compatibility 2018-02-23 13:07:45 -05:00
kepler155c
77b2632f3f crafter bug fixes 2018-02-23 12:44:52 -05:00
kepler155c
f253afd221 crafter bug fixes 2018-02-23 12:37:06 -05:00
kepler155c
90024b3460 crafter bug fixes 2018-02-23 08:28:44 -05:00
kepler155c
a58ad48b69 crafter bug fixes 2018-02-23 00:10:33 -05:00
kepler155c
71677604e0 crafter bug fixes 2018-02-23 00:02:17 -05:00
kepler155c
ba308022bb 1.7.10 compatibility 2018-02-21 13:06:43 -05:00
kepler155c
3dd8341605 1.7.10 compatibility 2018-02-21 11:27:10 -05:00
kepler155c
e37a10c931 1.7.10 compatibility 2018-02-21 09:40:01 -05:00
kepler155c
038e6b185b 1.7.10 compatibility 2018-02-21 09:34:22 -05:00
kepler155c
4df6923062 1.7.10 compatibility 2018-02-21 09:24:07 -05:00
kepler155c
b92b620e4d fuzzy recipe ingredients 2018-02-21 04:44:37 -05:00
kepler155c
ade176e977 fix for not finding name 2018-02-21 04:21:49 -05:00
kepler155c
3e8b01be93 1.7.10 compatibility 2018-02-20 23:59:33 -05:00
kepler155c
6eff117d4a fuzzy recipe ingredients 2018-02-20 01:22:25 -05:00
kepler155c
cfc1502f2a fuzzy recipe ingredients 2018-02-20 01:15:31 -05:00
kepler155c
2cfa6c8c7f recipe book selection 2018-02-19 22:19:01 -05:00
kepler155c
1bee7c97f7 1.7.10 compatibility 2018-02-18 12:37:50 -05:00
kepler155c
74fb1d0fbc 1.7.10 compatibility 2018-02-18 12:29:03 -05:00
kepler155c
ebad012099 1.7.10 compatibility 2018-02-17 04:51:04 -05:00
kepler155c
d7b91eca7b 1.7.10 compatibility 2018-02-17 01:48:34 -05:00
kepler155c
204777ba41 1.7.10 compatibility 2018-02-16 23:17:31 -05:00
kepler155c
3d9cdcc10f 1.7.10 compatibility 2018-02-15 11:28:14 -05:00
kepler155c
fe0ad66618 1.7.10 compatibility 2018-02-15 11:26:34 -05:00
kepler155c
398e0bfaec 1.7.10 compatibility 2018-02-13 23:35:11 -05:00
kepler155c
2a8617dab1 persist utility 2018-02-11 06:47:57 -05:00
kepler155c
ebe55c265e use new installer 2018-02-08 02:44:50 -05:00
kepler155c
c874d3e0ea error handling 2018-02-06 08:48:50 -05:00
kepler155c
dcb9e9e73a robot wars 2018-02-05 22:42:08 -05:00
kepler155c
059bcbaedb robot wars 2018-02-05 06:20:49 -05:00
kepler155c
fc394801a1 robot wars 2018-02-05 06:17:26 -05:00
kepler155c
2343823734 tweak crafter 2018-02-04 23:42:32 -05:00
kepler155c
9920c0b65a tweak crafter 2018-02-04 23:25:02 -05:00
kepler155c
37e15489ee handle AE again 2018-02-03 06:23:04 -05:00
kepler155c
3298c41f3a handle AE again 2018-02-02 16:53:50 -05:00
kepler155c
8d8c18c3db handle AE again 2018-02-02 16:50:55 -05:00
kepler155c
bfe1d5a789 handle AE again 2018-02-02 16:34:12 -05:00
kepler155c
d5b550d074 handle AE again 2018-02-02 16:31:15 -05:00
kepler155c
d1f16ddc0a handle AE again 2018-02-02 16:29:24 -05:00
kepler155c
86d427fd13 handle AE again 2018-02-02 16:22:27 -05:00
kepler155c
e8824aec3a recipes 2018-01-27 03:28:12 -05:00
kepler155c
6dbb2774b4 autocrafting fix 2018-01-27 00:37:00 -05:00
kepler155c
bcf08b0fe3 autocrafting fix 2018-01-26 07:19:42 -05:00
kepler155c
fcaaf87052 regressions 2018-01-26 06:36:53 -05:00
kepler155c
1a0bf56b70 transition to kernel 2018-01-21 17:23:28 -05:00
kepler155c
663d8c3696 transition to kernel 2018-01-20 07:19:32 -05:00
kepler155c
40da3f1683 transition to kernel 2018-01-13 15:18:17 -05:00
kepler155c
f7893ab2fe autocrafting improvements 2018-01-10 16:48:23 -05:00
399 changed files with 56543 additions and 20716 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
/etc/fstab /etc/fstab
/etc/recipes2.db /etc/recipes2.db
/.vscode

View File

@@ -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

View File

@@ -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

View File

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

View File

@@ -1,101 +0,0 @@
local Blocks = require('builder.blocks')
local class = require('class')
local Message = require('message')
local Util = require('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

View File

@@ -1,84 +0,0 @@
local Builder = require('builder.builder')
local Event = require('event')
local Util = require('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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,150 +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
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
if not itemDB:get(v) then
local t = { }
for _,k in pairs(keys) do
t[k] = v[k]
end
itemDB:add(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()
end
function ChestAdapter:craftItems()
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
os.sleep(1)
pcall(function() self.pullItem(self.direction, slot, qty) end)
end
end
return ChestAdapter

View File

@@ -1,162 +0,0 @@
local class = require('class')
local Util = require('util')
local itemDB = require('itemDB')
local Peripheral = require('peripheral')
local ChestAdapter = class()
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('list')
else
chest = Peripheral.getBySide(self.side)
if chest and not chest.list then
chest = nil
end
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 cached = itemDB:get(item)
if cached then
return cached
end
local s, detail = pcall(self.getItemMeta, k)
if not s or not detail or detail.name ~= item.name then
-- debug({ s, detail })
-- error('Inventory has changed')
return
end
return itemDB:add(detail)
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()
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()
if not Util.empty(items) then
self.cache = cache
return items
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:craft()
end
function ChestAdapter:craftItems()
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 s, m = pcall(function()
local stacks = self.list()
for key,stack in Util.rpairs(stacks) do
if stack.name == item.name and
(not item.damage or stack.damage == item.damage) and
(not item.nbtHash or stack.nbtHash == item.nbtHash) 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)
return s, m
end
function ChestAdapter:eject(item, qty, direction)
local s, m = pcall(function()
local stacks = self.list()
local maxStack = itemDB:getMaxCount(item)
for key,stack in Util.rpairs(stacks) do
if stack.name == item.name and
(not item.damage or stack.damage == item.damage) and
(not item.nbtHash or stack.nbtHash == item.nbtHash) then
local amount = math.min(maxStack, math.min(qty, stack.count))
if amount > 0 then
self.drop(key, amount, direction)
end
qty = qty - amount
if qty <= 0 then
break
end
end
end
end)
return s, m
end
function ChestAdapter:extract(slot, qty, toSlot)
self.pushItems(self.direction, slot, qty, toSlot)
end
function ChestAdapter:insert(slot, qty, toSlot)
self.pullItems(self.direction, slot, qty, toSlot)
end
return ChestAdapter

View File

@@ -1,18 +0,0 @@
local Adapter = { }
function Adapter.wrap(args)
local adapters = {
'refinedAdapter',
'meAdapter',
}
for _,adapterType in ipairs(adapters) do
local adapter = require(adapterType)(args)
if adapter:isValid() then
return adapter
end
end
end
return Adapter

View File

@@ -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

View File

@@ -1,52 +0,0 @@
local Util = require('util')
local Adapter = { }
function Adapter.wrap(args)
local adapters = {
--'refinedAdapter',
'chestAdapter18',
'meAdapter',
'chestAdapter',
}
if args and args.side and args.facing and not args.direction then
args = Util.shallowCopy(args)
local horz = { top = 'down', bottom = 'up' }
args.direction = horz[args.side]
if not args.direction then
local sides = {
front = 0,
back = 2,
right = 1,
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',
}
args.direction = icards[(cards[args.facing] + sides[args.side]) % 4]
end
end
for _,adapterType in ipairs(adapters) do
local adapter = require(adapterType)(args)
if adapter:isValid() then
return adapter
end
end
end
return Adapter

View File

@@ -1,197 +0,0 @@
local nameDB = require('nameDB')
local TableDB = require('tableDB')
local Util = require('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
local function makeKey(item)
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)
if type(key) == 'string' then
key = self:splitKey(key)
end
local item = TableDB.get(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, 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
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
--[[
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
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
TableDB.add(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
return nameDB:getName(item.name .. ':' .. (item.damage or '*'))
end
function itemDB:getMaxCount(item)
local detail = self:get(item)
if detail then
return detail.maxCount
end
return 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

View File

@@ -1,245 +0,0 @@
local class = require('class')
local itemDB = require('itemDB')
local Peripheral = require('peripheral')
local Util = require('util')
local os = _G.os
local peripheral = _G.peripheral
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
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 = self.getAvailableItems('all')
for _,v in pairs(self.items) do
Util.merge(v, v.item)
convertItem(v)
if not itemDB:get(v) then
itemDB:add(v, v)
end
end
itemDB:flush()
return self.items
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,
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 = 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
os.sleep(1)
s, m = pcall(function() self.pullItem(self.direction, slot, count) end)
if not s and m then
error(m)
end
end
end
return MEAdapter

View File

@@ -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

View File

@@ -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
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

View File

@@ -1,149 +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',
direction = 'up',
wrapSide = 'bottom',
}
Util.merge(self, defaults)
Util.merge(self, args)
local controller
if self.autoDetect then
controller = Peripheral.getByType('refinedstorage:controller')
else
controller = Peripheral.getBySide(self.wrapSide)
if controller and not controller.listAvailableItems then
controller = nil
end
end
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 detail = itemDB:get(item)
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(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 item = itemDB:get(fingerprint)
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()
return false
end
function RefinedAdapter:provide()
end
function RefinedAdapter:extract()
-- self.pushItems(self.direction, slot, qty)
end
function RefinedAdapter:insert()
-- self.pullItems(self.direction, slot, qty)
end
return RefinedAdapter

View File

@@ -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

View File

@@ -1,299 +0,0 @@
local itemDB = require('itemDB')
local Util = require('util')
local fs = _G.fs
local turtle = _G.turtle
local RECIPES_DIR = 'usr/etc/recipes'
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
-- inventory is possibly full
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
function Craft.getItemCount(items, item)
if type(item) == 'string' then
item = splitKey(item)
end
local count = 0
for _,v in pairs(items) do
if v.name == item.name and
(not item.damage or v.damage == item.damage) and
v.nbtHash == item.nbtHash then
if item.damage then
return v.count
end
count = count + v.count
end
end
return count
end
local function turtleCraft(recipe, qty, inventoryAdapter)
if not clearGrid(inventoryAdapter) then
return false
end
for k,v in pairs(recipe.ingredients) do
local item = splitKey(v)
local provideQty = qty
--[[
Turtles can only craft 1 item at a time when using a tool.
if recipe.craftingTools and recipe.craftingTools[k] then
provideQty = 1
end
]]--
inventoryAdapter:provide(item, provideQty, k)
if turtle.getItemCount(k) == 0 then -- ~= qty then
-- FIX: ingredients cannot be stacked
--debug('failed ' .. v .. ' - ' .. provideQty)
return false
end
end
return turtle.craft()
end
function Craft.loadRecipes()
Craft.recipes = Util.readTable(fs.combine(RECIPES_DIR, 'minecraft.db')) or { }
local files = fs.list('usr/etc/recipes')
table.sort(files)
Util.removeByValue(files, 'minecraft.db')
for _,file in ipairs(files) do
local recipes = Util.readTable(fs.combine(RECIPES_DIR, file))
Util.merge(Craft.recipes, recipes)
end
local recipes = Util.readTable('usr/config/recipes.db') or { }
Util.merge(Craft.recipes, recipes)
end
function Craft.sumIngredients(recipe)
-- produces { ['minecraft:planks:0'] = 8 }
local t = { }
for _,item in pairs(recipe.ingredients) do
t[item] = (t[item] or 0) + 1
end
-- need a check for crafting tool
return t
end
function Craft.craftRecipe(recipe, count, inventoryAdapter)
if type(recipe) == 'string' then
recipe = Craft.recipes[recipe]
if not recipe then
return 0, 'No recipe'
end
end
local items = inventoryAdapter:listItems()
if not items then
return 0, 'Inventory changed'
end
count = math.ceil(count / recipe.count)
local maxCount = recipe.maxCount or math.floor(64 / recipe.count)
for key,icount in pairs(Craft.sumIngredients(recipe)) do
local itemCount = Craft.getItemCount(items, key)
if itemCount < icount * count then
local irecipe = Craft.recipes[key]
if irecipe then
local iqty = icount * count - itemCount
local crafted = Craft.craftRecipe(irecipe, iqty, inventoryAdapter)
if crafted ~= iqty then
turtle.select(1)
return 0
end
end
end
end
local crafted = 0
repeat
if not turtleCraft(recipe, math.min(count, maxCount), inventoryAdapter) then
turtle.select(1)
break
end
crafted = crafted + math.min(count, maxCount)
count = count - maxCount
until count <= 0
turtle.select(1)
return crafted * recipe.count
end
local function makeRecipeKey(item)
if type(item) == 'string' then
item = splitKey(item)
end
return table.concat({ item.name, item.damage or 0, item.nbtHash }, ':')
end
function Craft.findRecipe(item)
return Craft.recipes[makeRecipeKey(item)]
end
-- determine the full list of ingredients needed to craft
-- a quantity of a recipe.
function Craft.getResourceList(recipe, items, inCount)
local summed = { }
local function sumItems(key, count)
local item = itemDB:splitKey(key)
local summedItem = summed[key]
if not summedItem then
summedItem = Util.shallowCopy(item)
summedItem.recipe = Craft.findRecipe(key)
summedItem.count = Craft.getItemCount(items, item)
summedItem.displayName = itemDB:getName(item)
summedItem.total = 0
summedItem.need = 0
summedItem.used = 0
summed[key] = summedItem
end
local total = count
local used = math.min(summedItem.count, total)
local need = total - used
if summedItem.recipe and summedItem.recipe.craftingTools and summedItem.recipe.craftingTools[key] then
summedItem.total = 1
if summedItem.count > 0 then
summedItem.used = 1
summedItem.need = 0
need = 0
else
if not summedItem.recipe then
summedItem.need = 1
need = 1
end
end
else
summedItem.total = summedItem.total + total
summedItem.count = summedItem.count - used
summedItem.used = summedItem.used + used
if not summedItem.recipe then
summedItem.need = summedItem.need + need
end
end
if need > 0 and summedItem.recipe then
need = math.ceil(need / summedItem.recipe.count)
for ikey,iqty in pairs(Craft.sumIngredients(summedItem.recipe)) do
sumItems(ikey, math.ceil(need * iqty))
end
end
end
inCount = math.ceil(inCount / recipe.count)
for ikey,iqty in pairs(Craft.sumIngredients(recipe)) do
sumItems(ikey, math.ceil(inCount * iqty))
end
return summed
end
function Craft.getResourceList4(inRecipe, items, count)
local summed = Craft.getResourceList(inRecipe, items, count)
-- filter down to just raw materials
return Util.filter(summed, function(a) return a.used > 0 or a.need > 0 end)
end
-- given a certain quantity, return how many of those can be crafted
function Craft.getCraftableAmount(recipe, count, items, missing)
local function sumItems(recipe, summedItems, count)
local canCraft = 0
for _ = 1, count do
for _,item in pairs(recipe.ingredients) do
local summedItem = summedItems[item] or Craft.getItemCount(items, item)
local irecipe = Craft.recipes[item]
if irecipe and summedItem <= 0 then
summedItem = summedItem + sumItems(irecipe, summedItems, 1)
end
if summedItem <= 0 then
if missing then
missing.name = item
end
return canCraft
end
if not recipe.craftingTools or not recipe.craftingTools[item] then
summedItems[item] = summedItem - 1
end
end
canCraft = canCraft + recipe.count
end
return canCraft
end
return sumItems(recipe, { }, math.ceil(count / recipe.count))
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
Craft.loadRecipes()
return Craft

View File

@@ -1,67 +0,0 @@
local Adapter = require('inventoryAdapter')
local Craft = require('turtle.craft')
local turtle = _G.turtle
local CRAFTING_TABLE = 'minecraft:crafting_table'
local function clearGrid(inventory)
print('clearing')
for i = 1, 16 do
local count = turtle.getItemCount(i)
if count > 0 then
inventory:insert(i, count)
if turtle.getItemCount(i) ~= 0 then
print('failed to insert')
return false
end
end
end
return true
end
function turtle.craftItem(item, count, inventoryInfo)
local success, msg
local inventory = Adapter.wrap(inventoryInfo)
if not inventory then
return false, 'Invalid inventory'
end
local equipped, side
if not turtle.isEquipped('workbench') then
local modemSide = turtle.isEquipped('modem') or 'right'
local osides = { left = 'right', right = 'left' }
side = osides[modemSide]
if not turtle.select(CRAFTING_TABLE) then
clearGrid(inventory)
if not turtle.selectOpenSlot() then
return false, 'Inventory is full'
end
if not inventory:provide({ name = CRAFTING_TABLE, damage = 0 }, 1) then
return false, 'Missing crafting table'
end
end
local slot = turtle.select(CRAFTING_TABLE)
turtle.equip(side, CRAFTING_TABLE)
equipped = turtle.getItemDetail(slot.index)
end
clearGrid(inventory)
success, msg = Craft.craftRecipe(item, count or 1, inventory)
if equipped then
turtle.selectOpenSlot()
inventory:provide({ name = equipped.name, damage = equipped.damage }, 1)
turtle.equip(side, equipped.name .. ':' .. equipped.damage)
end
return success, msg
end
function turtle.canCraft(item, count, items)
return Craft.canCraft(item, count, items)
end
return true

View File

@@ -1,42 +0,0 @@
local Config = require('config')
local GPS = require('gps')
local turtle = _G.turtle
local Home = { }
function Home.go()
local config = { }
Config.load('gps', config)
if config.home then
if turtle.enableGPS() then
return turtle.pathfind(config.home)
end
end
end
function Home.set()
local config = { }
Config.load('gps', config)
local pt = GPS.getPoint()
if pt then
local originalHeading = turtle.point.heading
local heading = GPS.getHeading()
if heading then
local turns = (turtle.point.heading - originalHeading) % 4
pt.heading = (heading - turns) % 4
config.home = pt
Config.update('gps', config)
pt = GPS.getPoint()
pt.heading = heading
turtle.setPoint(pt, true)
turtle._goto(config.home)
return config.home
end
end
end
return Home

View File

@@ -1,170 +0,0 @@
local Point = require('point')
local Util = require('util')
local turtle = _G.turtle
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
local 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
function turtle.level(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 Point.inBox(firstPt, box) then
error('Starting point is not in leveling area')
end
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._goto(node) then
break
end
until turtle.isAborted()
turtle.resetState()
turtle.setMoveCallback(oldCallback)
end
return true

View File

@@ -1,412 +0,0 @@
_G.requireInjector()
local Ansi = require('ansi')
local SHA1 = require('sha1')
local UI = require('ui')
local Util = require('util')
local fs = _G.fs
local http = _G.http
local multishell = _ENV.multishell
local os = _G.os
local shell = _ENV.shell
local REGISTRY_DIR = 'usr/.registry'
-- FIX SOMEDAY
local function registerApp(app, key)
app.key = SHA1.sha1(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, SHA1.sha1(key))
if fs.exists(filename) then
fs.delete(filename)
os.queueEvent('os_register_app')
end
end
local sandboxEnv = Util.shallowCopy(_ENV)
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)
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 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 = '\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 = {
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
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 = 'Catalog', dropdown = sources },
{ text = 'Category', name = 'categoryButton', dropdown = { } },
},
},
grid = UI.ScrollingGrid {
y = 2, ey = -2,
columns = {
{ heading = 'Title', key = 'title' },
},
sortColumn = 'title',
},
statusBar = UI.StatusBar(),
accelerators = {
l = 'lua',
q = 'quit',
},
}
function categoryPage:setCategory(source, 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', 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({
buttons = buttons,
})
source.index, source.name = Util.first(source.storeCatagoryNames)
categoryPage.menuBar.categoryButton:add({
categoryMenu = source.categoryMenu
})
end
self.source = source
self.menuBar.categoryButton.dropmenu = 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()

View File

@@ -1,140 +0,0 @@
_G.requireInjector()
local Event = require('event')
local UI = require('ui')
local Util = require('util')
local multishell = _ENV.multishell
local os = _G.os
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 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()

View File

@@ -1,208 +0,0 @@
_G.requireInjector()
local Ansi = require('ansi')
local Event = require('event')
local UI = require('ui')
local Util = require('util')
local colors = _G.colors
local multishell = _ENV.multishell
local peripheral = _G.peripheral
multishell.setTitle(multishell.getCurrent(), 'Devices')
--[[ -- PeripheralsPage -- ]] --
local peripheralsPage = UI.Page {
grid = UI.ScrollingGrid {
ey = -2,
columns = {
{ heading = 'Type', key = 'type' },
{ heading = 'Side', key = 'side' },
},
sortColumn = 'type',
autospace = true,
},
statusBar = UI.StatusBar {
values = '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 {
backgroundColor = colors.black,
doc = UI.TextArea {
backgroundColor = colors.black,
x = 2, y = 2, ex = -1, ey = -7,
},
grid = UI.ScrollingGrid {
y = -6, ey = -2,
columns = {
{ heading = 'Name', key = 'name', width = UI.term.width }
},
sortColumn = 'name',
},
statusBar = UI.StatusBar {
status = 'q to return',
},
accelerators = {
q = 'back',
backspace = 'back',
},
}
function methodsPage:enable(p)
self.peripheral = p or self.peripheral
p = peripheral.wrap(self.peripheral.side)
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 name in pairs(p) do
table.insert(self.grid.values, {
name = name,
noext = true,
})
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 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:pullEvents()

View File

@@ -1,375 +0,0 @@
_G.requireInjector()
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')
local colors = _G.colors
local fs = _G.fs
local multishell = _ENV.multishell
local network = _G.network
local os = _G.os
local shell = _ENV.shell
local term = _G.term
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 socket
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,
inactive = true,
}
},
},
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 _ = 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
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: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 == 'tab_select' then
config.tab = event.button.text
Config.update('Turtles', config)
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 _ = 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)
if config.tab then
page.tabs.tabBar:selectTab(config.tab)
end
UI:setPage(page)
UI:pullEvents()

View File

@@ -1,781 +0,0 @@
if not _G.turtle and not _G.commands then
error('Must be run on a turtle or a command computer')
end
_G.requireInjector()
local Adapter = require('inventoryAdapter')
local Event = require('event')
local GPS = require('gps')
local itemDB = require('itemDB')
local Schematic = require('builder.schematic')
local TableDB = require('tableDB')
local UI = require('ui')
local Util = require('util')
local colors = _G.colors
local fs = _G.fs
local multishell = _ENV.multishell
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:setCursorPos(1, 1)
self:print(' Replace ' .. inName .. '\n')
--self:print(' ' .. sub.id .. ':' .. sub.dmg .. '\n', nil, colors.yellow)
self:print(' 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.value = ''
self.menuBar.filter.pos = 1
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
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 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' }
}
},
throttle = UI.Throttle { },
accelerators = {
x = 'test',
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:eventHandler(event)
if event.type == 'startLevel' then
local dialog = UI.Dialog({
title = 'Enter Starting Level',
height = 7,
form = UI.Form {
y = 3, x = 2, height = 4,
text = UI.Text({ x = 5, y = 1, textColor = colors.gray,
value = '0 - ' .. Builder.schematic.height }),
textEntry = UI.TextEntry({ x = 15, y = 1, '0 - 11', width = 7 }),
},
statusBar = UI.StatusBar(),
})
function dialog:eventHandler(event)
if event.type == 'form_complete' then
local l = tonumber(self.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)
UI:setPreviousPage()
break
end
end
else
self.statusBar:timedStatus('Invalid Level', 3)
end
elseif event.type == 'form_cancel' or event.type == 'cancel' then
UI:setPreviousPage()
else
return UI.Dialog.eventHandler(self, event)
end
return true
end
dialog:setFocus(dialog.form.textEntry)
UI:setPage(dialog)
elseif event.type == 'startBlock' then
local dialog = UI.Dialog {
title = 'Enter Block Number',
height = 7,
form = UI.Form {
y = 3, x = 2, height = 4,
text = UI.Text { x = 2, y = 1,
value = '1 - ' .. #Builder.schematic.blocks, textColor = colors.gray },
textEntry = UI.TextEntry { x = 16, y = 1,
value = tostring(Builder.index), width = 10, limit = 8 }
},
statusBar = UI.StatusBar(),
}
function dialog:eventHandler(event)
if event.type == 'form_complete' then
local bn = tonumber(self.form.textEntry.value)
if bn and bn < #Builder.schematic.blocks and bn >= 0 then
Builder.index = bn
Builder:saveProgress(Builder.index)
UI:setPreviousPage()
else
self.statusBar:timedStatus('Invalid Block', 3)
end
elseif event.type == 'form_cancel' or event.type == 'cancel' then
UI:setPreviousPage()
else
return UI.Dialog.eventHandler(self, event)
end
return true
end
dialog:setFocus(dialog.form.textEntry)
UI:setPage(dialog)
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
local dialog = UI.Dialog {
title = 'Set starting point',
height = 11,
width = 30,
form = UI.Form {
y = 2, x = 2, ey = -2,
values = loc,
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' },
},
statusBar = UI.StatusBar({ values = 'Optional start point'}),
}
function dialog:eventHandler(event)
if event.type == 'form_complete' then
for k,v in pairs(loc) do
Builder.loc[k] = tonumber(v)
end
Builder:saveProgress(Builder.index)
UI:setPreviousPage()
elseif event.type == 'revert' then
Builder.loc = { }
Builder:saveProgress(Builder.index)
UI:setPreviousPage()
elseif event.type == 'form_invalid' then
self.statusBar:setStatus(event.message)
elseif event.type == 'form_cancel' or event.type == 'cancel' then
UI:setPreviousPage()
else
return UI.Dialog.eventHandler(self, event)
end
return true
end
UI:setPage(dialog)
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')
print('Reloading schematic')
Builder:reloadSchematic(Util.throttle())
Builder:begin()
elseif event.type == 'quit' then
UI.term:reset()
Event.exitPullEvents()
end
return UI.Page.eventHandler(self, event)
end
--[[-- startup logic --]]--
local args = {...}
if #args < 1 then
error('supply file name')
end
Builder.itemAdapter = Adapter.wrap()
if not Builder.itemAdapter then
error('A chest or ME interface must be below turtle')
end
multishell.setTitle(multishell.getCurrent(), 'Builder')
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:pullEvents()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,376 +0,0 @@
_G.requireInjector()
local InventoryAdapter = require('inventoryAdapter')
local Config = require('config')
local Event = require('event')
local itemDB = require('itemDB')
local UI = require('ui')
local Util = require('util')
local colors = _G.colors
local multishell = _ENV.multishell
local rs = _G.rs
local RESOURCE_FILE = 'usr/config/levelEmitter.db'
multishell.setTitle(multishell.getCurrent(), 'Level Emitter')
local config = {
inventorySide = 'bottom',
}
Config.loadWithCheck('levelEmitter', config)
local inventoryAdapter = InventoryAdapter.wrap({ wrapSide = config.inventorySide })
if not inventoryAdapter then
error('No inventory found')
end
local resources
local function getItem(items, inItem, ignoreDamage, ignoreNbtHash)
for _,item in pairs(items) do
if item.name == inItem.name and
(ignoreDamage or item.damage == inItem.damage) and
(ignoreNbtHash or item.nbtHash == inItem.nbtHash) then
return item
end
end
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 _,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 getItemWithQty(items, res, ignoreDamage, ignoreNbtHash)
local item = getItem(items, res, ignoreDamage, ignoreNbtHash)
if item and (ignoreDamage or ignoreNbtHash) then
local count = 0
for _,v in pairs(items) do
if item.name == v.name and
(ignoreDamage or item.damage == v.damage) and
(ignoreNbtHash or item.nbtHash == v.nbtHash) then
count = count + v.count
end
end
item.count = count
end
return item
end
local function loadResources()
resources = Util.readTable(RESOURCE_FILE) or { }
for k,v in pairs(resources) do
Util.merge(v, itemDB:splitKey(k))
end
end
local function saveResources()
local t = { }
for k,v in pairs(resources) do
v = Util.shallowCopy(v)
local keys = Util.transpose({ 'limit', 'low', 'ignoreDamage', 'ignoreNbtHash' })
for _,key in pairs(Util.keys(v)) do
if not keys[key] then
v[key] = nil
end
end
if not Util.empty(v) then
t[k] = v
end
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 = 'Output a signal if below'
},
[2] = UI.TextEntry {
width = 7,
formLabel = 'Max', formKey = 'limit', help = 'Output a signal if above'
},
[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 = 'Ignore NBT', formKey = 'ignoreNbtHash',
nochoice = 'No',
choices = {
{ name = 'Yes', value = true },
{ name = 'No', value = false },
},
help = 'Ignore NBT of item'
},
},
statusBar = UI.StatusBar { }
}
function itemPage:enable(item)
self.item = Util.shallowCopy(item)
self.form:setValues(item)
self.titleBar.title = item.displayName or item.name
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 filtered = Util.shallowCopy(self.form.values)
if filtered.ignoreDamage == true then
filtered.damage = 0
else
filtered.ignoreDamage = nil
end
if filtered.ignoreNbtHash == true then
filtered.nbtHash = nil
else
filtered.ignoreNbtHash = nil
end
local originalKey = uniqueKey(self.item)
resources[originalKey] = nil
filtered.low = tonumber(filtered.low)
filtered.limit = tonumber(filtered.limit)
if filtered.limit or filtered.low then
resources[uniqueKey(filtered)] = filtered
else
resources[uniqueKey(filtered)] = nil
end
saveResources()
UI:setPreviousPage()
else
return UI.Page.eventHandler(self, event)
end
return true
end
local listingPage = UI.Page {
menuBar = UI.MenuBar {
buttons = {
{ text = 'Forget', event = 'forget' },
{ 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 = 'limit' , width = 4 },
{ heading = 'Max', key = 'low' , width = 4 },
},
sortColumn = 'displayName',
},
statusBar = UI.StatusBar {
filterText = UI.Text {
x = 2,
value = 'Filter',
},
filter = UI.TextEntry {
x = 9, ex = -5,
limit = 50,
backgroundColor = colors.gray,
backgroundFocusColor = colors.gray,
},
display = UI.Button {
x = -3,
event = 'toggle_display',
value = 0,
text = 'A',
},
},
accelerators = {
r = 'refresh',
q = 'quit',
},
displayMode = 0,
}
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: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 == 'toggle_display' then
local values = {
[0] = 'A',
[1] = 'I',
[2] = 'C',
}
event.button.value = (event.button.value + 1) % 3
self.displayMode = event.button.value
event.button.text = values[event.button.value]
event.button:draw()
self:applyFilter()
self.grid:draw()
elseif event.type == 'forget' then
local item = self.grid:getSelected()
if item then
local key = uniqueKey(item)
if resources[key] then
resources[key] = nil
saveResources()
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 filter = self.filter
local displayMode = self.displayMode
local t = self.allItems
if filter or displayMode > 0 then
t = { }
if filter then
filter = filter:lower()
end
for _,v in pairs(self.allItems) do
if not filter or string.find(v.lname, filter, 1, true) then
if not displayMode or
displayMode == 0 or
displayMode == 1 and v.count > 0 or
displayMode == 2 and v.has_recipe then
table.insert(t, v)
end
end
end
end
self.grid:setValues(t)
end
loadResources()
UI:setPages({
listing = listingPage,
item = itemPage,
})
UI:setPage(listingPage)
listingPage:setFocus(listingPage.statusBar.filter)
Event.onInterval(5, function()
local items = inventoryAdapter:listItems()
if items and Util.size(items) > 0 then
for _,res in pairs(resources) do
local item = getItemWithQty(items, res, res.ignoreDamage, res.ignoreNbtHash)
rs.setOutput('bottom', (res.limit and
item and item.count > res.limit) or
(res.low and
(not item or item.count < res.low)) or false)
end
end
end)
UI:pullEvents()

View File

@@ -1,126 +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
--[[-- ScrollingText --]]--
UI.ScrollingText = class(UI.Window)
UI.ScrollingText.defaults = {
UIElement = 'ScrollingText',
backgroundColor = colors.black,
buffer = { },
}
function UI.ScrollingText:appendLine(text)
if #self.buffer+1 >= self.height then
table.remove(self.buffer, 1)
end
table.insert(self.buffer, text)
end
function UI.ScrollingText:clear()
self.buffer = { }
UI.Window.clear(self)
end
function UI.ScrollingText:draw()
for k,text in ipairs(self.buffer) do
self:write(1, k, Util.widthify(text, self.width), self.backgroundColor)
end
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()

View File

@@ -1,28 +0,0 @@
_G.requireInjector()
local Terminal = require('terminal')
local shell = _ENV.shell
local term = _G.term
local args = { ... }
local mon = _G.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

View File

@@ -1,88 +0,0 @@
_G.requireInjector()
local Event = require('event')
local Logger = require('logger')
local Socket = require('socket')
local Util = require('util')
local multishell = _ENV.multishell
local os = _G.os
Logger.setScreenLogging()
local remoteId
local args = { ... }
if #args == 1 then
remoteId = tonumber(args[1])
else
print('Enter host ID')
remoteId = tonumber(_G.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

View File

@@ -1,52 +0,0 @@
_G.requireInjector()
local Event = require('event')
local Logger = require('logger')
local Socket = require('socket')
local colors = _G.colors
local term = _G.term
Logger.setScreenLogging()
local mon = term.current()
local args = { ... }
if args[1] then
mon = _G.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)
while true do
Event.pullEvent()
if not socket.connected then
break
end
end
print('connection lost')
socket:close()
end

View File

@@ -1,537 +0,0 @@
local injector = _G.requireInjector or load(http.get('https://raw.githubusercontent.com/kepler155c/opus/develop/sys/apis/injector.lua').readAll())()
injector()
local Canvas = require('ui.canvas')
local Util = require('util')
local colors = _G.colors
local os = _G.os
local peripheral = _G.peripheral
local printError = _G.printError
local shell = _ENV.shell
local term = _G.term
local window = _G.window
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 'usr/config/mwm'
local running
local monitor
local defaultEnv = Util.shallowCopy(_ENV)
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(filename)
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(filename, t)
end
function multishell.loadSession(filename)
local config = Util.readTable(filename)
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)

View File

@@ -1,632 +0,0 @@
_G.requireInjector()
local Pathing = require('turtle.pathfind')
local Point = require('point')
local Util = require('util')
local fs = _G.fs
local read = _G.read
local os = _G.os
local turtle = _G.turtle
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 = turtle.getFuelLimit()
local PROGRESS_FILE = 'usr/config/mining.progress'
local TRASH_FILE = 'usr/config/mining.trash'
local mining = {
diameter = 1,
chunkIndex = 0,
chunks = -1,
}
local trash, boreDirection, unload
local 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
local dir = dirs[math.floor(index / (diameter - 1)) + 1]
dir.x = x + dir.xd * 16
dir.z = z + dir.zd * 16
return dir
end
local 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)
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
local function getCornerOf(c)
return math.floor(c.x / 16) * 16, math.floor(c.z / 16) * 16
end
local 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
local function addTrash()
if not trash then
trash = { }
end
local slots = turtle.getFilledSlots()
for _,slot in pairs(slots) do
trash[slot.iddmg] = true
end
trash['minecraft:bucket:0'] = nil
Util.writeTable(TRASH_FILE, trash)
end
local function log(text)
print(text)
end
local function status(newStatus)
turtle.setStatus(newStatus)
log(newStatus)
end
local function refuel()
if turtle.getFuelLevel() < MIN_FUEL then
local oldStatus = turtle.getStatus()
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
local function safeGoto(x, z, y, h)
local oldStatus = turtle.getStatus()
-- only pathfind above or around other turtles (never down)
Pathing.setBox({ x = turtle.point.x, y = turtle.point.y, z = turtle.point.z, ex = x, ey = y, ez = z })
while not turtle.pathfind({ x = x, z = z, y = y or turtle.point.y, heading = h }) do
--status('stuck')
if turtle.isAborted() then
return false
end
os.sleep(3)
end
turtle.setStatus(oldStatus)
return true
end
local function safeGotoY(y)
local oldStatus = turtle.getStatus()
while not turtle.gotoY(y) do
status('stuck')
if turtle.isAborted() then
return false
end
os.sleep(1)
end
turtle.setStatus(oldStatus)
return true
end
local 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
--[[
local 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
]]
local function normalChestUnload()
local oldStatus = turtle.getStatus()
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, 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
local 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(slot.count - 36)
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
local function checkSpace()
if turtle.getItemCount(16) > 0 then
refuel()
local oldStatus = turtle.getStatus()
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
local function collectDrops(suckAction)
for _ = 1, 50 do
if not suckAction() then
break
end
checkSpace()
end
end
local 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
local 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 action.dig()
turtle.select('minecraft:diamond_pickaxe')
turtle.equipRight()
turtle.select(1)
return true
end
end
local 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
local function bore()
local loc = turtle.point
local level = loc.y
turtle.select(1)
status('boring down')
boreDirection = 'down'
while true do
if turtle.isAborted() then
status('aborting')
return false
end
if loc.y <= -mining.depth then
break
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.isAborted() then
status('aborting')
return false
end
while not Util.tryTimed(3, turtle.up) do
status('stuck')
end
if turtle.getStatus() == '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 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
local function inspect(action, name)
local r, block = action.inspect()
if r and block.name == name then
return true
end
end
local function boreCommand()
local pt = getClosestLocation(mining.locations)
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
-- TODO: this won't work - need to Util.merge file into mining
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(main)
if not s and m then
_G.printError(m)
end
turtle.abort(false)
safeGotoY(0)
safeGoto(0, 0, 0, 0)
unload()
turtle.reset()
end)

View File

@@ -1,164 +0,0 @@
_G.requireInjector()
local InventoryAdapter = require('inventoryAdapter')
local Event = require('event')
local UI = require('ui')
local Util = require('util')
local colors = _G.colors
local multishell = _ENV.multishell
local storage = InventoryAdapter.wrap()
if not storage then
error('Not connected to a valid inventory')
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 = 'Rate', key = 'rate', 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.grid:clear()
self.grid: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.timestamp = os.clock()
self.grid:setValues({ })
else
self.elapsed = os.clock() - self.timestamp
local changedItems = { }
local found
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 _,v in pairs(t) do
v.lastCount = 0
table.insert(changedItems, v)
end
for _,v in pairs(changedItems) do
v.change = v.count - v.lastCount
v.rate = Util.round(60 / self.elapsed * v.change, 1)
end
self.grid:setValues(changedItems)
end
self.grid:draw()
end
Event.onInterval(5, function()
changedPage:refresh()
changedPage:sync()
end)
UI:setPage(changedPage)
changedPage:draw()
UI:pullEvents()

View File

@@ -1,437 +0,0 @@
_G.requireInjector()
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')
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('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
_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(...)
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.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)

View File

@@ -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

View File

@@ -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

View File

@@ -1,736 +0,0 @@
_G.requireInjector()
--[[
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
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 Point = require('point')
local Util = require('util')
local os = _G.os
local read = _G.read
local turtle = _G.turtle
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 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, msg
if safePlaceBlock(CHEST) then
Util.print('Crafting %d %s', (qty or 1), item)
success, msg = turtle.craftItem(item, qty or 1, {
wrapSide = 'top',
direction = 'down',
})
repeat until not turtle.suckUp()
if not success then
print(msg)
end
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()
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
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 _ = 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._goto(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 turtle.has(FURNACE) or 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)
if not turtle.has(STONE, 2) then
cook(COBBLESTONE, 2, STONE, OAK_PLANK, 2)
end
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 then
return
end
if state.perimeter and
turtle.getFuelLevel() > FUEL_GOOD and
turtle.canCraft(CHEST, 4, turtle.getSummedInventory()) then
print('Adding storage')
if turtle.has(CHEST, 2) or craftItem(CHEST, 2) then
local pt = Point.copy(GRID.BL)
pt.x = pt.x + 1
pt.y = pt.y - 1
pt.z = pt.z + 1
turtle.digDownAt(pt)
turtle.placeDown(CHEST)
pt.z = pt.z + 1
turtle.digDownAt(pt)
turtle.placeDown(CHEST)
setState('chest', Util.shallowCopy(pt))
turtle.drop(DIRT)
turtle.refuel(OAK_PLANK)
end
end
return true
end
local function dropOffItems()
if state.chest then
local slots = turtle.getSummedInventory()
if state.chest 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))
turtle.dropDown('minecraft:log')
turtle.dropDown('minecraft:log2')
for _, sapling in pairs(ALL_SAPLINGS) do
if slots[sapling] and slots[sapling].count > MAX_SAPLINGS then
turtle.dropDown(sapling, slots[sapling].count - MAX_SAPLINGS)
end
end
turtle.dropDown(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
turtle.canCraft(TORCH, 4, turtle.getSummedInventory()) then
print('Placing torches')
if turtle.has(TORCH, 4) or 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
turtle.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 rpt = table.remove(pts, math.random(1, #pts))
-- give the pathfinder hints about what to avoid (state.trees)
if not turtle.faceAgainst(rpt, { 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, rpt)
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 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
local 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
local 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
local 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) - state.facing) % 4
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
turtle.setPathingBox({
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.addFeatures('level', 'crafting')
turtle.setPolicy("attack")
while not turtle.isAborted() do
print('fuel: ' .. turtle.getFuelLevel())
for _,task in ipairs(Util.shallowCopy(tasks)) do
--print(task.desc)
turtle.setStatus(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(m or 'Failed')
end

10
builder/.package Normal file
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

1265
builder/apis/turtle.lua Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,4 @@
_G.requireInjector() local Base64 = require('builder.base64')
local Base64 = require('base64')
local http = _G.http local http = _G.http
local os = _G.os local os = _G.os

771
builder/builder.lua Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,5 @@
return {
isatty = function()
return false
end,
}

8
busted/etc/fstab Normal file
View 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
View 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
View File

@@ -0,0 +1,7 @@
{
[ "16323efd4cb1f1f639d67b94161c2ef37a905517e" ] = {
title = "Cash",
category = "Apps",
run = "packages/cash/cash.lua",
},
}

1
cash/etc/fstab Normal file
View File

@@ -0,0 +1 @@
packages/cash/cash.lua urlfs https://raw.githubusercontent.com/MCJack123/cash/master/cash.lua

8
ccemux/.package Normal file
View 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',
}

View 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
View 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

View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

1
collections/etc/fstab Normal file
View 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

File diff suppressed because it is too large Load Diff

19
common/.package Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

86
common/etc/apps.db Normal file
View 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\
¨¨¨¨f€0¨\
¨¨f€0¨¨f€",
},
[ "3f00927a719345edd4a8316599d3b328857987547f8884306861161ffa09647e" ] = {
title = "Write",
category = "Apps",
run = "write",
},
[ "c543ece81605c7d202121c62080a0db4020fc2c75bfac35d101d7f3e93c93949" ] = {
category = "Apps",
run = "ascii",
title = "Ascii",
},
}

8
common/etc/fstab Normal file
View 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

View 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

View 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)

View 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

View File

@@ -1,10 +1,7 @@
local function summon(id) local function summon(id)
local GPS = require('opus.gps')
requireInjector(getfenv(1)) local Point = require('opus.point')
local Socket = require('opus.socket')
local GPS = require('gps')
local Point = require('point')
local Socket = require('socket')
turtle.setStatus('GPSing') turtle.setStatus('GPSing')
turtle.setPoint({ x = 0, y = 0, z = 0, heading = 0 }) turtle.setPoint({ x = 0, y = 0, z = 0, heading = 0 })
@@ -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._goto(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()

498
common/etc/sounds.txt Normal file
View 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
View 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

View File

@@ -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()
if curArg == "-i" then
showInput, ySize = true, ySize + 1 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()
@@ -91,7 +102,7 @@ local function loadAPI(url, env)
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))
local s, m = 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')
@@ -130,9 +141,10 @@ 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
if type(func) == 'function' then
recTerm[key] = function(...) recTerm[key] = function(...)
local result = { func(...) } local result = { func(...) }
@@ -141,26 +153,22 @@ for key, func in pairs(oldTerm) do
end end
callCount = callCount + 1 callCount = callCount + 1
curCalls[callCount] = { key, ... } curCalls[callCount] = { key, ... }
return unpack(result) return table.unpack(result)
end
end end
end end
local tabId = multishell.getCurrent() local tabId = multishell.getCurrent()
multishell.hideTab(tabId)
multishell.addHotkey('control-p', function() if not options.noResize then
os.queueEvent('term_resize')
end
_G.device.keyboard.addHotkey('control-p', function()
os.queueEvent('recorder_stop') os.queueEvent('recorder_stop')
end) 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
local curTime = os.clock() - 1 local curTime = os.clock() - 1
while true do while true do
@@ -185,10 +193,10 @@ while true do
end end
end end
multishell.removeHotkey('control-p') _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)
@@ -199,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
View File

@@ -0,0 +1,6 @@
{
title = 'Compress',
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/compress',
description = [[untar / gunzip]],
license = 'MIT',
}

View 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

File diff suppressed because it is too large Load Diff

29
compress/compress.lua Normal file
View 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

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