ads.lua 35 KB

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