digivend.lua 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. --{{GLOBAL}}--
  2. STORE_CREDIT = "default:grass_1"
  3. -- reports the contents of a list
  4. -- channel intended for LCDs
  5. local function showlist(channel, list, empty_msg)
  6. empty_msg = empty_msg or "Empty List."
  7. local size = table.maxn(list)
  8. if size <= 0 then
  9. digiline_send(channel, empty_msg)
  10. return
  11. end
  12. local longstr= ""
  13. for _, v in ipairs(list) do
  14. longstr = longstr .. v.name .. ", ".. v.count .."; \n"
  15. end
  16. digiline_send(channel, longstr)
  17. end
  18. local function is_in_list(string, list)
  19. for i, v in ipairs(list) do
  20. if v.name == string then
  21. return i;
  22. end
  23. end
  24. return -1
  25. end
  26. local function additem(list, stack)
  27. local index = is_in_list(stack.name, list)
  28. -- if the item is not in the list or the list is empty
  29. if (index < 0 or table.maxn(list) <= 0) and stack.count > 0 then
  30. table.insert(list, stack)
  31. return
  32. elseif index > 0 then
  33. -- if the item is in the list and it must be updated
  34. list[index].count = list[index].count + stack.count
  35. if list[index].count <= 0 then
  36. table.remove(list, index)
  37. end
  38. end
  39. end
  40. -- use for both payment chest and stock chest
  41. -- payment chest responds to uput or utake
  42. -- stock chest responds to tput or ttake
  43. local function detect(msg, list)
  44. if msg == nil then
  45. return
  46. end
  47. if msg.stack == nil then
  48. return
  49. end
  50. local n = msg.stack.name
  51. local c = msg.stack.count
  52. if msg.action == "uput" then
  53. -- player has put an item stack in the digichest
  54. additem(list, {name=n, count= c})
  55. elseif msg.action == "utake" then
  56. -- player has taken an item
  57. additem(list, {name=n, count= -c})
  58. elseif msg.action == "tput" then
  59. -- a tube has put an item
  60. additem(list, {name=n, count= c})
  61. -- showlist("lcd", mem.paid)
  62. elseif msg.action == "ttake" then
  63. -- a tube has taken an item
  64. additem(list, {name=n, count= -c})
  65. end
  66. end
  67. -- only take specified item as currency
  68. -- to either load funds or refund
  69. local function process_payment(dfi, list, currency)
  70. local size = table.maxn(list)
  71. if size <= 0 then
  72. return false
  73. end
  74. for i, v in ipairs(list) do
  75. if v.name == currency then
  76. digiline_send(dfi, v)
  77. return true
  78. end
  79. end
  80. end
  81. local function process_sale(dfi, order, itemname)
  82. if itemname == nil then
  83. return "Programming error: var 'itemname' is nil"
  84. end
  85. if tonumber(order[itemname]) == nil then
  86. return "Error: invalid input: not a number."
  87. end
  88. local quantity = math.floor(order[itemname])
  89. if quantity <= 0 then
  90. return "Error: invalid quantity (".. quantity ..")"
  91. end
  92. for i, v in ipairs(mem.stock) do
  93. if v.name == itemname and quantity > v.count then
  94. return "Order quantity exceeds stock!"
  95. end
  96. end
  97. local cost = 1 * quantity
  98. if table.maxn(mem.paid) <= 0 then
  99. -- note: tried to check mem.paid == {} - doesn't work
  100. return "Error: no funds loaded."
  101. end
  102. if mem.paid[1].count < cost then
  103. return "Error: Insufficient funds."
  104. end
  105. -- sale is good to go
  106. digiline_send(dfi, {name=itemname, count=quantity})
  107. additem(mem.paid, {name=mem.paid[1].name, count=-cost})
  108. return "Sale confirmed"
  109. end
  110. -- dfi_receive = customer payment
  111. -- dfi_send = send to customer (refunds, wares)
  112. local function touch_response(msg, dfi_receive, dfi_send)
  113. -- ignore the event when a player closes menu
  114. local lcd_status = "lcd3"
  115. local credit = STORE_CREDIT
  116. if msg["key_enter_field"] == nil and msg["quit"] ~= nil then
  117. return "Hello! Use the touchscreen to begin."
  118. end
  119. if msg["pay"] ~= nil then
  120. if process_payment(dfi_receive, mem.queue, credit) then
  121. return "Sent payment."
  122. else
  123. return "No payment items detected."
  124. end
  125. elseif msg["refund"] ~= nil then
  126. if process_payment(dfi_send, mem.paid, credit) then
  127. return "Sent refund."
  128. else
  129. return "No payment to return."
  130. end
  131. end
  132. for i, itemstack in ipairs(mem.stock) do
  133. if msg["key_enter_field"] == itemstack.name then
  134. return process_sale(dfi_send, msg, itemstack.name)
  135. end
  136. end
  137. end
  138. local function get_texture_from_itemname(itemname)
  139. -- no access to :gsub()
  140. local newChar = "_"
  141. local skip = 0
  142. -- mods with texture files that do not use prefixes
  143. if string.sub(itemname, 1,5) == "nssm:" then --can't access find() ?'
  144. skip = 5
  145. -- elseif string.sub(itemname,1,7) == "bucket:" then
  146. -- skip = 7
  147. elseif itemname == "bucket:bucket_empty" then
  148. return "bucket.png"
  149. end
  150. local charTable = {}
  151. for i=1, #itemname do
  152. if i > skip then
  153. local foo = string.sub(itemname, i, i)
  154. if foo ~= ":" then
  155. table.insert(charTable,foo)
  156. else
  157. table.insert(charTable,"_")
  158. end
  159. end
  160. end
  161. return table.concat(charTable) .. ".png"
  162. end
  163. local function spawnfield(channel, name, label, default, y)
  164. local filename = get_texture_from_itemname(name)
  165. local height = 1
  166. local width = 7
  167. local n = {}
  168. n.command = "addfield"
  169. n["X"] = 2
  170. n["Y"] = 1 + y
  171. n["W"] = width
  172. n["H"] = height
  173. n["name"] = name
  174. n["label"] = label
  175. n["default"] = default
  176. digiline_send(channel, n)
  177. local icon = {}
  178. icon.command = "addimage"
  179. icon["X"] = 0.5
  180. icon["Y"] = 0.5 + y
  181. icon["W"] = 1
  182. icon["H"] = 1
  183. icon.texture_name = filename
  184. digiline_send(channel,icon)
  185. end
  186. local function touch_init(channel, wares)
  187. local reset = {}
  188. reset.command = "clear"
  189. digiline_send(channel, reset)
  190. local y_offset = 0
  191. local btn_pay = {}
  192. btn_pay.command = "addbutton"
  193. btn_pay["X"] = 1
  194. btn_pay["Y"] = y_offset
  195. btn_pay["W"] = 4
  196. btn_pay["H"] = 1
  197. btn_pay["name"] = "pay"
  198. btn_pay["label"] = "Deposit"
  199. digiline_send(channel, btn_pay)
  200. local btn_refund = {}
  201. btn_refund.command = "addbutton"
  202. btn_refund["X"] = 5
  203. btn_refund["Y"] = y_offset
  204. btn_refund["W"] = 4
  205. btn_refund["H"] = 1
  206. btn_refund["name"] = "refund"
  207. btn_refund["label"] = "Refund"
  208. digiline_send(channel, btn_refund)
  209. y_offset = y_offset + 1
  210. local guide = {}
  211. guide.command = "addlabel"
  212. guide["X"] = 2.5
  213. guide["Y"] = y_offset
  214. guide.label = "Press ENTER (or RETURN) key to place order."
  215. digiline_send(channel, guide)
  216. y_offset = y_offset + 0.5
  217. local price = {}
  218. price.command = "addlabel"
  219. price["X"] = 2.5
  220. price["Y"] = y_offset
  221. price.label = "Items cost 1x Grass (default:grass_1) each."
  222. digiline_send(channel, price)
  223. -- let for loop increment y
  224. y_offset = y_offset - 0.5 -- (0.5 - 1 = -0.5)
  225. for i, v in ipairs(wares) do
  226. local label = v.name .. " (stock: " .. v.count .. ")"
  227. spawnfield(channel, v.name, label, "0", y_offset + i)
  228. end
  229. end
  230. local function main()
  231. local ts = "ts" -- touchscreen, order form
  232. local pay = "input" -- player pays w/ digichest
  233. local dfi1 = "accept" -- DFI sends payment from 'pay'
  234. local store = "store" -- digichest holds stock & payment
  235. local dfi2 = "deploy" -- DFI sends wares from stock
  236. local card = "swipe" -- experimental card reader
  237. local lcd_queue = "lcd"
  238. local lcd_paid = "lcd2"
  239. if event.type == "program" then
  240. mem.queue = {} -- items placed in chest
  241. mem.paid = {} -- items paid by customers
  242. mem.stock = {} -- items stocked by owner
  243. touch_init(ts, mem.stock)
  244. digiline_send("lcd", "Initialized!")
  245. digiline_send("lcd2", "Initialized!")
  246. digiline_send("lcd3", "Initialized!")
  247. end
  248. if event.type == "digiline" and event.channel == ts then
  249. local status = touch_response(event.msg, dfi1, dfi2)
  250. touch_init(ts, mem.stock)
  251. digiline_send("lcd3", status)
  252. showlist(lcd_paid, mem.paid, "No funds loaded.")
  253. end
  254. if event.type == "digiline" and event.channel == pay then
  255. detect(event.msg, mem.queue)
  256. showlist(lcd_queue, mem.queue, "Place payment in the chest below.")
  257. end
  258. if event.type == "digiline" and event.channel == store then
  259. if event.msg.action == "uput"
  260. or event.msg.action == "utake" then
  261. -- store stock directly accessed by player
  262. -- (as opposed to vending)
  263. detect(event.msg, mem.stock)
  264. elseif event.msg.action == "tput" then
  265. -- vending, payment confirmation
  266. -- update mem.paid to show current store credits
  267. detect(event.msg, mem.paid)
  268. showlist(lcd_paid, mem.paid, "No funds loaded.")
  269. elseif event.msg.action == "ttake" then
  270. -- vending, update stock after selling
  271. if event.msg.stack.name == STORE_CREDIT then
  272. detect(event.msg, mem.paid)
  273. else
  274. detect(event.msg, mem.stock)
  275. end
  276. showlist(lcd_paid, mem.paid, "No funds loaded.")
  277. -- process_sale already deducts store creditss
  278. -- note: upon refunds, the code will attempt to deduct
  279. -- currency from the stock list
  280. -- however additems() does allow negative quantities to be indexed.
  281. end
  282. touch_init(ts, mem.stock)
  283. end
  284. end
  285. return main()