init.lua 67 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696
  1. -- /$$$$$$$$ /$$ /$$ /$$
  2. -- | $$_____/ | $$ | $$ | $$
  3. -- | $$ /$$$$$$ /$$$$$$$ /$$$$$$$ /$$ /$$ | $$ | $$ /$$$$$$ /$$$$$$$ /$$$$$$$
  4. -- | $$$$$|____ $$| $$__ $$ /$$_____/| $$ | $$ | $$ / $$//$$__ $$| $$__ $$ /$$__ $$
  5. -- | $$__/ /$$$$$$$| $$ \ $$| $$ | $$ | $$ \ $$ $$/| $$$$$$$$| $$ \ $$| $$ | $$
  6. -- | $$ /$$__ $$| $$ | $$| $$ | $$ | $$ \ $$$/ | $$_____/| $$ | $$| $$ | $$
  7. -- | $$ | $$$$$$$| $$ | $$| $$$$$$$| $$$$$$$ \ $/ | $$$$$$$| $$ | $$| $$$$$$$
  8. -- |__/ \_______/|__/ |__/ \_______/ \____ $$ \_/ \_______/|__/ |__/ \_______/
  9. -- /$$ | $$
  10. -- | $$$$$$/
  11. -- \______/
  12. --
  13. -- A full-featured, fully-integrated vendor mod for Minetest
  14. local settings = minetest.settings
  15. local display_node = (settings:get("fancy_vend.display_node") or "default:obsidian_glass")
  16. local max_logs = (tonumber(settings:get("fancy_vend.log_max")) or 40)
  17. local autorotate_speed = (tonumber(settings:get("fancy_vend.autorotate_speed")) or 1)
  18. local no_alerts = settings:get_bool("fancy_vend.no_alerts")
  19. local drop_vendor = "fancy_vend:player_vendor"
  20. -- Register a copy of the display node with no drops to make players separating the obsidian glass with something like a piston a non-issue.
  21. local display_node_def = table.copy(minetest.registered_nodes[display_node])
  22. display_node_def.drop = ""
  23. display_node_def.pointable = false
  24. display_node_def.groups.not_in_creative_inventory = 1
  25. display_node_def.description = "Fancy Vendor Display Node (you hacker you!)"
  26. if pipeworks then
  27. display_node_def.digiline = {
  28. wire = {
  29. rules = pipeworks.digilines_rules
  30. }
  31. }
  32. end
  33. minetest.register_node("fancy_vend:display_node", display_node_def)
  34. -- Craftitem to display when vendor is inactive (Use just image for this???)
  35. minetest.register_craftitem("fancy_vend:inactive",{inventory_image = "inactive.png",})
  36. minetest.register_privilege("admin_vendor", "Enables the user to set regular vendors to admin vendors.")
  37. minetest.register_privilege("disable_vendor", "Enables the user to set all vendors to inactive.")
  38. local function bts(bool)
  39. if bool == false then
  40. return "false"
  41. elseif bool == true then
  42. return "true"
  43. else
  44. return bool
  45. end
  46. end
  47. local function stb(str)
  48. if str == "false" then
  49. return false
  50. elseif str == "true" then
  51. return true
  52. else
  53. return str
  54. end
  55. end
  56. local modstorage = minetest.get_mod_storage()
  57. if modstorage:get_string("all_inactive_force") == "" then
  58. modstorage:set_string("all_inactive_force", "false")
  59. end
  60. local all_inactive_force = stb(modstorage:get_string("all_inactive_force"))
  61. minetest.register_chatcommand("disable_all_vendors", {
  62. description = "Toggle vendor inactivity.",
  63. privs = {disable_vendor=true},
  64. func = function(name, param)
  65. if all_inactive_force then
  66. all_inactive_force = false
  67. modstorage:set_string("all_inactive_force", "false")
  68. else
  69. all_inactive_force = true
  70. modstorage:set_string("all_inactive_force", "true")
  71. end
  72. end,
  73. })
  74. table.length = function(table)
  75. local length
  76. for i in pairs(table) do
  77. length = length + 1
  78. end
  79. return length
  80. end
  81. local function send_message(pos, channel, msg)
  82. if channel and channel ~= "" then
  83. digilines.receptor_send(pos, digilines.rules.default, channel, msg)
  84. end
  85. end
  86. -- Awards
  87. if minetest.get_modpath("awards") then
  88. awards.register_award("fancy_vend:getting_fancy",{
  89. title = "Getting Fancy",
  90. description = "Craft a fancy vendor.",
  91. trigger = {
  92. type = "craft",
  93. item = drop_vendor,
  94. target = 1,
  95. },
  96. icon = "player_vend_front.png^awards_level1.png",
  97. })
  98. awards.register_award("fancy_vend:wizard",{
  99. title = "You're a Wizard",
  100. description = "Craft a copy tool.",
  101. trigger = {
  102. type = "craft",
  103. item = "fancy_vend:copy_tool",
  104. target = 1,
  105. },
  106. icon = "copier.png",
  107. })
  108. awards.register_award("fancy_vend:trader",{
  109. title = "Trader",
  110. description = "Configure a depositor.",
  111. icon = "player_depo_front.png",
  112. })
  113. awards.register_award("fancy_vend:seller",{
  114. title = "Seller",
  115. description = "Configure a vendor.",
  116. icon = "player_vend_front.png^awards_level2.png",
  117. })
  118. awards.register_award("fancy_vend:shop_keeper",{
  119. title = "Shop Keeper",
  120. description = "Configure 10 vendors or depositors.",
  121. icon = "player_vend_front.png^awards_level3.png",
  122. })
  123. awards.register_award("fancy_vend:merchant",{
  124. title = "Merchant",
  125. description = "Configure 25 vendors or depositors.",
  126. icon = "player_vend_front.png^awards_level4.png",
  127. })
  128. awards.register_award("fancy_vend:super_merchant",{
  129. title = "Super Merchant",
  130. description = "Configure 100 vendors or depositors. How do you even have this much stuff to sell?",
  131. icon = "player_vend_front.png^awards_level5.png",
  132. })
  133. awards.register_award("fancy_vend:god_merchant",{
  134. title = "God Merchant",
  135. description = "Configure 9001 vendors or depositors. Ok wot.",
  136. icon = "player_vend_front.png^awards_level6.png",
  137. secret = true, -- Oi. Cheater.
  138. })
  139. end
  140. local tmp = {}
  141. minetest.register_entity("fancy_vend:display_item",{
  142. hp_max = 1,
  143. visual = "wielditem",
  144. visual_size = {x = 0.33, y = 0.33},
  145. collisionbox = {0, 0, 0, 0, 0, 0},
  146. physical = false,
  147. textures = {"air"},
  148. on_activate = function(self, staticdata)
  149. if tmp.nodename ~= nil and tmp.texture ~= nil then
  150. self.nodename = tmp.nodename
  151. tmp.nodename = nil
  152. self.texture = tmp.texture
  153. tmp.texture = nil
  154. else
  155. if staticdata ~= nil and staticdata ~= "" then
  156. local data = staticdata:split(';')
  157. if data and data[1] and data[2] then
  158. self.nodename = data[1]
  159. self.texture = data[2]
  160. end
  161. end
  162. end
  163. if self.texture ~= nil then
  164. self.object:set_properties({textures = {self.texture}})
  165. end
  166. self.object:set_properties({automatic_rotate = autorotate_speed})
  167. end,
  168. get_staticdata = function(self)
  169. if self.nodename ~= nil and self.texture ~= nil then
  170. return self.nodename .. ';' .. self.texture
  171. end
  172. return ""
  173. end,
  174. })
  175. local function remove_item(pos)
  176. local objs = nil
  177. objs = minetest.get_objects_inside_radius(pos, .5)
  178. if objs then
  179. for _, obj in ipairs(objs) do
  180. if obj and obj:get_luaentity() and obj:get_luaentity().name == "fancy_vend:display_item" then
  181. obj:remove()
  182. end
  183. end
  184. end
  185. end
  186. local function update_item(pos, node)
  187. pos.y = pos.y + 1
  188. remove_item(pos)
  189. if minetest.get_node(pos).name ~= "fancy_vend:display_node" then
  190. minetest.log("warning", "[fancy_vend]: Placing display item inside "..minetest.get_node(pos).name.." at "..minetest.pos_to_string(pos).." is not permitted, aborting")
  191. return
  192. end
  193. pos.y = pos.y - 1
  194. local meta = minetest.get_meta(pos)
  195. if meta:get_string("item") ~= "" then
  196. pos.y = pos.y + (12 / 16 + 0.11)
  197. tmp.nodename = node.name
  198. tmp.texture = ItemStack(meta:get_string("item")):get_name()
  199. local e = minetest.add_entity(pos,"fancy_vend:display_item")
  200. pos.y = pos.y - (12 / 16 + 0.11)
  201. end
  202. end
  203. -- LBM to refresh entities after clearobjects
  204. minetest.register_lbm({
  205. label = "Refresh vendor display",
  206. name = "fancy_vend:display_refresh",
  207. nodenames = {"fancy_vend:display_node"},
  208. run_at_every_load = true,
  209. action = function(pos, node)
  210. if not next(minetest.get_objects_inside_radius(pos, 0.5)) then
  211. pos.y = pos.y - 1
  212. update_item(pos, node)
  213. end
  214. end
  215. })
  216. local function set_vendor_settings(pos, SettingsDef)
  217. local meta = minetest.get_meta(pos)
  218. meta:set_string("settings", minetest.serialize(SettingsDef))
  219. end
  220. local function reset_vendor_settings(pos)
  221. local settings_default = {
  222. input_item = "", -- Don't change this unless you plan on setting this up to add this item to the inventories
  223. output_item = "", -- Don't change this unless you plan on setting this up to add this item to the inventories
  224. input_item_qty = 1,
  225. output_item_qty = 1,
  226. admin_vendor = false,
  227. depositor = false,
  228. currency_eject = false,
  229. accept_output_only = false,
  230. split_incoming_stacks = false,
  231. inactive_force = false,
  232. accept_worn_input = true,
  233. accept_worn_output = true,
  234. digiline_channel = "",
  235. co_sellers = "",
  236. banned_buyers = "",
  237. auto_sort = false,
  238. }
  239. set_vendor_settings(pos, settings_default)
  240. return settings_default
  241. end
  242. local function get_vendor_settings(pos)
  243. local meta = minetest.get_meta(pos)
  244. local settings = minetest.deserialize(meta:get_string("settings"))
  245. if not settings then
  246. return reset_vendor_settings(pos)
  247. else
  248. -- If settings added by newer versions of fancy_vend are nil then send defaults
  249. if settings.auto_sort == nil then
  250. settings.auto_sort = false
  251. end
  252. -- Sanitatize number values (backwards compat)
  253. settings.input_item_qty = (type(settings.input_item_qty) == "number" and math.abs(settings.input_item_qty) or 1)
  254. settings.output_item_qty = (type(settings.output_item_qty) == "number" and math.abs(settings.output_item_qty) or 1)
  255. return settings
  256. end
  257. end
  258. local function can_buy_from_vendor(pos, player)
  259. local settings = get_vendor_settings(pos)
  260. local banned_buyers = string.split((settings.banned_buyers or ""),",")
  261. for i in pairs(banned_buyers) do
  262. if banned_buyers[i] == player:get_player_name() then
  263. return false
  264. end
  265. end
  266. return true
  267. end
  268. local function can_modify_vendor(pos, player)
  269. local meta = minetest.get_meta(pos)
  270. local inv = meta:get_inventory()
  271. local is_owner = false
  272. if meta:get_string("owner") == player:get_player_name() or minetest.check_player_privs(player, {protection_bypass=true}) then
  273. is_owner = true
  274. end
  275. return is_owner
  276. end
  277. local function can_dig_vendor(pos, player)
  278. local meta = minetest.get_meta(pos);
  279. local inv = meta:get_inventory()
  280. return inv:is_empty("main") and can_modify_vendor(pos, player)
  281. end
  282. local function can_access_vendor_inv(player, pos)
  283. local meta = minetest.get_meta(pos)
  284. if minetest.check_player_privs(player, {protection_bypass=true}) or meta:get_string("owner") == player:get_player_name() then return true end
  285. local settings = get_vendor_settings(pos)
  286. local co_sellers = string.split(settings.co_sellers,",")
  287. for i in pairs(co_sellers) do
  288. if co_sellers[i] == player:get_player_name() then
  289. return true
  290. end
  291. end
  292. return false
  293. end
  294. -- Inventory helpers:
  295. -- Function to sort inventory (Taken from technic_chests)
  296. local function sort_inventory(inv)
  297. local inlist = inv:get_list("main")
  298. local typecnt = {}
  299. local typekeys = {}
  300. for _, st in ipairs(inlist) do
  301. if not st:is_empty() then
  302. local n = st:get_name()
  303. local w = st:get_wear()
  304. local m = st:get_metadata()
  305. local k = string.format("%s %05d %s", n, w, m)
  306. if not typecnt[k] then
  307. typecnt[k] = {
  308. name = n,
  309. wear = w,
  310. metadata = m,
  311. stack_max = st:get_stack_max(),
  312. count = 0,
  313. }
  314. table.insert(typekeys, k)
  315. end
  316. typecnt[k].count = typecnt[k].count + st:get_count()
  317. end
  318. end
  319. table.sort(typekeys)
  320. local outlist = {}
  321. for _, k in ipairs(typekeys) do
  322. local tc = typecnt[k]
  323. while tc.count > 0 do
  324. local c = math.min(tc.count, tc.stack_max)
  325. table.insert(outlist, ItemStack({
  326. name = tc.name,
  327. wear = tc.wear,
  328. metadata = tc.metadata,
  329. count = c,
  330. }))
  331. tc.count = tc.count - c
  332. end
  333. end
  334. if #outlist > #inlist then return end
  335. while #outlist < #inlist do
  336. table.insert(outlist, ItemStack(nil))
  337. end
  338. inv:set_list("main", outlist)
  339. end
  340. local function free_slots(inv, listname, itemname, quantity)
  341. local size = inv:get_size(listname)
  342. local free = 0
  343. for i=1,size do
  344. local stack = inv:get_stack(listname, i)
  345. if stack:is_empty() or stack:get_free_space() > 0 then
  346. if stack:is_empty() then
  347. free = free + ItemStack(itemname):get_stack_max()
  348. elseif stack:get_name() == itemname then
  349. free = free + stack:get_free_space()
  350. end
  351. end
  352. end
  353. if free < quantity then
  354. return false
  355. else
  356. return true
  357. end
  358. end
  359. local function inv_insert(inv, listname, itemstack, quantity, from_table, pos, input_eject)
  360. local stackmax = itemstack:get_stack_max()
  361. local name = itemstack:get_name()
  362. local stacks = {}
  363. local remaining_quantity = quantity
  364. -- Add the full stacks to the list
  365. while remaining_quantity > stackmax do
  366. table.insert(stacks, {name = name, count = stackmax})
  367. remaining_quantity = remaining_quantity - stackmax
  368. end
  369. -- Add the remaining stack to the list
  370. table.insert(stacks, {name = name, count = remaining_quantity})
  371. -- If tool add wears ignores if from_table = nil (eg, due to vendor beig admin vendor)
  372. if minetest.registered_tools[name] and from_table then
  373. for i in pairs(stacks) do
  374. local from_item_table = from_table[i].item:to_table()
  375. stacks[i].wear = from_item_table.wear
  376. end
  377. end
  378. -- if has metadata add metadata
  379. if from_table then
  380. for i in pairs(stacks) do
  381. local from_item_table = from_table[i].item:to_table()
  382. if from_item_table.name == name then
  383. if from_item_table.metadata then
  384. stacks[i].metadata = from_item_table.metadata -- Apparently some mods *cough* digtron *cough* do use deprecated metadata strings
  385. end
  386. if from_item_table.meta then
  387. stacks[i].meta = from_item_table.meta -- Most mods use metadata tables which is the correct method but ok
  388. end
  389. end
  390. end
  391. end
  392. -- Add to inventory or eject to pipeworks/hoppers (whichever is applicable)
  393. local output_tube_connected = false
  394. local output_hopper_connected = false
  395. if input_eject and pos then
  396. local pos_under = vector.new(pos)
  397. pos_under.y = pos_under.y - 1
  398. local node_under = minetest.get_node(pos_under)
  399. if minetest.get_item_group(node_under.name, "tubedevice") > 0 then
  400. output_tube_connected = true
  401. end
  402. if node_under.name == "hopper:hopper" or node_under.name == "hopper:hopper_side" then
  403. output_hopper_connected = true
  404. end
  405. end
  406. for i in pairs(stacks) do
  407. if output_tube_connected then
  408. pipeworks.tube_inject_item(pos, pos, vector.new(0, -1, 0), stacks[i], minetest.get_meta(pos):get_string("owner"))
  409. else
  410. local leftovers = ItemStack(stacks[i])
  411. if output_hopper_connected then
  412. local pos_under = {x = pos.x, y = pos.y-1, z = pos.z}
  413. local hopper_inv = minetest.get_meta(pos_under):get_inventory()
  414. leftovers = hopper_inv:add_item("main", leftovers)
  415. end
  416. if not leftovers:is_empty() then
  417. inv:add_item(listname, leftovers)
  418. end
  419. end
  420. end
  421. end
  422. local function inv_remove(inv, listname, remove_table, itemstring, quantity)
  423. local count = 0
  424. for i in pairs(remove_table) do
  425. count = count + remove_table[i].item:get_count()
  426. inv:set_stack(listname, remove_table[i].id, nil)
  427. end
  428. -- Add back items if too many were taken
  429. if count > quantity then
  430. inv:add_item(listname, ItemStack({name = itemstring, count = count - quantity}))
  431. end
  432. end
  433. local function inv_contains_items(inv, listname, itemstring, quantity, ignore_wear)
  434. local minimum = quantity
  435. local get_items = {}
  436. local count = 0
  437. for i=1,inv:get_size(listname) do
  438. local stack = inv:get_stack(listname, i)
  439. if stack:get_name() == itemstring then
  440. if ignore_wear or (not minetest.registered_tools[itemstring] or stack:get_wear() == 0) then
  441. count = count + stack:get_count()
  442. table.insert(get_items, {id=i, item=stack})
  443. if count >= minimum then
  444. return true, get_items
  445. end
  446. end
  447. end
  448. end
  449. return false
  450. end
  451. local function get_vendor_status(pos)
  452. local settings = get_vendor_settings(pos)
  453. local meta = minetest.get_meta(pos)
  454. local inv = meta:get_inventory()
  455. if all_inactive_force then
  456. return false, "all_inactive_force"
  457. elseif settings.input_item == "" or settings.output_item == "" then
  458. return false, "unconfigured"
  459. elseif settings.inactive_force then
  460. return false, "inactive_force"
  461. elseif not minetest.check_player_privs(meta:get_string("owner"), {admin_vendor=true}) and settings.admin_vendor == true then
  462. return false, "no_privs"
  463. elseif not inv_contains_items(inv, "main", settings.output_item, settings.output_item_qty, settings.accept_worn_output) and not settings.admin_vendor then
  464. return false, "no_output"
  465. elseif not free_slots(inv, "main", settings.input_item, settings.input_item_qty) and not settings.admin_vendor then
  466. return false, "no_room"
  467. else
  468. return true
  469. end
  470. end
  471. local function make_inactive_string(errorcode)
  472. local status_str = ""
  473. if errorcode == "unconfigured" then
  474. status_str = status_str.." (unconfigured)"
  475. elseif errorcode == "inactive_force" then
  476. status_str = status_str.." (forced)"
  477. elseif errorcode == "no_output" then
  478. status_str = status_str.." (out of stock)"
  479. elseif errorcode == "no_room" then
  480. status_str = status_str.." (no room)"
  481. elseif errorcode == "no_privs" then
  482. status_str = status_str.." (seller has insufficient privilages)"
  483. elseif errorcode == "all_inactive_force" then
  484. status_str = status_str.." (all vendors disabled temporarily by admin)"
  485. end
  486. return status_str
  487. end
  488. -- Various email and tell mod support
  489. -- This function takes the position of a vendor and alerts the owner if it has just been emptied
  490. local email_loaded, tell_loaded, mail_loaded = minetest.get_modpath("email"), minetest.get_modpath("tell"), minetest.get_modpath("mail")
  491. local function alert_owner_if_empty(pos)
  492. if no_alerts then return end
  493. local meta = minetest.get_meta(pos)
  494. local settings = get_vendor_settings(pos)
  495. local owner = meta:get_string("owner")
  496. local alerted = stb(meta:get_string("alerted") or "false") -- check
  497. local status, errorcode = get_vendor_status(pos)
  498. -- Message to send
  499. local stock_msg = "Your vendor trading "..settings.input_item_qty.." "..minetest.registered_items[settings.input_item].description.." for "..settings.output_item_qty.." "..minetest.registered_items[settings.output_item].description.." at position "..minetest.pos_to_string(pos, 0).." has just run out of stock."
  500. if not alerted and not status and errorcode == "no_output" then
  501. -- Rubenwardy's Email Mod: https://github.com/rubenwardy/email
  502. if mail_loaded then
  503. local inbox = {}
  504. -- load messages
  505. if not mail.apiversion then
  506. -- cheapie's mail mod https://cheapiesystems.com/git/mail/
  507. if not mail.messages[owner] then mail.messages[owner] = {} end
  508. inbox = mail.messages[owner]
  509. elseif mail.apiversion >= 1.1 then
  510. -- webmail fork https://github.com/thomasrudin-mt/mail (per player storage)
  511. inbox = mail.getMessages(owner)
  512. end
  513. -- Instead of filling their inbox with mail, get the last message sent by "Fancy Vend" and append to the message
  514. -- If there is no last message, then create a new one
  515. local message
  516. for i, msg in pairs(inbox) do
  517. if msg.sender == "Fancy Vend" then -- Put a space in the name to avoid impersonation
  518. message = msg
  519. end
  520. end
  521. if message then
  522. -- Set the message as unread
  523. message.unread = true
  524. -- Append to the end
  525. message.body = message.body..stock_msg.."\n"
  526. else
  527. mail.send("Fancy Vend", owner, "You have unstocked vendors!", stock_msg.."\n")
  528. end
  529. -- save messages
  530. if not mail.apiversion then
  531. -- cheapie's mail mod https://cheapiesystems.com/git/mail/
  532. mail.save()
  533. elseif mail.apiversion >= 1.1 then
  534. -- webmail fork https://github.com/thomasrudin-mt/mail
  535. mail.setMessages(owner, inbox)
  536. end
  537. meta:set_string("alerted", "true")
  538. return
  539. elseif email_loaded then
  540. email.send_mail("Fancy Vend", owner, stock_msg)
  541. meta:set_string("alerted", "true")
  542. return
  543. elseif tell_loaded then
  544. -- octacians tell mod https://github.com/octacian/tell
  545. tell.add(owner, "Fancy Vend", stock_msg)
  546. meta:set_string("alerted", "true")
  547. return
  548. end
  549. end
  550. end
  551. local function run_inv_checks(pos, player, lots)
  552. local settings = get_vendor_settings(pos)
  553. local meta = minetest.get_meta(pos)
  554. local inv = meta:get_inventory()
  555. local player_inv = player:get_inventory()
  556. local ct = {}
  557. -- Get input and output quantities after multiplying by lot count
  558. local output_qty = settings.output_item_qty * lots
  559. local input_qty = settings.input_item_qty * lots
  560. -- Perform inventory checks
  561. ct.player_has, ct.player_item_table = inv_contains_items(player_inv, "main", settings.input_item, input_qty, settings.accept_worn_input)
  562. ct.vendor_has, ct.vendor_item_table = inv_contains_items(inv, "main", settings.output_item, output_qty, settings.accept_worn_output)
  563. ct.player_fits = free_slots(player_inv, "main", settings.output_item, output_qty)
  564. ct.vendor_fits = free_slots(inv, "main", settings.input_item, input_qty)
  565. if ct.player_has and ct.vendor_has and ct.player_fits and ct.vendor_fits then
  566. ct.overall = true
  567. else
  568. ct.overall = false
  569. end
  570. return ct
  571. end
  572. local function get_max_lots(pos, player)
  573. local max = 0
  574. while run_inv_checks(pos, player, max).overall do
  575. max = max + 1
  576. end
  577. return max
  578. end
  579. local function make_purchase(pos, player, lots)
  580. if not can_buy_from_vendor(pos, player) then
  581. return false, "You cannot purchase from this vendor"
  582. end
  583. local settings = get_vendor_settings(pos)
  584. local meta = minetest.get_meta(pos)
  585. local inv = meta:get_inventory()
  586. local player_inv = player:get_inventory()
  587. local status, errorcode = get_vendor_status(pos)
  588. -- Double check settings, vendors which were incorrectly set up before this bug fix won't matter anymore
  589. settings.input_item_qty = math.abs(settings.input_item_qty)
  590. settings.output_item_qty = math.abs(settings.output_item_qty)
  591. if status then
  592. -- Get input and output quantities after multiplying by lot count
  593. local output_qty = settings.output_item_qty * lots
  594. local input_qty = settings.input_item_qty * lots
  595. -- Perform inventory checks
  596. local ct = run_inv_checks(pos, player, lots)
  597. if ct.player_has then
  598. if ct.player_fits then
  599. if settings.admin_vendor then
  600. minetest.log("action", player:get_player_name().." trades "..settings.input_item_qty.." "..settings.input_item.." for "..settings.output_item_qty.." "..settings.output_item.." using vendor at "..minetest.pos_to_string(pos))
  601. inv_remove(player_inv, "main", ct.player_item_table, settings.input_item, input_qty)
  602. inv_insert(player_inv, "main", ItemStack(settings.output_item), output_qty, nil)
  603. if minetest.get_modpath("digilines") then
  604. send_message(pos, settings.digiline_channel, msg)
  605. end
  606. return true, "Trade successful"
  607. elseif ct.vendor_has then
  608. if ct.vendor_fits then
  609. minetest.log("action", player:get_player_name().." trades "..settings.input_item_qty.." "..settings.input_item.." for "..settings.output_item_qty.." "..settings.output_item.." using vendor at "..minetest.pos_to_string(pos))
  610. inv_remove(inv, "main", ct.vendor_item_table, settings.output_item, output_qty)
  611. inv_remove(player_inv, "main", ct.player_item_table, settings.input_item, input_qty)
  612. inv_insert(player_inv, "main", ItemStack(settings.output_item), output_qty, ct.vendor_item_table)
  613. inv_insert(inv, "main", ItemStack(settings.input_item), input_qty, ct.player_item_table, pos, (minetest.get_modpath("pipeworks") and settings.currency_eject))
  614. -- Run mail mod checks
  615. alert_owner_if_empty(pos)
  616. return true, "Trade successful"
  617. else
  618. return false, "Vendor has insufficient space"
  619. end
  620. else
  621. return false, "Vendor has insufficient resources"
  622. end
  623. else
  624. return false, "You have insufficient space"
  625. end
  626. else
  627. return false, "You have insufficient funds"
  628. end
  629. else
  630. return false, "Vendor is inactive"..make_inactive_string(errorcode)
  631. end
  632. end
  633. local function get_vendor_buyer_fs(pos, player, lots)
  634. local base = "size[8,9]"..
  635. "label[0,0;Owner wants:]"..
  636. "label[0,1.25;for:]"..
  637. "button[0,2.7;2,1;buy;Buy]"..
  638. "label[2.8,2.9;lots.]"..
  639. "button[0,3.6;2,1;lot_fill;Fill lots to max.]"..
  640. "list[current_player;main;0,4.85;8,1;]"..
  641. "list[current_player;main;0,6.08;8,3;8]"..
  642. "listring[current_player;main]"..
  643. "field_close_on_enter[lot_count;false]"
  644. -- Add dynamic elements
  645. local settings = get_vendor_settings(pos)
  646. local meta = minetest.get_meta(pos)
  647. local status, errorcode = get_vendor_status(pos)
  648. local itemstuff =
  649. "item_image_button[0,0.4;1,1;"..settings.input_item..";ignore;]"..
  650. "label[0.9,0.6;"..settings.input_item_qty.." "..minetest.registered_items[settings.input_item].description.."]"..
  651. "item_image_button[0,1.7;1,1;"..settings.output_item..";ignore;]"..
  652. "label[0.9,1.9;"..settings.output_item_qty.." "..minetest.registered_items[settings.output_item].description.."]"
  653. local status_str
  654. if status then
  655. status_str = "active"
  656. else
  657. status_str = "inactive"..make_inactive_string(errorcode)
  658. end
  659. local status_fs =
  660. "label[4,0.4;Vendor status: "..status_str.."]"..
  661. "label[4,0.8;Message: "..meta:get_string("message").."]"..
  662. "label[4,0;Vendor owned by: "..meta:get_string("owner").."]"
  663. local setting_specific = ""
  664. if not settings.accept_worn_input then
  665. setting_specific = setting_specific.."label[4,1.6;Vendor will not accept worn tools.]"
  666. end
  667. if not settings.accept_worn_output then
  668. setting_specific = setting_specific.."label[4,1.2;Vendor will not sell worn tools.]"
  669. end
  670. local fields = "field[2.2,3.2;1,0.6;lot_count;;"..(lots or 1).."]"
  671. local fs = base..itemstuff..status_fs..setting_specific..fields
  672. return fs
  673. end
  674. local function get_vendor_settings_fs(pos)
  675. local base = "size[9,9]"..
  676. "label[2.8,0.5;Input item]"..
  677. "label[6.8,0.5;Output item]"..
  678. "image[0,1.3;1,1;debug_btn.png]"..
  679. "item_image_button[0,2.3;1,1;default:book;button_log;]"..
  680. "item_image_button[0,3.3;1,1;default:gold_ingot;button_buy;]"..
  681. "list[current_player;main;1,4.85;8,1;]"..
  682. "list[current_player;main;1,6.08;8,3;8]"..
  683. "listring[current_player;main]"..
  684. "button_exit[0,8;1,1;btn_exit;Done]"
  685. -- Add dynamic elements
  686. local pos_str = pos.x..","..pos.y..","..pos.z
  687. local settings = get_vendor_settings(pos)
  688. if settings.admin_vendor then
  689. base = base.."item_image[0,0.3;1,1;default:chest]"
  690. else
  691. base = base.."item_image_button[0,0.3;1,1;default:chest;button_inv;]"
  692. end
  693. local inv =
  694. "list[nodemeta:"..pos_str..";wanted_item;1,0.3;1,1;]"..
  695. "list[nodemeta:"..pos_str..";given_item;5,0.3;1,1;]"..
  696. "listring[nodemeta:"..pos_str..";wanted_item]"..
  697. "listring[nodemeta:"..pos_str..";given_item]"
  698. local fields =
  699. "field[2.2,0.8;1,0.6;input_item_qty;;"..settings.input_item_qty.."]"..
  700. "field[6.2,0.8;1,0.6;output_item_qty;;"..settings.output_item_qty.."]"..
  701. "field[1.3,4.1;2.66,1;co_sellers;Co-Sellers:;"..settings.co_sellers.."]"..
  702. "field[3.86,4.1;2.66,1;banned_buyers;Banned Buyers:;"..settings.banned_buyers.."]"..
  703. "field_close_on_enter[input_item_qty;false]"..
  704. "field_close_on_enter[output_item_qty;false]"..
  705. "field_close_on_enter[co_sellers;false]"..
  706. "field_close_on_enter[banned_buyers;false]"
  707. local checkboxes =
  708. "checkbox[1,2.2;inactive_force;Force vendor into an inactive state.;"..bts(settings.inactive_force).."]"..
  709. "checkbox[1,2.6;depositor;Set this vendor to a Depositor.;"..bts(settings.depositor).."]"..
  710. "checkbox[1,3.0;accept_worn_output;Sell worn tools.;"..bts(settings.accept_worn_output).."]"..
  711. "checkbox[5,3.0;accept_worn_input;Buy worn tools.;"..bts(settings.accept_worn_input).."]"..
  712. "checkbox[5,2.6;auto_sort;Automatically sort inventory.;"..bts(settings.auto_sort).."]"
  713. -- Admin vendor checkbox only if owner is admin
  714. local meta = minetest.get_meta(pos)
  715. if minetest.check_player_privs(meta:get_string("owner"), {admin_vendor=true}) or settings.admin_vendor then
  716. checkboxes = checkboxes..
  717. "checkbox[5,2.2;admin_vendor;Set vendor to an admin vendor.;"..bts(settings.admin_vendor).."]"
  718. end
  719. -- Optional dependancy specific elements
  720. if minetest.get_modpath("pipeworks") or minetest.get_modpath("hopper") then
  721. checkboxes = checkboxes..
  722. "checkbox[1,1.7;currency_eject;Eject incoming currency.;"..bts(settings.currency_eject).."]"
  723. if minetest.get_modpath("pipeworks") then
  724. checkboxes = checkboxes..
  725. "checkbox[5,1.3;accept_output_only;Accept for-sale item only.;"..bts(settings.accept_output_only).."]"..
  726. "checkbox[1,1.3;split_incoming_stacks;Split incoming stacks.;"..bts(settings.split_incoming_stacks).."]"
  727. end
  728. end
  729. if minetest.get_modpath("digilines") then
  730. fields = fields..
  731. "field[6.41,4.1;2.66,1;digiline_channel;Digiline Channel:;"..settings.digiline_channel.."]"..
  732. "field_close_on_enter[digiline_channel;false]"
  733. end
  734. local fs = base..inv..fields..checkboxes
  735. return fs
  736. end
  737. local function get_vendor_default_fs(pos, player)
  738. local base = "size[16,11]"..
  739. "item_image[0,0.3;1,1;default:chest]"..
  740. "list[current_player;main;4,6.85;8,1;]"..
  741. "list[current_player;main;4,8.08;8,3;8]"..
  742. "listring[current_player;main]"..
  743. "button[1,6.85;3,1;inv_tovendor;All To Vendor]"..
  744. "button[12,6.85;3,1;inv_fromvendor;All From Vendor]"..
  745. "button[1,8.08;3,1;inv_output_tovendor;Output To Vendor]"..
  746. "button[12,8.08;3,1;inv_input_fromvendor;Input From Vendor]"..
  747. "button[1,9.31;3,1;sort;Sort Inventory]"..
  748. "button_exit[0,10;1,1;btn_exit;Done]"
  749. -- Add dynamic elements
  750. local pos_str = pos.x..","..pos.y..","..pos.z
  751. local inv_lists =
  752. "list[nodemeta:"..pos_str..";main;1,0.3;15,6;]"..
  753. "listring[nodemeta:"..pos_str..";main]"
  754. local settings_btn = ""
  755. if can_modify_vendor(pos, player) then
  756. settings_btn =
  757. "image_button[0,1.3;1,1;debug_btn.png;button_settings;]"..
  758. "item_image_button[0,2.3;1,1;default:book;button_log;]"..
  759. "item_image_button[0,3.3;1,1;default:gold_ingot;button_buy;]"
  760. else
  761. settings_btn =
  762. "image[0,1.3;1,1;debug_btn.png]"..
  763. "item_image[0,2.3;1,1;default:book]"..
  764. "item_image[0,3.3;1,1;default:gold_ingot;button_buy;]"
  765. end
  766. local fs = base..inv_lists..settings_btn
  767. return fs
  768. end
  769. local function get_vendor_log_fs(pos)
  770. local base = "size[9,9]"..
  771. "image_button[0,1.3;1,1;debug_btn.png;button_settings;]"..
  772. "item_image[0,2.3;1,1;default:book]"..
  773. "item_image_button[0,3.3;1,1;default:gold_ingot;button_buy;]"..
  774. "button_exit[0,8;1,1;btn_exit;Done]"
  775. -- Add dynamic elements
  776. local meta = minetest.get_meta(pos)
  777. local logs = minetest.deserialize(meta:get_string("log"))
  778. local settings = get_vendor_settings(pos)
  779. if settings.admin_vendor then
  780. base = base.."item_image[0,0.3;1,1;default:chest]"
  781. else
  782. base = base.."item_image_button[0,0.3;1,1;default:chest;button_inv;]"
  783. end
  784. if not logs then logs = {"Error loading logs",} end
  785. local logs_tl =
  786. "textlist[1,0.5;7.8,8.6;logs;"..table.concat(logs, ",").."]"..
  787. "label[1,0;Showing (up to "..max_logs..") recent log entries:]"
  788. local fs = base..logs_tl
  789. return fs
  790. end
  791. local function show_buyer_formspec(player, pos)
  792. minetest.show_formspec(player:get_player_name(), "fancy_vend:buyer;"..minetest.pos_to_string(pos), get_vendor_buyer_fs(pos, player, nil))
  793. end
  794. local function show_vendor_formspec(player, pos)
  795. local settings = get_vendor_settings(pos)
  796. if can_access_vendor_inv(player, pos) then
  797. local status, errorcode = get_vendor_status(pos)
  798. if ((not status and errorcode == "unconfigured") and can_modify_vendor(pos, player)) or settings.admin_vendor then
  799. minetest.show_formspec(player:get_player_name(), "fancy_vend:settings;"..minetest.pos_to_string(pos), get_vendor_settings_fs(pos))
  800. else
  801. minetest.show_formspec(player:get_player_name(), "fancy_vend:default;"..minetest.pos_to_string(pos), get_vendor_default_fs(pos, player))
  802. end
  803. else
  804. show_buyer_formspec(player, pos)
  805. end
  806. end
  807. local function swap_vendor(pos, vendor_type)
  808. local node = minetest.get_node(pos)
  809. node.name = vendor_type
  810. minetest.swap_node(pos, node)
  811. end
  812. local function get_correct_vendor(settings)
  813. if settings.admin_vendor then
  814. if settings.depositor then
  815. return "fancy_vend:admin_depo"
  816. else
  817. return "fancy_vend:admin_vendor"
  818. end
  819. else
  820. if settings.depositor then
  821. return "fancy_vend:player_depo"
  822. else
  823. return "fancy_vend:player_vendor"
  824. end
  825. end
  826. end
  827. local function is_vendor(name)
  828. local vendor_names = {
  829. "fancy_vend:player_vendor",
  830. "fancy_vend:player_depo",
  831. "fancy_vend:admin_vendor",
  832. "fancy_vend:admin_depo",
  833. }
  834. for i,n in ipairs(vendor_names) do
  835. if name == n then
  836. return true
  837. end
  838. end
  839. return false
  840. end
  841. local function refresh_vendor(pos)
  842. local node = minetest.get_node(pos)
  843. if node.name:split(":")[1] ~= "fancy_vend" then
  844. return false, "not a vendor"
  845. end
  846. local settings = get_vendor_settings(pos)
  847. local meta = minetest.get_meta(pos)
  848. local status, errorcode = get_vendor_status(pos)
  849. local correct_vendor = get_correct_vendor(settings)
  850. if status or errorcode ~= "no_output" then
  851. meta:set_string("alerted", "false")
  852. end
  853. if status then
  854. -- meta:set_string("infotext", (settings.admin_vendor and "Admin" or "Player").." Vendor trading "..settings.input_item_qty.." "..minetest.registered_items[settings.input_item].description.." for "..settings.output_item_qty.." "..minetest.registered_items[settings.output_item].description.." (owned by " .. meta:get_string("owner") .. ")")
  855. if meta:get_string("configured") == "" then
  856. meta:set_string("configured", "true")
  857. if minetest.get_modpath("awards") then
  858. local name = meta:get_string("owner")
  859. local data = awards.player(name)
  860. -- Ensure fancy_vend_configure table is in data
  861. if not data.fancy_vend_configure then
  862. data.fancy_vend_configure = {}
  863. end
  864. awards.increment_item_counter(data, "fancy_vend_configure", correct_vendor)
  865. total_item_count = 0
  866. for k, v in pairs(data.fancy_vend_configure) do
  867. total_item_count = total_item_count + v
  868. end
  869. if awards.get_item_count(data, "fancy_vend_configure", "fancy_vend:player_vendor") >= 1 then
  870. awards.unlock(name, "fancy_vend:seller")
  871. end
  872. if awards.get_item_count(data, "fancy_vend_configure", "fancy_vend:player_depo") >= 1 then
  873. awards.unlock(name, "fancy_vend:trader")
  874. end
  875. if total_item_count >= 10 then
  876. awards.unlock(name, "fancy_vend:shop_keeper")
  877. end
  878. if total_item_count >= 25 then
  879. awards.unlock(name, "fancy_vend:merchant")
  880. end
  881. if total_item_count >= 100 then
  882. awards.unlock(name, "fancy_vend:super_merchant")
  883. end
  884. if total_item_count >= 9001 then
  885. awards.unlock(name, "fancy_vend:god_merchant")
  886. end
  887. end
  888. end
  889. if settings.depositor then
  890. if meta:get_string("item") ~= settings.input_item then
  891. meta:set_string("item", settings.input_item)
  892. update_item(pos, node)
  893. end
  894. else
  895. if meta:get_string("item") ~= settings.output_item then
  896. meta:set_string("item", settings.output_item)
  897. update_item(pos, node)
  898. end
  899. end
  900. else
  901. meta:set_string("infotext", "Inactive "..(settings.admin_vendor and "Admin" or "Player").." Vendor"..make_inactive_string(errorcode).." (owned by " .. meta:get_string("owner") .. ")")
  902. if meta:get_string("item") ~= "fancy_vend:inactive" then
  903. meta:set_string("item", "fancy_vend:inactive")
  904. update_item(pos, node)
  905. end
  906. if not alerted and not status and errorcode == "no_room" then
  907. minetest.chat_send_player(meta:get_string("owner"), "[Fancy_Vend]: Error with vendor at "..minetest.pos_to_string(pos, 0)..": does not have room for payment.")
  908. meta:set_string("alerted", "true")
  909. end
  910. end
  911. if correct_vendor ~= node.name then
  912. swap_vendor(pos, correct_vendor)
  913. end
  914. end
  915. local function move_inv(frominv, toinv, filter)
  916. for i, v in ipairs(frominv:get_list("main") or {}) do
  917. if v:get_name() == filter or not filter then
  918. if toinv:room_for_item("main", v) then
  919. local leftover = toinv:add_item("main", v)
  920. frominv:remove_item("main", v)
  921. if leftover
  922. and not leftover:is_empty() then
  923. frominv:add_item("main", v)
  924. end
  925. end
  926. end
  927. end
  928. end
  929. minetest.register_on_player_receive_fields(function(player, formname, fields)
  930. local name = formname:split(":")[1]
  931. if name ~= "fancy_vend" then return end
  932. local formtype = formname:split(":")[2]
  933. formtype = formtype:split(";")[1]
  934. local pos = minetest.string_to_pos(formname:split(";")[2])
  935. if not pos then return end
  936. local node = minetest.get_node(pos)
  937. if not is_vendor(node.name) then return end
  938. local meta = minetest.get_meta(pos)
  939. local inv = meta:get_inventory()
  940. local player_inv = player:get_inventory()
  941. local settings = get_vendor_settings(pos)
  942. -- Handle settings changes
  943. if can_modify_vendor(pos, player) then
  944. for i in pairs(fields) do
  945. if stb(fields[i]) ~= settings[i] then
  946. settings[i] = stb(fields[i])
  947. end
  948. end
  949. -- Check number-only fields contain only numbers
  950. if not tonumber(settings.input_item_qty) then
  951. settings.input_item_qty = 1
  952. else
  953. settings.input_item_qty = math.floor(math.abs(tonumber(settings.input_item_qty)))
  954. end
  955. if not tonumber(settings.output_item_qty) then
  956. settings.output_item_qty = 1
  957. else
  958. settings.output_item_qty = math.floor(math.abs(tonumber(settings.output_item_qty)))
  959. end
  960. -- Check item quantities aren't too high (which could lead to additional processing for no reason), if so, set it to the maximum the player inventory can handle
  961. if ItemStack(settings.output_item):get_stack_max() * player_inv:get_size("main") < settings.output_item_qty then
  962. settings.output_item_qty = ItemStack(settings.output_item):get_stack_max() * player_inv:get_size("main")
  963. end
  964. if ItemStack(settings.input_item):get_stack_max() * player_inv:get_size("main") < settings.input_item_qty then
  965. settings.input_item_qty = ItemStack(settings.input_item):get_stack_max() * player_inv:get_size("main")
  966. end
  967. -- Admin vendor priv check
  968. if not minetest.check_player_privs(meta:get_string("owner"), {admin_vendor=true}) and fields.admin_vendor == "true" then
  969. settings.admin_vendor = false
  970. end
  971. set_vendor_settings(pos, settings)
  972. refresh_vendor(pos)
  973. end
  974. if fields.quit then
  975. if can_access_vendor_inv(player, pos) and settings.auto_sort then
  976. sort_inventory(inv)
  977. end
  978. return true
  979. end
  980. if fields.sort and can_access_vendor_inv(player, pos) then
  981. sort_inventory(inv)
  982. end
  983. if fields.buy then
  984. local lots = math.floor(tonumber(fields.lot_count) or 1)
  985. -- prevent negative numbers
  986. lots = math.max(lots, 1)
  987. local success, message = make_purchase(pos, player, lots)
  988. if success then
  989. -- Add to vendor logs
  990. local logs = minetest.deserialize(meta:get_string("log"))
  991. for i in pairs(logs) do
  992. if i >= max_logs then
  993. table.remove(logs, 1)
  994. end
  995. end
  996. table.insert(logs, "Player "..player:get_player_name().." purchased "..lots.." lots from this vendor.")
  997. meta:set_string("log", minetest.serialize(logs))
  998. -- Send digiline message if applicable
  999. if minetest.get_modpath("digilines") then
  1000. local msg = {
  1001. buyer = player:get_player_name(),
  1002. lots = lots,
  1003. settings = settings,
  1004. }
  1005. send_message(pos, settings.digiline_channel, msg)
  1006. end
  1007. end
  1008. -- Set message and refresh vendor
  1009. if message then
  1010. meta:set_string("message", message)
  1011. end
  1012. refresh_vendor(pos)
  1013. elseif fields.lot_fill then
  1014. minetest.show_formspec(player:get_player_name(), "fancy_vend:buyer;"..minetest.pos_to_string(pos), get_vendor_buyer_fs(pos, player, get_max_lots(pos, player)))
  1015. return true
  1016. end
  1017. if can_access_vendor_inv(player, pos) then
  1018. if fields.inv_tovendor then
  1019. minetest.log("action", player:get_player_name().." moves inventory contents to vendor at "..minetest.pos_to_string(pos))
  1020. move_inv(player_inv, inv, nil)
  1021. refresh_vendor(pos)
  1022. elseif fields.inv_output_tovendor then
  1023. minetest.log("action", player:get_player_name().." moves output items in inventory to vendor at "..minetest.pos_to_string(pos))
  1024. move_inv(player_inv, inv, settings.output_item)
  1025. refresh_vendor(pos)
  1026. elseif fields.inv_fromvendor then
  1027. minetest.log("action", player:get_player_name().." moves inventory contents from vendor at "..minetest.pos_to_string(pos))
  1028. move_inv(inv, player_inv, nil)
  1029. refresh_vendor(pos)
  1030. elseif fields.inv_input_fromvendor then
  1031. minetest.log("action", player:get_player_name().." moves input items from vendor at "..minetest.pos_to_string(pos))
  1032. move_inv(inv, player_inv, settings.input_item)
  1033. refresh_vendor(pos)
  1034. end
  1035. end
  1036. -- Handle page changes
  1037. if fields.button_log then
  1038. minetest.show_formspec(player:get_player_name(), "fancy_vend:log;"..minetest.pos_to_string(pos), get_vendor_log_fs(pos))
  1039. return
  1040. elseif fields.button_settings then
  1041. minetest.show_formspec(player:get_player_name(), "fancy_vend:settings;"..minetest.pos_to_string(pos), get_vendor_settings_fs(pos))
  1042. return
  1043. elseif fields.button_inv then
  1044. minetest.show_formspec(player:get_player_name(), "fancy_vend:default;"..minetest.pos_to_string(pos), get_vendor_default_fs(pos, player))
  1045. return
  1046. elseif fields.button_buy then
  1047. minetest.show_formspec(player:get_player_name(), "fancy_vend:buyer;"..minetest.pos_to_string(pos), get_vendor_buyer_fs(pos, player, (tonumber(fields.lot_count) or 1)))
  1048. return
  1049. end
  1050. -- Update formspec
  1051. if formtype == "log" then
  1052. minetest.show_formspec(player:get_player_name(), "fancy_vend:log;"..minetest.pos_to_string(pos), get_vendor_log_fs(pos, player))
  1053. elseif formtype == "settings" then
  1054. minetest.show_formspec(player:get_player_name(), "fancy_vend:settings;"..minetest.pos_to_string(pos), get_vendor_settings_fs(pos, player))
  1055. elseif formtype == "default" then
  1056. minetest.show_formspec(player:get_player_name(), "fancy_vend:default;"..minetest.pos_to_string(pos), get_vendor_default_fs(pos, player))
  1057. elseif formtype == "buyer" then
  1058. minetest.show_formspec(player:get_player_name(), "fancy_vend:buyer;"..minetest.pos_to_string(pos), get_vendor_buyer_fs(pos, player, (tonumber(fields.lot_count) or 1)))
  1059. end
  1060. end)
  1061. local vendor_template = {
  1062. description = "Vending Machine",
  1063. legacy_facedir_simple = true,
  1064. paramtype2 = "facedir",
  1065. groups = {choppy=2, oddly_breakable_by_hand=2, tubedevice=1, tubedevice_receiver=1},
  1066. selection_box = {
  1067. type = "fixed",
  1068. fixed = {-0.5, -0.5, -0.5, 0.5, 1.5, 0.5},
  1069. },
  1070. is_ground_content = false,
  1071. light_source = 8,
  1072. sounds = default.node_sound_wood_defaults(),
  1073. drop = drop_vendor,
  1074. on_construct = function(pos)
  1075. local meta = minetest.get_meta(pos)
  1076. meta:set_string("infotext", "Unconfigured Player Vendor")
  1077. meta:set_string("message", "Vendor initialized")
  1078. meta:set_string("owner", "")
  1079. local inv = meta:get_inventory()
  1080. inv:set_size("main", 15*6)
  1081. inv:set_size("wanted_item", 1*1)
  1082. inv:set_size("given_item", 1*1)
  1083. reset_vendor_settings(pos)
  1084. meta:set_string("log", "")
  1085. end,
  1086. can_dig = can_dig_vendor,
  1087. on_place = function(itemstack, placer, pointed_thing)
  1088. if pointed_thing.type ~= "node" then return end
  1089. local pointed_node_pos = minetest.get_pointed_thing_position(pointed_thing, false)
  1090. local pointed_node = minetest.get_node(pointed_node_pos)
  1091. if minetest.registered_nodes[pointed_node.name].buildable_to then
  1092. pointed_thing.above = pointed_node_pos
  1093. end
  1094. -- Set variables for access later (for various checks, etc.)
  1095. local name = placer:get_player_name()
  1096. local above_node_pos = table.copy(pointed_thing.above)
  1097. above_node_pos.y = above_node_pos.y + 1
  1098. local above_node = minetest.get_node(above_node_pos).name
  1099. -- If node above is air or the display node, and it is not protected, attempt to place the vendor. If vendor sucessfully places, place display node above, otherwise alert the user
  1100. if (minetest.registered_nodes[above_node].buildable_to or above_node == "fancy_vend:display_node") and not minetest.is_protected(above_node_pos, name) then
  1101. local itemstack, success = minetest.item_place(itemstack, placer, pointed_thing, nil)
  1102. if above_node ~= "fancy_vend:display_node" and success then
  1103. minetest.set_node(above_node_pos, minetest.registered_nodes["fancy_vend:display_node"])
  1104. end
  1105. -- Set owner
  1106. local meta = minetest.get_meta(pointed_thing.above)
  1107. meta:set_string("owner", placer:get_player_name() or "")
  1108. -- Set default meta
  1109. meta:set_string("log", minetest.serialize({"Vendor placed by "..placer:get_player_name(),}))
  1110. reset_vendor_settings(pointed_thing.above)
  1111. refresh_vendor(pointed_thing.above)
  1112. else
  1113. minetest.chat_send_player(name, "Vendors require 2 nodes of space.")
  1114. end
  1115. if minetest.get_modpath("pipeworks") then
  1116. pipeworks.after_place(pointed_thing.above)
  1117. end
  1118. return itemstack
  1119. end,
  1120. on_dig = function(pos, node, digger)
  1121. -- Set variables for access later (for various checks, etc.)
  1122. local name = digger:get_player_name()
  1123. local above_node_pos = table.copy(pos)
  1124. above_node_pos.y = above_node_pos.y + 1
  1125. -- abandon if player shouldn't be able to dig node
  1126. local can_dig = can_dig_vendor(pos, digger)
  1127. if not can_dig then return end
  1128. -- Try remove display node, if the whole node is able to be removed by the player, remove the display node and continue to remove vendor, if it doesn't exist and vendor can be dug continue to remove vendor.
  1129. local success
  1130. if minetest.get_node(above_node_pos).name == "fancy_vend:display_node" then
  1131. if not minetest.is_protected(above_node_pos, name) and not minetest.is_protected(pos, name) then
  1132. minetest.remove_node(above_node_pos)
  1133. remove_item(above_node_pos)
  1134. success = true
  1135. else
  1136. success = false
  1137. end
  1138. else
  1139. if not minetest.is_protected(pos, name) then
  1140. success = true
  1141. else
  1142. success = false
  1143. end
  1144. end
  1145. -- If failed to remove display node, don't remove vendor. since protection for whole vendor was checked at display removal, protection need not be re-checked
  1146. if success then
  1147. minetest.remove_node(pos)
  1148. minetest.handle_node_drops(pos, {drop_vendor}, digger)
  1149. if minetest.get_modpath("pipeworks") then
  1150. pipeworks.after_dig(pos)
  1151. end
  1152. end
  1153. end,
  1154. tube = {
  1155. input_inventory = "main",
  1156. connect_sides = {left = 1, right = 1, back = 1, bottom = 1},
  1157. insert_object = function(pos, node, stack, direction)
  1158. local meta = minetest.get_meta(pos)
  1159. local inv = meta:get_inventory()
  1160. local remaining = inv:add_item("main", stack)
  1161. refresh_vendor(pos)
  1162. return remaining
  1163. end,
  1164. can_insert = function(pos, node, stack, direction)
  1165. local meta = minetest.get_meta(pos)
  1166. local inv = meta:get_inventory()
  1167. local settings = get_vendor_settings(pos)
  1168. if settings.split_stacks then
  1169. stack = stack:peek_item(1)
  1170. end
  1171. if settings.accept_output_only then
  1172. if stack:get_name() ~= settings.output_item then
  1173. return false
  1174. end
  1175. end
  1176. return inv:room_for_item("main", stack)
  1177. end,
  1178. },
  1179. allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
  1180. if (not can_access_vendor_inv(player, pos)) or to_list == "wanted_item" or to_list == "given_item" then
  1181. return 0
  1182. end
  1183. return count
  1184. end,
  1185. allow_metadata_inventory_put = function(pos, listname, index, stack, player)
  1186. if not can_access_vendor_inv(player, pos) then
  1187. return 0
  1188. end
  1189. if listname == "wanted_item" or listname == "given_item" then
  1190. local inv = minetest.get_meta(pos):get_inventory()
  1191. inv:set_stack(listname, index, ItemStack(stack:get_name()))
  1192. local settings = get_vendor_settings(pos)
  1193. if listname == "wanted_item" then
  1194. settings.input_item = stack:get_name()
  1195. elseif listname == "given_item" then
  1196. settings.output_item = stack:get_name()
  1197. end
  1198. set_vendor_settings(pos, settings)
  1199. return 0
  1200. end
  1201. return stack:get_count()
  1202. end,
  1203. allow_metadata_inventory_take = function(pos, listname, index, stack, player)
  1204. if not can_access_vendor_inv(player, pos) then
  1205. return 0
  1206. end
  1207. if listname == "wanted_item" or listname == "given_item" then
  1208. local inv = minetest.get_meta(pos):get_inventory()
  1209. local fake_stack = inv:get_stack(listname, index)
  1210. fake_stack:take_item(stack:get_count())
  1211. inv:set_stack(listname, index, fake_stack)
  1212. local settings = get_vendor_settings(pos)
  1213. if listname == "wanted_item" then
  1214. settings.input_item = ""
  1215. elseif listname == "given_item" then
  1216. settings.output_item = ""
  1217. end
  1218. set_vendor_settings(pos, settings)
  1219. return 0
  1220. end
  1221. return stack:get_count()
  1222. end,
  1223. on_rightclick = function(pos, node, clicker)
  1224. node = minetest.get_node(pos)
  1225. if node.name == "fancy_vend:display_node" then
  1226. pos.y = pos.y - 1
  1227. end
  1228. show_vendor_formspec(clicker, pos)
  1229. end,
  1230. on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
  1231. minetest.log("action", player:get_player_name().." moves stuff in vendor at "..minetest.pos_to_string(pos))
  1232. refresh_vendor(pos)
  1233. end,
  1234. on_metadata_inventory_put = function(pos, listname, index, stack, player)
  1235. minetest.log("action", player:get_player_name().." moves "..stack:get_name().." to vendor at "..minetest.pos_to_string(pos))
  1236. refresh_vendor(pos)
  1237. end,
  1238. on_metadata_inventory_take = function(pos, listname, index, stack, player)
  1239. minetest.log("action", player:get_player_name().." takes "..stack:get_name().." from vendor at "..minetest.pos_to_string(pos))
  1240. refresh_vendor(pos)
  1241. end,
  1242. on_blast = function()
  1243. -- TNT immunity
  1244. end,
  1245. }
  1246. if pipeworks then
  1247. vendor_template.digiline = {
  1248. receptor = {},
  1249. effector = {
  1250. action = function() end
  1251. },
  1252. wire = {
  1253. rules = pipeworks.digilines_rules
  1254. },
  1255. }
  1256. end
  1257. local player_vendor = table.copy(vendor_template)
  1258. player_vendor.tiles = {
  1259. "player_vend.png", "player_vend.png",
  1260. "player_vend.png", "player_vend.png",
  1261. "player_vend.png", "player_vend_front.png",
  1262. }
  1263. local player_depo = table.copy(vendor_template)
  1264. player_depo.tiles = {
  1265. "player_depo.png", "player_depo.png",
  1266. "player_depo.png", "player_depo.png",
  1267. "player_depo.png", "player_depo_front.png",
  1268. }
  1269. player_depo.groups.not_in_creative_inventory = 1
  1270. local admin_vendor = table.copy(vendor_template)
  1271. admin_vendor.tiles = {
  1272. "admin_vend.png", "admin_vend.png",
  1273. "admin_vend.png", "admin_vend.png",
  1274. "admin_vend.png", "admin_vend_front.png",
  1275. }
  1276. admin_vendor.groups.not_in_creative_inventory = 1
  1277. local admin_depo = table.copy(vendor_template)
  1278. admin_depo.tiles = {
  1279. "admin_depo.png", "admin_depo.png",
  1280. "admin_depo.png", "admin_depo.png",
  1281. "admin_depo.png", "admin_depo_front.png",
  1282. }
  1283. admin_depo.groups.not_in_creative_inventory = 1
  1284. minetest.register_node("fancy_vend:player_vendor", player_vendor)
  1285. minetest.register_node("fancy_vend:player_depo", player_depo)
  1286. minetest.register_node("fancy_vend:admin_vendor", admin_vendor)
  1287. minetest.register_node("fancy_vend:admin_depo", admin_depo)
  1288. minetest.register_craft({
  1289. output = "fancy_vend:player_vendor",
  1290. recipe = {
  1291. { "default:gold_ingot",display_node, "default:gold_ingot"},
  1292. { "default:diamond", "default:mese_crystal", "default:diamond"},
  1293. { "default:gold_ingot","default:chest_locked","default:gold_ingot"},
  1294. }
  1295. })
  1296. -- Hopper support
  1297. if minetest.get_modpath("hopper") then
  1298. hopper:add_container({
  1299. {"side", "fancy_vend:player_vendor", "main"}
  1300. })
  1301. hopper:add_container({
  1302. {"side", "fancy_vend:player_depo", "main"}
  1303. })
  1304. end
  1305. ---------------
  1306. -- Copy Tool --
  1307. ---------------
  1308. local function get_vendor_pos_and_settings(pointed_thing)
  1309. if pointed_thing.type ~= "node" then return false end
  1310. local pos = minetest.get_pointed_thing_position(pointed_thing, false)
  1311. local node = minetest.get_node(pos)
  1312. if node.name == "fancy_vend:display_node" then
  1313. pos.y = pos.y - 1
  1314. node = minetest.get_node(pos)
  1315. end
  1316. if not is_vendor(node.name) then return false end
  1317. local settings = get_vendor_settings(pos)
  1318. return pos, settings
  1319. end
  1320. minetest.register_tool("fancy_vend:copy_tool",{
  1321. inventory_image = "copier.png",
  1322. description = "Geminio Wand (For copying vendor settings, right click to save settings, left click to set settings.)",
  1323. stack_max = 1,
  1324. on_place = function(itemstack, placer, pointed_thing)
  1325. local pos, settings = get_vendor_pos_and_settings(pointed_thing)
  1326. if not pos then return end
  1327. local meta = itemstack:get_meta()
  1328. meta:set_string("settings", minetest.serialize(settings))
  1329. minetest.chat_send_player(placer:get_player_name(), "Settings saved.")
  1330. return itemstack
  1331. end,
  1332. on_use = function(itemstack, user, pointed_thing)
  1333. local pos, current_settings = get_vendor_pos_and_settings(pointed_thing)
  1334. if not pos then return end
  1335. local meta = itemstack:get_meta()
  1336. local node_meta = minetest.get_meta(pos)
  1337. local new_settings = minetest.deserialize(meta:get_string("settings"))
  1338. if not new_settings then
  1339. minetest.chat_send_player(user:get_player_name(), "No settings to set with. Right-click first on the vendor you want to copy settings from.")
  1340. return
  1341. end
  1342. if can_modify_vendor(pos, user) then
  1343. -- Admin vendor priv check
  1344. if not minetest.check_player_privs(node_meta:get_string("owner"), {admin_vendor=true}) and new_settings.admin_vendor == true then
  1345. settings.admin_vendor = false
  1346. end
  1347. new_settings.input_item = current_settings.input_item
  1348. new_settings.input_item_qty = current_settings.input_item_qty
  1349. new_settings.output_item = current_settings.output_item
  1350. new_settings.output_item_qty = current_settings.output_item_qty
  1351. -- Admin vendor priv check
  1352. if not minetest.check_player_privs(node_meta:get_string("owner"), {admin_vendor=true}) and new_settings.admin_vendor then
  1353. new_settings.admin_vendor = current_settings.admin_vendor
  1354. end
  1355. set_vendor_settings(pos, new_settings)
  1356. refresh_vendor(pos)
  1357. minetest.chat_send_player(user:get_player_name(), "Settings set.")
  1358. else
  1359. minetest.chat_send_player(user:get_player_name(), "You cannot modify this vendor.")
  1360. end
  1361. end,
  1362. })
  1363. minetest.register_craft({
  1364. output = "fancy_vend:copy_tool",
  1365. recipe = {
  1366. {"default:stick","", "" },
  1367. {"", "default:obsidian_shard","" },
  1368. {"", "", "default:diamond"},
  1369. }
  1370. })
  1371. minetest.register_craft({
  1372. output = "fancy_vend:copy_tool",
  1373. recipe = {
  1374. {"", "", "default:stick"},
  1375. {"", "default:obsidian_shard","" },
  1376. {"default:diamond","", "" },
  1377. }
  1378. })
  1379. ---------------------------
  1380. -- Vendor Upgrade System --
  1381. ---------------------------
  1382. local old_vendor_mods = string.split((minetest.setting_get("fancy_vend_old_vendor_mods") or ""), ",")
  1383. local old_vendor_mods_table = {}
  1384. for i in pairs(old_vendor_mods) do
  1385. old_vendor_mods_table[old_vendor_mods[i]] = true
  1386. end
  1387. local base_upgrade_template = {
  1388. description = "Shop Upgrade (Try and place to upgrade)",
  1389. legacy_facedir_simple = true,
  1390. paramtype2 = "facedir",
  1391. groups = {choppy=2, oddly_breakable_by_hand=2, not_in_creative_inventory=1},
  1392. is_ground_content = false,
  1393. light_source = 8,
  1394. sounds = default.node_sound_wood_defaults(),
  1395. drop = drop_vendor,
  1396. tiles = {
  1397. "player_vend.png", "player_vend.png",
  1398. "player_vend.png", "player_vend.png",
  1399. "player_vend.png", "upgrade_front.png",
  1400. },
  1401. on_place = function(itemstack, placer, pointed_thing)
  1402. return ItemStack(drop_vendor.." "..itemstack:get_count())
  1403. end,
  1404. allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
  1405. local meta = minetest.get_meta(pos)
  1406. if player:get_player_name() ~= meta:get_string("owner") then return 0 end
  1407. return count
  1408. end,
  1409. allow_metadata_inventory_put = function(pos, listname, index, stack, player)
  1410. local meta = minetest.get_meta(pos)
  1411. if player:get_player_name() ~= meta:get_string("owner") then return 0 end
  1412. return stack:get_count()
  1413. end,
  1414. allow_metadata_inventory_take = function(pos, listname, index, stack, player)
  1415. local meta = minetest.get_meta(pos)
  1416. if player:get_player_name() ~= meta:get_string("owner") then return 0 end
  1417. return stack:get_count()
  1418. end,
  1419. }
  1420. local clear_craft_vendors = {}
  1421. if old_vendor_mods_table["currency"] then
  1422. local currency_template = table.copy(base_upgrade_template)
  1423. currency_template.can_dig = function(pos, player)
  1424. local meta = minetest.get_meta(pos)
  1425. local inv = meta:get_inventory()
  1426. return inv:is_empty("stock") and inv:is_empty("customers_gave") and inv:is_empty("owner_wants") and inv:is_empty("owner_gives") and (meta:get_string("owner") == player:get_player_name() or minetest.check_player_privs(player:get_player_name(), {protection_bypass=true}))
  1427. end
  1428. currency_template.on_rightclick = function(pos, node, clicker, itemstack)
  1429. local meta = minetest.get_meta(pos)
  1430. local list_name = "nodemeta:"..pos.x..','..pos.y..','..pos.z
  1431. if clicker:get_player_name() == meta:get_string("owner") then
  1432. minetest.show_formspec(clicker:get_player_name(),"fancy_vend:currency_shop_formspec",
  1433. "size[8,9.5]"..
  1434. "label[0,0;" .. "Customers gave:" .. "]"..
  1435. "list["..list_name..";customers_gave;0,0.5;3,2;]"..
  1436. "label[0,2.5;" .. "Your stock:" .. "]"..
  1437. "list["..list_name..";stock;0,3;3,2;]"..
  1438. "label[5,0;" .. "You want:" .. "]"..
  1439. "list["..list_name..";owner_wants;5,0.5;3,2;]"..
  1440. "label[5,2.5;" .. "In exchange, you give:" .. "]"..
  1441. "list["..list_name..";owner_gives;5,3;3,2;]"..
  1442. "list[current_player;main;0,5.5;8,4;]"
  1443. )
  1444. end
  1445. end
  1446. minetest.register_node(":currency:shop", currency_template)
  1447. table.insert(clear_craft_vendors, "currency:shop")
  1448. end
  1449. if old_vendor_mods_table["easyvend"] then
  1450. local nodes = {"easyvend:vendor", "easyvend:vendor_on", "easyvend:depositor", "easyvend:depositor_on"}
  1451. for i in pairs(nodes) do
  1452. minetest.register_node(":"..nodes[i], base_upgrade_template)
  1453. table.insert(clear_craft_vendors, nodes[i])
  1454. end
  1455. end
  1456. if old_vendor_mods_table["vendor"] then
  1457. local nodes = {"vendor:vendor", "vendor:depositor"}
  1458. for i in pairs(nodes) do
  1459. minetest.register_node(":"..nodes[i], base_upgrade_template)
  1460. table.insert(clear_craft_vendors, nodes[i])
  1461. end
  1462. end
  1463. if old_vendor_mods_table["money"] then
  1464. local money_template = table.copy(base_upgrade_template)
  1465. money_template.can_dig = function(pos, player)
  1466. local meta = minetest.get_meta(pos)
  1467. local inv = meta:get_inventory()
  1468. return inv:is_empty("main") and (meta:get_string("owner") == player:get_player_name() or minetest.check_player_privs(player:get_player_name(), {protection_bypass=true}))
  1469. end
  1470. money_template.on_rightclick = function(pos, node, clicker, itemstack)
  1471. local meta = minetest.get_meta(pos)
  1472. local list_name = "nodemeta:"..pos.x..','..pos.y..','..pos.z
  1473. if clicker:get_player_name() == meta:get_string("owner") then
  1474. minetest.show_formspec(clicker:get_player_name(),"fancy_vend:money_shop_formspec",
  1475. "size[8,10;]"..
  1476. "list["..listname..";main;0,0;8,4;]"..
  1477. "list[current_player;main;0,6;8,4;]"
  1478. )
  1479. end
  1480. end
  1481. local nodes = {"money:barter_shop", "money:shop", "money:admin_shop", "money:admin_barter_shop"}
  1482. for i in pairs(nodes) do
  1483. minetest.register_node(":"..nodes[i], money_template)
  1484. table.insert(clear_craft_vendors, nodes[i])
  1485. end
  1486. end
  1487. for i_n in pairs(clear_craft_vendors) do
  1488. local currency_crafts = minetest.get_all_craft_recipes(i_n)
  1489. if currency_crafts then
  1490. for i in pairs(currency_crafts) do
  1491. minetest.clear_craft(currency_crafts[i])
  1492. end
  1493. end
  1494. end