124 Commits

Author SHA1 Message Date
Kan18
1ee2751cbb fix potion 2023-09-16 13:46:49 -04:00
Kan18
c0ffb90ba6 fix craft 2023-09-16 13:46:22 -04:00
Kan18
c990465263 Merge most of iuzx's changes into develop-1.8 2023-07-07 13:52:42 -04:00
rspx
f22cad1644 Update recipeBook.db 2023-07-07 13:52:34 -04:00
rspx
a9202a7034 Fixed auto-feeder 2023-07-07 13:52:34 -04:00
rspx
aef1bfa186 Remote fix
Fixed itmes with nbt crashing milo
2023-07-07 13:52:34 -04:00
rspx
488576e0df Revert "Revert "Merge pull request #1 from MarcelskyXD/develop-1.8""
This reverts commit c185cffa227af8116fcfc0f0d920e62e2f98d2b8.
2023-07-07 13:52:34 -04:00
rspx
43e662e68c Revert "Merge pull request #1 from MarcelskyXD/develop-1.8"
This reverts commit f7ac0b1674ee7558578a3c127a4f252dc159eeac, reversing
changes made to a76c59fb361732b111ee3d7965d61372c1c33da8.
2023-07-07 13:52:34 -04:00
Marcelsky
4321f336db Replace getItemMeta with getItemDetail 2023-07-07 13:52:34 -04:00
rspx
1ae380d3b1 Fixed some corrupted recipes 2023-07-07 13:52:34 -04:00
rspx
0db0e58225 Update minecraft.db 2023-07-07 13:52:34 -04:00
rspx
4fe0035498 Fixed item names 2023-07-07 13:52:34 -04:00
rspx
9f8f56064c Update minecraft.db 2023-07-07 13:52:34 -04:00
rspx
f73da8a3da Updated recipes for 1.19.3 2023-07-07 13:52:34 -04:00
rspx
0bbe3a1b06 Hopefully final fixes for remote 2023-07-07 13:52:34 -04:00
iuzx0
e683e834d8 fixing remote 2023-07-07 13:52:34 -04:00
iuzx0
34dab3ccf0 Attempt to fix milo remote 2023-07-07 13:52:34 -04:00
iuzx0
761f388f12 Attempt to fix remote 2023-07-07 13:52:34 -04:00
Kan18
62477844fc Update miniAdapter.lua 2023-07-04 13:27:53 -04:00
Kan18
2e81cdf1f5 Update itemDB.lua 2023-05-26 13:20:06 -04:00
Kan18
fceb99af0b speed up imports by 100x 2023-04-16 14:45:45 -04:00
Kan18
693abd4ec6 FIX THE WEIRD NBT ITEM ISSUES WITH THIS ONE WEIRD TRICK 2023-03-17 12:26:30 -04:00
Kan18
36abdd5d0c second try 2022-12-29 09:13:09 +04:00
Kan18
bf8f9c5f0d Revert "hopefully this works 9removed damage values)"
This reverts commit 13aa4631be.
2022-12-29 09:12:41 +04:00
Kan18
13aa4631be hopefully this works 9removed damage values) 2022-12-29 09:11:53 +04:00
Kan18
f231173236 oops, forgot to update itemDB 2022-12-29 01:26:19 +04:00
Kan18
2f67fb2ef8 Try to update Milo for 1.19
Removes Milo trying to access damage on items (`nil` because of The
Flattening). We might also need to reimplement showing durability in the
item's display name - I got a little too carried away with removing
mentions of "damage". Also renames nbtHash to nbt to be consistent with
new CC:T naming. I tried not to touch anything related to MiloRemote for
now, and there are probably still many bugs remaining that need to be
ironed out. Most of the basic functionality works now, though.
2022-12-24 15:39:14 +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
186 changed files with 20499 additions and 7437 deletions

1
.gitignore vendored
View File

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

View File

@@ -1,2 +1,18 @@
# opus-apps
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

@@ -236,10 +236,7 @@ function substitutionPage.info:draw()
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)
self:print(' Replace ' .. inName .. '\n' .. ' With ' .. outName)
end
function substitutionPage:enable()
@@ -276,7 +273,7 @@ function substitutionPage:eventHandler(event)
self.info:draw()
elseif event.type == 'text_change' then
local text = event.text
local text = event.text or ''
if #text == 0 then
self.grid.values = self.allItems
else
@@ -536,7 +533,7 @@ local startPage = UI.Page {
event = 'setStartLevel',
cancelEvent = 'slide_hide',
text = UI.Text {
x = 5, y = 1, width = 20,
x = 5, y = 1, width = 10,
textColor = colors.gray,
},
textEntry = UI.TextEntry {
@@ -554,7 +551,7 @@ local startPage = UI.Page {
event = 'setStartBlock',
cancelEvent = 'slide_hide',
text = UI.Text {
x = 2, y = 1, width = 20,
x = 2, y = 1, width = 13,
textColor = colors.gray,
},
textEntry = UI.TextEntry {
@@ -727,8 +724,7 @@ function startPage:eventHandler(event)
Builder:begin()
elseif event.type == 'quit' then
UI.term:reset()
Event.exitPullEvents()
UI:quit()
end
return UI.Page.eventHandler(self, event)
@@ -772,5 +768,4 @@ UI:setPages({
})
UI:setPage('start')
UI:pullEvents()
UI:start()

View File

@@ -27,7 +27,7 @@ Copy a schematic file to the turtle/command computer (see downloading instructio
Building
========
Run:
> builder <schematic file>
> 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.
@@ -47,9 +47,13 @@ Simply copy a schematic file into the computer's folder.
Multiplayer
-----------
Option 1: Use wget if the schematic is available for download (unreliable).
Option 1: Pass the url of the schematic.
Option 2: Transferring via pastebin (reliable)
> 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:

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

View File

@@ -1,16 +1,20 @@
local ccemux = _G.ccemux
local fs = _G.fs
local peripheral = _G.peripheral
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')
local Config = require('opus.config')
for k,v in pairs(Config.load('ccemux')) do
if not peripheral.getType(k) then
ccemux.attach(k, v.type, v.args)
_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
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",
},
}

View File

@@ -1,15 +1,16 @@
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 {
tabTitle = 'CCEmuX',
title = 'CCEmuX',
description = 'CCEmuX peripherals',
form = UI.Form {
x = 2, ex = -2, y = 1, ey = 4,
x = 2, ex = -2, y = 2, ey = 5,
values = {
side = 'bottom',
type = 'wireless_modem',
@@ -28,23 +29,24 @@ local tab = UI.Tab {
},
},
drive_id = UI.TextEntry {
x = 20, y = 3,
x = 19, y = 3,
formKey = 'drive_id',
shadowText = 'id',
width = 5,
limit = 3,
transform = 'number',
},
add = UI.Button {
x = 28, y = 3,
x = -6, y = 3, width = 5,
text = 'Add', event = 'form_ok',
help = 'Add items to turtle to add to filter',
},
},
grid = UI.Grid {
x = 3, ex = -3, y = 6, ey = -2,
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 },
},
},
}
@@ -55,13 +57,29 @@ function tab:updatePeripherals(config)
table.insert(self.grid.values, {
side = k,
type = v.type,
args = v.args,
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 = { }
@@ -78,23 +96,29 @@ end
function tab:eventHandler(event)
if event.type == 'form_complete' then
ccemux.detach(event.values.side)
ccemux.attach(event.values.side, event.values.type)
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' and tonumber(event.values.drive_id) then
config[event.values.side].args = {
id = tonumber(event.values.drive_id)
local config = Config.load('ccemux')
config[event.values.side] = {
type = event.values.type
}
end
Config.update('ccemux', config)
self:updatePeripherals(config)
self.grid:draw()
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' })
self:emit({ type = 'success_message', message = 'Attached' })
end
elseif event.type == 'choice_change' then
if event.element == self.form.ptype then

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

View File

@@ -13,4 +13,7 @@
* and more...
]],
license = 'MIT',
required = {
'core',
},
}

View File

@@ -8,10 +8,10 @@ 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)
@@ -27,30 +27,15 @@ local function unregisterApp(key)
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" },
]]
local source = {
text = "STD Default",
event = 'source',
url = "https://github.com/LDDestroier/STD-GUI/raw/master/list.lua",
}
shell.setDir(APP_DIR)
@@ -72,7 +57,7 @@ local function downloadApp(app)
end
local function runApp(app, checkExists, ...)
local env = shell.makeEnv(_ENV)
local path, fn
local args = { ... }
@@ -87,20 +72,19 @@ local function runApp(app, checkExists, ...)
error('Failed to download')
end
local fn = loadstring(program, app.name)
fn = _G.loadstring(program, app.name)
if not fn then
error('Failed to download')
end
setfenv(fn, sandboxEnv)
fn(unpack(args))
_G.setfenv(fn, env)
fn(table.unpack(args))
end
end
multishell.openTab({
multishell.openTab(_ENV, {
title = app.name,
env = sandboxEnv,
path = path,
fn = fn,
focused = true,
@@ -134,16 +118,16 @@ local viewApp = function(app)
return true
end
local getSourceListing = function(source)
local getSourceListing = function()
local contents = http.get(source.url)
if contents then
local fn = loadstring(contents.readAll(), source.text)
local fn = _G.loadstring(contents.readAll(), source.text)
contents.close()
local env = { std = { } }
setmetatable(env, { __index = _G })
setfenv(fn, env)
_G.setfenv(fn, env)
fn()
if env.contextualGet then
@@ -172,9 +156,28 @@ local getSourceListing = function(source)
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 {
-- showBackButton = not pocket,
buttons = {
{ text = '\027', event = 'back' },
{ text = 'Install', event = 'install' },
@@ -204,16 +207,14 @@ function appPage.container.viewport:draw()
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)
function appPage:enable(app)
self.source = source
self.app = app
UI.Page.enable(self)
@@ -293,8 +294,7 @@ end
local categoryPage = UI.Page {
menuBar = UI.MenuBar {
buttons = {
{ text = 'Catalog', dropdown = sources },
{ text = 'Category', name = 'categoryButton', dropdown = { } },
{ text = 'Category', name = 'categoryButton', dropdown = buttons },
},
},
grid = UI.ScrollingGrid {
@@ -309,62 +309,21 @@ local categoryPage = UI.Page {
l = 'lua',
[ 'control-q' ] = 'quit',
},
source = source,
}
function categoryPage:setCategory(source, name, index)
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', source.text, name))
self.statusBar:setStatus(string.format('%s: %s', self.source.text or '', 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
@@ -377,12 +336,11 @@ function categoryPage.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())
UI:setPage(appPage, self.grid:getSelected())
elseif event.type == 'category' then
self:setCategory(self.source, event.button.text, event.button.index)
self:setCategory(event.button.text, event.button.index)
self:setFocus(self.grid)
self:draw()
@@ -392,7 +350,7 @@ function categoryPage:eventHandler(event)
self:draw()
elseif event.type == 'quit' then
UI:exitPullEvents()
UI:quit()
else
return UI.Page.eventHandler(self, event)
@@ -401,8 +359,7 @@ function categoryPage:eventHandler(event)
end
print("Retrieving catalog list")
categoryPage:setSource(sources[1])
categoryPage:setCategory(source.name, source.index)
UI:setPage(categoryPage)
UI:pullEvents()
UI.term:reset()
UI:start()

View File

@@ -1,23 +1,34 @@
_G.requireInjector(_ENV)
local Ansi = require('opus.ansi')
local Event = require('opus.event')
local UI = require('opus.ui')
local Util = require('opus.util')
local colors = _G.colors
local peripheral = _G.peripheral
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',
@@ -25,52 +36,35 @@ local peripheralsPage = UI.Page {
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,
}
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,
backgroundColor = 'black',
ey = -7,
marginLeft = 1, marginTop = 1,
},
grid = UI.ScrollingGrid {
y = -6, ey = -2,
columns = {
{ heading = 'Name', key = 'name', width = UI.term.width }
{ heading = 'Name', key = 'name' }
},
sortColumn = 'name',
},
@@ -81,50 +75,50 @@ local methodsPage = UI.Page {
[ '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: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)
@@ -136,10 +130,9 @@ function methodsPage:eventHandler(event)
end
function methodsPage:getDocumentation()
local method = self.grid:getSelected()
if method.noext then -- computercraft docs
if not method or method.noext then -- computercraft docs
return 'No documentation'
end
@@ -202,4 +195,4 @@ UI:setPages({
methods = methodsPage,
})
UI:pullEvents()
UI:start()

View File

@@ -94,7 +94,7 @@ function page:enable()
self.eject.value = config.eject
self.automatic.value = config.automatic
self.dir.x = math.floor((self.width / 2) - 3) + 1
self.dir:move(math.floor((self.width / 2) - 3) + 1, self.dir.y)
UI.Page.enable(self)
end
@@ -122,7 +122,6 @@ function page:drawInfo(drive, textArea)
return isValid(drive) and fs.getFreeSpace(drive.getMountPath()) or 0
end
textArea:setCursorPos(1, 1)
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,
@@ -138,6 +137,7 @@ function page:scan()
self.copyButton.inactive = not valid
self:draw()
self.progress:clear()
self.progress:centeredWrite(1, 'Analyzing Disks..')
self.progress:sync()
@@ -167,6 +167,7 @@ function page:copy()
throttle()
end
self.progress:clear()
self.progress:centeredWrite(1, 'Computing..')
self.progress:sync()
@@ -211,11 +212,13 @@ function page:copy()
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.progress:clear()
self:scan()
@@ -270,4 +273,4 @@ Event.onTimeout(.2, function()
end)
UI:setPage(page)
UI:pullEvents()
UI:start()

View File

@@ -27,6 +27,15 @@ local page = UI.Page {
},
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',
@@ -36,106 +45,68 @@ local page = UI.Page {
[ 'control-q' ] = 'quit',
},
filtered = { },
}
eventHandler = function(self, event)
if event.type == 'filter' then
local entry = self.grid:getSelected()
self.filtered[entry.event] = true
function page:eventHandler(event)
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()
if event.type == 'filter' then
local entry = self.grid:getSelected()
self.filtered[entry.event] = true
elseif event.type == 'grid_select' then
multishell.openTab(_ENV, {
path = 'sys/apps/Lua.lua',
args = { event.selected },
focused = 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
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,
}
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
local updated = false
local timerId = os.startTimer(1)
Event.addRoutine(function()
while true do
local _, id = os.pullEvent('timer')
if id == timerId then
if updated then
while #page.grid.values > 100 do -- page.grid.height do
table.remove(page.grid.values, 100) -- #page.grid.values)
end
updated = false
page.grid:update()
page.grid:draw()
page:sync()
while #page.grid.values > 100 do
table.remove(page.grid.values)
end
timerId = os.startTimer(1)
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
updated = true
table.insert(page.grid.values, 1, {
event = event,
p1 = e[1],
@@ -144,12 +115,13 @@ local hookFunction = function(event, e)
p4 = e[4],
p5 = e[5],
})
timerId = timerId or os.startTimer(.1)
end
end
kernel.hook('*', hookFunction)
UI:setPage(page)
UI:pullEvents()
UI:start()
kernel.unhook('*', hookFunction)

View File

@@ -42,7 +42,6 @@ local page = UI.Page {
},
range = UI.SlideOut {
y = -7, height = 7,
backgroundColor = colors.cyan,
titleBar = UI.TitleBar {
event = 'cancel',
title = 'Enter range',
@@ -239,6 +238,6 @@ Event.addRoutine(function()
end)
UI:setPage(page)
UI:pullEvents()
UI:start()
swarm:stop()

View File

@@ -5,13 +5,13 @@ local Util = require('opus.util')
local peripheral = _G.peripheral
if not peripheral.find('speaker') then
error('No speaker attached')
error('No speaker attached')
end
local rawSounds = Util.readLines('packages/games/etc/sounds.txt') or error('Unable to read sounds file')
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 })
table.insert(sounds, { name = s })
end
UI:configure('SoundPlayer', ...)
@@ -25,21 +25,21 @@ local page = UI.Page {
x = 10, y = 2, ex = -3,
limit = 32,
},
grid = UI.ScrollingGrid {
grid = UI.ScrollingGrid {
y = 4,
columns = {
{ heading = 'Name', key = 'name' },
},
values = sounds,
},
columns = {
{ heading = 'Name', key = 'name' },
},
values = sounds,
},
}
function page:eventHandler(event)
if event.type == 'grid_select' then
Sound.play(event.selected.name)
if event.type == 'grid_select' then
Sound.play(event.selected.name)
elseif event.type == 'text_change' then
if #event.text == 0 then
if not event.text then
self.grid.values = sounds
else
self.grid.values = { }
@@ -51,13 +51,13 @@ function page:eventHandler(event)
end
self.grid:update()
self.grid:setIndex(1)
self.grid:draw()
self.grid:draw()
else
return UI.Page.eventHandler(self, event)
end
return true
else
return UI.Page.eventHandler(self, event)
end
return true
end
UI:setPage(page)
UI:pullEvents()
UI:start()

View File

@@ -2,19 +2,14 @@ local Config = require('opus.config')
local Event = require('opus.event')
local itemDB = require('core.itemDB')
local Socket = require('opus.socket')
local Terminal = require('opus.terminal')
local UI = require('opus.ui')
local Util = require('opus.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
--UI.Button.defaults.focusIndicator = ' '
UI:configure('Turtles', ...)
local config = { }
@@ -31,66 +26,172 @@ local options = {
local SCRIPTS_PATH = 'packages/common/etc/scripts'
local nullTerm = Terminal.getNullTerm(term.current())
local socket
local socket, turtle, page
local page = UI.Page {
page = UI.Page {
coords = UI.Window {
backgroundColor = colors.black,
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,
scripts = UI.ScrollingGrid {
tabTitle = 'Run',
backgroundColor = colors.cyan,
columns = {
{ heading = '', key = 'label' },
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,
},
disableHeader = true,
sortColumn = 'label',
autospace = true,
},
turtles = UI.ScrollingGrid {
tabTitle = 'Select',
backgroundColor = colors.cyan,
columns = {
{ heading = 'label', key = 'label' },
{ heading = 'Dist', key = 'distance' },
{ heading = 'Status', key = 'status' },
{ heading = 'Fuel', key = 'fuel' },
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,
},
disableHeader = true,
sortColumn = 'label',
autospace = true,
},
inventory = UI.ScrollingGrid {
backgroundColor = colors.cyan,
tabTitle = 'Inv',
columns = {
{ heading = '', key = 'index', width = 2 },
{ heading = '', key = 'count', width = 2 },
{ heading = 'Inventory', key = 'key', width = UI.term.width - 7 },
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,
},
disableHeader = true,
sortColumn = 'index',
},
--[[
policy = UI.ScrollingGrid {
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 = colors.cyan,
UI.Tab {
title = 'Action',
backgroundColor = 'primary',
moveUp = UI.Button {
x = 5, y = 2,
text = 'up',
@@ -122,10 +223,43 @@ local page = UI.Page {
fn = 'turtle.turnRight',
},
info = UI.TextArea {
x = 15, y = 2,
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 = { },
@@ -134,6 +268,15 @@ local page = UI.Page {
{ 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 = {
@@ -141,15 +284,10 @@ local page = UI.Page {
},
}
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)
socket = Socket.connect(turtle.id, 161)
end
if socket then
@@ -170,203 +308,76 @@ function page:runFunction(script, nowrap)
end
function page:runScript(scriptName)
if self.turtle then
if 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'
local script = Util.readFile(fs.combine(SCRIPTS_PATH, scriptName))
if not script then
print('Unable to read script file')
end
self:print(string.format('%s : %d,%d,%d',
ind, t.point.x, t.point.y, t.point.z))
end
end
--[[ Inventory Tab ]]--
function page.tabs.inventory:getRowTextColor(row, selected)
if page.turtle and row.selected then
return colors.yellow
end
return UI.ScrollingGrid.getRowTextColor(self, row, selected)
end
function page.tabs.inventory:draw()
local t = page.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),
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,
}
table.insert(self.values, v)
end
for _,v in pairs(t.inventory or { }) do
if v.count > 0 then
table.insert(self.values, v)
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
for _,v in pairs(self.values) do
if v.index == t.slotIndex then
v.selected = true
if processVariables(script) then
local socket = Socket.connect(turtle.id, 161)
if not socket then
self.notification:error('Unable to connect')
return
end
if v.key then
v.key = itemDB:getName(v.key)
end
end
end
self:adjustWidth()
self:update()
UI.ScrollingGrid.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.ScrollingGrid.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.ScrollingGrid.draw(self)
end
function page.tabs.scripts:eventHandler(event)
if event.type == 'grid_select' then
page:runScript(event.selected.label)
else
return UI.ScrollingGrid.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.ScrollingGrid.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:write({ type = 'script', args = script })
socket:close()
socket = nil
self.notification:success('Sent')
end
else
return UI.ScrollingGrid.eventHandler(self, event)
end
return true
end
function page.statusBar:draw()
local t = self.parent.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
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()
UI:quit()
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
turtle = _G.network[options.turtle.value]
if turtle then
break
end
os.sleep(1)
@@ -374,18 +385,13 @@ if options.turtle.value >= 0 then
end
Event.onInterval(1, function()
if page.turtle then
local t = _G.network[page.turtle.id]
page.turtle = t
if turtle then
--local t = _G.network[turtle.id]
--turtle = t
page:draw()
page:sync()
end
end)
if config.tab then
page.tabs.tabBar:selectTab(config.tab)
end
UI:setPage(page)
UI:pullEvents()
UI:start()

View File

@@ -6,3 +6,49 @@ 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)

View File

@@ -1,16 +1,21 @@
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 = args[1] and peripheral.wrap(args[1]) or
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()
mon.setTextScale(.5)
if mon.setTextScale then
mon.setTextScale(.5)
end
mon.setCursorPos(1, 1)
local oldDebug = _G._syslog
@@ -19,13 +24,16 @@ _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()
mon.setTextScale(.5)
if mon.setTextScale then
mon.setTextScale(.5)
end
mon.setCursorPos(1, 1)
end
until e == 'terminate'

File diff suppressed because it is too large Load Diff

View File

@@ -57,6 +57,30 @@
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 = "packages/common/hexedit.lua",
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",
},
}

View File

@@ -1,4 +1,8 @@
packages/common/ascii.lua urlfs http://pastebin.com/raw/u3kcnyjd
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/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

@@ -1,5 +1,4 @@
_G.requireInjector(_ENV)
local config = require('config').load('gps')
local config = require('opus.config').load('gps')
if config.home then
if turtle.enableGPS() then
return turtle.pathfind(config.home)

View File

@@ -1,29 +0,0 @@
turtle.run(function()
_G.requireInjector(_ENV)
local GPS = require('gps')
local Socket = require('socket')
local id = {COMPUTER_ID}
if not turtle.enableGPS() then
error('turtle: No GPS found')
end
local socket = Socket.connect(id, 161)
if not socket then
error('turtle: Unable to connect to ' .. id)
end
socket:write({ type = 'gps' })
local pt = socket:read(3)
if not pt then
error('turtle: No GPS response')
end
if not turtle.pathfind(pt) then
error('Unable to go to location')
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

@@ -1,5 +1,4 @@
_G.requireInjector(_ENV)
local Config = require('config')
local Config = require('opus.config')
local pt = turtle.enableGPS()
if pt then
local config = Config.load('gps', { })

View File

@@ -1,10 +1,7 @@
local function summon(id)
_G.requireInjector(_ENV)
local GPS = require('gps')
local Point = require('point')
local Socket = require('socket')
local GPS = require('opus.gps')
local Point = require('opus.point')
local Socket = require('opus.socket')
turtle.setStatus('GPSing')
turtle.setPoint({ x = 0, y = 0, z = 0, heading = 0 })

View File

@@ -89,7 +89,7 @@ local function hijackTurtle(remoteId)
local socket, msg = Socket.connect(remoteId, 188)
if not socket then
error(msg)
error(msg, 0)
end
socket:write('turtle')
@@ -101,7 +101,7 @@ local function hijackTurtle(remoteId)
socket:write({ method, ... })
local resp = socket:read()
if not resp then
error('timed out: ' .. method)
error('T/O: ' .. method, 0)
end
return table.unpack(resp)
end
@@ -308,7 +308,7 @@ local containerText = {
}
local containTab = UI.Tab {
tabTitle = 'Contain',
title = 'Contain',
button = UI.Button {
x = 2, y = 2,
text = 'Set corner',
@@ -321,7 +321,7 @@ local containTab = UI.Tab {
}
local blocksTab = UI.Tab {
tabTitle = 'Blocks',
title = 'Blocks',
grid = UI.ScrollingGrid {
y = 1,
columns = {
@@ -333,7 +333,7 @@ local blocksTab = UI.Tab {
}
local turtlesTab = UI.Tab {
tabTitle = 'Turtles',
title = 'Turtles',
grid = UI.ScrollingGrid {
y = 1,
values = pool,
@@ -351,8 +351,8 @@ local page = UI.Page {
menuBar = UI.MenuBar {
buttons = {
{ text = 'Scan', event = 'scan' },
{ text = 'Abort', event = 'abort' },
pauseResume[1],
{ text = 'Abort', event = 'abort', x = -7 },
},
},
tabs = UI.Tabs {

View File

@@ -19,10 +19,14 @@ local Util = require('opus.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 greys, buttons = {["0"] = true, ["7"] = true, ["8"] = true, ["f"] = true}, {"l", "r", "m"}
local charW, charH, chars, resp
local charW, charH, chars
local calls = { }
local curCalls = { delay = 0 }
@@ -32,38 +36,44 @@ local callCount = 0
local function showSyntax()
print('Gif Recorder by Bomb Bloke\n')
print('Syntax: recGif [-i] [-s] [-ld:<delay>] filename')
print(' -i : show input')
print(' -s : skip last')
print(' -ld : last delay')
print(' --showInput : show input')
print(' --skipLast : skip last')
print(' --lastDelay : last delay')
print(' --noResize : dont resize')
end
for i = #arg, 1, -1 do
local curArg = arg[i]:lower()
if curArg == "-i" then
showInput, ySize = true, ySize + 1
table.remove(arg, i)
elseif curArg == "-s" then
skipLast = true
table.remove(arg, i)
elseif curArg:sub(1, 4) == "-ld:" then
curArg = tonumber(curArg:sub(5))
if curArg then lastDelay = curArg end
table.remove(arg, i)
elseif curArg == '-?' then
showSyntax()
return
elseif i ~= #arg then
showSyntax()
printError('\nInvalid argument')
return
end
if options.showInput then
showInput, ySize = true, ySize + 1
end
print('Gif Recorder by Bomb Bloke\n')
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
print('Enter file name:')
filename = read()
@@ -131,37 +141,34 @@ end
-- Build a terminal that records stuff:
recTerm = multishell.term
local recTerm = _G.device.terminal
for key, func in pairs(oldTerm) do
recTerm[key] = function(...)
local result = { func(...) }
if type(func) == 'function' then
recTerm[key] = function(...)
local result = { func(...) }
if callCount == 0 then
os.queueEvent('capture_frame')
if callCount == 0 then
os.queueEvent('capture_frame')
end
callCount = callCount + 1
curCalls[callCount] = { key, ... }
return table.unpack(result)
end
callCount = callCount + 1
curCalls[callCount] = { key, ... }
return unpack(result)
end
end
local tabId = multishell.getCurrent()
multishell.hideTab(tabId)
if not options.noResize then
os.queueEvent('term_resize')
end
_G.device.keyboard.addHotkey('control-p', function()
os.queueEvent('recorder_stop')
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
while true do
@@ -189,7 +196,7 @@ end
_G.device.keyboard.removeHotkey('control-p')
for k,fn in pairs(oldTerm) do
multishell.term[k] = fn
_G.device.terminal[k] = fn
end
multishell.unhideTab(tabId)
@@ -200,8 +207,12 @@ if skipLast and #calls > 1 then calls[#calls] = nil end
calls[#calls].delay = lastDelay
if options.rawOutput then
Util.writeTable('tmp/raw.txt', calls)
return
end
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):

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

54
compress/uncompress.lua Normal file
View File

@@ -0,0 +1,54 @@
local DEFLATE = require('compress.deflatelua')
local LZW = require('opus.compress.lzw')
local Tar = require('opus.compress.tar')
local Util = require('opus.util')
local io = _G.io
local shell = _ENV.shell
local args = { ... }
if not args[2] then
error('Syntax: tar FILE DESTDIR')
end
local inFile = shell.resolve(args[1])
local outDir = shell.resolve(args[2])
if inFile:match('(.+)%.[gG][zZ]$') then
-- uncompress a file created with: tar czf ...
local fh = io.open(inFile, 'rb') or error('Error opening ' .. inFile)
local t = { }
local function writer(b)
table.insert(t, b)
end
DEFLATE.gunzip {input=fh, output=writer, disable_crc=true}
fh:close()
local s, m = Tar.untar_string(string.char(table.unpack(t)), outDir, true)
if not s then
error(m)
end
elseif inFile:match('(.+)%.tar%.lzw$') then
local c = Util.readFile(inFile, 'rb')
if not c then
error('Unable to open ' .. inFile)
end
local s, m = Tar.untar_string(LZW.decompress(c), outDir, true)
if not s then
error(m)
end
else
local s, m = Tar.untar(inFile, outDir, true)
if not s then
error(m)
end
end

View File

@@ -33,20 +33,19 @@ end
function itemDB:makeKey(item)
if not item then error('itemDB:makeKey: item is required', 2) end
return table.concat({ item.name, item.damage or '*', item.nbtHash }, ':')
return table.concat({ item.name, item.nbt }, ':')
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)
if t[3] then
item.nbt = t[3]
t[3] = nil
end
item.name = table.concat(t, ':')
return item
@@ -82,36 +81,20 @@ function itemDB:_get(key)
return item
end
-- try finding an item that has damage values
if type(key.damage) == 'number' then
item = TableDB.get(self, self:makeKey({ name = key.name, nbtHash = key.nbtHash }))
if item and item.maxDamage then
for k,item in pairs(self.data) do
if key.name == item.name and
key.nbt == item.nbt then
item = Util.shallowCopy(item)
item.damage = key.damage
if item.maxDamage > 0 and type(item.damage) == 'number' and item.damage > 0 then
item.displayName = string.format('%s (damage: %s)', item.displayName, item.damage)
end
return item
end
else
for k,item in pairs(self.data) do
if key.name == item.name and
key.nbtHash == key.nbtHash and
item.maxDamage > 0 then
item = Util.shallowCopy(item)
item.nbtHash = key.nbtHash
return item
end
end
end
if key.nbtHash then
item = self:get({ name = key.name, damage = key.damage })
if key.nbt then
item = self:get({ name = key.name })
if item and item.ignoreNBT then
item = Util.shallowCopy(item)
item.nbtHash = key.nbtHash
item.damage = key.damage
item.nbt = key.nbt
return item
end
end
@@ -134,8 +117,7 @@ end
function itemDB:add(baseItem)
local nItem = {
name = baseItem.name,
damage = baseItem.damage,
nbtHash = baseItem.nbtHash,
nbt = baseItem.nbt,
}
-- if detail.maxDamage > 0 then
-- nItem.damage = '*'
@@ -143,7 +125,6 @@ function itemDB:add(baseItem)
nItem.displayName = safeString(baseItem.displayName)
nItem.maxCount = baseItem.maxCount
nItem.maxDamage = baseItem.maxDamage
-- enchanted items
if baseItem.enchantments then
@@ -156,7 +137,7 @@ function itemDB:add(baseItem)
if k > 1 then
nItem.displayName = nItem.displayName .. ', '
end
nItem.displayName = nItem.displayName .. v.fullName
nItem.displayName = nItem.displayName .. v.displayName
end
-- turtles / computers / etc
@@ -192,18 +173,8 @@ function itemDB:add(baseItem)
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
if nItem.nbt ~= item.nbt then
nItem.nbt = nil
nItem.ignoreNBT = true
self.data[k] = nil
break
@@ -214,8 +185,7 @@ function itemDB:add(baseItem)
TableDB.add(self, self:makeKey(nItem), nItem)
nItem = Util.shallowCopy(nItem)
nItem.damage = baseItem.damage
nItem.nbtHash = baseItem.nbtHash
nItem.nbt = baseItem.nbt
return nItem
end
@@ -234,8 +204,8 @@ function itemDB:getName(item)
-- fallback to nameDB
local strId = self:makeKey(item)
local name = nameDB.data[strId]
if not name and not item.damage then
name = nameDB.data[self:makeKey({ name = item.name, damage = 0, nbtHash = item.nbtHash })]
if not name then
name = nameDB.data[self:makeKey({ name = item.name, nbt = item.nbt })]
end
return name or strId
end
@@ -250,24 +220,17 @@ function itemDB:load()
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
v.nbt = nil
if v.maxCount == 64 then
v.maxCount = nil
end

View File

@@ -23,14 +23,10 @@ function nameDB:loadDirectory(directory)
error('Unable to read ' .. fs.combine(directory, file))
end
for strId, block in pairs(blocks) do
for strId, blockName in pairs(blocks) do
strId = string.format('%s:%s', mod, 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
if type(blockName) == 'string' then
self.data[strId] = blockName
end
end

File diff suppressed because one or more lines are too long

6
debugger/.package Normal file
View File

@@ -0,0 +1,6 @@
{
title = 'Lua Debugger',
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/debugger',
description = [[Lua interactive debugger]],
license = 'MIT',
}

229
debugger/apis/init.lua Normal file
View File

@@ -0,0 +1,229 @@
-- this code is loaded into the code being debugged
-- some portions from https://github.com/slembcke/debugger.lua
local fs = _G.fs
local dbg = {
hooks = { },
waits = { },
breakpoints = nil,
}
local function breakpointHook(depth, lineNo)
if dbg.breakpoints then
local info
for _,v in ipairs(dbg.breakpoints) do
if v.line == lineNo then
if not info then
info = debug.getinfo(depth)
end
if (v.file == info.short_src or v.bfile == info.short_src) then
return not v.disabled
end
end
end
end
end
local function functionHook(fn)
return function()
return debug.getinfo(3).func == fn
end
end
local function stepHook()
return function()
return true
end
end
local function stackSizeHook(n)
local i = 2
while true do
local info = debug.getinfo(i)
if not info then
break
end
i = i + 1
end
return function(depth, lineNo)
return not debug.getinfo(i - n)
or breakpointHook(depth + 1, lineNo)
end
end
local function stepOutHook()
return stackSizeHook(1)
end
local function stepOverHook()
return stackSizeHook(0)
end
-- Create a table of all the locally accessible variables.
local function local_bindings(offset, stack_inspect_offset)
offset = offset + 1 + stack_inspect_offset -- add this function to the offset
local func = debug.getinfo(offset).func
local bindings = { }
-- Retrieve the upvalues
do local i = 1; while true do
local name, value = debug.getupvalue(func, i)
if not name then break end
bindings[name] = { type = 'U', raw = value }
i = i + 1
end end
-- Retrieve the locals (overwriting any upvalues)
do local i = 1; while true do
local name, value = debug.getlocal(offset, i)
if not name then break end
bindings[name] = { type = 'L', raw = value }
i = i + 1
end end
-- Retrieve the varargs (works in Lua 5.2 and LuaJIT)
local varargs = { }
do local i = 1; while true do
local name, value = debug.getlocal(offset, -i)
if not name then break end
varargs[i] = value
i = i + 1
end end
if #varargs > 0 then
bindings["..."] = { type = 'V', value = varargs }
end
local t = { }
for k,v in pairs(bindings) do
if k ~= '(*temporary)' then
v.name = k
v.value = tostring(v.raw)
--if type(v.raw) == 'table' and not next(v.raw) then
-- v.value = 'table: (empty)'
--end
table.insert(t, v)
end
end
return t
end
local function get_trace(offset, stack_inspect_offset)
local function format_loc(file, line) return file..":"..line end
local function format_stack_frame_info(info)
local filename = info.source:match("@(.*)")
local source = filename and fs.getName(filename) or info.short_src
local namewhat = (info.namewhat == "" and "chunk at" or info.namewhat)
local name = (info.name and "'"..info.name.."'" or format_loc(source, info.linedefined))
return format_loc(source, info.currentline).." in "..namewhat.." "..name
end
offset = offset + 1 -- add this function to the offset
local t = { }
local i = 0
while true do
local info = debug.getinfo(offset + i)
if not info then break end
t[i] = {
index = i,
current = (i == stack_inspect_offset),
desc = format_stack_frame_info(info),
info = info,
}
i = i + 1
end
return t
end
local function hook(_, lineNo)
local h = dbg.hooks[coroutine.running()]
if h and h.eval(3, lineNo) then
local inspectOffset = 0
repeat
local done = true
local snapshot = {
info = debug.getinfo(2 + inspectOffset),
locals = local_bindings(2, inspectOffset),
stack = get_trace(2, inspectOffset),
}
inspectOffset = 0 -- reset
table.insert(dbg.waits, h)
while dbg.waits[1] ~= h do
os.sleep(.1)
end
local cmd, param = dbg.read(snapshot)
table.remove(dbg.waits, 1)
if cmd == 's' then
h.eval = stepHook()
elseif cmd == 'n' then
h.eval = stepOverHook()
elseif cmd == 'f' then
h.eval = stepOutHook()
elseif cmd == 'c' then
h.eval = breakpointHook
elseif cmd == 'i' then
-- get snapshot of stack at this offset
inspectOffset = param
done = false
else
done = false
end
until done
end
end
function dbg.call(fn, ...)
local args = { ... }
return xpcall(
function()
return fn(table.unpack(args))
end,
function(err)
dbg.hooks[coroutine.running()].eval = stepHook()
-- An error has occurred
return err
end)
end
dbg.stopIn = function(fn)
dbg.hooks[coroutine.running()].eval = functionHook(fn)
end
_ENV.coroutine = setmetatable({
create = function(fn)
local co = _G.coroutine.create(function(...)
local r = { dbg.call(fn, ...) }
dbg.hooks[coroutine.running()] = nil
if not r[1] then
error(r[2], -1)
end
return table.unpack(r, 2)
end)
dbg.hooks[co] = {
co = co,
eval = breakpointHook,
}
debug.sethook(co, hook, 'l')
return co
end
}, { __index = coroutine })
dbg.hooks[coroutine.running()] = {
co = coroutine.running(),
eval = function() return false end,
}
debug.sethook(hook, 'l')
return dbg

View File

@@ -0,0 +1,4 @@
local completion = require('cc.shell.completion')
_ENV.shell.setCompletionFunction("packages/debugger/debug.lua",
completion.build(completion.program))

590
debugger/debug.lua Normal file
View File

@@ -0,0 +1,590 @@
local class = require('opus.class')
local Config = require('opus.config')
local Event = require('opus.event')
local UI = require('opus.ui')
local Util = require('opus.util')
local fs = _G.fs
local getfenv = _G.getfenv
local kernel = _G.kernel
local multishell = _ENV.multishell
local shell = _ENV.shell
local args = { ... }
local filename = shell.resolveProgram(table.remove(args, 1))
if not filename then
error('file not found')
end
UI:disableEffects()
local config = Config.load('debugger')
if not config[filename] then
config[filename] = { }
end
local breakpoints = config[filename]
local currentFile
local debugFile, debugLine
local debugger = kernel.getCurrent()
local client
local function startClient()
local env = kernel.makeEnv(_ENV, fs.getDir(filename))
currentFile = nil
local clientId = multishell.openTab(nil, {
env = env,
title = fs.getName(filename):match('([^%.]+)'),
args = args,
fn = function()
local dbg = require('debugger')
local fn, msg = loadfile(filename, env)
if not fn then
error(msg, -1)
end
dbg.read = function(snapshot)
os.sleep(0) -- not sure why, but we need a sleep before :resume
-- directly resuming debugger routine to prevent
-- serialization of the snapshot
dbg.debugger:resume('debuggerX', 'break', snapshot)
local e, cmd, param
repeat
e, cmd, param = os.pullEvent('debugger')
until e == 'debugger'
return cmd, param
end
_ENV.arg = { table.unpack(args) }
_ENV.arg[0] = filename
-- breakpoint table is shared across processes
dbg.breakpoints = breakpoints
dbg.debugger = debugger
dbg.stopIn(fn)
local s, m = dbg.call(fn, table.unpack(args))
dbg.debugger:resume('debuggerX', 'disconnect')
if not s then
error(m, -1)
end
print('Process ended normally')
print('Press enter to exit')
while true do
local e, code = os.pullEventRaw('key')
if e == 'terminate' or e == 'key' and code == _G.keys.enter then
break
end
end
end,
})
client = kernel.find(clientId)
end
local romFiles = {
files = { },
load = function(self)
local function recurse(dir)
local files = fs.list(dir)
for _,f in ipairs(files) do
local fullName = fs.combine(dir, f)
if fs.isDir(fullName) then
recurse(fullName)
else
self.files[f] = fullName
end
end
end
recurse('rom/apis')
self.reversed = Util.transpose(self.files)
end,
get = function(self, file)
return self.files[file]
end,
lookup = function(self, file)
return self.reversed[file]
end,
}
romFiles:load()
local function loadSource(file)
currentFile = romFiles:get(file) or file:match('@?(.*)')
local src = { }
local lines = Util.readLines(currentFile) or type(file) == 'string' and Util.split(file)
if lines then
for i = 1, #lines do
table.insert(src, { line = i, source = lines[i] })
end
end
return src
end
local function message(...)
client:resume('debugger', ...)
end
UI.InverseButton = class(UI.Button)
UI.InverseButton.defaults = {
UIElement = 'InverseButton',
backgroundColor = 'primary',
backgroundFocusColor = 'gray',
textFocusColor = 'primary',
textColor = 'gray',
}
local page = UI.Page {
backgroundColor = 'gray',
container = UI.Window {
y = 1, ey = '50%',
tabs = UI.Tabs {
ey = -2,
barBackgroundColor = 'tertiary',
locals = UI.Tab {
title = 'Locals',
index = 1,
grid = UI.ScrollingGrid {
disableHeader = true,
unfocusedBackgroundSelectedColor = 'black',
columns = {
{ heading = 'localname', key = 'name' },
{ heading = 'Value', key = 'value', textColor = 'yellow' },
},
autospace = true,
accelerators = {
grid_select = 'show_variable',
},
getRowTextColor = function(self, row, selected)
return row.type == 'U' and 'cyan'
or row.type == 'V' and 'lime'
or UI.Grid.getRowTextColor(self, row, selected)
end,
},
},
stack = UI.Tab {
title = 'Stack',
index = 3,
grid = UI.ScrollingGrid {
disableHeader = true,
sortColumn = 'index',
unfocusedBackgroundSelectedColor = 'black',
columns = {
{ key = 'index', width = 2 },
{ heading = 'heading', key = 'desc' },
},
getRowTextColor = function(self, row, selected)
return row.current and 'yellow'
or UI.Grid.getRowTextColor(self, row, selected)
end,
eventHandler = function(self, event)
if event.type == 'grid_select' then
message('i', event.selected.index)
else
return UI.Grid.eventHandler(self, event)
end
end,
},
},
env = UI.Tab {
title = 'Env',
index = 4,
grid = UI.ScrollingGrid {
disableHeader = true,
autospace = true,
unfocusedBackgroundSelectedColor = 'black',
columns = {
{ heading = 'Key', key = 'name' },
{ heading = 'Value', key = 'value', textColor = 'yellow' },
},
accelerators = {
grid_select = 'show_variable',
},
sortCompare = function() end,
},
},
breaks = UI.Tab {
title = 'Breakpoints',
index = 2,
UI.MenuBar {
buttons = {
{ text = 'Toggle', event = 'toggle' },
{ text = 'Remove', event = 'remove' },
{ text = 'Clear', event = 'clear' },
},
},
grid = UI.ScrollingGrid {
y = 2,
values = breakpoints,
autospace = true,
columns = {
{ heading = 'Line', key = 'line', width = 5 },
{ heading = 'Name', key = 'short' },
{ heading = 'Path', key = 'path', textColor = 'lightGray' },
},
getDisplayValues = function(_, row)
return {
line = row.line,
short = fs.getName(row.file),
path = fs.getDir(row.file),
}
end,
getRowTextColor = function(self, row, selected)
return row.disabled and 'lightGray'
or UI.Grid.getRowTextColor(self, row, selected)
end,
},
eventHandler = function(self, event)
if event.type == 'clear' then
Util.clear(self.grid.values)
self:emit({ type = 'update_breakpoints' })
elseif event.type == 'toggle' then
local bp = self.grid:getSelected()
if bp then
bp.disabled = not bp.disabled
self:emit({ type = 'update_breakpoints' })
end
elseif event.type == 'grid_select' then
self:emit({
type = 'open_file',
file = event.selected.file,
line = event.selected.line,
})
elseif event.type == 'remove' then
local bp = self.grid:getSelected()
if bp then
Util.removeByValue(self.grid.values, bp)
self:emit({ type = 'update_breakpoints' })
end
end
return UI.Tab.eventHandler(self, event)
end,
},
},
UI.MenuBar {
y = -1,
backgroundColor = 'primary',
buttonClass = 'InverseButton',
buttons = {
{ text = 'Continue', event = 'cmd', cmd = 'c' },
{ text = 'Step', event = 'cmd', cmd = 's' },
{ text = 'Over', event = 'cmd', cmd = 'n' },
{ text = 'Out', event = 'cmd', cmd = 'f' },
{ text = 'Restart', event = 'restart', width = 9, ex = -1 },
},
},
},
source = UI.ScrollingGrid {
y = '50%', ey = -2,
disableHeader = true,
backgroundColor = 'gray',
backgroundSelectedColor = 'lightGray',
unfocusedBackgroundSelectedColor = 'lightGray',
columns = {
{ key = 'marker', width = 1 },
{ key = 'line', textColor = 'cyan', width = 4 },
{ heading = 'heading', key = 'source' },
},
accelerators = {
t = 'toggle_enabled'
},
getDisplayValues = function(_, row)
for _,v in pairs(breakpoints) do
if v.file == currentFile and v.line == row.line then
return {
marker = v.disabled and 'x' or '!',
line = row.line,
source = row.source,
}
end
end
return row
end,
getRowTextColor = function(self, row, selected)
return row.line == debugLine and currentFile == debugFile and 'yellow'
or UI.Grid.getRowTextColor(self, row, selected)
end,
eventHandler = function(self, event)
if event.type == 'grid_select' then
self:emit({
type = 'toggle_breakpoint',
file = currentFile,
line = event.selected.line,
})
elseif event.type == 'toggle_enabled' then
local line = self:getSelected() and self:getSelected().line
if line then
for _,v in pairs(breakpoints) do
if v.file == currentFile and v.line == line then
v.disabled = not v.disabled
self:emit({ type = 'update_breakpoints' })
break
end
end
end
end
return UI.Grid.eventHandler(self, event)
end,
},
statusBar = UI.StatusBar {
ex = -12, y = -1,
backgroundColor = 'gray',
textColor = 'orange',
},
UI.FlatButton {
y = -1, x = -5,
textColor = 'orange',
event = 'open',
text = 'Open',
},
UI.FlatButton {
y = -1, x = -10,
textColor = 'orange',
event = 'edit_file',
text = 'Edit',
},
quick_open = UI.QuickSelect {
y = '50%',
modal = true,
enable = function() end,
getFiles = function()
local paths = { }
for _,v in pairs(Util.split(client.env.package.path, '(.-);')) do
v = v:match('(.+)%?') or ''
if v:sub(1, 1) ~= '/' then
v = fs.combine(fs.getDir(filename), v)
end
if fs.exists(v) and fs.isDir(v) then
paths[fs.combine(v, '')] = true
end
end
local t = { }
for k in pairs(paths) do
local function recurse(dir)
local files = fs.list(dir)
for _,f in ipairs(files) do
local fullName = fs.combine(dir, f)
if fs.isDir(fullName) then
-- skip virtual dirs
if f ~= '.git' and fs.native.isDir(fullName) then
recurse(fullName)
end
elseif fullName:match('(.+)%.lua$') then
table.insert(t, {
name = f,
dir = dir,
lname = f:lower(),
fullName = fullName,
})
end
end
end
recurse(k)
end
return t
end,
show = function(self)
UI.QuickSelect.enable(self)
self:focusFirst()
self:draw()
self:addTransition('expandUp', { easing = 'outBounce', ticks = 12 })
end,
eventHandler = function(self, event)
if event.type == 'select_cancel' then
self:disable()
elseif event.type == 'select_file' then
self.parent:openFile(event.file)
self:disable()
end
return UI.QuickSelect.eventHandler(self, event)
end,
},
textDisplay = UI.SlideOut {
ey = '50%',
textArea = UI.TextArea {
ey = -2,
},
UI.Button {
x = '50%', y = -1,
text = 'Ok',
event = 'slide_hide',
}
},
openFile = function(self, file, line)
if file ~= currentFile then
local src = loadSource(file)
self.source:setValues(src)
end
if line then
self.source:setIndex(#self.source.values)
self.source:setIndex(math.max(1, line - 4))
end
self.source:setIndex(line or 1)
if currentFile == debugFile then
self.statusBar:setStatus(
string.format('%s : %d', fs.getName(file), debugLine))
else
self.statusBar:setStatus(fs.getName(file))
end
self:draw()
end,
editFile = function(self, file)
if fs.exists(file) then
local line = self.source:getSelected().line
multishell.openTab(_ENV, {
path = 'sys/apps/shell.lua',
args = { ('edit --line=%d %s'):format(line , '/' .. file) },
focused = true,
})
end
end,
eventHandler = function(self, event)
if event.type == 'cmd' then
self.statusBar:setStatus('Running...')
self:sync()
message(event.element.cmd)
elseif event.type == 'restart' then
if kernel.find(client.uid) then
client:resume('terminate')
end
startClient()
elseif event.type == 'open' then
self.quick_open:show()
elseif event.type == 'edit_file' then
self:editFile(currentFile)
elseif event.type == 'open_file' then
self:openFile(event.file, event.line)
elseif event.type == 'update_breakpoints' then
self.container.tabs.breaks.grid:update()
self.container.tabs.breaks.grid:draw()
self.source:draw()
Config.update('debugger', config)
elseif event.type == 'toggle_breakpoint' then
for k,v in pairs(breakpoints) do
if v.file == event.file and v.line == event.line then
table.remove(breakpoints, k)
self:emit({ type = 'update_breakpoints' })
return
end
end
table.insert(breakpoints, {
file = event.file,
line = event.line,
bfile = romFiles:lookup(event.file),
})
self:emit({ type = 'update_breakpoints' })
elseif event.type == 'show_variable' then
if type(event.selected.raw) == 'table' then
if event.selected.children then
event.selected.children = nil
else
event.selected.children = { }
local t = event.selected.raw
for k,v in pairs(t) do
local depth = event.selected.depth or 0
table.insert(event.selected.children, {
name = (' '):rep(depth + 2) .. tostring(k),
value = tostring(v),
raw = v,
depth = depth + 2
})
end
table.sort(event.selected.children, function(a, b) return a.name < b.name end)
end
local t = { }
local function insert(values)
for _,v in pairs(values) do
table.insert(t, v)
if v.children then
insert(v.children)
end
end
end
insert(event.element.orig)
event.element:setValues(t)
event.element:draw()
else
self.textDisplay.textArea:setValue(event.selected.value)
self.textDisplay:show()
end
end
return UI.Page.eventHandler(self, event)
end,
enable = function(self)
UI.Page.enable(self)
startClient()
end,
}
Event.on('debuggerX', function(_, cmd, data)
if cmd == 'disconnect' then
page.statusBar:setStatus('Finished')
page:sync()
elseif cmd == 'break' then
kernel.raise(debugger.uid)
-- local tab
table.sort(data.locals, function(a, b) return a.name < b.name end)
page.container.tabs.locals.grid:setValues(data.locals)
page.container.tabs.locals.grid.orig = Util.shallowCopy(data.locals)
-- env tab
local t = { }
for k,v in pairs(getfenv(data.info.func)) do
table.insert(t, { name = k, value = tostring(v), raw = v })
end
table.sort(t, function(a, b) return a.name < b.name end)
page.container.tabs.env.grid:setValues(t)
page.container.tabs.env.grid.orig = Util.shallowCopy(t)
debugLine = data.info.currentline
debugFile = data.info.source:match('@?(.*)')
-- source tab
page:openFile(debugFile, debugLine)
-- stack
page.container.tabs.stack.grid:setValues(data.stack)
page:draw()
page:sync()
end
end)
UI:setPage(page)
UI:start()
if kernel.find(client.uid) then
client:resume('terminate')
end

8
debugger/etc/apps.db Normal file
View File

@@ -0,0 +1,8 @@
{
[ "1a03bd2fd107c453f3183e30b9716f82200671e8270fbbefbe602f5a48705527" ] = {
run = "fileui --exec=debug.lua --title=debug",
title = "Debug",
category = "Apps",
iconExt = "\30\50\31\50\128\31\102\152\144\30\102\31\50\159\155\30\50\128\10\30\50\31\102\130\152\30\101\31\53\143\143\30\102\31\50\155\30\50\31\102\129\10\30\50\31\102\130\31\50\128\31\101\134\137\31\50\128\31\102\129",
},
}

6
debugger/help/debug.txt Normal file
View File

@@ -0,0 +1,6 @@
An interactive debugger for lua
debug must be enabled!
Run from a shell prompt
> debug FILE [ARGS]

View File

@@ -17,12 +17,6 @@
run = "Pipes",
iconExt = "\030 \031 \128\0300\0310\128\030 \031 \128\0310\143\0300\031 \130\030 \128\0300\0310\128\010\0300\031 \131\0310\128\031 \131\131\030 \0310\159\031 \128\0310\143\010\030 \031 \128\0315\143\031 \128\128\128\128\0300\131",
},
[ "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",
},
[ "a2accffe95b2c8be30e8a05e0c6ab7e8f5966f43" ] = {
title = "Strafe",
category = "Games",

View File

@@ -6,7 +6,6 @@ local Event = require('opus.event')
local colors = _G.colors
local fs = _G.fs
local gps = _G.gps
local os = _G.os
local peripheral = _G.peripheral
local read = _G.read
@@ -200,7 +199,7 @@ local function server(mode)
error('Modem is not activated or connected: ' .. k)
end
if mode == 'gps' then
modem.open(gps.CHANNEL_GPS)
modem.open(GPS.CHANNEL_GPS)
elseif mode == 'snmp' then
modem.open(999)
end
@@ -247,43 +246,35 @@ local function server(mode)
computers[computerId] = nil
page.grid.values = positions
page.grid:update()
page.grid:draw()
page.grid:sync()
end
end
Event.on('modem_message', function(_, side, channel, computerId, message, distance)
if distance and modems[side] then
if mode == 'gps' and channel == gps.CHANNEL_GPS and message == "PING" then
for _, modem in pairs(modems) do
modem.transmit(computerId, gps.CHANNEL_GPS, { modem.x, modem.y, modem.z })
end
getPosition(computerId, modems[side], distance)
local modem = modems[side]
if distance and modem then
if mode == 'gps' and channel == GPS.CHANNEL_GPS and message == "PING" then
modem.transmit(computerId, GPS.CHANNEL_GPS, { modem.x, modem.y, modem.z })
getPosition(computerId, modem, distance)
end
if mode == 'snmp' and channel == 999 then
getPosition(computerId, modems[side], distance, message)
getPosition(computerId, modem, distance, message)
end
end
end)
Event.onInterval(1, function()
local resync = false
for _, detail in pairs(positions) do
if os.clock() - detail.lastChanged > 10 then
detail.changed = false
resync = true
end
if os.clock() - detail.timestamp > 60 and detail.alive then
detail.alive = false
detail.hbeat = false
resync = true
end
end
if resync then
page:draw()
page:sync()
end
page:draw()
page:sync()
end)
end
@@ -305,6 +296,7 @@ elseif args[1] == 'snmp' then
table.insert(page.grid.columns,
{ heading = 'Label', key = 'label', textColor = colors.cyan }
)
page.grid.sortColumn = 'label'
page.grid:adjustWidth()
server('snmp')
@@ -314,4 +306,4 @@ else
end
UI:setPage(page)
UI:pullEvents()
UI:start()

View File

@@ -250,5 +250,3 @@ turtle.setStatus('Jamming')
UI:pullEvents()
turtle.setStatus('idle')
page:play(false)
UI.term:reset()

View File

@@ -1,15 +0,0 @@
local w, h = term.getSize()
term.clear()
term.setCursorPos(1, 1)
local t = { }
for i = 1, 8 do
table.insert(t, '---')
end
for i = 1, 255 do
table.insert(t, string.format('%d %c', i, i))
end
textutils.pagedTabulate(t)

View File

@@ -1,196 +0,0 @@
local class = require('opus.class')
local UI = require('opus.ui')
local Event = require('opus.event')
local Peripheral = require('opus.peripheral')
--[[-- Glasses device --]]--
local Glasses = class()
function Glasses:init(args)
local defaults = {
backgroundColor = colors.black,
textColor = colors.white,
textScale = .5,
backgroundOpacity = .5,
multiplier = 2.6665,
-- multiplier = 2.333,
}
defaults.width, defaults.height = term.getSize()
UI:setProperties(defaults, args)
UI:setProperties(self, defaults)
self.bridge = Peripheral.get({
type = 'openperipheral_bridge',
method = 'addBox',
})
self.bridge.clear()
self.setBackgroundColor = function(...) end
self.setTextColor = function(...) end
self.t = { }
for i = 1, self.height do
self.t[i] = {
text = string.rep(' ', self.width+1),
--text = self.bridge.addText(0, 40+i*4, string.rep(' ', self.width+1), 0xffffff),
bg = { },
textFields = { },
}
end
end
function Glasses:setBackgroundBox(boxes, ax, bx, y, bgColor)
local colors = {
[ colors.black ] = 0x000000,
[ colors.brown ] = 0x7F664C,
[ colors.blue ] = 0x253192,
[ colors.red ] = 0xFF0000,
[ colors.gray ] = 0x272727,
[ colors.lime ] = 0x426A0D,
[ colors.green ] = 0x2D5628,
[ colors.white ] = 0xFFFFFF
}
local function overlap(box, ax, bx)
if bx < box.ax or ax > box.bx then
return false
end
return true
end
for _,box in pairs(boxes) do
if overlap(box, ax, bx) then
if box.bgColor == bgColor then
ax = math.min(ax, box.ax)
bx = math.max(bx, box.bx)
box.ax = box.bx + 1
elseif ax == box.ax then
box.ax = bx + 1
elseif ax > box.ax then
if bx < box.bx then
table.insert(boxes, { -- split
ax = bx + 1,
bx = box.bx,
bgColor = box.bgColor
})
box.bx = ax - 1
break
else
box.ax = box.bx + 1
end
elseif ax < box.ax then
if bx > box.bx then
box.ax = box.bx + 1 -- delete
else
box.ax = bx + 1
end
end
end
end
if bgColor ~= colors.black then
table.insert(boxes, {
ax = ax,
bx = bx,
bgColor = bgColor
})
end
local deleted
repeat
deleted = false
for k,box in pairs(boxes) do
if box.ax > box.bx then
if box.box then
box.box.delete()
end
table.remove(boxes, k)
deleted = true
break
end
if not box.box then
box.box = self.bridge.addBox(
math.floor(self.x + (box.ax - 1) * self.multiplier),
self.y + y * 4,
math.ceil((box.bx - box.ax + 1) * self.multiplier),
4,
colors[bgColor],
self.backgroundOpacity)
else
box.box.setX(self.x + math.floor((box.ax - 1) * self.multiplier))
box.box.setWidth(math.ceil((box.bx - box.ax + 1) * self.multiplier))
end
end
until not deleted
end
function Glasses:write(x, y, text, bg)
if x < 1 then
error(' less ', 6)
end
if y <= #self.t then
local line = self.t[y]
local str = line.text
str = str:sub(1, x-1) .. text .. str:sub(x + #text)
self.t[y].text = str
for _,tf in pairs(line.textFields) do
tf.delete()
end
line.textFields = { }
local function split(st)
local words = { }
local offset = 0
while true do
local b,e,w = st:find('(%S+)')
if not b then
break
end
table.insert(words, {
offset = b + offset - 1,
text = w,
})
offset = offset + e
st = st:sub(e + 1)
end
return words
end
local words = split(str)
for _,word in pairs(words) do
local tf = self.bridge.addText(self.x + word.offset * self.multiplier,
self.y+y*4, '', 0xffffff)
tf.setScale(self.textScale)
tf.setZ(1)
tf.setText(word.text)
table.insert(line.textFields, tf)
end
self:setBackgroundBox(line.bg, x, x + #text - 1, y, bg)
end
end
function Glasses:clear(bg)
for _,line in pairs(self.t) do
for _,tf in pairs(line.textFields) do
tf.delete()
end
line.textFields = { }
line.text = string.rep(' ', self.width+1)
-- self.t[i].text.setText('')
end
end
function Glasses:reset()
self:clear()
self.bridge.clear()
self.bridge.sync()
end
function Glasses:sync()
self.bridge.sync()
end
return Glasses

77
ignore/pal.lua Normal file
View File

@@ -0,0 +1,77 @@
local pals = {
{ -- molokai
0x101010,
0x960050,
0x66aa11,
0xc47f2c,
0x30309b,
0x7e40a5,
0x3579a8,
0x9999aa,
0x303030,
0xff0090,
0x80ff00,
0xffba68,
0x5f5fee,
0xbb88dd,
0x4eb4fa,
0xd0d0d0,
},
{ -- solarized
0xffffd7,
0xd75f00, -- orange
0x585858,
0x0087ff, -- light blue
0x1c1c1c,
0x8a8a8a,
0xd70000, -- light red
0x808080, -- gray
0xe4e4e4, -- light gray
0x00afaf, -- cyan
0x626262,
0x5f5faf, -- blue
0xaf8700, -- brown
0x5f8700, -- green
0xaf005f, -- dark red
0x262626, -- black
},
{
0xf7f7f7,
0xc4a500, -- mustard
0xf79aff, -- magenta
0x8dcff0, -- light blue
0xfee14d, -- yellow
0xc4f137, -- lime
0x207383, -- dark green
0x7a7a7a,
0xa1a1a1,
0x6ad9cf, -- greenish blue
0xba8acc, -- purple
0x62a3c4, -- blue gray
0xd6837c, -- orange/brown
0x7da900, -- green
0xb84131, -- redish brown
0x1b1b1b,
}
}
term.setPaletteColor(2^0,0xFFFFFF)
term.setPaletteColor(2^1,0xFF6300)
term.setPaletteColor(2^2,0xFF00DE)
term.setPaletteColor(2^3,0x00C3FF)
term.setPaletteColor(2^4,0xFFFF00)
term.setPaletteColor(2^5,0x91FF00)
term.setPaletteColor(2^6,0xFF6DA8)
term.setPaletteColor(2^7,0x585757)
term.setPaletteColor(2^8,0xA9A9A9)
term.setPaletteColor(2^9,0x00FFFF)
term.setPaletteColor(2^10,0x7700FF)
term.setPaletteColor(2^11,0x0000FF)
term.setPaletteColor(2^12,0x4C2700)
term.setPaletteColor(2^13,0x00FF00)
term.setPaletteColor(2^14,0xFF0000)
term.setPaletteColor(2^15,0x000000)
local pal = pals[tonumber(({...})[1])]
for k,v in pairs(pal) do
term.setPaletteColour(2^(k - 1), v)
end

174
ignore/passthrough.lua Normal file
View File

@@ -0,0 +1,174 @@
local _rep = string.rep
local _sub = string.sub
local colors = _G.colors
local palette = { }
for n = 1, 16 do
palette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n)
end
local swindow = { }
function swindow.createPassthrough(parent, wx, wy, width, height)
local window = { }
local cx, cy = 1, 1
local blink = false
local fg = colors.white
local bg = colors.black
local function crop(text, x)
local w = #text
if x < 1 then
text = _sub(text, 2 - x)
w = w + x - 1
x = 1
end
if x + w - 1 > width then
text = _sub(text, 1, width - x + 1)
end
return text
end
local function blit(text, fg, bg)
parent.setCursorPos(cx + wx - 1, cy + wy - 1)
parent.blit(text, fg, bg)
cx = cx + #text
end
function window.write(text)
if cy > 0 and cy <= height then
text = crop(tostring(text), cx)
if #text > 0 then
--parent.setCursorPos(cx + wx - 1, cy + wy - 1)
blit(text, _rep(palette[fg], #text), _rep(palette[bg], #text))
end
end
end
function window.blit(text, fg, bg)
if cy > 0 and cy <= height then
text = crop(tostring(text), cx)
if #text > 0 then
blit(text, crop(tostring(fg), cx), crop(tostring(bg), cx))
end
end
end
function window.clear()
local filler = _rep(' ', width)
for i = 1, height do
parent.setCursorPos(wx, i + wy - 1)
parent.write(filler)
end
end
function window.clearLine()
if cy > 0 and cy <= height then
local filler = _rep(' ', width)
parent.setCursorPos(cx + wx - 1, cy + wy - 1)
parent.write(filler)
end
end
function window.getCursorPos()
return cx, cy
end
function window.setCursorPos(x, y)
cx = math.floor(x)
cy = math.floor(y)
parent.setCursorPos(cx + wx - 1, cy + wy - 1)
end
function window.setCursorBlink(b)
blink = b
parent.setCursorBlink(b)
end
function window.getCursorBlink()
return blink
end
window.isColor = parent.isColor
window.isColour = parent.isColour
window.setPaletteColour = parent.setPaletteColour
window.setPaletteColor = parent.setPaletteColor
window.getPaletteColour = parent.getPaletteColour
window.getPaletteColor = parent.getPaletteColour
window.setBackgroundColor = parent.setBackgroundColor
window.setBackgroundColour = parent.setBackgroundColor
window.getBackgroundColor = parent.getBackgroundColor
window.getBackgroundColour = parent.getBackgroundColor
window.setVisible = parent.setVisible
window.redraw = function() end --parent.redraw
function window.getTextColor()
return fg
end
window.getTextColour = window.getTextColor
function window.setTextColor(textColor)
fg = textColor
parent.setTextColor(fg)
end
window.setTextColour = window.setTextColor
function window.restoreCursor()
parent.setCursorPos(cx + wx - 1, cy + wy - 1)
parent.setTextColor(fg)
parent.setCursorBlink(blink)
end
function window.getSize()
return width, height
end
function window.scroll( n )
if n ~= 0 then
local lines = { }
for i = 1, height do
lines[i] = { parent.getLine(wy + i - 1) }
end
for newY = 1, height do
local y = newY + n
parent.setCursorPos(wx, wy + newY - 1)
if y >= 1 and y <= height then
parent.blit(table.unpack(lines[y]))
else
parent.blit(
_rep(' ', width),
_rep(palette[fg], width),
_rep(palette[bg], width))
end
end
parent.setCursorPos(cx + wx - 1, cy + wy - 1)
end
end
function window.getLine(y)
local t, tc, bc = parent.getLine(y + cy - 1)
return t:sub(1, width), tc:sub(1, width), bc:sub(1, width)
end
function window.getPosition()
return wx, wy
end
function window.reposition(nNewX, nNewY, nNewWidth, nNewHeight, newParent)
wx = nNewX
wy = nNewY
width = nNewWidth
height = nNewHeight
window.restoreCursor()
end
return window
end
return swindow

View File

@@ -1,173 +0,0 @@
--[[
Breed rabbits with a rabbit.
]]
local neural = require('neural.interface')
local Point = require('point')
local Sound = require('sound')
local Util = require('util')
local os = _G.os
local BREEDING = 'Rabbit'
local WALK_SPEED = 1.3
local MAX_GROWN = 100
local BREED_DELAY = 120
neural.assertModules({
'plethora:sensor',
'plethora:scanner',
'plethora:laser',
'plethora:kinetic',
'plethora:introspection',
})
local ID = neural.getID()
local fed = { }
local function resupply()
local slot = neural.getEquipment().list()[1]
if slot and slot.count > 32 then
return
end
print('resupplying')
local dispenser = Util.find(neural.scan(), 'name', 'minecraft:wooden_pressure_plate')
if dispenser then
if math.abs(dispenser.x) > 1 or math.abs(dispenser.z) > 1 then
neural.walkTo({ x = dispenser.x, y = 0, z = dispenser.z }, WALK_SPEED)
end
neural.lookAt(dispenser)
neural.getEquipment().suck(1, 64)
end
end
local function breed(entity)
print('breeding')
entity.lastFed = os.clock()
fed[entity.id] = entity
neural.walkTo(entity, WALK_SPEED, .5)
entity = neural.getMetaByID(entity.id)
if entity and not entity.isChild then
neural.lookAt(entity)
neural.use(1)
os.sleep(.1)
end
end
local function kill(entity)
print('killing')
neural.walkTo(entity, WALK_SPEED, 2)
entity = neural.getMetaByID(entity.id)
if entity and not entity.isChild then
neural.lookAt(entity)
neural.fireAt({ x = entity.x, y = 0, z = entity.z })
Sound.play('entity.firework.launch')
os.sleep(.2)
end
end
local function getEntities()
return Util.filter(neural.sense(), function(entity)
if entity.name == BREEDING and entity.y > -.5 and entity.id ~= ID then
return true
end
end)
end
local function getHungry(entities)
for _,v in pairs(entities) do
if not fed[v.id] or
os.clock() - fed[v.id].lastFed > BREED_DELAY then
return v
end
end
end
local function randomEntity(entities)
local r = math.random(1, Util.size(entities))
local i = 1
for _, v in pairs(entities) do
i = i + 1
if i > r then
return v
end
end
end
local function dropOff()
print('dropping')
if neural.getEquipment().list()[2] then
local b = Util.find(neural.scan(), 'name', 'minecraft:hopper')
if b then
neural.walkTo({ x = b.x, y = 0, z = b.z }, 2)
b = Util.find(neural.scan(), 'name', 'minecraft:hopper')
if b and math.abs(b.x) < 1 and math.abs(b.z) < 1 then
print('dropped')
neural.getEquipment().drop(2)
end
end
end
end
local function pickup(id)
local b = neural.getMetaByID(id)
if b then
neural.walkTo(b, 2)
local main = neural.getEquipment().list()[1]
local amount = neural.getEquipment().suck(not main and 2 or nil)
print('sucked: ' .. amount)
if amount > 0 then
Sound.play('entity.item.pickup')
return true
end
end
end
local function drops()
local sensed = Util.reduce(neural.sense(), function(acc, s)
if Util.round(s.y) == 0 and s.name == 'Item' then
acc[s.id] = s
end
return acc
end, { })
local pt = { x = 0, y = 0, z = 0 }
while true do
local b = Point.closest(pt, sensed)
if not b then
break
end
sensed[b.id] = nil
if pickup(b.id) then
pt = b
else
dropOff()
break
end
end
end
while true do
resupply()
local entities = getEntities()
if Util.size(entities) > MAX_GROWN then
kill(randomEntity(entities))
else
local entity = getHungry(entities)
if entity then
breed(entity)
else
print('sleeping')
os.sleep(5)
end
drops()
end
end

441
ignore/twm.lua Normal file
View File

@@ -0,0 +1,441 @@
local Terminal = require('opus.terminal')
local trace = require('opus.trace')
local Util = require('opus.util')
local colors = _G.colors
local os = _G.os
local printError = _G.printError
local term = _G.term
local window = _G.window
local UID = 0
local multishell = { }
local processes = { }
local parentTerm = term.current()
local sessionFile = 'usr/config/twm'
local running
local parentMon = term.current()
local defaultEnv = Util.shallowCopy(_ENV)
defaultEnv.multishell = multishell
local monDim, termDim = { }, { }
monDim.width, monDim.height = parentMon.getSize()
termDim.width, termDim.height = parentTerm.getSize()
-- even though the monitor window is set to visible
-- the canvas is not (possibly change default in terminal.lua)
-- canvas is not visible so that redraws
-- are done once in the event loop
local monitor = Terminal.window(parentMon, 1, 1, monDim.width, monDim.height, true)
monitor.setBackgroundColor(colors.gray)
monitor.clear()
local function nextUID()
UID = UID + 1
return UID
end
local function xprun(env, path, ...)
setmetatable(env, { __index = _G })
local fn, m = loadfile(path, env)
if fn then
return trace(fn, ...)
end
return fn, m
end
local function write(win, x, y, text)
win.setCursorPos(x, y)
win.write(text)
end
local function redraw()
monitor.canvas:dirty()
monitor.canvas:clear(colors.gray)
for _, process in ipairs(processes) do
process.container.canvas:dirty()
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 math.floor(termDim.width * .75)
args.height = args.height or math.floor(termDim.height * .75)
-- TODO: randomize start position
local self = setmetatable({
uid = nextUID(),
x = args.x or 1,
y = args.y or 1,
width = args.width,
height = args.height + 1,
path = args.path,
args = args.args or { },
title = args.title or 'shell',
timestamp = os.clock(),
isMoving = false,
isResizing = false,
}, { __index = Process })
self:adjustDimensions()
if not args.x then
self.x = math.random(1, monDim.width - self.width + 1)
self.y = math.random(1, monDim.height - self.height + 1)
end
self.container = Terminal.window(monitor, self.x, self.y, self.width, self.height, true)
self.window = window.create(self.container, 1, 2, args.width, args.height, true)
self.terminal = self.window
self.container.canvas.parent = monitor.canvas
table.insert(monitor.canvas.layers, 1, self.container.canvas)
self.container.canvas:setVisible(true)
--self.container.getSize = self.window.getSize
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 = xprun(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)
self.container.setBackgroundColor(focused and colors.yellow or colors.lightGray)
self.container.setTextColor(colors.black)
write(self.container, 1, 1, string.rep(' ', self.width))
write(self.container, 2, 1, self.title)
write(self.container, self.width - 1, 1, '*')
write(self.container, self.width - 3, 1, '\029')
end
function Process:drawSizers()
self.container.setBackgroundColor(colors.black)
self.container.setTextColor(colors.yellow)
local str = string.format('%d x %d', self.width - 2, self.height - 3)
write(self.container, (self.width - #str) / 2, 1, str)
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(resizing)
self:adjustDimensions()
self.container.reposition(self.x, self.y, self.width, self.height)
self.window.reposition(1, 2, self.width, self.height - 1)
if self.window ~= self.terminal then
if self.terminal.reposition then -- ??
self.terminal.reposition(1, 1, self.width, self.height - 1)
end
end
if resizing then
self:focus(self == processes[#processes])
end
redraw()
end
function Process:click(x, y, rx, ry)
if y == 1 then -- title bar
if x == self.width - 1 then
self:resume('terminate')
elseif x == self.width - 3 then
self.isResizing = { x = rx, y = ry, h = self.height, w = self.width }
else
self.isMoving = { x = rx, y = ry, ox = self.x, oy = self.y }
end
else
if self.isMoving then
self.isMoving = false
end
self:resume('mouse_click', 1, x, y - 1)
self:resume('mouse_up', 1, x, y - 1)
end
end
function Process:resize(x, y)
self.height = y - self.isResizing.y + self.isResizing.h
self.width = x - self.isResizing.x + self.isResizing.w
self:reposition(true)
self:resume('term_resize')
self:drawSizers()
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 previousTerm = 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()
term.redirect(previousTerm)
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)
process.container.canvas:raise()
process:focus(true)
process.container.canvas:dirty()
process.window.restoreCursor()
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)
--local previousTerm = term.current()
process:resume()
--term.redirect(previousTerm)
multishell.saveSession(sessionFile)
return process.uid
end
function multishell.removeProcess(process)
Util.removeByValue(processes, process)
process.container.canvas:removeLayer()
multishell.saveSession(sessionFile)
redraw()
end
function multishell.saveSession(filename)
local t = { }
for _,process in ipairs(processes) do
if process.path then
table.insert(t, {
x = process.x,
y = process.y,
width = process.width,
height = process.height - 1,
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 k = #config, 1, -1 do
multishell.openTab(config[k])
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 then
focused:resume('terminate')
if #processes == 0 then
break
end
end
elseif event[1] == 'monitor_touch' or event[1] == 'mouse_click' then --or event[1] == 'mouse_up' 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)
multishell.saveSession(sessionFile)
end
process:click(x - process.x + 1, y - process.y + 1, x, y)
end
elseif event[1] == 'mouse_up' then
local focused = processes[#processes]
if focused and (focused.isResizing or focused.isMoving) then
multishell.saveSession(sessionFile)
if focused.isResizing then
focused:focus(true)
end
end
if focused then
focused.isResizing = nil
focused.isMoving = false
end
elseif event[1] == 'mouse_drag' then
local focused = processes[#processes]
if focused then
if focused.isResizing then
focused:resize(event[3], event[4])
elseif focused.isMoving then
focused.x = event[3] - focused.isMoving.x + focused.isMoving.ox
focused.y = event[4] - focused.isMoving.y + focused.isMoving.oy
focused:reposition(false)
end
end
elseif event[1] == 'char' or
event[1] == 'key' or
event[1] == 'key_up' or
event[1] == 'paste' or
event[1] == 'mouse_scroll' then
local focused = processes[#processes]
if focused then
focused:resume(table.unpack(event))
end
else
for _,process in pairs(Util.shallowCopy(processes)) do
process:resume(table.unpack(event))
end
end
monitor.canvas:render(parentMon)
local focused = processes[#processes]
if focused then
focused.window.restoreCursor()
end
end
end
multishell.loadSession(sessionFile)
if #processes == 0 then
multishell.openTab({
path = 'sys/apps/shell.lua',
title = 'shell',
})
end
processes[#processes]:focus(true)
multishell.start()
term.redirect(parentTerm)
parentTerm.clear()
parentTerm.setCursorPos(1, 1)

11
lfs/.package Normal file
View File

@@ -0,0 +1,11 @@
{
title = 'LuaFileSystem',
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/lfs',
description = [[LuaFileSystem port for computercraft
See: https://github.com/keplerproject/luafilesystem
LuaFileSystem is a Lua library developed to complement the set of functions related to file systems offered by the standard Lua distribution.
LuaFileSystem offers a portable way to access the underlying directory structure and file attributes. LuaFileSystem is free software and uses the same license as Lua 5.x (MIT).]],
license = 'MIT',
}

237
lfs/apis/init.lua Normal file
View File

@@ -0,0 +1,237 @@
-- a port of LuaFileSystem
-- https://keplerproject.github.io/luafilesystem/manual.html
local fs = _G.fs
local shell = _ENV.shell
local lfs = {
_VERSION = '1.8.0.computercraft'
}
-- lfs.attributes (filepath [, request_name | result_table])
-- Returns a table with the file attributes corresponding to filepath (or nil followed
-- by an error message and a system-dependent error code in case of error). If the second
-- optional argument is given and is a string, then only the value of the named attribute
-- is returned (this use is equivalent to lfs.attributes(filepath)[request_name], but the
-- table is not created and only one attribute is retrieved from the O.S.). if a table is
-- passed as the second argument, it (result_table) is filled with attributes and returned
-- instead of a new table. The attributes are described as follows; attribute mode is a
-- string, all the others are numbers, and the time related attributes use the same time
-- reference of os.time:
function lfs.attributes(path, request_name)
path = shell.resolve(path)
local s, fsattr = pcall(fs.attributes, path)
if not s then
return nil, fsattr, 1
end
local attributes = type(request_name) == 'table' and request_name or { }
-- on Unix systems, this represents the device that the inode resides on.
-- On Windows systems, represents the drive number of the disk containing the file
attributes.dev = fs.getDrive(path)
-- on Unix systems, this represents the inode number.
-- On Windows systems this has no meaning
attributes.ino = nil
--string representing the associated protection mode
-- (the values could be file, directory, link, socket, named pipe,
-- char device, block device or other)
attributes.mode = fsattr.isDir and 'directory' or 'file'
-- number of hard links to the file
attributes.nlink = 0
-- user-id of owner (Unix only, always 0 on Windows)
attributes.uid = 0
-- group-id of owner (Unix only, always 0 on Windows)
attributes.gid = 0
-- on Unix systems, represents the device type, for special file inodes.
-- On Windows systems represents the same as dev
attributes.rdev = attributes.dev
-- time of last access
attributes.access = fsattr.modification
-- time of last data modification
attributes.modification = fsattr.modification
-- time of last file status change
attributes.change = fsattr.modification
-- file size, in bytes
attributes.size = fsattr.size
-- file permissions string
local perm = (fs.isDir or fs.isReadOnly(path)) and 'r-x' or 'rwx'
attributes.permissions = perm .. perm .. perm
-- block allocated for file; (Unix only)
attributes.blocks = nil
-- optimal file system I/O blocksize; (Unix only)
attributes.blksize = nil
return type(request_name) ~= 'string' and attributes or attributes[request_name]
end
-- lfs.chdir (path)
-- Changes the current working directory to the given path.
-- Returns true in case of success or nil plus an error string.
function lfs.chdir(path)
path = shell.resolve(path)
if fs.isDir(path) then
shell.setDir(path)
return true
end
return nil, path .. ': No such directory'
end
-- lfs.currentdir ()
-- Returns a string with the current working directory or nil plus an error string.
function lfs.currentdir()
return '/' .. shell.dir()
end
-- iter, dir_obj = lfs.dir (path)
-- Lua iterator over the entries of a given directory.
-- Each time the iterator is called with dir_obj it returns a directory
-- entry's name as a string, or nil if there are no more entries.
-- You can also iterate by calling dir_obj:next(), and explicitly close the
-- directory before the iteration finished with dir_obj:close().
-- Raises an error if path is not a directory.
function lfs.dir(path)
path = shell.resolve(path)
local set = fs.list(path)
local iter = function()
local key, value = next(set)
set[key or false] = nil
return value
end
return iter, {
valid = true,
closed = false,
next = function(self)
if not self.valid then
error('file iterator invalid')
end
local n = iter()
if not n then
self.valid = false
end
return n
end,
close = function(self)
if self.closed then
error('file iterator invalid')
end
self.closed = true
self.valid = false
end,
}
end
-- lfs.link (old, new[, symlink])
-- Creates a link. The first argument is the object to link to and the second is the
-- name of the link. If the optional third argument is true, the link will by a symbolic
-- link (by default, a hard link is created).
function lfs.link(old, new, symlink)
if not symlink then
return false
end
-- hard links are not supported in vfs :(
old = shell.resolve(old)
new = shell.resolve(new)
return not not fs.mount(new, 'linkfs', old)
end
-- lfs.mkdir (dirname)
-- Creates a new directory. The argument is the name of the new directory.
-- Returns true in case of success or nil, an error message and a system-dependent
-- error code in case of error.
function lfs.mkdir(dirname)
dirname = shell.resolve(dirname)
if fs.exists(fs.getDir(dirname)) then
fs.makeDir(dirname)
if fs.isDir(dirname) then
return true
end
end
return nil, dirname .. ': Unable to create directory', 1
end
-- lfs.rmdir (dirname)
-- Removes an existing directory. The argument is the name of the directory.
-- Returns true in case of success or nil, an error message and a system-dependent
-- error code in case of error.
function lfs.rmdir(dirname)
dirname = shell.resolve(dirname)
if not fs.exists(dirname) or not fs.isDir(dirname) then
return false, dirname .. ': Not a directory', 1
end
pcall(fs.delete, dirname)
return not fs.exists(dirname) or false, dirname .. ': Unable to remove directory', 1
end
-- lfs.setmode (file, mode)
-- Sets the writing mode for a file. The mode string can be either "binary" or "text".
-- Returns true followed the previous mode string for the file, or nil followed by an
-- error string in case of errors. On non-Windows platforms, where the two modes are
-- identical, setting the mode has no effect, and the mode is always returned as binary.
function lfs.setmode(file)
if tostring(file) == 'file (closed)' then
error('closed file')
end
return true, 'binary'
end
-- lfs.symlinkattributes (filepath [, request_name])
-- Identical to lfs.attributes except that it obtains information about the link itself
-- (not the file it refers to). It also adds a target field, containing the file name that
-- the symlink points to. On Windows this function does not yet support links, and is
-- identical to lfs.attributes.
function lfs.symlinkattributes(filepath, request_name)
filepath = shell.resolve(filepath)
local target = fs.resolve(filepath)
local attribs = lfs.attributes('/' .. target)
if filepath ~= target then
attribs.target = '/' .. target
attribs.mode = 'link'
end
return request_name and attribs[request_name] or attribs
end
-- lfs.touch (filepath [, atime [, mtime]])
-- Set access and modification times of a file. This function is a bind to utime function.
-- The first argument is the filename, the second argument (atime) is the access time, and
-- the third argument (mtime) is the modification time. Both times are provided in seconds
-- (which should be generated with Lua standard function os.time). If the modification time
-- is omitted, the access time provided is used; if both times are omitted, the current time
-- is used.
-- Returns true in case of success or nil, an error message and a system-dependent error
-- code in case of error.
function lfs.touch(filename, atime, mtime)
mtime = mtime or atime
filename = shell.resolve(filename)
if atime or mtime then
error('setting access/modification time is not supported')
end
-- cc does not suport setting atime/mtime
-- error('lfs.touch not supported')
if not fs.exists(filename) then
local f = fs.open(filename, 'w')
if f then
f.close()
end
end
end
return lfs

1
lfs/etc/fstab Normal file
View File

@@ -0,0 +1 @@
packages/lfs/tests/test.lua urlfs https://raw.githubusercontent.com/keplerproject/luafilesystem/master/tests/test.lua

9
lpeg/.package Normal file
View File

@@ -0,0 +1,9 @@
{
title = 'LPeg',
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/lpeg',
description = [[A pure Lua port of LPeg, Roberto Ierusalimschy's Parsing Expression Grammars library.
This version of LuLPeg emulates LPeg v0.12.
see: https://github.com/pygy/LuLPeg/]],
license = 'MIT',
}

1
lpeg/etc/fstab Normal file
View File

@@ -0,0 +1 @@
rom/modules/main/lpeg.lua urlfs https://raw.githubusercontent.com/pygy/LuLPeg/master/lulpeg.lua

View File

@@ -19,9 +19,9 @@ local SIGC = 'LZWC'
local basedictcompress = {}
local basedictdecompress = {}
for i = 0, 255 do
local ic, iic = char(i), char(i, 0)
basedictcompress[ic] = iic
basedictdecompress[iic] = ic
local ic, iic = char(i), char(i, 0)
basedictcompress[ic] = iic
basedictdecompress[iic] = ic
end
local native = { open = fs.open }
@@ -29,123 +29,123 @@ local enabled = false
local filters = { }
local function dictAddA(str, dict, a, b)
if a >= 256 then
a, b = 0, b+1
if b >= 256 then
dict = {}
b = 1
end
end
dict[str] = char(a,b)
a = a+1
return dict, a, b
if a >= 256 then
a, b = 0, b+1
if b >= 256 then
dict = {}
b = 1
end
end
dict[str] = char(a,b)
a = a+1
return dict, a, b
end
local function compress(input)
if type(input) ~= "string" then
error ("string expected, got "..type(input))
end
local len = #input
if len <= 1 then
return input
end
if type(input) ~= "string" then
error ("string expected, got "..type(input))
end
local len = #input
if len <= 1 then
return input
end
local dict = {}
local a, b = 0, 1
local dict = {}
local a, b = 0, 1
local result = { SIGC }
local resultlen = 1
local n = 2
local word = ""
for i = 1, len do
local c = sub(input, i, i)
local wc = word..c
if not (basedictcompress[wc] or dict[wc]) then
local write = basedictcompress[word] or dict[word]
if not write then
error "algorithm error, could not fetch word"
end
result[n] = write
resultlen = resultlen + #write
n = n+1
if len <= resultlen then
return input
end
dict, a, b = dictAddA(wc, dict, a, b)
word = c
else
word = wc
end
end
result[n] = basedictcompress[word] or dict[word]
resultlen = resultlen+#result[n]
if len <= resultlen then
return input
end
return tconcat(result)
local result = { SIGC }
local resultlen = 1
local n = 2
local word = ""
for i = 1, len do
local c = sub(input, i, i)
local wc = word..c
if not (basedictcompress[wc] or dict[wc]) then
local write = basedictcompress[word] or dict[word]
if not write then
error "algorithm error, could not fetch word"
end
result[n] = write
resultlen = resultlen + #write
n = n+1
if len <= resultlen then
return input
end
dict, a, b = dictAddA(wc, dict, a, b)
word = c
else
word = wc
end
end
result[n] = basedictcompress[word] or dict[word]
resultlen = resultlen+#result[n]
if len <= resultlen then
return input
end
return tconcat(result)
end
local function dictAddB(str, dict, a, b)
if a >= 256 then
a, b = 0, b+1
if b >= 256 then
dict = {}
b = 1
end
end
dict[char(a,b)] = str
a = a+1
return dict, a, b
if a >= 256 then
a, b = 0, b+1
if b >= 256 then
dict = {}
b = 1
end
end
dict[char(a,b)] = str
a = a+1
return dict, a, b
end
local function decompress(input)
if type(input) ~= "string" then
error( "string expected, got "..type(input))
end
if type(input) ~= "string" then
error( "string expected, got "..type(input))
end
if #input <= 1 then
return input
end
if #input <= 1 then
return input
end
local control = sub(input, 1, 4)
if control ~= SIGC then
return input
end
input = sub(input, 5)
local len = #input
local control = sub(input, 1, 4)
if control ~= SIGC then
return input
end
input = sub(input, 5)
local len = #input
if len < 2 then
error("invalid input - not a compressed string")
end
if len < 2 then
error("invalid input - not a compressed string")
end
local dict = {}
local a, b = 0, 1
local dict = {}
local a, b = 0, 1
local result = {}
local n = 1
local last = sub(input, 1, 2)
result[n] = basedictdecompress[last] or dict[last]
n = n+1
for i = 3, len, 2 do
local code = sub(input, i, i+1)
local lastStr = basedictdecompress[last] or dict[last]
if not lastStr then
error( "could not find last from dict. Invalid input?")
end
local toAdd = basedictdecompress[code] or dict[code]
if toAdd then
result[n] = toAdd
n = n+1
dict, a, b = dictAddB(lastStr..sub(toAdd, 1, 1), dict, a, b)
else
local tmp = lastStr..sub(lastStr, 1, 1)
result[n] = tmp
n = n+1
dict, a, b = dictAddB(tmp, dict, a, b)
end
last = code
end
return tconcat(result)
local result = {}
local n = 1
local last = sub(input, 1, 2)
result[n] = basedictdecompress[last] or dict[last]
n = n+1
for i = 3, len, 2 do
local code = sub(input, i, i+1)
local lastStr = basedictdecompress[last] or dict[last]
if not lastStr then
error( "could not find last from dict. Invalid input?")
end
local toAdd = basedictdecompress[code] or dict[code]
if toAdd then
result[n] = toAdd
n = n+1
dict, a, b = dictAddB(lastStr..sub(toAdd, 1, 1), dict, a, b)
else
local tmp = lastStr..sub(lastStr, 1, 1)
result[n] = tmp
n = n+1
dict, a, b = dictAddB(tmp, dict, a, b)
end
last = code
end
return tconcat(result)
end
local function split(str, pattern)
@@ -157,32 +157,39 @@ local function split(str, pattern)
end
local function matchesFilter(fname)
if not fname:find('lzwfs') then -- don't compress anything with lzwfs in name (sigh)
for _, filter in pairs(filters) do
if fname:match(filter) then
return true
end
end
end
if not fname:find('lzwfs') then -- don't compress anything with lzwfs in name (sigh)
for _, filter in pairs(filters) do
if fname:match(filter) then
return true
end
end
end
end
function fs.open(fname, flags)
if not enabled then
return native.open(fname, flags)
end
if not enabled then
return native.open(fname, flags)
end
if flags == 'r' then
local f, err = native.open(fname, 'rb')
if not f then
return f, err
end
if flags == 'r' then
local f, err = native.open(fname, 'rb')
if not f then
return f, err
end
local ctr = 0
local lines
return {
read = function()
if not lines then
lines = decompress(f.readAll())
end
ctr = ctr + 1
return lines:sub(ctr, ctr)
end,
readLine = function()
if not lines then
lines = split(decompress(f.readAll()))
lines = split(decompress(f.readAll()))
end
ctr = ctr + 1
return lines[ctr]
@@ -190,61 +197,61 @@ function fs.open(fname, flags)
readAll = function()
return decompress(f.readAll())
end,
close = function()
f.close()
end,
}
elseif flags == 'w' or flags == 'a' then
if not matchesFilter(fs.combine(fname, '')) then
return native.open(fname, flags)
end
local c = { }
if flags == 'a' then
local f = fs.open(fname, 'r')
if f then
tinsert(c, f.readAll())
f.close()
end
end
local f, err = native.open(fname, 'wb')
if not f then
return f, err
end
return {
write = function(str)
tinsert(c, str)
end,
writeLine = function(str)
tinsert(c, str)
tinsert(c, '\n')
end,
flush = function()
-- this isn't gonna work...
-- f.write(compress(tconcat(c)))
f.flush();
end,
close = function()
f.write(compress(tconcat(c)))
f.close()
close = function()
f.close()
end,
}
end
elseif flags == 'w' or flags == 'a' then
if not matchesFilter(fs.combine(fname, '')) then
return native.open(fname, flags)
end
return native.open(fname, flags)
local c = { }
if flags == 'a' then
local f = fs.open(fname, 'r')
if f then
tinsert(c, f.readAll())
f.close()
end
end
local f, err = native.open(fname, 'wb')
if not f then
return f, err
end
return {
write = function(str)
tinsert(c, str)
end,
writeLine = function(str)
tinsert(c, str)
tinsert(c, '\n')
end,
flush = function()
-- this isn't gonna work...
-- f.write(compress(tconcat(c)))
f.flush();
end,
close = function()
f.write(compress(tconcat(c)))
f.close()
end,
}
end
return native.open(fname, flags)
end
function fs.option(category, action, option)
if category == 'compression' then
if action == 'enabled' then
enabled = option
elseif action == 'filters' then
filters = option
end
end
if category == 'compression' then
if action == 'enabled' then
enabled = option
elseif action == 'filters' then
filters = option
end
end
end
print('lzwfs started')

View File

@@ -6,11 +6,11 @@ local CONFIG = 'usr/config/lzwfs'
local config = { }
if fs.exists(CONFIG) then
local f = fs.open(CONFIG, 'r')
local f = fs.open(CONFIG, 'r')
if f then
config = textutils.unserialize(f.readAll())
f.close()
end
end
end
os.run(_ENV, '/packages/lzwfs/lzwfs.lua')

View File

@@ -14,19 +14,21 @@ local config = Config.load('lzwfs', {
})
local tab = UI.Tab {
tabTitle = 'Compression',
title = 'Compression',
description = 'Disk compression',
[1] = UI.Window {
x = 2, y = 2, ex = -2, ey = 6,
},
label1 = UI.Text {
x = 2, y = 2,
x = 3, y = 3,
value = 'Enable compression',
},
checkbox = UI.Checkbox {
x = 20, y = 2,
x = 21, y = 3,
value = config.enabled
},
entry = UI.TextEntry {
x = 2, y = 4, ex = -2,
limit = 256,
x = 3, y = 5 , ex = -3,
shadowText = 'enter new path',
accelerators = {
enter = 'add_path',
@@ -34,7 +36,7 @@ local tab = UI.Tab {
help = 'add a new path',
},
grid = UI.Grid {
x = 2, ex = -2, y = 6, ey = -5,
x = 2, ex = -2, y = 8, ey = -5,
disableHeader = true,
columns = { { key = 'value' } },
autospace = true,
@@ -45,7 +47,7 @@ local tab = UI.Tab {
},
},
button = UI.Button {
x = -9, ex = -2, y = -3,
x = -8, ex = -2, y = -3,
text = 'Apply',
event = 'apply',
},
@@ -85,7 +87,7 @@ local function rewriteFiles(p)
end
function tab:eventHandler(event)
if event.type == 'add_path' then
if event.type == 'add_path' and self.entry.value then
table.insert(self.grid.values, {
value = self.entry.value,
})

View File

@@ -8,15 +8,4 @@ https://github.com/SquidDev-CC/mbs
MBS is a series of utilities for improving the default CraftOS experience.
]],
license = 'MIT',
install = [[
local Alt = require('opus.alternate')
Alt.set('shell', '.mbs/bin/shell.lua')
Alt.add('lua', '.mbs/bin/lua.lua')
]],
uninstall = [[
local Alt = require('opus.alternate')
Alt.remove('shell', '.mbs/bin/shell.lua')
Alt.remove('lua', '.mbs/bin/lua.lua')
fs.delete('.mbs')
]],
}

View File

@@ -3,7 +3,7 @@ local shell = _ENV.shell
if not fs.exists('.mbs') then
print('Installing MBS')
shell.run('mbs download')
--shell.run('mbs download')
end
print('Initializing MBS')
shell.run('mbs startup')
--shell.run('mbs startup')

View File

@@ -2,6 +2,7 @@ local Event = require('opus.event')
local Milo = require('milo')
local Sound = require('opus.sound')
local Storage = require('milo.storage')
local TurtleInv = require('milo.turtleInv')
local UI = require('opus.ui')
local Util = require('opus.util')
@@ -18,7 +19,6 @@ multishell.setTitle(multishell.getCurrent(), 'Milo')
local function Syntax(msg)
print([[
Turtle must be provided with:
* Introspection module (never bound)
* Workbench
Turtle must be connected to:
@@ -46,10 +46,6 @@ if not modem.getNameLocal() then
Syntax('Wired modem is not active')
end
local introspection = device['plethora:introspection'] or
turtle.equip('left', 'plethora:module:0') and device['plethora:introspection'] or
Syntax('Introspection module missing')
if not device.workbench then
turtle.equip('right', 'minecraft:crafting_table:0')
if not device.workbench then
@@ -58,6 +54,7 @@ if not device.workbench then
end
local localName = modem.getNameLocal()
TurtleInv.setLocalName(localName)
local context = {
resources = Util.readTable(Milo.RESOURCE_FILE) or { },
@@ -76,7 +73,7 @@ local context = {
turtleInventory = {
name = localName,
mtype = 'hidden',
adapter = introspection.getInventory(),
adapter = TurtleInv,
}
}
@@ -216,10 +213,12 @@ _G._syslog = function(...)
end
local s, m = pcall(function()
UI:pullEvents()
UI:start()
end)
turtle.setStatus('idle')
if turtle.setStatus then
turtle.setStatus('idle')
end
_G._syslog = oldDebug
if not s then error(m) end

View File

@@ -1,6 +1,6 @@
local Config = require('opus.config')
local Event = require('opus.event')
local fuzzy = require('milo.fuzzyMatch')
local fuzzy = require('opus.fuzzy')
local Sound = require('opus.sound')
local Socket = require('opus.socket')
local sync = require('opus.sync').sync
@@ -12,8 +12,12 @@ local fs = _G.fs
local peripheral = _G.peripheral
local shell = _ENV.shell
local configName = ({...})[1]
local configPath = 'miloRemote' .. (configName and "_"..configName or "")
local context = {
state = Config.load('miloRemote', { displayMode = 0, deposit = true }),
state = Config.load(configPath, { displayMode = 0, deposit = true }),
configPath = configPath,
responseHandlers = { },
}
@@ -60,11 +64,11 @@ local page = UI.Page {
statusBar = UI.Window {
y = -1,
filter = UI.TextEntry {
x = 1, ex = -12,
x = 1, ex = -13,
limit = 50,
shadowText = 'filter',
backgroundColor = colors.cyan,
backgroundFocusColor = colors.cyan,
backgroundColor = 'primary',
backgroundFocusColor = 'primary',
accelerators = {
[ 'enter' ] = 'eject',
[ 'up' ] = 'grid_up',
@@ -73,8 +77,8 @@ local page = UI.Page {
},
},
amount = UI.TextEntry {
x = -11, ex = -7,
limit = 3,
x = -12, ex = -7,
limit = 4,
shadowText = '1',
shadowTextColor = colors.gray,
backgroundColor = colors.black,
@@ -158,7 +162,7 @@ function page.grid:eventHandler(event)
if event.type == 'grid_sort' then
context.state.sortColumn = event.sortColumn
context.state.inverseSort = event.inverseSort
Config.update('miloRemote', context.state)
Config.update(configPath, context.state)
end
return UI.Grid.eventHandler(self, event)
end
@@ -169,7 +173,7 @@ end
function page:eventHandler(event)
if event.type == 'quit' then
UI:exitPullEvents()
UI:quit()
elseif event.type == 'setup' then
self.setup.form:setValues(context.state)
@@ -181,7 +185,7 @@ function page:eventHandler(event)
self.statusBar:draw()
context:setStatus(depositMode[context.state.deposit].help)
context:notifyInfo(depositMode[context.state.deposit].help)
Config.update('miloRemote', context.state)
Config.update(configPath, context.state)
elseif event.type == 'focus_change' then
context:setStatus(event.focused.help)
@@ -243,10 +247,10 @@ function page:eventHandler(event)
context:setStatus(event.button.help)
context:notifyInfo(event.button.help)
self.grid:draw()
Config.update('miloRemote', context.state)
Config.update(configPath, context.state)
elseif event.type == 'text_change' and event.element == self.statusBar.filter then
self.filter = event.text
self.filter = event.text or ''
if #self.filter == 0 then
self.filter = nil
end
@@ -277,11 +281,12 @@ end
local function splitKey(key)
local t = Util.split(key, '(.-):')
local item = { }
if #t[#t] > 8 then
item.nbtHash = table.remove(t)
if t[3] then
item.nbt = t[3]
end
item.damage = tonumber(table.remove(t))
t[3] = nil
item.name = table.concat(t, ':')
return item
end
@@ -318,7 +323,7 @@ function page:applyFilter()
v.score = fuzzy(v.lname, filter)
if v.score then
if v.count > 0 then
v.score = v.score + 1
v.score = v.score + .2
end
table.insert(r, v)
end
@@ -445,7 +450,7 @@ end
function context:setState(key, value)
self.state[key] = value
Config.update('miloRemote', self.state)
Config.update(configPath, self.state)
end
context.responseHandlers['received'] = function(response)
@@ -517,14 +522,14 @@ local function loadDirectory(dir)
})
end
end
page.menuBar.config:add({ dropmenu = UI.DropMenu { buttons = dropdown } })
page.menuBar.config.dropdown = dropdown
end
local programDir = fs.getDir(shell.getRunningProgram())
loadDirectory(fs.combine(programDir, 'plugins/remote'))
UI:setPage(page)
UI:pullEvents()
UI:start()
if context.socket then
context.socket:close()

View File

@@ -20,10 +20,12 @@ local Craft = {
local function splitKey(key)
local t = Util.split(key, '(.-):')
local item = { }
if #t[#t] > 8 then
item.nbtHash = table.remove(t)
if t[3] then
item.nbt = t[3]
t[3] = nil
end
item.damage = tonumber(table.remove(t))
item.name = table.concat(t, ':')
return item
end
@@ -32,7 +34,7 @@ local function makeRecipeKey(item)
if type(item) == 'string' then
item = splitKey(item)
end
return table.concat({ item.name, item.damage or 0, item.nbtHash }, ':')
return table.concat({ item.name, item.nbt }, ':')
end
local function convert(ingredient)
@@ -47,8 +49,7 @@ local function getCraftingTool(storage, item)
for _,v in pairs(items) do
if item.name == v.name and
(not item.damage or item.damage == v.damage) and
(not item.nbtHash or item.nbtHash == v.nbtHash) then
(not item.nbt or item.nbt == v.nbt) then
return v
end
end
@@ -93,11 +94,7 @@ function Craft.getItemCount(items, item)
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
v.nbt == item.nbt then
count = count + v.count
end
end
@@ -309,7 +306,7 @@ function Craft.craftRecipeInternal(recipe, count, storage, origItem, path)
local function maxBatch()
local max = 64
for _, i in Craft.ingredients(recipe) do
max = math.min(max, math.floor(64 / i.count))
max = math.min(max, math.floor(itemDB:getMaxCount(i.key) / i.count))
end
return max
end
@@ -387,18 +384,7 @@ function Craft.findRecipe(key)
end
local item = itemDB:splitKey(key)
if item.damage then
return Craft.recipes[makeRecipeKey(item)]
end
-- handle cases where the request is like : IC2:reactorVent:*
for rkey,recipe in pairs(Craft.recipes) do
local r = itemDB:splitKey(rkey)
if item.name == r.name and
(not item.nbtHash or r.nbtHash == item.nbtHash) then
return recipe
end
end
return Craft.recipes[makeRecipeKey(item)]
end
-- determine the full list of ingredients needed to craft

View File

@@ -1,19 +0,0 @@
-- Based on Squid's fuzzy search
-- https://github.com/SquidDev-CC/artist/blob/vnext/artist/lib/match.lua
--
-- not very fuzzy anymore
local SCORE_WEIGHT = 1000
local LEADING_LETTER_PENALTY = -3
local LEADING_LETTER_PENALTY_MAX = -9
local _find = string.find
local _max = math.max
return function(str, pattern)
local start = _find(str, pattern, 1, true)
if start then
-- All letters before the current one are considered leading, so add them to our penalty
return SCORE_WEIGHT + _max(LEADING_LETTER_PENALTY * (start - 1), LEADING_LETTER_PENALTY_MAX)
end
end

View File

@@ -93,7 +93,7 @@ function Milo:getMatches(item, flags)
local count = 0
local items = self:listItems()
if not flags.ignoreDamage and not flags.ignoreNbtHash then
if not flags.ignoreNbt then
local key = item.key or itemDB:makeKey(item)
local v = items[key]
if v then
@@ -104,8 +104,7 @@ function Milo:getMatches(item, flags)
else
for key,v in pairs(items) do
if item.name == v.name and
(flags.ignoreDamage or item.damage == v.damage) and
(flags.ignoreNbtHash or item.nbtHash == v.nbtHash) then
(flags.ignoreNbt or item.nbt == v.nbt) then
t[key] = Util.shallowCopy(v)
count = count + v.count
@@ -125,7 +124,7 @@ function Milo:getTurtleInventory()
for i, v in pairs(self.context.turtleInventory.adapter.list()) do
list[i] = itemDB:get(v, function()
return self.context.turtleInventory.adapter.getItemMeta(i)
return self.context.turtleInventory.adapter.getItemDetail(i)
end)
end
@@ -277,22 +276,15 @@ function Milo:learnRecipe()
for _,v1 in pairs(results) do
for _,v2 in pairs(ingredients) do
if v1.name == v2.name and
v1.nbtHash == v2.nbtHash and
(v1.damage == v2.damage or
(v1.maxDamage > 0 and v2.maxDamage > 0 and
v1.damage ~= v2.damage)) then
v1.nbt == v2.nbt then
if not newRecipe.crafingTools then
newRecipe.craftingTools = { }
end
local tool = Util.shallowCopy(v2)
if tool.maxDamage > 0 then
tool.damage = '*'
v2.damage = '*'
end
--[[
Turtles can only craft one item at a time using a tool :(
]]--
]] -- Todo: check if this still applies
maxCount = 1
newRecipe.craftingTools[itemDB:makeKey(tool)] = true

View File

@@ -1,53 +0,0 @@
local class = require('opus.class')
local itemDB = require('core.itemDB')
local Mini = require('milo.miniAdapter')
local os = _G.os
local Adapter = class(Mini)
function Adapter:init(args)
Mini.init(self, args)
self._rawList = self.list
function self.list()
-- wait for up to 1 sec until any items that have been inserted
-- into interface are added to the system
for _ = 0, 20 do
if #self._rawList() == 0 then
break
end
os.sleep(0)
end
local list = { }
for _, v in pairs(self.listAvailableItems()) do
list[itemDB:makeKey(v)] = v
end
return list
end
function self.getItemMeta(key)
local item = self.findItem(itemDB:splitKey(key))
if item and item.getMetadata then
return item.getMetadata()
end
end
function self.pushItems(target, key, amount, slot)
local item = self.findItem(itemDB:splitKey(key))
if item and item.export then
return item.export(target, amount, slot)
end
return 0
end
function self.pullItems(target, key, amount, slot)
_G._syslog({target, key, amount, slot })
return 0
end
end
return Adapter

View File

@@ -19,15 +19,17 @@ function Adapter:listItems(throttle)
local cache = { }
throttle = throttle or Util.throttle()
for k,v in pairs(self.list()) do
local list = self.list()
for k,v in pairs(list) do
if v.count > 0 then
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')
local key = table.concat({ v.name, v.nbt }, ':')
local entry = cache[key]
if entry then
entry.count = entry.count + v.count
else
cache[key] = itemDB:get(v, function() return self.getItemMeta(k) end)
cache[key] = itemDB:get(v, function() return self.getItemDetail(k) end)
end
throttle()
end
@@ -37,6 +39,10 @@ function Adapter:listItems(throttle)
-- useful for when inserting into chests
-- ie. insert only if chest does not have item and has free slots
-- bodge to make statsView not delay
-- todo: handle this better properly
self.__used = Util.size(list)
self.cache = cache
end

View File

@@ -9,6 +9,8 @@ local device = _G.device
local os = _G.os
local parallel = _G.parallel
local SCAN_CHUNK_SIZE = 16
local Storage = class()
local function loadOld(storage)
@@ -263,7 +265,22 @@ function Storage:listItems(throttle)
end
if #t > 0 then
parallel.waitForAll(table.unpack(t))
local chunk = {}
-- Split the work into chunks to avoid spamming too many coroutines.
for i = 1, #t do
table.insert(chunk, t[i])
-- When we reach the chunk limit, execute them and start a new chunk
if #chunk >= SCAN_CHUNK_SIZE then
parallel.waitForAll(table.unpack(chunk))
chunk = {}
end
end
if #chunk > 0 then
parallel.waitForAll(table.unpack(chunk))
end
end
for _, adapter in self:onlineAdapters() do
@@ -291,6 +308,134 @@ function Storage:listItems(throttle)
return self.cache
end
-- provide a raw list of all the items in all storage chests
-- it might be beneficial to move this to the adapter class at some point and cache the raw item list in this class at some point
function Storage:listItemsRaw(throttle)
local res = {}
throttle = throttle or Util.throttle()
for _, v in pairs(self.nodes) do
if v.category == "storage" then
local chest = device[v.name]
local items = chest.list()
for slot, item in pairs(items) do
items[slot] = itemDB:get(item, function() return chest.getItemDetail(slot) end)
end
res[v.name] = items
throttle()
end
end
return res
end
-- provide a list of items and which chests provide them
function Storage:listProviders(throttle)
local res = {}
local rawItems = self:listItemsRaw(throttle)
for chest, items in pairs(rawItems) do
for slot, item in pairs(items) do
local key = table.concat({item.name, item.nbt}, ":")
if not res[key] then
res[key] = {}
end
table.insert(res[key], {item = item, device = device[chest], lockedToThis = (self.nodes[chest].lock or {})[key] or false, slot = slot})
end
end
return res
end
-- defrags the storage system
function Storage:defrag(throttle)
local items = self:listProviders(throttle)
local slotsSaved = 0
-- This will make sure the table is sorted in the following order:
-- Unlocked stacks with less than maxCount items | Locked stacks with less than maxCount items | stacks with more than maxCount items
-- This way the locked stacks will be filled first
local function sortFunction(a, b)
local preferenceA, preferenceB
preferenceA = (a.item.count == a.item.maxCount and 3)
or (a.lockedToThis and 2)
or 1
preferenceB = (b.item.count == b.item.maxCount and 3)
or (b.lockedToThis and 2)
or 1
if preferenceA < preferenceB then
return true
elseif preferenceB > preferenceA then
return false
else
return a.item.count < b.item.count
end
end
for _, providers in pairs(items) do
table.sort(providers, sortFunction)
-- We're done when we either compressed the stacks so far, that there's only one left (#providers == 1)
-- Or when we've compressed so far, that the there's only one stack which has a lower count than the maxCount
-- Because of the sorting, we know that this will be the stack in providers[1], so we check if providers[2] is at the maxCount
while #providers > 1 and providers[2].item.count ~= providers[2].item.maxCount do
local from = providers[1]
local to
-- We're pushing to the highest stack which is still below the maxCount, this way as many slots as possible will be filled
-- This loop is guarenteed to assign a value to "to", as the only cases where it wouldn't (#providers == 1 or no provider with less than maxCount)
-- are ruled out by the condition of the outer while loop
for i = 2, #providers do
-- Give preference to locked chests
if not (to and to.lockedToThis or false) and providers[i].lockedToThis then
to = providers[i]
elseif ((to and to.lockedToThis or false) == providers[i].lockedToThis) and providers[i].item.count < providers[i].item.maxCount then
to = providers[i]
elseif providers[i].item.count == providers[i].item.maxCount then
-- As this slot is already at maxCount, all the remaining ones will also be due to sorting
-- If any of the remaining providers is locked that doesn't matter. We wouldn't have been able to push there anyways
break
end
end
local toMove = math.min(to.item.maxCount - to.item.count, from.item.count)
local s, m = pcall(function()
from.device.pushItems(to.device.name, from.slot, toMove, to.slot)
end)
if not s and m then
_G._syslog(m)
end
if s then
to.item.count = to.item.count + toMove
from.item.count = from.item.count - toMove
else
-- Do not try to send to the target again after it failed
for i = 2, #providers do
if to == providers[i] then
table.remove(providers, i)
break
end
end
end
if from.item.count <= 0 then
table.remove(providers, 1)
slotsSaved = slotsSaved + 1
end
table.sort(providers, sortFunction)
end
end
return slotsSaved
end
function Storage:updateCache(adapter, item, count)
if not adapter.cache then
adapter.dirty = true
@@ -298,7 +443,7 @@ function Storage:updateCache(adapter, item, count)
return
end
local key = item.key or table.concat({ item.name, item.damage, item.nbtHash }, ':')
local key = item.key or table.concat({ item.name, item.nbt }, ':')
local entry = adapter.cache[key]
if not entry then
@@ -344,15 +489,18 @@ function Storage:_sn(name)
end
local function isValidTransfer(adapter, target)
if string.find(target,":inventory") or string.find(target,":equipment") then
return false
end
-- lazily cache transfer locations
if not adapter.transferLocations then
--[[if not adapter.transferLocations then
adapter.transferLocations = adapter.getTransferLocations()
end
for _,v in pairs(adapter.transferLocations) do
if v == target then
for _,v in pairs(adapter.transferLocations) do]]
-- if v == target then
return true
end
end
-- end
--end
end
local function rawExport(source, target, item, qty, slot)
@@ -384,14 +532,15 @@ local function rawExport(source, target, item, qty, slot)
local stacks = source.list()
for key,stack in Util.rpairs(stacks) do
if stack.name == item.name and
stack.damage == item.damage and
stack.nbtHash == item.nbtHash then
stack.nbt == item.nbt then
local amount = math.min(qty, stack.count)
if amount > 0 then
amount = transfer(key, amount, slot)
if amount > 0 then
source.lastUpdate = os.clock()
target.lastUpdate = os.clock()
else
-- break -- this should work ?? is cache out of sync ?
end
end
qty = qty - amount
@@ -413,7 +562,7 @@ end
function Storage:export(target, slot, count, item)
local timer = Util.timer()
local total = 0
local key = item.key or table.concat({ item.name, item.damage, item.nbtHash }, ':')
local key = item.key or table.concat({ item.name, item.nbt }, ':')
local function provide(adapter, pcount)
-- update cache before export to allow for simultaneous calls
@@ -423,6 +572,7 @@ function Storage:export(target, slot, count, item)
if amount ~= pcount then
-- this *should* only happen if cache is out of sync
-- or... the target is full
self:updateCache(adapter, item, pcount - amount)
end
@@ -433,28 +583,43 @@ function Storage:export(target, slot, count, item)
end
count = count - amount
total = total + amount
return amount
end
-- request from adapters with this item
for _, adapter in self:onlineAdapters() do
local cache = adapter.cache and adapter.cache[key]
if cache then
provide(adapter, math.min(count, cache.count))
local request = math.min(count, cache.count)
local amount = provide(adapter, request)
-- couldn't provide the amount that was requested
-- either the target must be full - or the cache is invalid
if amount ~= request then
break
end
if count <= 0 then
return total
end
end
end
_G._syslog('STORAGE warning: %s(%d): %s%s %s failed to export',
item.displayName or item.name, count, self:_sn(target.name),
slot and string.format('[%d]', slot) or '[*]', key)
if slot then -- ignore warning when exporting to all slots
_G._syslog('STORAGE warning: %s(%d): %s%s %s failed to export',
item.displayName or item.name, count, self:_sn(target.name),
slot and string.format('[%d]', slot) or '[*]', key)
end
-- TODO: If there are misses when a slot is specified than something is wrong...
-- The caller should confirm the quantity beforehand
-- If no slot and full amount is not exported, then no need to check rest of adapters
-- ... so should not reach here
-- but... there is the case where exporting to all slots of the target
-- this is valid
return total
end
@@ -488,7 +653,7 @@ function Storage:import(source, slot, count, item)
local timer = Util.timer()
local total = 0
local key = item.key or table.concat({ item.name, item.damage, item.nbtHash }, ':')
local key = item.key or table.concat({ item.name, item.nbt }, ':')
if not self.cache then
self:listItems()
@@ -501,13 +666,15 @@ function Storage:import(source, slot, count, item)
entry = itemDB:add(item)
else
-- get the metadata from the device and add to db
entry = itemDB:add(source.adapter.getItemMeta(slot))
entry = itemDB:add(source.adapter.getItemDetail(slot))
end
itemDB:flush()
end
item = entry
local function insert(adapter)
if adapter.__used and adapter.__size and adapter.__used == adapter.__size then return 0 end
local amount = rawInsert(adapter, source.adapter, slot, count)
if amount > 0 then

41
milo/apis/turtleInv.lua Normal file
View File

@@ -0,0 +1,41 @@
--[[ Emulate a CC:T inventory on the turtle
]]
local TurtleInv = {}
local turtle = _G.turtle
local inventorySize = 16
local localName = "milo_local_name_unset"
function TurtleInv.setLocalName(name)
localName = name
end
function TurtleInv.size()
return inventorySize
end
function TurtleInv.list()
local list = {}
for slot = 1, inventorySize do
list[slot] = turtle.getItemDetail(slot)
end
return list
end
function TurtleInv.getItemDetail(slot)
return turtle.getItemDetail(slot, true)
end
function TurtleInv.pullItems(fromName, fromSlot, limit, toSlot)
return peripheral.call(fromName, "pushItems", localName, fromSlot, limit, toSlot)
end
function TurtleInv.pushItems(toName, fromSlot, limit, toSlot)
return peripheral.call(toName, "pullItems", localName, fromSlot, limit, toSlot)
end
return TurtleInv

View File

@@ -6,6 +6,7 @@ fs.delete('packages/milo/Milo.lua')
fs.delete('packages/milo/plugins/listing.lua')
fs.delete('packages/milo/apis/milo.lua')
fs.delete('packages/milo/plugins/manipulator.lua')
fs.delete('packages/milo/apps')
if peripheral.find('workbench') and shell.openForegroundTab then
shell.openForegroundTab('MiloLocal')

View File

@@ -1,33 +1,29 @@
local Milo = require('milo')
local UI = require('opus.ui')
local turtle = _G.turtle
local learnPage = UI.Page {
titleBar = UI.TitleBar { title = 'Learn Recipe' },
wizard = UI.Wizard {
y = 2, ey = -2,
pages = {
general = UI.WizardPage {
index = 1,
grid = UI.ScrollingGrid {
x = 2, ex = -2, y = 2, ey = -2,
disableHeader = true,
columns = {
{ heading = 'Name', key = 'name'},
},
sortColumn = 'name',
},
accelerators = {
grid_select = 'nextView',
general = UI.WizardPage {
index = 1,
grid = UI.ScrollingGrid {
x = 2, ex = -2, y = 2, ey = -2,
disableHeader = true,
columns = {
{ heading = 'Name', key = 'name'},
},
sortColumn = 'name',
},
accelerators = {
grid_select = 'nextView',
},
},
},
notification = UI.Notification { },
}
local general = learnPage.wizard.pages.general
local general = learnPage.wizard.general
function general:validate()
Milo:setState('learnType', self.grid:getSelected().value)
@@ -37,7 +33,7 @@ end
function learnPage:enable()
local t = { }
for _, page in pairs(self.wizard.pages) do
for _, page in pairs(self.wizard:getPages()) do
if page.validFor then
t[page.validFor] = {
name = page.validFor,
@@ -63,7 +59,7 @@ function learnPage.wizard:getPage(index)
local pages = { }
table.insert(pages, general)
local selected = general.grid:getSelected()
for _, page in pairs(self.pages) do
for _, page in pairs(self:getPages()) do
if page.validFor and (not selected or selected.value == page.validFor) then
table.insert(pages, page)
end

View File

@@ -1,6 +1,6 @@
local Craft = require('milo.craft2')
local Event = require('opus.event')
local fuzzy = require('milo.fuzzyMatch')
local fuzzy = require('opus.fuzzy')
local Milo = require('milo')
local Sound = require('opus.sound')
local UI = require('opus.ui')
@@ -33,6 +33,11 @@ local page = UI.Page {
event = 'rescan',
help = 'Rescan all inventories'
},
{
text = 'Defragment storage',
event = 'defrag',
help = 'Defragments the storage'
}
},
},
},
@@ -50,12 +55,12 @@ local page = UI.Page {
},
statusBar = UI.StatusBar {
filter = UI.TextEntry {
x = 1, ex = -17,
x = 1, ex = -18,
limit = 50,
shadowText = 'filter',
shadowTextColor = colors.gray,
backgroundColor = colors.cyan,
backgroundFocusColor = colors.cyan,
backgroundColor = 'primary',
backgroundFocusColor = 'primary',
accelerators = {
[ 'enter' ] = 'eject',
[ 'up' ] = 'grid_up',
@@ -64,14 +69,14 @@ local page = UI.Page {
},
},
storageStatus = UI.Text {
x = -16, ex = -9,
x = -17, ex = -10,
textColor = colors.lime,
backgroundColor = colors.cyan,
backgroundColor = 'primary',
value = '',
},
amount = UI.TextEntry {
x = -8, ex = -4,
limit = 3,
x = -9, ex = -4,
limit = 4,
shadowText = '1',
shadowTextColor = colors.gray,
backgroundColor = colors.black,
@@ -208,7 +213,7 @@ end
function page:eventHandler(event)
if event.type == 'quit' then
UI:exitPullEvents()
UI:quit()
elseif event.type == 'eject' or event.type == 'grid_select' then
self:eject(1)
@@ -250,6 +255,10 @@ function page:eventHandler(event)
self.grid:draw()
self:setFocus(self.statusBar.filter)
elseif event.type == 'defrag' then
self:defrag()
self:refresh(true)
elseif event.type == 'toggle_display' then
displayMode = (displayMode + 1) % 2
Util.merge(event.button, displayModes[displayMode])
@@ -272,7 +281,7 @@ function page:eventHandler(event)
end
elseif event.type == 'text_change' and event.element == self.statusBar.filter then
self.filter = event.text
self.filter = event.text or ''
if #self.filter == 0 then
self.filter = nil
end
@@ -357,6 +366,15 @@ function page:refresh(force)
self.throttle:disable()
end
function page:defrag()
local throttle = function() self.throttle:update() end
self.throttle:enable()
local saved = context.storage:defrag(throttle)
self.throttle:disable()
self:notifyInfo(("Saved %d slots"):format(saved))
end
function page:applyFilter()
local function filterItems(t, filter)
self.grid.sortColumn = Milo:getState('sortColumn') or 'count'
@@ -372,7 +390,7 @@ function page:applyFilter()
v.score = fuzzy(v.lname, filter)
if v.score then
if v.count > 0 then
v.score = v.score + 1
v.score = v.score + .2
end
table.insert(r, v)
end

View File

@@ -7,7 +7,6 @@ local Util = require('opus.util')
local colors = _G.colors
local device = _G.device
local turtle = _G.turtle
local context = Milo:getContext()
@@ -22,20 +21,50 @@ local networkPage = UI.Page {
y = -2, x = 1, ex = -9,
limit = 50,
shadowText = 'filter',
backgroundColor = colors.cyan,
backgroundFocusColor = colors.cyan,
backgroundColor = 'primary',
backgroundFocusColor = 'primary',
},
grid = UI.ScrollingGrid {
y = 2, ey = -3,
values = context.storage.nodes,
columns = {
{ key = 'suffix', width = 4, align = 'right' },
{ key = 'suffix', width = 5, align = 'right' },
{ heading = 'Name', key = 'displayName' },
{ heading = 'Type', key = 'mtype', width = 4 },
{ heading = 'Pri', key = 'priority', width = 3 },
},
sortColumn = 'displayName',
help = 'Select Node',
getDisplayValues = function(_, row)
row = Util.shallowCopy(row)
local t = { row.name:match(':(.+)_(%d+)$') }
if #t ~= 2 then
t = { row.name:match('(.+)_(%d+)$') }
end
if t and #t == 2 then
row.name, row.suffix = table.unpack(t)
row.name = row.name .. '_' .. row.suffix
end
row.displayName = row.displayName or row.name
return row
end,
getRowTextColor = function(self, row, selected)
if not device[row.name] then
return colors.red
end
if row.mtype == 'ignore' then
return colors.lightGray
end
return UI.Grid.getRowTextColor(self, row, selected)
end,
sortCompare = function(self, a, b)
if self.sortColumn == 'displayName' then
local an = a.displayName or a.name
local bn = b.displayName or b.name
return an:lower() < bn:lower()
end
return UI.Grid.sortCompare(self, a, b)
end,
},
remove = UI.Button {
y = -2, x = -4,
@@ -56,39 +85,6 @@ local networkPage = UI.Page {
}
}
function networkPage.grid:getDisplayValues(row)
row = Util.shallowCopy(row)
local t = { row.name:match(':(.+)_(%d+)$') }
if #t ~= 2 then
t = { row.name:match('(.+)_(%d+)$') }
end
if t and #t == 2 then
row.name, row.suffix = table.unpack(t)
row.name = row.name .. '_' .. row.suffix
end
row.displayName = row.displayName or row.name
return row
end
function networkPage.grid:getRowTextColor(row, selected)
if not device[row.name] then
return colors.red
end
if row.mtype == 'ignore' then
return colors.lightGray
end
return UI.Grid:getRowTextColor(row, selected)
end
function networkPage.grid:sortCompare(a, b)
if self.sortColumn == 'displayName' then
local an = a.displayName or a.name
local bn = b.displayName or b.name
return an:lower() < bn:lower()
end
return UI.Grid.sortCompare(self, a, b)
end
function networkPage:getList()
for _, v in pairs(device) do
if not context.storage.nodes[v.name] then
@@ -97,7 +93,7 @@ function networkPage:getList()
mtype = 'ignore',
category = 'ignore',
}
for _, page in pairs(nodeWizard.wizard.pages) do
for _, page in pairs(nodeWizard.wizard:getPages()) do
if page.isValidType and page:isValidType(node) then
context.storage.nodes[v.name] = node
break
@@ -192,55 +188,99 @@ nodeWizard = UI.Page {
titleBar = UI.TitleBar { title = 'Configure' },
wizard = UI.Wizard {
y = 2, ey = -2,
pages = {
general = UI.WizardPage {
index = 1,
backgroundColor = colors.cyan,
form = UI.Form {
x = 2, ex = -2, y = 1, ey = 3,
manualControls = true,
[1] = UI.TextEntry {
formLabel = 'Name', formKey = 'displayName',
help = 'Set a friendly name',
limit = 64,
},
[2] = UI.Chooser {
width = 25,
formLabel = 'Type', formKey = 'mtype',
--nochoice = 'Storage',
help = 'Select type',
},
general = UI.WizardPage {
index = 1,
form = UI.Form {
x = 2, ex = -2, y = 1, ey = 3,
manualControls = true,
[1] = UI.TextEntry {
formLabel = 'Name', formKey = 'displayName',
help = 'Set a friendly name',
limit = 64,
},
grid = UI.ScrollingGrid {
y = 5, ey = -2, x = 2, ex = -2,
columns = {
{ heading = 'Slot', key = 'slot', width = 4 },
{ heading = 'Name', key = 'displayName', },
{ heading = 'Qty', key = 'count' , width = 3 },
},
sortColumn = 'slot',
help = 'Contents of inventory',
[2] = UI.Chooser {
width = 25,
formLabel = 'Type', formKey = 'mtype',
--nochoice = 'Storage',
help = 'Select type',
},
},
confirmation = UI.WizardPage {
title = 'Confirm changes',
index = 2,
notice = UI.TextArea {
x = 2, ex = -2, y = 2, ey = -2,
value =
grid = UI.ScrollingGrid {
y = 5, ey = -2, x = 2, ex = -2,
columns = {
{ heading = 'Slot', key = 'slot', width = 4 },
{ heading = 'Name', key = 'displayName', },
{ heading = 'Qty', key = 'count' , width = 3 },
},
sortColumn = 'slot',
help = 'Contents of inventory',
getDisplayValues = function(_, row)
row = Util.shallowCopy(row)
row.displayName = itemDB:getName(row)
return row
end,
},
enable = function(self)
UI.WizardPage.enable(self)
self:focusFirst()
end,
isValidFor = function()
return false
end,
showInventory = function(self, node)
local inventory
if device[node.name] and device[node.name].list then
pcall(function()
inventory = device[node.name].list()
for k,v in pairs(inventory) do
v.slot = k
end
end)
end
self.grid:setValues(inventory or { })
end,
validate = function(self)
if self.form:save() then
nodeWizard.node.category = Util.find(nodeWizard.choices, 'value', nodeWizard.node.mtype).category
nodeWizard.nodePages = { }
table.insert(nodeWizard.nodePages, nodeWizard.wizard.general)
for _, page in pairs(nodeWizard.wizard:getPages()) do
if not page.isValidFor or page:isValidFor(nodeWizard.node) then
table.insert(nodeWizard.nodePages, page)
if page.setNode then
page:setNode(nodeWizard.node)
end
end
end
table.insert(nodeWizard.nodePages, nodeWizard.wizard.confirmation)
return true
end
end,
},
confirmation = UI.WizardPage {
title = 'Confirm changes',
index = 2,
notice = UI.TextArea {
x = 2, ex = -2, y = 2, ey = -2,
value =
[[Press accept to save the changes.
The settings will take effect immediately!]],
},
},
isValidFor = function()
return false
end,
},
},
statusBar = UI.StatusBar {
backgroundColor = colors.cyan,
backgroundColor = 'primary',
},
notification = UI.Notification { },
filter = UI.SlideOut {
backgroundColor = colors.cyan,
noFill = true,
menuBar = UI.MenuBar {
buttons = {
{ text = 'Save', event = 'save' },
@@ -248,7 +288,8 @@ The settings will take effect immediately!]],
},
},
grid = UI.ScrollingGrid {
x = 2, ex = -6, y = 2, ey = -6,
x = 2, ex = -6, y = 3, ey = -7,
disableHeader = true,
columns = {
{ heading = 'Name', key = 'displayName' },
},
@@ -256,24 +297,25 @@ The settings will take effect immediately!]],
accelerators = {
delete = 'remove_entry',
},
getDisplayValues = function(_, row)
row = Util.shallowCopy(row)
row.displayName = itemDB:getName(row)
return row
end,
},
remove = UI.Button {
x = -4, y = 4,
text = '-', event = 'remove_entry', help = 'Remove',
},
form = UI.Form {
x = 2, y = -4, height = 3,
x = 2, y = -5, height = 3,
margin = 1,
manualControls = true,
[1] = UI.Checkbox {
formLabel = 'Ignore Dmg', formKey = 'ignoreDamage',
help = 'Ignore damage of item',
},
[2] = UI.Checkbox {
formLabel = 'Ignore NBT', formKey = 'ignoreNbtHash',
formLabel = 'Ignore NBT', formKey = 'ignoreNbt',
help = 'Ignore NBT of item',
},
[3] = UI.Chooser {
[2] = UI.Chooser {
width = 13,
formLabel = 'Mode', formKey = 'blacklist',
nochoice = 'whitelist',
@@ -281,7 +323,7 @@ The settings will take effect immediately!]],
{ name = 'whitelist', value = false },
{ name = 'blacklist', value = true },
},
help = 'Ignore damage of item'
help = 'Accept item by default or deny by default'
},
scan = UI.Button {
x = -11, y = 1,
@@ -290,145 +332,78 @@ The settings will take effect immediately!]],
},
},
statusBar = UI.StatusBar {
backgroundColor = colors.cyan,
backgroundColor = 'primary',
},
show = function(self, entry, callback, whitelistOnly)
self.entry = entry
self.callback = callback
if not self.entry.filter then
self.entry.filter = { }
end
self.form:setValues(entry)
self:resetGrid()
self.form[2].inactive = whitelistOnly
UI.SlideOut.show(self)
self:setFocus(self.form.scan)
Milo:pauseCrafting({ key = 'gridInUse', msg = 'Crafting paused' })
end,
hide = function(self)
UI.SlideOut.hide(self)
Milo:resumeCrafting({ key = 'gridInUse' })
end,
resetGrid = function(self)
local t = { }
for k in pairs(self.entry.filter) do
table.insert(t, itemDB:splitKey(k))
end
self.grid:setValues(t)
end,
eventHandler = function(self, event)
if event.type == 'focus_change' then
self.statusBar:setStatus(event.focused.help)
elseif event.type == 'scan_turtle' then
local inventory = Milo:getTurtleInventory()
for _,item in pairs(inventory) do
self.entry.filter[itemDB:makeKey(item)] = true
end
self:resetGrid()
self.grid:update()
self.grid:draw()
Milo:emptyInventory()
elseif event.type == 'remove_entry' then
local row = self.grid:getSelected()
if row then
Util.removeByValue(self.grid.values, row)
self.grid:update()
self.grid:draw()
end
elseif event.type == 'save' then
self.form:save()
self.entry.filter = { }
for _,v in pairs(self.grid.values) do
self.entry.filter[itemDB:makeKey(v)] = true
end
self:hide()
self.callback()
elseif event.type == 'cancel' then
self:hide()
else
return UI.SlideOut.eventHandler(self, event)
end
return true
end,
},
}
--[[ Filter slide out ]] --
function nodeWizard.filter:show(entry, callback, whitelistOnly)
self.entry = entry
self.callback = callback
if not self.entry.filter then
self.entry.filter = { }
end
self.form:setValues(entry)
self:resetGrid()
self.form[3].inactive = whitelistOnly
UI.SlideOut.show(self)
self:setFocus(self.form.scan)
Milo:pauseCrafting({ key = 'gridInUse', msg = 'Crafting paused' })
end
function nodeWizard.filter:hide()
UI.SlideOut.hide(self)
Milo:resumeCrafting({ key = 'gridInUse' })
end
function nodeWizard.filter:resetGrid()
local t = { }
for k in pairs(self.entry.filter) do
table.insert(t, itemDB:splitKey(k))
end
self.grid:setValues(t)
end
function nodeWizard.filter.grid:getDisplayValues(row)
row = Util.shallowCopy(row)
row.displayName = itemDB:getName(row)
return row
end
function nodeWizard.filter:eventHandler(event)
if event.type == 'focus_change' then
self.statusBar:setStatus(event.focused.help)
elseif event.type == 'scan_turtle' then
local inventory = Milo:getTurtleInventory()
for _,item in pairs(inventory) do
self.entry.filter[itemDB:makeKey(item)] = true
end
self:resetGrid()
self.grid:update()
self.grid:draw()
Milo:emptyInventory()
elseif event.type == 'remove_entry' then
local row = self.grid:getSelected()
if row then
Util.removeByValue(self.grid.values, row)
self.grid:update()
self.grid:draw()
end
elseif event.type == 'save' then
self.form:save()
self.entry.filter = { }
for _,v in pairs(self.grid.values) do
self.entry.filter[itemDB:makeKey(v)] = true
end
self:hide()
self.callback()
elseif event.type == 'cancel' then
self:hide()
else
return UI.SlideOut.eventHandler(self, event)
end
return true
end
--[[ General Page ]] --
function nodeWizard.wizard.pages.general:enable()
UI.WizardPage.enable(self)
self:focusFirst()
end
function nodeWizard.wizard.pages.general:isValidFor()
return false
end
function nodeWizard.wizard.pages.general:showInventory(node)
local inventory
if device[node.name] and device[node.name].list then
pcall(function()
inventory = device[node.name].list()
for k,v in pairs(inventory) do
v.slot = k
end
end)
end
self.grid:setValues(inventory or { })
end
function nodeWizard.wizard.pages.general.grid:getDisplayValues(row)
row = Util.shallowCopy(row)
row.displayName = itemDB:getName(row)
return row
end
function nodeWizard.wizard.pages.general:validate()
if self.form:save() then
nodeWizard.node.category = Util.find(nodeWizard.choices, 'value', nodeWizard.node.mtype).category
nodeWizard.nodePages = { }
table.insert(nodeWizard.nodePages, nodeWizard.wizard.pages.general)
for _, page in pairs(nodeWizard.wizard.pages) do
if not page.isValidFor or page:isValidFor(nodeWizard.node) then
table.insert(nodeWizard.nodePages, page)
if page.setNode then
page:setNode(nodeWizard.node)
end
end
end
table.insert(nodeWizard.nodePages, nodeWizard.wizard.pages.confirmation)
return true
end
end
--[[ Confirmation ]]--
function nodeWizard.wizard.pages.confirmation:isValidFor()
return false
end
--[[ Wizard ]] --
function nodeWizard:enable(node)
local adapter = node.adapter
@@ -441,7 +416,7 @@ function nodeWizard:enable(node)
{ name = 'Ignore', value = 'ignore', category = 'ignore' },
{ name = 'Hidden', value = 'hidden', category = 'ignore', help = 'Do not show in list' },
}
for _, page in pairs(self.wizard.pages) do
for _, page in pairs(self.wizard:getPages()) do
if page.isValidType then
local choice = page:isValidType(self.node)
if choice and not Util.find(self.choices, 'value', choice.value) then
@@ -449,15 +424,15 @@ function nodeWizard:enable(node)
end
end
end
self.wizard.pages.general.form[1].shadowText = self.node.name
self.wizard.pages.general.form[2].choices = self.choices
self.wizard.pages.general.form:setValues(self.node)
self.wizard.general.form[1].shadowText = self.node.name
self.wizard.general.form[2].choices = self.choices
self.wizard.general.form:setValues(self.node)
self.wizard.pages.general:showInventory(self.node)
self.wizard.general:showInventory(self.node)
self.nodePages = { }
table.insert(self.nodePages, self.wizard.pages.general)
table.insert(self.nodePages, self.wizard.pages.confirmation)
table.insert(self.nodePages, self.wizard.general)
table.insert(self.nodePages, self.wizard.confirmation)
UI.Page.enable(self)
end

View File

@@ -17,7 +17,6 @@ Right-clicking on the activity monitor will reset the totals.]]
local wizardPage = UI.WizardPage {
title = 'Activity Monitor',
index = 2,
backgroundColor = colors.cyan,
[1] = UI.TextArea {
x = 2, ex = -2, y = 2, ey = 6,
marginRight = 0,

View File

@@ -3,7 +3,6 @@ local Event = require('opus.event')
local Milo = require('milo')
local UI = require('opus.ui')
local colors = _G.colors
local device = _G.device
local fs = _G.fs
local os = _G.os
@@ -23,7 +22,6 @@ Backup configuration files each minecraft day.
local wizardPage = UI.WizardPage {
title = 'Backup Drive',
index = 2,
backgroundColor = colors.cyan,
[1] = UI.TextArea {
x = 2, ex = -2, y = 2, ey = -2,
value = string.format(template, Ansi.yellow, Ansi.reset),

View File

@@ -18,7 +18,6 @@ Note that you do not need to import items from the brewing stand or export blaze
local wizardPage = UI.WizardPage {
title = 'Brewing Stand',
index = 2,
backgroundColor = colors.cyan,
[1] = UI.TextArea {
x = 2, ex = -2, y = 2, ey = -2,
value = string.format(template, Ansi.yellow, Ansi.reset),

View File

@@ -0,0 +1,29 @@
local itemDB = require('core.itemDB')
local Milo = require('milo')
local device = _G.device
local EmitterTask = {
name = 'emitter',
priority = 5,
}
local function filter(a)
return a.emitter
end
function EmitterTask:cycle(context)
for node in context.storage:filterActive('emitter', filter) do
local config = node.emitter
local item = Milo:getItem(itemDB:splitKey(config.item))
config.signal = not not config.signal
if item and item.count >= config.amount then
device[node.name].setOutput(config.side, config.signal)
else
device[node.name].setOutput(config.side, not config.signal)
end
end
end
Milo:registerTask(EmitterTask)

View File

@@ -0,0 +1,107 @@
local Milo = require('milo')
local UI = require('opus.ui')
local itemDB = require('core.itemDB')
local colors = _G.colors
local device = _G.device
local wizardPage = UI.WizardPage {
title = 'Level Emitter',
index = 2,
[1] = UI.TextArea {
x = 2, y = 1,
height = 2,
textColor = colors.yellow,
value = 'Emit a redstone signal if an\nitem amount if over a threshold',
},
form = UI.Form {
x = 1, ex = -1, y = 3, ey = -1,
manualControls = true,
itemName = UI.TextEntry {
formLabel = 'Item', formKey = 'item', formIndex = 1,
help = 'Item to monitor',
required = true,
},
side = UI.Chooser {
formLabel = 'Side', formKey = 'side', formIndex = 2,
width = 10,
choices = {
{name = 'Down', value = 'down'},
{name = 'Up', value = 'up'},
{name = 'North', value = 'north'},
{name = 'South', value = 'south'},
{name = 'West', value = 'west'},
{name = 'East', value = 'east'},
},
required = true,
},
amount = UI.TextEntry {
formLabel = 'Amount', formKey = 'amount', formIndex = 3,
width = 7,
transform = 'number',
help = 'Threshold value',
required = true,
},
signal = UI.Checkbox {
formLabel = 'Signal', formKey = 'signal', formIndex = 4,
help = 'Enable redstone signal when over threshold',
},
scanItem = UI.Button {
x = 15, y = 6,
text = 'Scan item', event = 'scan_turtle',
help = 'Scan an item from the turtle inventory',
},
},
}
function wizardPage:setNode(node)
self.node = node
if not self.node.emitter then
self.node.emitter = {
signal = { value = true }
}
end
self.form:setValues(self.node.emitter)
end
function wizardPage:validate()
return self.form:save()
end
function wizardPage:isValidType(node)
local m = device[node.name]
return m and m.type == 'redstone_integrator' and {
name = 'Level Emitter',
value = 'emitter',
category = 'custom',
help = 'Emit redstone signals',
}
end
function wizardPage:isValidFor(node)
return node.mtype == 'emitter'
end
function wizardPage:enable()
Milo:pauseCrafting({ key = 'gridInUse', msg = 'Crafting paused' })
UI.WizardPage.enable(self)
end
function wizardPage:disable()
Milo:resumeCrafting({ key = 'gridInUse' })
UI.WizardPage.disable(self)
end
function wizardPage:eventHandler(event)
if event.type == 'scan_turtle' then
local inventory = Milo:getTurtleInventory()
for _,item in pairs(inventory) do
self.form.itemName.value = itemDB:makeKey(item)
break
end
self:draw()
Milo:emptyInventory()
end
end
UI:getPage('nodeWizard').wizard:add({ emiter = wizardPage })

View File

@@ -18,6 +18,9 @@ function ExportTask:cycle(context)
for node in context.storage:filterActive('machine', filter) do
tasks:add(function()
local slots
for _, entry in pairs(node.exports) do
if not entry.filter then
@@ -27,7 +30,7 @@ function ExportTask:cycle(context)
end
local function exportSingleSlot()
local slot = node.adapter.getItemMeta(entry.slot)
local slot = node.adapter.getItemDetail(entry.slot)
if slot and slot.count == slot.maxCount then
return
@@ -38,8 +41,7 @@ function ExportTask:cycle(context)
for key in pairs(entry.filter) do
local filterItem = itemDB:splitKey(key)
if (slot.name == filterItem.name and
(entry.ignoreDamage or slot.damage == filterItem.damage) and
(entry.ignoreNbtHash or slot.nbtHash == filterItem.nbtHash)) then
(entry.ignoreNbt or slot.nbt == filterItem.nbt)) then
local items = Milo:getMatches(filterItem, entry)
local _, item = next(items)
@@ -66,13 +68,35 @@ function ExportTask:cycle(context)
end
local function exportItems()
local function canExport(item)
if not node.adapter.__size then
node.adapter.__size = node.adapter.size()
end
if not slots then
slots = node.adapter.list()
end
for i = 1, node.adapter.__size do
local slot = slots[i]
if (not slot or slot.name == item.name and
(entry.ignoreNbt or slot.nbt == item.nbt) and
slot.count < item.maxCount) then
return true
end
end
end
for key in pairs(entry.filter) do
local items = Milo:getMatches(itemDB:splitKey(key), entry)
for _,item in pairs(items) do
if context.storage:export(node, nil, item.count, item) == 0 then
-- TODO: really shouldn't break here as there may be room in other slots
-- leaving for now for performance reasons
break
if canExport(item) then
if context.storage:export(node, nil, item.count, item) == 0 then
break
end
-- refresh the slots
slots = nil
end
end
end

View File

@@ -21,7 +21,7 @@ function ImportTask:cycle(context)
for _, entry in pairs(node.imports) do
local function itemMatchesFilter(item)
if not entry.ignoreDamage and not entry.ignoreNbtHash then
if not entry.ignoreNbt then
local key = itemDB:makeKey(item)
return entry.filter[key]
end
@@ -29,8 +29,7 @@ function ImportTask:cycle(context)
for key in pairs(entry.filter) do
local v = itemDB:splitKey(key)
if item.name == v.name and
(entry.ignoreDamage or item.damage == v.damage) and
(entry.ignoreNbtHash or item.nbtHash == v.nbtHash) then
(entry.ignoreNbt or item.nbt == v.nbt) then
return true
end
end
@@ -52,7 +51,7 @@ function ImportTask:cycle(context)
local function importSlot(slotNo)
local item = itemDB:get(list[slotNo], function()
return node.adapter.getItemMeta(slotNo)
return node.adapter.getItemDetail(slotNo)
end)
if item and matchesFilter(item) then
if context.storage:import(node, slotNo, item.count, item) ~= item.count then

View File

@@ -1,7 +1,6 @@
local Ansi = require('opus.ansi')
local UI = require('opus.ui')
local colors = _G.colors
local device = _G.device
--[[ Configuration Screen ]]
@@ -14,7 +13,6 @@ Any items placed in this chest will be imported into storage.
local inputChestWizardPage = UI.WizardPage {
title = 'Input Chest',
index = 2,
backgroundColor = colors.cyan,
[1] = UI.TextArea {
x = 2, ex = -2, y = 2, ey = -2,
value = string.format(template, Ansi.yellow, Ansi.reset),

View File

@@ -41,7 +41,6 @@ function page:eventHandler(event)
elseif event.type == 'focus_change' then
self.statusBar:setStatus(event.focused.help)
self.statusBar:draw()
elseif event.type == 'success_message' then
self.notification:success(event.message)

View File

@@ -1,14 +1,11 @@
local Ansi = require('opus.ansi')
local UI = require('opus.ui')
local colors = _G.colors
local infoTab = UI.Tab {
tabTitle = 'Info',
title = 'Info',
index = 4,
backgroundColor = colors.cyan,
textArea = UI.TextArea {
x = 2, ex = -2, y = 2,
x = 2, ex = -2, y = 2, ey = -2,
},
}
@@ -23,13 +20,18 @@ function infoTab:draw()
Ansi.orange, item.displayName, Ansi.reset,
item.name)
if item.nbtHash then
value = value .. item.nbtHash .. '\n'
if item.nbt then
value = value .. item.nbt .. '\n'
end
value = value .. string.format('\n%sDamage:%s %s',
Ansi.yellow, Ansi.reset, item.damage)
value = value .. string.format('\n%sCount:%s %s',
Ansi.yellow, Ansi.reset, item.count)
if item.durability then
value = value .. string.format('\n%Durability:%s %s',
Ansi.yellow, Ansi.reset, item.durability)
end
if item.maxDamage and item.maxDamage > 0 then
value = value .. string.format(' (max: %s)', item.maxDamage)
end

View File

@@ -7,9 +7,8 @@ local colors = _G.colors
local context = Milo:getContext()
local machinesTab = UI.Tab {
tabTitle = 'Machine',
title = 'Machine',
index = 3,
backgroundColor = colors.cyan,
grid = UI.ScrollingGrid {
x = 2, ex = -2, y = 2, ey = -2,
disableHeader = true,

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