ads.lua 35 KB

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