ads.lua 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336
  1. if not minetest.global_exists("ads") then ads = {} end
  2. ads.modpath = minetest.get_modpath("easyvend")
  3. ads.worldpath = minetest.get_worldpath()
  4. ads.data = ads.data or {}
  5. ads.players = ads.players or {}
  6. ads.datapath = ads.worldpath .. "/advertisements.dat"
  7. ads.dirty = true
  8. ads.bodylen = 1024
  9. ads.titlelen = 64
  10. ads.viewrange = 8000 -- Distance at which ads are visible.
  11. ads.marketrange = 15 -- Distance at which shops are visible (distance from ad source).
  12. ads.ad_cost = 5
  13. ads.tax = 0
  14. -- Localize for performance.
  15. local vector_distance = vector.distance
  16. local vector_round = vector.round
  17. local math_floor = math.floor
  18. local open_storage = {
  19. {1, 4},
  20. {17, 20},
  21. {33, 36},
  22. {49, 52},
  23. {65, 68},
  24. }
  25. function ads.is_open_index(index)
  26. for k, v in ipairs(open_storage) do
  27. if index >= v[1] and index <= v[2] then
  28. return true
  29. end
  30. end
  31. end
  32. function ads.generate_submission_formspec(data)
  33. local esc = minetest.formspec_escape
  34. local title_text = ""
  35. local body_text = ""
  36. local edit_mode_str = ""
  37. local submit_name = "submit"
  38. local submit_text = "Submit (Cost: " .. ads.ad_cost .. " MG)"
  39. if data then
  40. title_text = data.title_text
  41. body_text = data.body_text
  42. submit_name = "confirmedit"
  43. submit_text = "Confirm Edits"
  44. edit_mode_str = " [EDIT MODE]"
  45. end
  46. local formspec =
  47. "size[10,8]" ..
  48. default.gui_bg ..
  49. default.gui_bg_img ..
  50. default.gui_slots ..
  51. "label[0,0;" .. esc("Submit a public advertisement for your shop to enable remote trading." .. edit_mode_str) .. "]" ..
  52. "label[0,0.4;" .. esc("Having your shop listed in the public market directory also increases its visibility.") .. "]" ..
  53. "label[0,1.0;" .. esc("Write your shop’s tagline here. It is limited to " .. ads.titlelen .. " characters. Example: ‘Buy Wood Here!’") .. "]" ..
  54. "item_image[9,0;1,1;currency:minegeld_100]" ..
  55. "field[0.3,1.7;10,1;title;;" .. esc(title_text) .. "]"
  56. formspec = formspec ..
  57. "label[0,2.5;" .. esc("Enter a description of your shop. This might include details on items sold, and pricing.") .. "]" ..
  58. "label[0,2.9;" .. esc("You may also want to include instructions on how to find your shop.") .. "]" ..
  59. "label[0,3.3;" .. esc("You should make sure the text is " .. ads.bodylen .. " characters or less.") .. "]" ..
  60. "textarea[0.3,3.8;10,3.0;text;;" .. esc(body_text) .. "]"
  61. formspec = formspec ..
  62. "label[0,6.4;" .. esc("Note that you SHOULD submit your advertisement from the location of your shop.") .. "]" ..
  63. "label[0,6.8;" .. esc("If you submit elsewhere, vending/depositing machines will not be available for remote trading.") .. "]" ..
  64. "button[5,7.3;2,1;cancel;" .. esc("Cancel") .. "]" ..
  65. "button[7,7.3;3,1;" .. submit_name .. ";" .. esc(submit_text) .. "]" ..
  66. "field_close_on_enter[title;false]" ..
  67. "item_image[0,7.3;1,1;currency:minegeld_100]"
  68. return formspec
  69. end
  70. function ads.show_submission_formspec(pos, pname, booth, data)
  71. local formspec = ads.generate_submission_formspec(data)
  72. local b = "|"
  73. if booth then
  74. b = "|booth"
  75. end
  76. local key = "ads:submission_" .. minetest.pos_to_string(vector_round(pos)) .. b
  77. minetest.show_formspec(pname, key, formspec)
  78. end
  79. function ads.show_inventory_formspec(pos, pname, booth)
  80. pos = vector_round(pos)
  81. local spos = pos.x .. "," .. pos.y .. "," .. pos.z
  82. -- Obtain hooks into the trash mod's trash slot inventory.
  83. local ltrash, mtrash = trash.get_listname()
  84. local itrash = trash.get_iconname()
  85. local formspec =
  86. "size[16,11]" ..
  87. default.gui_bg ..
  88. default.gui_bg_img ..
  89. default.gui_slots ..
  90. "list[nodemeta:" .. spos .. ";storage;0,0;16,5;]" ..
  91. "list[nodemeta:" .. spos .. ";storage;9,5;7,6;80]" ..
  92. "list[current_player;main;0,6.6;8,1;]" ..
  93. "list[current_player;main;0,8;8,3;8]" ..
  94. "listring[nodemeta:" .. spos .. ";storage]" ..
  95. "listring[current_player;main]" ..
  96. default.get_hotbar_bg(0, 6.6) ..
  97. -- Vending icon.
  98. "item_image[5,5.3;1,1;easyvend:vendor_on]" ..
  99. -- Trash icon.
  100. "list[" .. ltrash .. ";" .. mtrash .. ";7,5.3;1,1;]" ..
  101. "image[7,5.3;1,1;" .. itrash .. "]"
  102. -- Slot overlay.
  103. for x = 4, 15 do
  104. for y = 0, 4 do
  105. formspec = formspec .. "image[" .. x .. "," .. y .. ";1,1;easyvend_overflow.png]"
  106. end
  107. end
  108. for x = 9, 15 do
  109. for y = 5, 10 do
  110. formspec = formspec .. "image[" .. x .. "," .. y .. ";1,1;easyvend_overflow.png]"
  111. end
  112. end
  113. -- Buttons.
  114. formspec = formspec ..
  115. "button[0,5.3;2,1;backinv;Back]"
  116. local add_setpoint_tip = function(formspec, name)
  117. local text = "Both you and your potential customers must have a trading booth\n" ..
  118. "registered as their remote delivery address in order for remote trading to work.\n" ..
  119. "This is also required if you are a buyer looking to purchase items remotely.\n" ..
  120. "\n"
  121. local dp = depositor.get_drop_location(pname)
  122. if dp then
  123. text = text .. "Your currently registered delivery address is " .. rc.pos_to_namestr(dp) .. ".\n"
  124. if vector.equals(dp, pos) then
  125. text = text .. "This is located at this market booth here."
  126. else
  127. text = text .. "This is located elsewhere than the current market booth."
  128. end
  129. else
  130. text = text .. "You currently have no remote delivery address set!"
  131. end
  132. formspec = formspec ..
  133. "tooltip[" .. name .. ";" .. minetest.formspec_escape(text) .. "]"
  134. return formspec
  135. end
  136. local p2 = depositor.get_drop_location(pname)
  137. if p2 and vector.equals(pos, p2) then
  138. formspec = formspec ..
  139. "button[2,5.3;3,1;unsetpoint;Revoke Delivery Point]"
  140. formspec = add_setpoint_tip(formspec, "unsetpoint")
  141. else
  142. formspec = formspec ..
  143. "button[2,5.3;3,1;setpoint;Mark Delivery Point]"
  144. formspec = add_setpoint_tip(formspec, "setpoint")
  145. end
  146. local b = "|"
  147. if booth then
  148. b = "|booth"
  149. end
  150. local key = "ads:inventory_" .. minetest.pos_to_string(vector_round(pos)) .. b
  151. minetest.show_formspec(pname, key, formspec)
  152. end
  153. function ads.on_receive_submission_fields(player, formname, fields)
  154. if string.sub(formname, 1, 15) ~= "ads:submission_" then
  155. return
  156. end
  157. local pos = minetest.string_to_pos(string.sub(formname, 16, string.find(formname, "|")-1))
  158. if not pos then
  159. --minetest.chat_send_all("Invalid!")
  160. return true
  161. end
  162. local pname = player:get_player_name()
  163. -- Determine if we were called from a market booth.
  164. local booth = false
  165. if string.find(formname, "|booth") then
  166. booth = true
  167. end
  168. if not booth then
  169. minetest.chat_send_player(pname, "# Server: This action can only be completed at a market kiosk.")
  170. easyvend.sound_error(pname)
  171. return true
  172. end
  173. -- Ensure player data exists.
  174. if not ads.players[pname] then
  175. minetest.chat_send_player(pname, "# Server: Error: userdata not initialized.")
  176. easyvend.sound_error(pname)
  177. return true
  178. end
  179. -- Check booth owner.
  180. local node = minetest.get_node(pos)
  181. local meta = minetest.get_meta(pos)
  182. if node.name == "market:booth" and (meta:get_string("owner") == pname or minetest.check_player_privs(pname, "server")) then
  183. -- Everything good.
  184. else
  185. minetest.chat_send_player(pname, "# Server: You don't have permission to do that.")
  186. easyvend.sound_error(pname)
  187. return true
  188. end
  189. if fields.submit or fields.confirmedit then
  190. local inv
  191. if fields.submit then
  192. inv = player:get_inventory()
  193. local gotgold = currency.has_cash_amount(inv, "main", ads.ad_cost)
  194. if not gotgold then
  195. minetest.chat_send_player(pname, "# Server: You must be able to pay " .. ads.ad_cost .. " minegeld to register a public notice!")
  196. easyvend.sound_error(pname)
  197. goto error
  198. end
  199. elseif fields.confirmedit then
  200. local str = ads.players[pname].editname
  201. if not str or str == "" then
  202. minetest.chat_send_player(pname, "# Server: The name of the advertisement to be edited was not specified.")
  203. easyvend.sound_error(pname)
  204. goto error
  205. end
  206. end
  207. if not passport.player_registered(pname) then
  208. minetest.chat_send_player(pname, "# Server: You must be a Citizen of the Colony before you can purchase or edit a public notice!")
  209. easyvend.sound_error(pname)
  210. goto error
  211. end
  212. if not fields.title or fields.title == "" or string.len(fields.title) > ads.titlelen then
  213. minetest.chat_send_player(pname, "# Server: You must write a name for your shop (not more than " .. ads.titlelen .. " characters).")
  214. easyvend.sound_error(pname)
  215. goto error
  216. end
  217. if not fields.text or fields.text == "" or string.len(fields.text) > ads.bodylen then
  218. minetest.chat_send_player(pname, "# Server: You must write the body of your advertisement (not more than " .. ads.bodylen .. " characters).")
  219. easyvend.sound_error(pname)
  220. goto error
  221. end
  222. -- Make sure a shop with this title doesn't already exist.
  223. if fields.submit then
  224. for k, v in ipairs(ads.data) do
  225. if v.shop == fields.title then
  226. minetest.chat_send_player(pname, "# Server: A public notice with that title already exists! Your notice title must be unique.")
  227. easyvend.sound_error(pname)
  228. goto error
  229. end
  230. end
  231. elseif fields.confirmedit then
  232. local original_name = ads.players[pname].editname or ""
  233. for k, v in ipairs(ads.data) do
  234. if v.shop ~= original_name then
  235. if v.shop == fields.title then
  236. minetest.chat_send_player(pname, "# Server: A public notice with that title already exists! Your notice title must be unique.")
  237. easyvend.sound_error(pname)
  238. goto error
  239. end
  240. end
  241. end
  242. end
  243. if anticurse.check(pname, fields.text, "foul") then
  244. minetest.chat_send_player(pname, "# Server: Don't include foul language, please!")
  245. easyvend.sound_error(pname)
  246. anticurse.log(pname, fields.text)
  247. goto error
  248. elseif anticurse.check(pname, fields.text, "curse") then
  249. minetest.chat_send_player(pname, "# Server: Don't include foul language, please!")
  250. easyvend.sound_error(pname)
  251. anticurse.log(pname, fields.text)
  252. goto error
  253. end
  254. if anticurse.check(pname, fields.title, "foul") then
  255. minetest.chat_send_player(pname, "# Server: Don't include foul language, please!")
  256. easyvend.sound_error(pname)
  257. anticurse.log(pname, fields.title)
  258. goto error
  259. elseif anticurse.check(pname, fields.title, "curse") then
  260. minetest.chat_send_player(pname, "# Server: Don't include foul language, please!")
  261. easyvend.sound_error(pname)
  262. anticurse.log(pname, fields.title)
  263. goto error
  264. end
  265. ambiance.sound_play("easyvend_activate", player:get_pos(), 0.5, 10)
  266. if fields.submit then
  267. currency.remove_cash(inv, "main", ads.ad_cost)
  268. local real_owner = pname
  269. -- This allows admins to create records owned by other players,
  270. -- based on who owns the land.
  271. if gdac.player_is_admin(pname) then
  272. local land_owner = protector.get_node_owner(pos) or ""
  273. if land_owner ~= "" then
  274. real_owner = land_owner
  275. end
  276. end
  277. ads.add_entry({
  278. shop = fields.title or "No Title Set",
  279. pos = pos, -- Records the position at which the advertisement was submitted.
  280. owner = real_owner,
  281. custom = fields.text or "No Text Submitted",
  282. date = os.time(),
  283. })
  284. elseif fields.confirmedit then
  285. for k, v in ipairs(ads.data) do
  286. if v.shop == ads.players[pname].editname then
  287. if v.owner == pname or minetest.check_player_privs(pname, "server") then
  288. v.shop = fields.title or "No Title Set"
  289. v.custom = fields.text or "No Text Submitted"
  290. ads.dirty = true
  291. ads.players = {} -- Force refetching data for all players.
  292. break
  293. else
  294. minetest.chat_send_player("# Server: Public notice was submitted by someone else! You do not have permission to edit it.")
  295. easyvend.sound_error(pname)
  296. goto error
  297. end
  298. end
  299. end
  300. end
  301. if fields.submit then
  302. minetest.chat_send_player(pname, "# Server: Notice submitted.")
  303. elseif fields.confirmedit then
  304. minetest.chat_send_player(pname, "# Server: Notice updated.")
  305. end
  306. ads.show_formspec(pos, pname, booth)
  307. do return true end
  308. ::error::
  309. end
  310. if fields.cancel or fields.quit then
  311. if booth then
  312. ads.show_formspec(pos, pname, true)
  313. else
  314. minetest.close_formspec(pname, formname)
  315. end
  316. end
  317. return true
  318. end
  319. local function in_market_range(mark, vend)
  320. local r = ads.marketrange
  321. if vend.x >= (mark.x - r) and vend.x <= (mark.x + r)
  322. and vend.y >= (mark.y - r) and vend.y <= (mark.y + r)
  323. and vend.z >= (mark.z - r) and vend.z <= (mark.z + r) then
  324. return true
  325. end
  326. return false
  327. end
  328. local function match_search(search, itemstr)
  329. if not search or search == "" then
  330. return true
  331. end
  332. local idef = minetest.registered_items[itemstr]
  333. if not idef then
  334. return false
  335. end
  336. local shortdesc = utility.get_short_desc(idef.description)
  337. shortdesc = shortdesc:lower()
  338. local tokens = search:split(" ")
  339. if not tokens or #tokens == 0 then
  340. return false
  341. end
  342. local count = 0
  343. for i = 1, #tokens do
  344. local tok = tokens[i]
  345. if tok ~= "" then
  346. tok = tok:lower()
  347. if itemstr:find(tok, 1, true) or shortdesc:find(tok, 1, true) then
  348. count = count + 1
  349. end
  350. end
  351. end
  352. -- All search tokens found for this item.
  353. if count == #tokens then
  354. return true
  355. end
  356. return false
  357. end
  358. function ads.get_valid_shops(ad_pos, owner, srchtxt)
  359. local db = {}
  360. for k, v in ipairs(depositor.shops) do
  361. if v.active and v.owner == owner and in_market_range(ad_pos, v.pos) and
  362. rc.same_realm(ad_pos, v.pos)
  363. then
  364. if (v.type == 1 or v.type == 2) and
  365. v.item ~= "none" and
  366. v.item ~= "" and
  367. v.item ~= "ignore"
  368. then
  369. if match_search(srchtxt, v.item) then
  370. table.insert(db, {
  371. owner = v.owner,
  372. item = v.item,
  373. number = v.number,
  374. cost = v.cost,
  375. currency = v.currency,
  376. type = v.type,
  377. pos = {x=v.pos.x, y=v.pos.y, z=v.pos.z},
  378. })
  379. end
  380. end
  381. end
  382. end
  383. return db
  384. end
  385. function ads.get_valid_ads(pos, srchtxt)
  386. pos = vector_round(pos)
  387. local temp = {}
  388. for i = 1, #(ads.data), 1 do
  389. local ad = ads.data[i]
  390. local str = ""
  391. local stack = ItemStack(ad.item)
  392. local def = minetest.registered_items[stack:get_name()]
  393. -- Skip ads with missing data.
  394. if not ad.shop or not ad.date then
  395. goto continue
  396. end
  397. -- Don't show ads for unknown items or items without descriptions.
  398. if not def or not def.description then
  399. goto continue
  400. end
  401. -- Don't show ads for far shops.
  402. -- That is, don't show ads that were submitted far from the current location.
  403. if vector_distance(pos, ad.pos) > ads.viewrange then
  404. goto continue
  405. end
  406. -- Ignore ads submitted in a different realm.
  407. if not rc.same_realm(pos, ad.pos) then
  408. goto continue
  409. end
  410. -- Skip adds without matching shops, if we're doing an item search.
  411. if srchtxt and srchtxt ~= "" then
  412. local shops = ads.get_valid_shops(ad.pos, ad.owner, srchtxt)
  413. if #shops == 0 then
  414. goto continue
  415. end
  416. end
  417. temp[#temp+1] = table.copy(ad)
  418. ::continue::
  419. end
  420. return temp
  421. end
  422. -- Constructs the main formspec, for viewing ads and shop listings.
  423. function ads.generate_formspec(pos, pname, booth)
  424. -- Set up player's view of the data.
  425. if not ads.players[pname] then
  426. ads.players[pname] = {}
  427. end
  428. local data = ads.players[pname]
  429. data.shops = data.shops or {}
  430. data.selected = data.selected or 0
  431. data.shopselect = data.shopselect or 0
  432. -- Caching the AD search is needed because depending on parameters/environment,
  433. -- this algorith is potentially expensive.
  434. if not data.ads or
  435. (data.srchtxt or "") ~= (data.cache_srchtxt or "") or
  436. (data.cache_time or 0) < os.time() then
  437. data.ads = ads.get_valid_ads(pos, data.srchtxt) or {}
  438. data.cache_srchtxt = data.srchtxt
  439. data.cache_time = os.time() + 60*5
  440. end
  441. if data.selected ~= 0 and data.selected > #data.ads then
  442. data.selected = #data.ads
  443. end
  444. if data.shopselect ~= 0 and data.shopselect > #data.shops then
  445. data.shopselect = #data.shops
  446. end
  447. local meta = minetest.get_meta(pos)
  448. local booth_owner = meta:get_string("owner")
  449. -- Count of how many ads player owns in this list.
  450. local ownadcount = 0
  451. local fs_size_x = 15.2
  452. local fs_size_y = 8.2
  453. -- If the formspec is viewed from an OWNED market booth, we need an extra row for more buttons.
  454. if booth and (booth_owner == pname or minetest.check_player_privs(pname, "server")) then
  455. fs_size_y = 9.1
  456. end
  457. local tax_notice = ""
  458. if ads.tax > 0 then
  459. tax_notice = " NOTICE: A " .. ads.tax .. "% tax is applied to all remote transactions."
  460. end
  461. local esc = minetest.formspec_escape
  462. local formspec =
  463. "size[" .. fs_size_x .. "," .. fs_size_y .. "]" ..
  464. default.gui_bg ..
  465. default.gui_bg_img ..
  466. default.gui_slots ..
  467. "label[0,0;" .. minetest.formspec_escape("View nearby shops & trading opportunities!" .. tax_notice) .. "]"
  468. if booth then
  469. formspec = formspec ..
  470. "label[0,0.4;" .. minetest.formspec_escape("You are viewing public notices that were posted within " .. ads.viewrange .. " meters of this kiosk.") .. "]"
  471. else
  472. formspec = formspec ..
  473. "label[0,0.4;" .. minetest.formspec_escape("You are viewing public notices within " .. ads.viewrange .. " meters of your position.") .. "]"
  474. end
  475. formspec = formspec ..
  476. "item_image[14.2,0;1,1;easyvend:depositor_on]" ..
  477. "textlist[0,1;5,5.8;adlist;"
  478. for i = 1, #(data.ads), 1 do
  479. local ad = data.ads[i]
  480. local str = ""
  481. if ad.owner == pname then
  482. ownadcount = ownadcount + 1
  483. end
  484. -- Display shop title in list.
  485. str = str .. ad.shop
  486. str = minetest.formspec_escape(str)
  487. formspec = formspec .. str
  488. -- Append comma.
  489. if i < #(data.ads) then
  490. formspec = formspec .. ","
  491. end
  492. ::continue::
  493. end
  494. local strad = "entries"
  495. if ownadcount == 1 then
  496. strad = "entry"
  497. end
  498. formspec = formspec .. ";" .. data.selected .. ";false]" ..
  499. "label[0,7;You bought " .. ownadcount .. " " .. strad .. " in this list.]"
  500. local addesc = "See your public notice here!"
  501. local shoplist = ""
  502. if data.selected and data.selected >= 1 and data.selected <= #(data.ads) then
  503. if data.ads[data.selected] then
  504. local ad = data.ads[data.selected]
  505. local owner_text = "<" .. rename.gpn(ad.owner) .. "> owns this listing."
  506. if gdac.player_is_admin(ad.owner) then
  507. owner_text = "Record of Public Notice."
  508. end
  509. formspec = formspec ..
  510. "label[5.35,5.0;" .. esc(owner_text) .. "]" ..
  511. "label[5.35,5.3;" .. esc("Submitted on " .. os.date("!%Y/%m/%d", ad.date) .. ".") .. "]" ..
  512. "label[5.35,5.6;" .. esc("From " .. rc.pos_to_namestr(ad.pos) .. ".") .. "]" ..
  513. "label[5.35,5.9;" .. esc("Distance " .. math_floor(vector_distance(ad.pos, pos)) .. " meters.") .. "]"
  514. if ad.custom then
  515. addesc = ad.shop .. "\n\n" .. ad.custom
  516. end
  517. -- List nearby shops belonging to the selected ad.
  518. local shops = ads.get_valid_shops(ad.pos, ad.owner, data.srchtxt)
  519. ads.players[pname].shops = shops
  520. for k, v in ipairs(shops) do
  521. local str = ""
  522. if v.type == 1 then
  523. str = str .. "Selling"
  524. elseif v.type == 2 then
  525. str = str .. "Buying"
  526. else
  527. str = str .. "Unknown"
  528. end
  529. str = str .. ": "
  530. local cost = currency.get_stack_value(v.currency, v.cost)
  531. cost = currency.calculate_tax(cost, v.type, ads.tax)
  532. local def = minetest.registered_items[v.item]
  533. if def then
  534. str = str .. v.number .. "x " .. utility.get_short_desc(def.description)
  535. str = str .. " For " .. cost .. " Minegeld"
  536. str = minetest.formspec_escape(str)
  537. shoplist = shoplist .. str
  538. -- Append comma.
  539. if k < #shops then
  540. shoplist = shoplist .. ","
  541. end
  542. end
  543. end -- end for
  544. end
  545. end
  546. formspec = formspec .. "textlist[10,1;5,5.8;shoplist;" .. shoplist
  547. formspec = formspec .. ";" .. (data.shopselect or 0) .. ";false]"
  548. addesc = minetest.formspec_escape(addesc)
  549. formspec = formspec ..
  550. "textarea[5.6,0.97;4.7,4.6;warning;;" .. addesc .. "]"
  551. formspec = formspec ..
  552. "field[5.6,6.81;2.7,1;srchtxt;;" .. esc(data.srchtxt or "") .. "]" ..
  553. "button[8.0,6.5;1,1;dosearch;" .. esc("?") .. "]" ..
  554. "button[9.0,6.5;1,1;clearsearch;X]" ..
  555. "field_close_on_enter[srchtxt;false]"
  556. if booth then
  557. -- Show inventory/purchase button only if player has permissions on this booth.
  558. if booth_owner == pname or minetest.check_player_privs(pname, "server") then
  559. -- List-shop button w/ vendor image.
  560. formspec = formspec ..
  561. "button[0,7.5;4,1;newadd;List Your Shop]" ..
  562. "tooltip[newadd;" .. minetest.formspec_escape(
  563. "Listing your shop makes it visible to other market kiosks within 5 kilometers of this one.\n" ..
  564. "This also allows citizens to trade using your vending/depositing machines remotely.\n" ..
  565. "\n" ..
  566. "Make sure you create the advertisement from the actual location of your shop!\n" ..
  567. "The market kiosk only links with vending/depositing machines within 15 meters.") .. "]" ..
  568. "item_image[4,7.5;1,1;easyvend:vendor_on]"
  569. formspec = formspec ..
  570. "button[11.2,7.5;2,1;storage;Inventory]" ..
  571. "tooltip[storage;" .. minetest.formspec_escape(
  572. "The inventory is used when trading remotely, while it is registered as your delivery address.\n" ..
  573. "You can trade remotely from any market kiosk that you own. But only one kiosk can be your delivery point.\n" ..
  574. "\n" ..
  575. "When purchasing an item remotely, your market kiosk's inventory must have currency to pay the cost.\n" ..
  576. "The purchased item will be sent to your market kiosk and cash will be removed from the same location.\n" ..
  577. "\n" ..
  578. "If you are depositing an item, your market kiosk must have the item in its inventory ready for transport.\n" ..
  579. "Cash for the deposit will be sent to the same location.") .. "]"
  580. local shops = ads.players[pname].shops
  581. local sel = (data.shopselect or 0)
  582. if shops and sel ~= 0 and shops[sel] then
  583. local text = ""
  584. local idef = minetest.registered_items[shops[sel].item]
  585. local curt = shops[sel].currency
  586. local cost = shops[sel].cost or 0
  587. local realcost = currency.get_stack_value(curt, cost)
  588. realcost = currency.calculate_tax(realcost, shops[sel].type, ads.tax)
  589. if idef and shops[sel].owner ~= pname then
  590. if shops[sel].type == 1 then
  591. text = "Purchase (" .. shops[sel].number .. "x " .. utility.get_short_desc(idef.description) .. " For " .. realcost .. " MG)"
  592. elseif shops[sel].type == 2 then
  593. text = "Deposit (" .. shops[sel].number .. "x " .. utility.get_short_desc(idef.description) .. " For " .. realcost .. " MG)"
  594. end
  595. if text ~= "" then
  596. formspec = formspec ..
  597. "button[5,7.5;6.2,1;dotrade;" .. minetest.formspec_escape(text) .. "]" ..
  598. "tooltip[dotrade;" .. minetest.formspec_escape(
  599. "This will try to execute a purchase or a deposit, depending on the type of shop selected.\n" ..
  600. "The transaction will be aborted if anything goes wrong.") .. "]"
  601. formspec = formspec ..
  602. "button[9,8.4;2.2,1;domark;" .. minetest.formspec_escape("Mark Location") .. "]"
  603. end
  604. end
  605. end
  606. -- Edit/remove add buttons.
  607. formspec = formspec ..
  608. "button[0,8.4;2,1;editrecord;" .. minetest.formspec_escape("Edit Record") .. "]" ..
  609. "button[2,8.4;4,1;deleterecord;" .. minetest.formspec_escape("Delete Record (With Refund)") .. "]"
  610. end
  611. end
  612. if booth and (booth_owner == pname or minetest.check_player_privs(pname, "server")) then
  613. -- All good.
  614. else
  615. if booth then
  616. formspec = formspec .. "label[0,7.5;" .. esc("You cannot use this market kiosk for remote trading, because you do not own it.") .. "]"
  617. else
  618. formspec = formspec .. "label[0,7.5;" .. esc("You cannot use this interface for remote trading, because it is detached.") .. "]"
  619. end
  620. end
  621. formspec = formspec ..
  622. "button[13.2,7.5;2,1;done;Done]"
  623. return formspec
  624. end
  625. function ads.on_receive_fields(player, formname, fields)
  626. if string.sub(formname, 1, 9) ~= "ads:main_" then
  627. return
  628. end
  629. local pos = minetest.string_to_pos(string.sub(formname, 10, string.find(formname, "|")-1))
  630. if not pos then
  631. --minetest.chat_send_all("Invalid!")
  632. return true
  633. end
  634. local pname = player:get_player_name()
  635. if not ads.players[pname] then
  636. return true
  637. end
  638. if fields.quit then
  639. return true
  640. end
  641. if fields.done then
  642. minetest.close_formspec(pname, formname)
  643. return true
  644. end
  645. local event = minetest.explode_textlist_event(fields["adlist"])
  646. if event.type == "CHG" then
  647. local index = event.index
  648. local max = #(ads.players[pname].ads)
  649. if index > max then index = max end
  650. -- Reset selected shop whenever selected ad changes.
  651. if index ~= ads.players[pname].selected then
  652. ads.players[pname].shopselect = 0
  653. end
  654. ads.players[pname].selected = index
  655. --minetest.chat_send_all("" .. index)
  656. end
  657. local event = minetest.explode_textlist_event(fields["shoplist"])
  658. if event.type == "CHG" then
  659. if ads.players[pname].shops then
  660. local index = event.index
  661. local max = #(ads.players[pname].shops)
  662. if index > max then index = max end
  663. ads.players[pname].shopselect = index
  664. --minetest.chat_send_all("" .. index)
  665. end
  666. end
  667. local booth = false
  668. if string.find(formname, "|booth") then
  669. booth = true
  670. end
  671. if fields.key_enter_field == "srchtxt" or fields.dosearch or fields.clearsearch then
  672. local srchtxt = fields.srchtxt or ""
  673. if fields.dosearch or fields.key_enter_field == "srchtxt" then
  674. if srchtxt ~= "" then
  675. ads.players[pname].srchtxt = srchtxt
  676. else
  677. ads.players[pname].srchtxt = nil
  678. end
  679. elseif fields.clearsearch then
  680. ads.players[pname].srchtxt = nil
  681. end
  682. end
  683. if fields.storage or fields.dotrade or fields.domark or fields.editrecord or fields.deleterecord or fields.newadd then
  684. if booth then
  685. local meta = minetest.get_meta(pos)
  686. if meta:get_string("owner") == pname or minetest.check_player_privs(pname, "server") then
  687. if fields.storage then
  688. ads.show_inventory_formspec(pos, pname, booth)
  689. return true
  690. elseif fields.dotrade then
  691. local sel = ads.players[pname].shopselect or 0
  692. local shops = ads.players[pname].shops
  693. if shops and sel ~= 0 and shops[sel] then
  694. local item = shops[sel].item
  695. local cost = shops[sel].cost
  696. local type = shops[sel].type
  697. local number = shops[sel].number
  698. local currency = shops[sel].currency
  699. local owner = shops[sel].owner or ""
  700. local putsite = depositor.get_drop_location(pname)
  701. local dropsite = depositor.get_drop_location(owner)
  702. local vpos = shops[sel].pos
  703. local valid_trade = true
  704. if not rc.same_realm(pos, vpos) then
  705. valid_trade = false
  706. end
  707. if vector.distance(pos, vpos) > (ads.viewrange + ads.marketrange) then
  708. valid_trade = false
  709. end
  710. if valid_trade then
  711. if putsite then
  712. if dropsite then
  713. local msg = depositor.execute_trade(vpos, pname, owner, putsite, dropsite, item, number, cost, ads.tax, currency, type)
  714. if msg then
  715. minetest.chat_send_player(pname, "# Server: " .. msg)
  716. end
  717. else
  718. minetest.chat_send_player(pname, "# Server: Cannot execute trade. <" .. rename.gpn(owner) .. "> has not registered an address for remote trading.")
  719. easyvend.sound_error(pname)
  720. end
  721. else
  722. minetest.chat_send_player(pname, "# Server: Cannot execute trade. You have not registered an address for remote trading.")
  723. easyvend.sound_error(pname)
  724. end
  725. else
  726. minetest.chat_send_player(pname, "# Server: 0xBAADF00D")
  727. end
  728. end
  729. elseif fields.domark then
  730. local sel = ads.players[pname].shopselect or 0
  731. local shops = ads.players[pname].shops
  732. if shops and sel ~= 0 and shops[sel] then
  733. local vpos = shops[sel].pos
  734. local lname = pname .. "'s Vend Locs"
  735. if marker.list_size(pname, lname) < marker.max_waypoints then
  736. marker.add_waypoint(pname, vpos, lname)
  737. marker.update_single_hud(pname)
  738. minetest.chat_send_player(pname, "# Server: Location added to marker list.")
  739. end
  740. end
  741. return true
  742. elseif fields.editrecord then
  743. local sel = ads.players[pname].selected or 0
  744. if sel ~= 0 then
  745. local data = ads.players[pname].ads or {}
  746. if sel >= 1 and sel <= #data then
  747. if data[sel].owner == pname or minetest.check_player_privs(pname, "server") then
  748. ads.players[pname].shopselect = 0
  749. ads.players[pname].editname = data[sel].shop -- Record name of the advertisement the player will be editing.
  750. local edit_info = {
  751. title_text = data[sel].shop,
  752. body_text = data[sel].custom,
  753. }
  754. ads.show_submission_formspec(pos, pname, booth, edit_info)
  755. return true
  756. else
  757. -- Player doesn't have privs to edit this record.
  758. minetest.chat_send_player(pname, "# Server: The selected notice does not belong to you.")
  759. easyvend.sound_error(pname)
  760. end
  761. else
  762. -- Selection index out of range.
  763. minetest.chat_send_player(pname, "# Server: You must select one of your own public notices, first.")
  764. easyvend.sound_error(pname)
  765. end
  766. else
  767. -- Nothing selected.
  768. minetest.chat_send_player(pname, "# Server: You must select one of your own public notices, first.")
  769. easyvend.sound_error(pname)
  770. end
  771. elseif fields.deleterecord then
  772. local sel = ads.players[pname].selected or 0
  773. if sel ~= 0 then
  774. local data = ads.players[pname].ads or {}
  775. if sel >= 1 and sel <= #data then
  776. local owner = data[sel].owner
  777. local title = data[sel].shop
  778. if owner == pname or minetest.check_player_privs(pname, "server") then
  779. local player_inv = player:get_inventory()
  780. if currency.room_for_cash(player_inv, "main", ads.ad_cost) then
  781. ads.players[pname].shopselect = 0 -- Unselect any vendor/depositor listing.
  782. ads.players[pname].selected = 0 -- Reset ad selection to nil to prevent double-deletions.
  783. --minetest.chat_send_player(pname, "# Server: Would delete advertisement titled: \"" .. title .. "\"!")
  784. -- Search for record by owner/title and delete it.
  785. local found = false
  786. local ad_real_owner = ""
  787. for k, v in ipairs(ads.data) do
  788. if v.shop == title and v.owner == owner then
  789. ad_real_owner = v.owner
  790. table.remove(ads.data, k)
  791. found = true
  792. ads.dirty = true
  793. break
  794. end
  795. end
  796. if found then
  797. minetest.chat_send_player(pname, "# Server: Advertisement titled: \"" .. title .. "\", owned by <" .. rename.gpn(ad_real_owner) .. "> was removed.")
  798. currency.add_cash(player_inv, "main", ads.ad_cost)
  799. else
  800. minetest.chat_send_player(pname, "# Server: Could not locate record for deletion!")
  801. easyvend.sound_error(pname)
  802. end
  803. else
  804. -- Player doesn't have room in their inventory for the cash.
  805. minetest.chat_send_player(pname, "# Server: You must have room in your inventory to receive the cash refund of " .. ads.ad_cost .. " mg.")
  806. easyvend.sound_error(pname)
  807. end
  808. else
  809. -- Player doesn't have privs to delete this record.
  810. minetest.chat_send_player(pname, "# Server: The selected public notice does not belong to you.")
  811. easyvend.sound_error(pname)
  812. end
  813. else
  814. -- Selection index out of range.
  815. minetest.chat_send_player(pname, "# Server: You must select one of your own public notices, first.")
  816. easyvend.sound_error(pname)
  817. end
  818. else
  819. -- Nothing selected.
  820. minetest.chat_send_player(pname, "# Server: You must select one of your own public notices, first.")
  821. easyvend.sound_error(pname)
  822. end
  823. elseif fields.newadd then
  824. ads.show_submission_formspec(pos, pname, booth)
  825. return true
  826. end
  827. else
  828. -- Player sent button click on a market booth they don't own.
  829. minetest.chat_send_player(pname, "# Server: You do not have permission to do that.")
  830. easyvend.sound_error(pname)
  831. end
  832. else
  833. -- Player sent fields requiring a market booth, but this is a "detached" formspec.
  834. minetest.chat_send_player(pname, "# Server: This action can only be completed at a market kiosk.")
  835. easyvend.sound_error(pname)
  836. end
  837. end
  838. ads.show_formspec(pos, pname, booth)
  839. return true
  840. end
  841. function ads.show_formspec(pos, pname, booth)
  842. local formspec = ads.generate_formspec(pos, pname, booth)
  843. local b = "|"
  844. if booth then
  845. b = "|booth"
  846. end
  847. local key = "ads:main_" .. minetest.pos_to_string(vector_round(pos)) .. b
  848. minetest.show_formspec(pname, key, formspec)
  849. end
  850. function ads.add_entry(data)
  851. ads.data[#(ads.data)+1] = {
  852. shop = data.shop, -- Brief Name of Shop
  853. pos = data.pos, -- {x,y,z}
  854. owner = data.owner, -- playername
  855. custom = data.custom, -- Custom text. Here are directions to my shop.
  856. date = data.date, -- timestamp of submission (integer)
  857. }
  858. ads.dirty = true
  859. ads.players = {}
  860. end
  861. function ads.load_data()
  862. ads.data = {}
  863. local file = io.open(ads.datapath, "r")
  864. if file then
  865. local data = file:read("*all")
  866. local db = minetest.deserialize(data)
  867. file:close()
  868. if type(db) == "table" then
  869. table.shuffle(db)
  870. ads.data = db
  871. ads.dirty = false
  872. ads.players = {}
  873. end
  874. end
  875. end
  876. function ads.save_data()
  877. if ads.dirty then
  878. local str = minetest.serialize(ads.data)
  879. if type(str) ~= "string" then return end -- Failsafe.
  880. local file = io.open(ads.datapath, "w")
  881. if file then
  882. file:write(str)
  883. file:close()
  884. end
  885. end
  886. ads.dirty = false
  887. end
  888. function ads._on_update_infotext(pos)
  889. local meta = minetest.get_meta(pos)
  890. local pname = meta:get_string("owner")
  891. meta:set_string("infotext", "Market Trade Booth\nOwned by <" .. rename.gpn(pname) .. ">!")
  892. end
  893. function ads.after_place_node(pos, placer)
  894. local pname = placer:get_player_name()
  895. -- If placed by admin, use landowner as real owner.
  896. if gdac.player_is_admin(pname) then
  897. local landowner = protector.get_node_owner(pos) or ""
  898. if landowner ~= "" then
  899. pname = landowner
  900. end
  901. end
  902. local meta = minetest.get_meta(pos)
  903. meta:set_string("owner", pname)
  904. meta:set_string("infotext", "Market Trade Booth\nOwned by <" .. rename.gpn(pname) .. ">!")
  905. local inv = meta:get_inventory()
  906. inv:set_size("storage", (5*16) + (7*6))
  907. depositor.update_info(pos, pname, "none", 0, 0, "none", "info")
  908. end
  909. local function has_inventory_privilege(meta, player)
  910. if minetest.check_player_privs(player, "server") then
  911. return true
  912. end
  913. if player:get_player_name() == meta:get_string("owner") then
  914. return true
  915. end
  916. return false
  917. end
  918. function ads.allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
  919. local meta = minetest.get_meta(pos)
  920. if from_list ~= to_list then
  921. return 0
  922. end
  923. if not has_inventory_privilege(meta, player) then
  924. return 0
  925. end
  926. -- Disallow storing items in the overflow pile.
  927. if ads.is_open_index(from_index) and not ads.is_open_index(to_index) then
  928. return 0
  929. end
  930. return count
  931. end
  932. function ads.allow_metadata_inventory_put(pos, listname, index, stack, player)
  933. local meta = minetest.get_meta(pos)
  934. if listname ~= "storage" then
  935. return 0
  936. end
  937. if not has_inventory_privilege(meta, player) then
  938. return 0
  939. end
  940. -- Disallow storing items in the overflow pile.
  941. if not ads.is_open_index(index) then
  942. return 0
  943. end
  944. return stack:get_count()
  945. end
  946. function ads.allow_metadata_inventory_take(pos, listname, index, stack, player)
  947. local meta = minetest.get_meta(pos)
  948. if listname ~= "storage" then
  949. return 0
  950. end
  951. if not has_inventory_privilege(meta, player) then
  952. return 0
  953. end
  954. return stack:get_count()
  955. end
  956. function ads.on_receive_inventory_fields(player, formname, fields)
  957. if string.sub(formname, 1, 14) ~= "ads:inventory_" then
  958. return
  959. end
  960. local pos = minetest.string_to_pos(string.sub(formname, 15, string.find(formname, "|")-1))
  961. if not pos then
  962. return true
  963. end
  964. local pname = player:get_player_name()
  965. if not ads.players[pname] then
  966. return true
  967. end
  968. if fields.done or fields.quit then
  969. minetest.close_formspec(pname, formname)
  970. return true
  971. end
  972. local booth = false
  973. if string.find(formname, "|booth") then
  974. booth = true
  975. end
  976. if booth and fields.backinv then
  977. ads.show_formspec(pos, pname, booth)
  978. return true
  979. end
  980. if booth and fields.setpoint then
  981. local node = minetest.get_node(pos)
  982. if node.name == "market:booth" then
  983. local meta = minetest.get_meta(pos)
  984. -- Owner or admin may use.
  985. if meta:get_string("owner") == pname or minetest.check_player_privs(player, "server") then
  986. depositor.set_drop_location(pos, pname)
  987. local p2 = depositor.get_drop_location(pname)
  988. if p2 then
  989. minetest.chat_send_player(pname, "# Server: Goods will be delivered to drop-point at " .. rc.pos_to_namestr(p2) .. "! Payments are also retrieved at this location.")
  990. ads.show_inventory_formspec(pos, pname, booth)
  991. else
  992. minetest.chat_send_player(pname, "# Server: Error, could not set delivery drop-point.")
  993. easyvend.sound_error(pname)
  994. end
  995. else
  996. minetest.chat_send_player(pname, "# Server: Cannot set delivery drop-point, you do not own this kiosk.")
  997. easyvend.sound_error(pname)
  998. end
  999. else
  1000. minetest.chat_send_player(pname, "# Server: Error: 0xDEADBEEF 5392 (please report).")
  1001. easyvend.sound_error(pname)
  1002. end
  1003. return true
  1004. end
  1005. if booth and fields.unsetpoint then
  1006. local p2 = depositor.get_drop_location(pname)
  1007. if p2 then
  1008. minetest.chat_send_player(pname, "# Server: Delivery point at " .. rc.pos_to_namestr(p2) .. " revoked by explicit request.")
  1009. end
  1010. depositor.unset_drop_location(pname)
  1011. ads.show_inventory_formspec(pos, pname, booth)
  1012. return true
  1013. end
  1014. return true
  1015. end
  1016. function ads.can_dig(pos, player)
  1017. local meta = minetest.get_meta(pos);
  1018. local inv = meta:get_inventory()
  1019. return inv:is_empty("storage")
  1020. end
  1021. function ads.on_blast(pos)
  1022. local def = minetest.reg_ns_nodes[minetest.get_node(pos).name]
  1023. local drops = {}
  1024. default.get_inventory_drops(pos, "storage", drops)
  1025. drops[#drops+1] = "market:booth"
  1026. minetest.remove_node(pos)
  1027. return drops
  1028. end
  1029. if not ads.run_once then
  1030. ads.load_data()
  1031. minetest.register_on_shutdown(function() ads.save_data() end)
  1032. minetest.register_on_mapsave(function() ads.save_data() end)
  1033. minetest.register_on_player_receive_fields(function(...)
  1034. return ads.on_receive_fields(...)
  1035. end)
  1036. minetest.register_on_player_receive_fields(function(...)
  1037. return ads.on_receive_submission_fields(...)
  1038. end)
  1039. minetest.register_on_player_receive_fields(function(...)
  1040. return ads.on_receive_inventory_fields(...)
  1041. end)
  1042. local c = "ads:core"
  1043. local f = ads.modpath .. "/ads.lua"
  1044. reload.register_file(c, f, false)
  1045. minetest.register_node(":market:booth", {
  1046. description = "Networked Trade Kiosk\n\nA market kiosk enables remote trading.\nWill link with nearby vending/depositing machines.\nCan also display public notices.",
  1047. tiles = {
  1048. "easyvend_vendor_side.png",
  1049. "easyvend_vendor_side.png",
  1050. "easyvend_ad_booth.png",
  1051. "easyvend_ad_booth.png",
  1052. "easyvend_ad_booth.png",
  1053. "easyvend_ad_booth.png",
  1054. },
  1055. paramtype2 = "facedir",
  1056. groups = utility.dig_groups("furniture", {flammable = 2}),
  1057. sounds = default.node_sound_wood_defaults(),
  1058. stack_max = 1,
  1059. after_place_node = function(pos, placer, itemstack, pt)
  1060. ads.after_place_node(pos, placer)
  1061. end,
  1062. on_punch = function(pos, node, puncher, pt)
  1063. depositor.check_machine(pos)
  1064. end,
  1065. on_construct = function(pos)
  1066. depositor.on_construct(pos)
  1067. end,
  1068. on_destruct = function(pos)
  1069. depositor.on_destruct(pos)
  1070. end,
  1071. on_rightclick = function(pos, node, clicker, itemstack, pt)
  1072. ads.show_formspec(vector_round(pos), clicker:get_player_name(), true)
  1073. return itemstack
  1074. end,
  1075. _on_update_infotext = function(...)
  1076. ads._on_update_infotext(...)
  1077. end,
  1078. allow_metadata_inventory_move = function(...)
  1079. return ads.allow_metadata_inventory_move(...)
  1080. end,
  1081. allow_metadata_inventory_put = function(...)
  1082. return ads.allow_metadata_inventory_put(...)
  1083. end,
  1084. allow_metadata_inventory_take = function(...)
  1085. return ads.allow_metadata_inventory_take(...)
  1086. end,
  1087. can_dig = function(...)
  1088. return ads.can_dig(...)
  1089. end,
  1090. on_blast = function(...)
  1091. return ads.on_blast(...)
  1092. end,
  1093. })
  1094. minetest.register_craft({
  1095. output = "market:booth",
  1096. recipe = {
  1097. {'', 'default:sign_wall_wood', ''},
  1098. {'chests:chest_public_closed', 'easyvend:vendor', 'chests:chest_public_closed'},
  1099. {'', 'fine_wire:copper', ''},
  1100. },
  1101. })
  1102. ads.run_once = true
  1103. end