diff --git a/config.lua b/config.lua index e335a83..48847f5 100644 --- a/config.lua +++ b/config.lua @@ -32,7 +32,9 @@ headerColor = colors.white, footerBgColor = colors.red, footerColor = colors.white, - productBgColor = colors.blue, + productBgColors = { + colors.blue, + }, outOfStockQtyColor = colors.red, lowQtyColor = colors.orange, warningQtyColor = colors.yellow, @@ -42,15 +44,19 @@ priceColor = colors.lime, addressColor = colors.white, currencyTextColor = colors.white, - currency1Color = colors.green, - currency2Color = colors.pink, - currency3Color = colors.lightBlue, - currency4Color = colors.yellow, + currencyBgColors = { + colors.green, + colors.pink, + colors.lightBlue, + colors.yellow, + }, catagoryTextColor = colors.white, - category1Color = colors.pink, - category2Color = colors.orange, - category3Color = colors.lime, - category4Color = colors.lightBlue, + categoryBgColors = { + colors.pink, + colors.orange, + colors.lime, + colors.lightBlue, + }, activeCategoryColor = colors.black, }, palette = { @@ -79,9 +85,8 @@ host = "ksbangelco", name = "radon.kst", pkey = "", - pkeyFormat = "raw", -- Either 'raw' or 'kristwallet', defaults to 'raw' - -- NOTE: It is not recommended to use kwallet, the best practice is to convert your pkey (using - -- kwallet format) to raw pkey yourself first, and then use that here. Thus improving security. + pkeyFormat = "raw", -- Currently must be 'raw', kwallet support is planned + -- You can get your raw pkey from kristweb or using https://pkey.its-em.ma/ value = 1.0 -- Default scaling on item prices, can be overridden on a per-item basis }, { @@ -90,24 +95,21 @@ host = "tttttttttt", name = "radon.tst", pkey = "", - pkeyFormat = "raw", -- Either 'raw' or 'kristwallet', defaults to 'raw' - -- NOTE: It is not recommended to use kwallet, the best practice is to convert your pkey (using - -- kwallet format) to raw pkey yourself first, and then use that here. Thus improving security. + pkeyFormat = "raw", -- Currently must be 'raw', kwallet support is planned + -- You can get your raw pkey from kristweb or using https://pkey.its-em.ma/ value = 0.1 -- Default scaling on item prices, can be overridden on a per-item basis }, }, peripherals = { - monitor = nil, - self = "west", -- Cardinal direction or network name of the turtle - -- relative to storage chest(s) used - -- only required if output chest is "self" - -- or a relative position - -- ex: "west", "turtle_1" - selfRelativeOutput = nil, -- Same as above but for output chest + monitor = nil, -- Modem to display on, if not specified, will use the first monitor found + modem = nil, -- Modem for inventories, if not specified, will use the first modem found exchangeChest = nil, outputChest = "self", -- Chest peripheral or self + -- NOTE: Chest dropping is NYI in plethora 1.19, so do not use unless + -- the output chest can be accessed }, exchange = { + -- Not yet implemented enabled = true, node = "https://localhost:8000/" } diff --git a/core/ConfigValidator.lua b/core/ConfigValidator.lua index f91fa09..c792f75 100644 --- a/core/ConfigValidator.lua +++ b/core/ConfigValidator.lua @@ -34,7 +34,11 @@ headerColor = "color", footerBgColor = "color", footerColor = "color", - productBgColor = "color", + productBgColors = { + __type = "array", + __min = 1, + __entry = "color" + }, outOfStockQtyColor = "color", lowQtyColor = "color", warningQtyColor = "color", @@ -44,15 +48,17 @@ priceColor = "color", addressColor = "color", currencyTextColor = "color", - currency1Color = "color", - currency2Color = "color", - currency3Color = "color", - currency4Color = "color", + currencyBgColors = { + __type = "array", + __min = 1, + __entry = "color" + }, catagoryTextColor = "color", - category1Color = "color", - category2Color = "color", - category3Color = "color", - category4Color = "color", + categoryBgColors = { + __type = "array", + __min = 1, + __entry = "color" + }, activeCategoryColor = "color", }, palette = { @@ -89,8 +95,7 @@ }, peripherals = { monitor = "string?", - self = "string?", - selfRelativeOutput = "string?", + modem = "modem?", exchangeChest = "chest?", outputChest = "chest", }, @@ -121,6 +126,97 @@ } } + +local function typeCheck(entryType, typeName, value, path) + if value then + if entryType == "table" and type(value) ~= "table" then + error("Config value " .. subpath .. " must be a table") + end + if entryType == "string" and type(value) ~= "string" then + error("Config value " .. subpath .. " must be a string") + end + if entryType == "number" and type(value) ~= "number" then + error("Config value " .. subpath .. " must be a number") + end + if entryType == "color" then + if type(value) ~= "number" then + error("Config value " .. subpath .. " must be a color") + end + m,n = math.frexp(value) + if m ~= 0.5 or n < 1 or n > 16 then + error("Config value " .. subpath .. " must be a color") + end + end + if entryType == "modem" then + if type(value) ~= "string" then + error("Config value " .. subpath .. " must be a modem name") + end + if peripheral.getType(value) ~= "modem" then + error("Config value " .. subpath .. " must refer to a modem") + end + end + if entryType == "chest" then + if type(value) ~= "string" then + error("Config value " .. subpath .. " must be a networked chest") + end + if not turtle and (value == "left" or value == "right" or value == "front" or value == "back" or value == "top" or value == "bottom") then + error("Config value " .. subpath .. " must not be a relative position") + end + if not turtle and value == "self" then + error("Config value " .. subpath .. " can only be self for turtles") + end + if value ~= "self" then + local chestMethods = peripheral.getMethods(value) + if not chestMethods then + error("Config value " .. subpath .. " must refer to a valid peripheral") + end + local hasDropMethod = false + for i = 1, #chestMethods do + if chestMethods[i] == "drop" then + hasDropMethod = true + break + end + end + if not hasDropMethod then + error("Config value " .. subpath .. " must refer to a peripheral with an inventory") + end + end + end + if entryType == "boolean" and type(value) ~= "boolean" then + error("Config value " .. subpath .. " must be a boolean") + end + if entryType:sub(1, 5) == "enum<" and entryType:sub(-1) == ">" then + local enum = entryType:sub(6, -2) + local found = false + for enumValue in enum:gmatch("[^|]+") do + enumValue = enumValue:sub(enumValue:find("'(.*)'")):sub(2, -2) + if value == enumValue then + found = true + break + end + end + if not found then + if typeName then + error("Config value " .. subpath .. " must be entryType " .. typeName .. " matching " .. enum) + else + error("Config value " .. subpath .. " must be one of " .. enum) + end + end + end + if entryType:sub(1, 6) == "regex<" and entryType:sub(-1) == ">" then + local regexString = entryType:sub(7, -2) + local regex = r2l.new(regexString) + if not regex(value) then + if typeName then + error("Config value " .. subpath .. " must be entryType " .. typeName .. " matching " .. regexString) + else + error("Config value " .. subpath .. " must match " .. regexString) + end + end + end + end +end + local function validate(config, schema, path) if not path then path = "" @@ -141,7 +237,11 @@ end if schema.__entry then for i = 1, #config do - validate(config[i], schema.__entry, path .. "[" .. i .. "]") + if type(config[i]) ~= "table" then + typeCheck(schema.__entry, schema.__entry, config[i], path .. "[" .. i .. "]") + else + validate(config[i], schema.__entry, path .. "[" .. i .. "]") + end end end end @@ -164,85 +264,7 @@ if v:sub(-1) == "?" then v = v:sub(1, -2) end - if config[k] then - if v == "table" and type(config[k]) ~= "table" then - error("Config value " .. subpath .. " must be a table") - end - if v == "string" and type(config[k]) ~= "string" then - error("Config value " .. subpath .. " must be a string") - end - if v == "number" and type(config[k]) ~= "number" then - error("Config value " .. subpath .. " must be a number") - end - if v == "color" then - if type(config[k]) ~= "number" then - error("Config value " .. subpath .. " must be a color") - end - m,n = math.frexp(config[k]) - if m ~= 0.5 or n < 1 or n > 16 then - error("Config value " .. subpath .. " must be a color") - end - end - if v == "chest" then - if type(config[k]) ~= "string" then - error("Config value " .. subpath .. " must be a networked chest") - end - if not turtle and (config[k] == "left" or config[k] == "right" or config[k] == "front" or config[k] == "back" or config[k] == "top" or config[k] == "bottom") then - error("Config value " .. subpath .. " must not be a relative position") - end - if not turtle and config[k] == "self" then - error("Config value " .. subpath .. " can only be self for turtles") - end - if config[k] ~= "self" then - local chestMethods = peripheral.getMethods(config[k]) - if not chestMethods then - error("Config value " .. subpath .. " must refer to a valid peripheral") - end - local hasDropMethod = false - for i = 1, #chestMethods do - if chestMethods[i] == "drop" then - hasDropMethod = true - break - end - end - if not hasDropMethod then - error("Config value " .. subpath .. " must refer to a peripheral with an inventory") - end - end - end - if v == "boolean" and type(config[k]) ~= "boolean" then - error("Config value " .. subpath .. " must be a boolean") - end - if v:sub(1, 5) == "enum<" and v:sub(-1) == ">" then - local enum = v:sub(6, -2) - local found = false - for enumValue in enum:gmatch("[^|]+") do - enumValue = enumValue:sub(enumValue:find("'(.*)'")):sub(2, -2) - if config[k] == enumValue then - found = true - break - end - end - if not found then - if typeName then - error("Config value " .. subpath .. " must be type " .. typeName .. " matching " .. enum) - else - error("Config value " .. subpath .. " must be one of " .. enum) - end - end - end - if v:sub(1, 6) == "regex<" and v:sub(-1) == ">" then - local regexString = v:sub(7, -2) - local regex = r2l.new(regexString) - if not regex(config[k]) then - if typeName then - error("Config value " .. subpath .. " must be type " .. typeName .. " matching " .. regexString) - else - error("Config value " .. subpath .. " must match " .. regexString) - end - end - end - end + typeCheck(v, typeName, config[k], subpath) end end end diff --git a/core/ShopState.lua b/core/ShopState.lua index b58c55e..5c75395 100644 --- a/core/ShopState.lua +++ b/core/ShopState.lua @@ -7,12 +7,13 @@ local ShopState = {} local ShopState_mt = { __index = ShopState } -function ShopState.new(config, products) +function ShopState.new(config, products, modem) local self = setmetatable({}, ShopState_mt) self.running = false self.config = config self.products = products + self.modem = modem self.selectedCurrency = config.currencies[1] self.selectedCategory = 1 self.numCategories = 1 @@ -94,19 +95,11 @@ print("Purchased " .. available .. " of " .. purchasedProduct.name .. " for " .. transaction.from .. " for " .. transaction.value .. " " .. transactionCurrency.name .. " (refund " .. refundAmount .. ")") if available > 0 then for _, productSource in ipairs(productSources) do - if isRelative(state.config.peripherals.outputChest) then - -- Move to self first - if not turtle then - error("Relative output but not a turtle!") - end - peripheral.call(productSource.inventory, "pushItems", state.config.peripherals.self, productSource.slot, productSource.amount, 1) - peripheral.call(state.config.peripherals.outputChest, "pullItems", state.config.peripherals.selfRelativeOutput, productSource.slot, productSource.amount, 1) - peripheral.call(state.config.peripherals.outputChest, "drop", 1, productSource.amount, state.config.settings.dropDirection) - elseif state.config.peripherals.outputChest == "self" then + if state.config.peripherals.outputChest == "self" then if not turtle then error("Self output but not a turtle!") end - peripheral.call(productSource.inventory, "pushItems", state.config.peripherals.self, productSource.slot, productSource.amount, 1) + peripheral.call(productSource.inventory, "pushItems", state.modem.getNameLocal(), productSource.slot, productSource.amount, 1) if state.config.settings.dropDirection == "forward" then turtle.drop(productSource.amount) elseif state.config.settings.dropDirection == "up" then @@ -118,7 +111,7 @@ end else peripheral.call(productSource.inventory, "pushItems", state.config.peripherals.outputChest, productSource.slot, productSource.amount, 1) - peripheral.call(state.config.peripherals.outputChest, "drop", 1, productSource.amount, state.config.settings.dropDirection) + --peripheral.call(state.config.peripherals.outputChest, "drop", 1, productSource.amount, state.config.settings.dropDirection) end end purchasedProduct.quantity = purchasedProduct.quantity - available diff --git a/modules/canvas.lua b/modules/canvas.lua index 4cef54a..0e41768 100644 --- a/modules/canvas.lua +++ b/modules/canvas.lua @@ -454,9 +454,9 @@ local partitionSize = 20 local partitions = {} for y = 1, self.height*3, partitionSize do - partitions[floor(y/partitionSize)+1] = {} + partitions[ceil(y/partitionSize)] = {} for x = 1, self.width*2, partitionSize do - partitions[floor(y/partitionSize)+1][floor(x/partitionSize)+1] = {} + partitions[ceil(y/partitionSize)][ceil(x/partitionSize)] = {} end end -- local t2 = os.epoch("utc") @@ -469,13 +469,13 @@ -- if PixelCanvas.is(other) then local otherCanvas, otherX, otherY = other[1], other[2], other[3] - local originPartitionX = floor(otherX/partitionSize)*partitionSize+1 - local originPartitionY = floor(otherY/partitionSize)*partitionSize+1 + local originPartitionX = (ceil(otherX/partitionSize) - 1)*partitionSize + 1 + local originPartitionY = (ceil(otherY/partitionSize) - 1)*partitionSize + 1 for y = originPartitionY, otherY+otherCanvas.height-1, partitionSize do for x = originPartitionX, otherX+otherCanvas.width-1, partitionSize do - local px = floor(x/partitionSize)+1 - local py = floor(y/partitionSize)+1 + local px = ceil(x/partitionSize) + local py = ceil(y/partitionSize) local partition = (partitions[py] or {})[px] if partition then partition[#partition + 1] = other @@ -577,8 +577,8 @@ for x, _ in pairs(row) do local targetX = ceil(x / 2) local currPixel = self.pixelCanvas.canvas[y][x] - partitionY = math.min(floor(y/partitionSize)+1, #partitions) - partitionX = math.min(floor(x/partitionSize)+1, #partitions[1]) + partitionY = ceil(y/partitionSize) --math.min(floor(y/partitionSize)+1, #partitions) + partitionX = ceil(x/partitionSize) --math.min(floor(x/partitionSize)+1, #partitions[1]) --print("getting partition (partizion size " .. partitionSize .. ") at x: " .. x .. ", y: " .. y .. " -> ".. floor(x/partitionSize)+1 .. ", " .. floor(y/partitionSize)+1) --print("Partitions size: " .. #partitions[1] .. ", " .. #partitions) local partition = partitions[partitionY][partitionX] diff --git a/radon.lua b/radon.lua index dbdf7b3..e93ef1a 100644 --- a/radon.lua +++ b/radon.lua @@ -33,8 +33,13 @@ ConfigValidator.validateConfig(config) ConfigValidator.validateProducts(products) -if config.peripherals.outputChest == "self" and not config.peripherals.self then - error("Output chest is set to self, but no self peripheral name is set") +local modem +if config.peripherals.modem then + modem = peripheral.wrap(config.peripherals.modem) +elseif peripheral.find("modem") then + modem = peripheral.find("modem") +else + error("No modem found") end local display = Display.new({theme=config.theme, monitor=config.peripherals.monitor}) @@ -144,10 +149,6 @@ productTextSize = theme.formatting.productTextSize end - if #shopProducts > 0 then - table.insert(flatCanvas, Rect { display=display, x=1, y=16, width=display.bgCanvas.width, height=1, color=theme.colors.productBgColor }) - end - local currency = props.shopState.selectedCurrency local currencySymbol = getCurrencySymbol(currency, productTextSize) for i = 1, #shopProducts do @@ -202,15 +203,16 @@ productAddr = product.address .. "@ " end end + local productBgColor = theme.colors.productBgColors[((i-1) % #theme.colors.productBgColors) + 1] if productTextSize == "large" then table.insert(flatCanvas, BigText { key="qty-"..catName..tostring(product.id), display=display, text=tostring(product.quantity), x=1, - y=17+((i-1)*15), + y=16+((i-1)*15), align="center", - bg=theme.colors.productBgColor, + bg=productBgColor, color=qtyColor, width=maxQtyWidth }) @@ -219,9 +221,9 @@ display=display, text=product.name, x=maxQtyWidth+1, - y=17+((i-1)*15), + y=16+((i-1)*15), align=theme.formatting.productNameAlign, - bg=theme.colors.productBgColor, + bg=productBgColor, color=productNameColor, width=display.bgCanvas.width-3-maxAddrWidth-maxPriceWidth-maxQtyWidth }) @@ -230,9 +232,9 @@ display=display, text=tostring(productPrice) .. currencySymbol, x=display.bgCanvas.width-3-maxAddrWidth-maxPriceWidth, - y=17+((i-1)*15), + y=16+((i-1)*15), align="right", - bg=theme.colors.productBgColor, + bg=productBgColor, color=theme.colors.priceColor, width=maxPriceWidth }) @@ -241,9 +243,9 @@ display=display, text=productAddr, x=display.bgCanvas.width-3-maxAddrWidth, - y=17+((i-1)*15), + y=16+((i-1)*15), align="right", - bg=theme.colors.productBgColor, + bg=productBgColor, color=theme.colors.addressColor, width=maxAddrWidth+4 }) @@ -254,8 +256,8 @@ x=1, y=1+(i*5), align="center", - bg=theme.colors.productBgColor, - color=theme.colors.productBgColor, + bg=productBgColor, + color=productBgColor, width=#(product.address .. "@" .. props.shopState.selectedCurrency.name) }) elseif productTextSize == "medium" then @@ -264,9 +266,9 @@ display=display, text=tostring(product.quantity), x=1, - y=17+((i-1)*9), + y=16+((i-1)*9), align="center", - bg=theme.colors.productBgColor, + bg=productBgColor, color=qtyColor, width=maxQtyWidth }) @@ -275,9 +277,9 @@ display=display, text=product.name, x=maxQtyWidth+1, - y=17+((i-1)*9), + y=16+((i-1)*9), align=theme.formatting.productNameAlign, - bg=theme.colors.productBgColor, + bg=productBgColor, color=productNameColor, width=display.bgCanvas.width-3-maxAddrWidth-maxPriceWidth-maxQtyWidth }) @@ -286,9 +288,9 @@ display=display, text=tostring(productPrice) .. currencySymbol, x=display.bgCanvas.width-3-maxAddrWidth-maxPriceWidth, - y=17+((i-1)*9), + y=16+((i-1)*9), align="right", - bg=theme.colors.productBgColor, + bg=productBgColor, color=theme.colors.priceColor, width=maxPriceWidth }) @@ -297,9 +299,9 @@ display=display, text=productAddr, x=display.bgCanvas.width-3-maxAddrWidth, - y=17+((i-1)*9), + y=16+((i-1)*9), align="right", - bg=theme.colors.productBgColor, + bg=productBgColor, color=theme.colors.addressColor, width=maxAddrWidth+4 }) @@ -310,8 +312,8 @@ x=1, y=3+(i*3), align="center", - bg=theme.colors.productBgColor, - color=theme.colors.productBgColor, + bg=productBgColor, + color=productBgColor, width=#(product.address .. "@" .. props.shopState.selectedCurrency.name) }) else @@ -322,7 +324,7 @@ x=1, y=6+((i-1)*1), align="center", - bg=theme.colors.productBgColor, + bg=productBgColor, color=qtyColor, width=maxQtyWidth }) @@ -333,7 +335,7 @@ x=maxQtyWidth+1, y=6+((i-1)*1), align=theme.formatting.productNameAlign, - bg=theme.colors.productBgColor, + bg=productBgColor, color=productNameColor, width=(display.bgCanvas.width/2)-1-maxAddrWidth-maxPriceWidth-maxQtyWidth }) @@ -344,7 +346,7 @@ x=(display.bgCanvas.width/2)-1-maxAddrWidth-maxPriceWidth, y=6+((i-1)*1), align="right", - bg=theme.colors.productBgColor, + bg=productBgColor, color=theme.colors.priceColor, width=maxPriceWidth }) @@ -355,7 +357,7 @@ x=(display.bgCanvas.width/2)-1-maxAddrWidth, y=6+((i-1)*1), align="right", - bg=theme.colors.productBgColor, + bg=productBgColor, color=theme.colors.addressColor, width=maxAddrWidth+2 }) @@ -367,16 +369,7 @@ for i = 1, #props.config.currencies do local symbol = getCurrencySymbol(props.config.currencies[i], productTextSize) local symbolSize = bigFont:getWidth(symbol)+6 - local bgColor - if i % 4 == 1 then - bgColor = theme.colors.currency1Color - elseif i % 4 == 2 then - bgColor = theme.colors.currency2Color - elseif i % 4 == 3 then - bgColor = theme.colors.currency3Color - elseif i % 4 == 0 then - bgColor = theme.colors.currency4Color - end + local bgColor = theme.colors.currencyBgColors[((i-1) % #theme.colors.currencyBgColors) + 1] table.insert(flatCanvas, Button { display = display, align = "center", @@ -404,14 +397,8 @@ if i == selectedCategory then categoryColor = theme.colors.activeCategoryColor categoryName = "[" .. categoryName .. "]" - elseif i % 4 == 1 then - categoryColor = theme.colors.category1Color - elseif i % 4 == 2 then - categoryColor = theme.colors.category2Color - elseif i % 4 == 3 then - categoryColor = theme.colors.category3Color - elseif i % 4 == 0 then - categoryColor = theme.colors.category4Color + else + categoryColor = theme.colors.categoryBgColors[((i-1) % #theme.colors.categoryBgColors) + 1] end local categoryWidth = smolFont:getWidth(categoryName)+6 categoryX = categoryX - categoryWidth - 2 @@ -500,7 +487,7 @@ lastCanvasHash = newCanvasHash end -local shopState = Core.ShopState.new(config, products) +local shopState = Core.ShopState.new(config, products, modem) local Profiler = require("profile")