ads.lua 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181
  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. formspec = formspec ..
  506. "button[9,8.4;2.2,1;domark;" .. minetest.formspec_escape("Mark Location") .. "]"
  507. end
  508. end
  509. end
  510. -- Edit/remove add buttons.
  511. formspec = formspec ..
  512. "button[0,8.4;2,1;editrecord;" .. minetest.formspec_escape("Edit Record") .. "]" ..
  513. "button[2,8.4;4,1;deleterecord;" .. minetest.formspec_escape("Delete Record (With Refund)") .. "]"
  514. end
  515. end
  516. if booth and (booth_owner == pname or minetest.check_player_privs(pname, "server")) then
  517. -- All good.
  518. else
  519. if booth then
  520. formspec = formspec .. "label[0,7.5;" .. esc("You cannot use this market kiosk for remote trading, because you do not own it.") .. "]"
  521. else
  522. formspec = formspec .. "label[0,7.5;" .. esc("You cannot use this interface for remote trading, because it is detached.") .. "]"
  523. end
  524. end
  525. formspec = formspec ..
  526. "button[13.2,7.5;2,1;done;Done]"
  527. return formspec
  528. end
  529. function ads.on_receive_fields(player, formname, fields)
  530. if string.sub(formname, 1, 9) ~= "ads:main_" then
  531. return
  532. end
  533. local pos = minetest.string_to_pos(string.sub(formname, 10, string.find(formname, "|")-1))
  534. if not pos then
  535. --minetest.chat_send_all("Invalid!")
  536. return true
  537. end
  538. local pname = player:get_player_name()
  539. if not ads.players[pname] then
  540. return true
  541. end
  542. if fields.quit then
  543. return true
  544. end
  545. if fields.done then
  546. minetest.close_formspec(pname, formname)
  547. return true
  548. end
  549. local event = minetest.explode_textlist_event(fields["adlist"])
  550. if event.type == "CHG" then
  551. local index = event.index
  552. local max = #(ads.players[pname].ads)
  553. if index > max then index = max end
  554. -- Reset selected shop whenever selected ad changes.
  555. if index ~= ads.players[pname].selected then
  556. ads.players[pname].shopselect = 0
  557. end
  558. ads.players[pname].selected = index
  559. --minetest.chat_send_all("" .. index)
  560. end
  561. local event = minetest.explode_textlist_event(fields["shoplist"])
  562. if event.type == "CHG" then
  563. if ads.players[pname].shops then
  564. local index = event.index
  565. local max = #(ads.players[pname].shops)
  566. if index > max then index = max end
  567. ads.players[pname].shopselect = index
  568. --minetest.chat_send_all("" .. index)
  569. end
  570. end
  571. local booth = false
  572. if string.find(formname, "|booth") then
  573. booth = true
  574. end
  575. if fields.storage or fields.dotrade or fields.domark or fields.editrecord or fields.deleterecord or fields.newadd then
  576. if booth then
  577. local meta = minetest.get_meta(pos)
  578. if meta:get_string("owner") == pname or minetest.check_player_privs(pname, "server") then
  579. if fields.storage then
  580. ads.show_inventory_formspec(pos, pname, booth)
  581. return true
  582. elseif fields.dotrade then
  583. local sel = ads.players[pname].shopselect or 0
  584. local shops = ads.players[pname].shops
  585. if shops and sel ~= 0 and shops[sel] then
  586. local item = shops[sel].item
  587. local cost = shops[sel].cost
  588. local type = shops[sel].type
  589. local number = shops[sel].number
  590. local currency = shops[sel].currency
  591. local owner = shops[sel].owner or ""
  592. local putsite = depositor.get_drop_location(pname)
  593. local dropsite = depositor.get_drop_location(owner)
  594. local vpos = shops[sel].pos
  595. if putsite then
  596. if dropsite then
  597. local msg = depositor.execute_trade(vpos, pname, owner, putsite, dropsite, item, number, cost, ads.tax, currency, type)
  598. if msg then
  599. minetest.chat_send_player(pname, "# Server: " .. msg)
  600. end
  601. else
  602. minetest.chat_send_player(pname, "# Server: Cannot execute trade. <" .. rename.gpn(owner) .. "> has not registered an address for remote trading.")
  603. easyvend.sound_error(pname)
  604. end
  605. else
  606. minetest.chat_send_player(pname, "# Server: Cannot execute trade. You have not registered an address for remote trading.")
  607. easyvend.sound_error(pname)
  608. end
  609. end
  610. elseif fields.domark then
  611. local sel = ads.players[pname].shopselect or 0
  612. local shops = ads.players[pname].shops
  613. if shops and sel ~= 0 and shops[sel] then
  614. local vpos = shops[sel].pos
  615. local lname = pname .. "'s Vend Locs"
  616. if marker.list_size(pname, lname) < marker.max_waypoints then
  617. marker.add_waypoint(pname, vpos, lname)
  618. marker.update_single_hud(pname)
  619. minetest.chat_send_player(pname, "# Server: Location added to marker list.")
  620. end
  621. end
  622. return true
  623. elseif fields.editrecord then
  624. local sel = ads.players[pname].selected or 0
  625. if sel ~= 0 then
  626. local data = ads.players[pname].ads or {}
  627. if sel >= 1 and sel <= #data then
  628. if data[sel].owner == pname or minetest.check_player_privs(pname, "server") then
  629. ads.players[pname].shopselect = 0
  630. ads.players[pname].editname = data[sel].shop -- Record name of the advertisement the player will be editing.
  631. local edit_info = {
  632. title_text = data[sel].shop,
  633. body_text = data[sel].custom,
  634. }
  635. ads.show_submission_formspec(pos, pname, booth, edit_info)
  636. return true
  637. else
  638. -- Player doesn't have privs to edit this record.
  639. minetest.chat_send_player(pname, "# Server: The selected notice does not belong to you.")
  640. easyvend.sound_error(pname)
  641. end
  642. else
  643. -- Selection index out of range.
  644. minetest.chat_send_player(pname, "# Server: You must select one of your own public notices, first.")
  645. easyvend.sound_error(pname)
  646. end
  647. else
  648. -- Nothing selected.
  649. minetest.chat_send_player(pname, "# Server: You must select one of your own public notices, first.")
  650. easyvend.sound_error(pname)
  651. end
  652. elseif fields.deleterecord then
  653. local sel = ads.players[pname].selected or 0
  654. if sel ~= 0 then
  655. local data = ads.players[pname].ads or {}
  656. if sel >= 1 and sel <= #data then
  657. local owner = data[sel].owner
  658. local title = data[sel].shop
  659. if owner == pname or minetest.check_player_privs(pname, "server") then
  660. local player_inv = player:get_inventory()
  661. if currency.room_for_cash(player_inv, "main", ads.ad_cost) then
  662. ads.players[pname].shopselect = 0 -- Unselect any vendor/depositor listing.
  663. ads.players[pname].selected = 0 -- Reset ad selection to nil to prevent double-deletions.
  664. --minetest.chat_send_player(pname, "# Server: Would delete advertisement titled: \"" .. title .. "\"!")
  665. -- Search for record by owner/title and delete it.
  666. local found = false
  667. local ad_real_owner = ""
  668. for k, v in ipairs(ads.data) do
  669. if v.shop == title and v.owner == owner then
  670. ad_real_owner = v.owner
  671. table.remove(ads.data, k)
  672. found = true
  673. ads.dirty = true
  674. break
  675. end
  676. end
  677. if found then
  678. minetest.chat_send_player(pname, "# Server: Advertisement titled: \"" .. title .. "\", owned by <" .. rename.gpn(ad_real_owner) .. "> was removed.")
  679. currency.add_cash(player_inv, "main", ads.ad_cost)
  680. else
  681. minetest.chat_send_player(pname, "# Server: Could not locate record for deletion!")
  682. easyvend.sound_error(pname)
  683. end
  684. else
  685. -- Player doesn't have room in their inventory for the cash.
  686. minetest.chat_send_player(pname, "# Server: You must have room in your inventory to receive the cash refund of " .. ads.ad_cost .. " mg.")
  687. easyvend.sound_error(pname)
  688. end
  689. else
  690. -- Player doesn't have privs to delete this record.
  691. minetest.chat_send_player(pname, "# Server: The selected public notice does not belong to you.")
  692. easyvend.sound_error(pname)
  693. end
  694. else
  695. -- Selection index out of range.
  696. minetest.chat_send_player(pname, "# Server: You must select one of your own public notices, first.")
  697. easyvend.sound_error(pname)
  698. end
  699. else
  700. -- Nothing selected.
  701. minetest.chat_send_player(pname, "# Server: You must select one of your own public notices, first.")
  702. easyvend.sound_error(pname)
  703. end
  704. elseif fields.newadd then
  705. ads.show_submission_formspec(pos, pname, booth)
  706. return true
  707. end
  708. else
  709. -- Player sent button click on a market booth they don't own.
  710. minetest.chat_send_player(pname, "# Server: You do not have permission to do that.")
  711. easyvend.sound_error(pname)
  712. end
  713. else
  714. -- Player sent fields requiring a market booth, but this is a "detached" formspec.
  715. minetest.chat_send_player(pname, "# Server: This action can only be completed at a market kiosk.")
  716. easyvend.sound_error(pname)
  717. end
  718. end
  719. ads.show_formspec(pos, pname, booth)
  720. return true
  721. end
  722. function ads.show_formspec(pos, pname, booth)
  723. local formspec = ads.generate_formspec(pos, pname, booth)
  724. local b = "|"
  725. if booth then
  726. b = "|booth"
  727. end
  728. local key = "ads:main_" .. minetest.pos_to_string(vector_round(pos)) .. b
  729. minetest.show_formspec(pname, key, formspec)
  730. end
  731. function ads.add_entry(data)
  732. ads.data[#(ads.data)+1] = {
  733. shop = data.shop, -- Brief Name of Shop
  734. pos = data.pos, -- {x,y,z}
  735. owner = data.owner, -- playername
  736. custom = data.custom, -- Custom text. Here are directions to my shop.
  737. date = data.date, -- timestamp of submission (integer)
  738. }
  739. ads.dirty = true
  740. ads.players = {}
  741. end
  742. function ads.load_data()
  743. ads.data = {}
  744. local file = io.open(ads.datapath, "r")
  745. if file then
  746. local data = file:read("*all")
  747. local db = minetest.deserialize(data)
  748. file:close()
  749. if type(db) == "table" then
  750. table.shuffle(db)
  751. ads.data = db
  752. ads.dirty = false
  753. ads.players = {}
  754. end
  755. end
  756. end
  757. function ads.save_data()
  758. if ads.dirty then
  759. local str = minetest.serialize(ads.data)
  760. if type(str) ~= "string" then return end -- Failsafe.
  761. local file = io.open(ads.datapath, "w")
  762. if file then
  763. file:write(str)
  764. file:close()
  765. end
  766. end
  767. ads.dirty = false
  768. end
  769. function ads._on_rename_check(pos)
  770. local meta = minetest.get_meta(pos)
  771. local pname = meta:get_string("owner")
  772. meta:set_string("infotext", "Market Trade Booth\nOwned by <" .. rename.gpn(pname) .. ">!")
  773. end
  774. function ads.after_place_node(pos, placer)
  775. local pname = placer:get_player_name()
  776. -- If placed by admin, use landowner as real owner.
  777. if gdac.player_is_admin(pname) then
  778. local landowner = protector.get_node_owner(pos) or ""
  779. if landowner ~= "" then
  780. pname = landowner
  781. end
  782. end
  783. local meta = minetest.get_meta(pos)
  784. meta:set_string("owner", pname)
  785. meta:set_string("infotext", "Market Trade Booth\nOwned by <" .. rename.gpn(pname) .. ">!")
  786. local inv = meta:get_inventory()
  787. inv:set_size("storage", (5*16) + (7*6))
  788. depositor.update_info(pos, pname, "none", 0, 0, "none", "info")
  789. end
  790. local function has_inventory_privilege(meta, player)
  791. if minetest.check_player_privs(player, "server") then
  792. return true
  793. end
  794. if player:get_player_name() == meta:get_string("owner") then
  795. return true
  796. end
  797. return false
  798. end
  799. function ads.allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
  800. local meta = minetest.get_meta(pos)
  801. if from_list ~= to_list then
  802. return 0
  803. end
  804. if not has_inventory_privilege(meta, player) then
  805. return 0
  806. end
  807. return count
  808. end
  809. function ads.allow_metadata_inventory_put(pos, listname, index, stack, player)
  810. local meta = minetest.get_meta(pos)
  811. if listname ~= "storage" then
  812. return 0
  813. end
  814. if not has_inventory_privilege(meta, player) then
  815. return 0
  816. end
  817. return stack:get_count()
  818. end
  819. function ads.allow_metadata_inventory_take(pos, listname, index, stack, player)
  820. local meta = minetest.get_meta(pos)
  821. if listname ~= "storage" then
  822. return 0
  823. end
  824. if not has_inventory_privilege(meta, player) then
  825. return 0
  826. end
  827. return stack:get_count()
  828. end
  829. function ads.on_receive_inventory_fields(player, formname, fields)
  830. if string.sub(formname, 1, 14) ~= "ads:inventory_" then
  831. return
  832. end
  833. local pos = minetest.string_to_pos(string.sub(formname, 15, string.find(formname, "|")-1))
  834. if not pos then
  835. return true
  836. end
  837. local pname = player:get_player_name()
  838. if not ads.players[pname] then
  839. return true
  840. end
  841. if fields.done or fields.quit then
  842. minetest.close_formspec(pname, formname)
  843. return true
  844. end
  845. local booth = false
  846. if string.find(formname, "|booth") then
  847. booth = true
  848. end
  849. if booth and fields.backinv then
  850. ads.show_formspec(pos, pname, booth)
  851. return true
  852. end
  853. if booth and fields.setpoint then
  854. local node = minetest.get_node(pos)
  855. if node.name == "market:booth" then
  856. local meta = minetest.get_meta(pos)
  857. -- Owner or admin may use.
  858. if meta:get_string("owner") == pname or minetest.check_player_privs(player, "server") then
  859. depositor.set_drop_location(pos, pname)
  860. local p2 = depositor.get_drop_location(pname)
  861. if p2 then
  862. 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.")
  863. ads.show_inventory_formspec(pos, pname, booth)
  864. else
  865. minetest.chat_send_player(pname, "# Server: Error, could not set delivery drop-point.")
  866. easyvend.sound_error(pname)
  867. end
  868. else
  869. minetest.chat_send_player(pname, "# Server: Cannot set delivery drop-point, you do not own this kiosk.")
  870. easyvend.sound_error(pname)
  871. end
  872. else
  873. minetest.chat_send_player(pname, "# Server: Error: 0xDEADBEEF 5392 (please report).")
  874. easyvend.sound_error(pname)
  875. end
  876. return true
  877. end
  878. if booth and fields.unsetpoint then
  879. local p2 = depositor.get_drop_location(pname)
  880. if p2 then
  881. minetest.chat_send_player(pname, "# Server: Delivery point at " .. rc.pos_to_namestr(p2) .. " revoked by explicit request.")
  882. end
  883. depositor.unset_drop_location(pname)
  884. ads.show_inventory_formspec(pos, pname, booth)
  885. return true
  886. end
  887. return true
  888. end
  889. function ads.can_dig(pos, player)
  890. local meta = minetest.get_meta(pos);
  891. local inv = meta:get_inventory()
  892. return inv:is_empty("storage")
  893. end
  894. function ads.on_blast(pos)
  895. local def = minetest.reg_ns_nodes[minetest.get_node(pos).name]
  896. local drops = {}
  897. default.get_inventory_drops(pos, "storage", drops)
  898. drops[#drops+1] = "market:booth"
  899. minetest.remove_node(pos)
  900. return drops
  901. end
  902. if not ads.run_once then
  903. ads.load_data()
  904. minetest.register_on_shutdown(function() ads.save_data() end)
  905. minetest.register_on_mapsave(function() ads.save_data() end)
  906. minetest.register_on_player_receive_fields(function(...)
  907. return ads.on_receive_fields(...)
  908. end)
  909. minetest.register_on_player_receive_fields(function(...)
  910. return ads.on_receive_submission_fields(...)
  911. end)
  912. minetest.register_on_player_receive_fields(function(...)
  913. return ads.on_receive_inventory_fields(...)
  914. end)
  915. local c = "ads:core"
  916. local f = ads.modpath .. "/ads.lua"
  917. reload.register_file(c, f, false)
  918. minetest.register_node(":market:booth", {
  919. description = "Networked Trade Kiosk\n\nA market kiosk enables remote trading.\nWill link with nearby vending/depositing machines.\nCan also display public notices.",
  920. tiles = {
  921. "easyvend_vendor_side.png",
  922. "easyvend_vendor_side.png",
  923. "easyvend_ad_booth.png",
  924. "easyvend_ad_booth.png",
  925. "easyvend_ad_booth.png",
  926. "easyvend_ad_booth.png",
  927. },
  928. paramtype2 = "facedir",
  929. groups = utility.dig_groups("furniture", {flammable = 2}),
  930. sounds = default.node_sound_wood_defaults(),
  931. after_place_node = function(pos, placer, itemstack, pt)
  932. ads.after_place_node(pos, placer)
  933. end,
  934. on_punch = function(pos, node, puncher, pt)
  935. depositor.check_machine(pos)
  936. end,
  937. on_construct = function(pos)
  938. depositor.on_construct(pos)
  939. end,
  940. on_destruct = function(pos)
  941. depositor.on_destruct(pos)
  942. end,
  943. on_rightclick = function(pos, node, clicker, itemstack, pt)
  944. ads.show_formspec(vector_round(pos), clicker:get_player_name(), true)
  945. return itemstack
  946. end,
  947. _on_rename_check = function(...)
  948. ads._on_rename_check(...)
  949. end,
  950. allow_metadata_inventory_move = function(...)
  951. return ads.allow_metadata_inventory_move(...)
  952. end,
  953. allow_metadata_inventory_put = function(...)
  954. return ads.allow_metadata_inventory_put(...)
  955. end,
  956. allow_metadata_inventory_take = function(...)
  957. return ads.allow_metadata_inventory_take(...)
  958. end,
  959. can_dig = function(...)
  960. return ads.can_dig(...)
  961. end,
  962. on_blast = function(...)
  963. return ads.on_blast(...)
  964. end,
  965. })
  966. minetest.register_craft({
  967. output = "market:booth",
  968. recipe = {
  969. {'', 'default:sign_wall_wood', ''},
  970. {'chests:chest_public_closed', 'easyvend:vendor', 'chests:chest_public_closed'},
  971. {'', 'fine_wire:copper', ''},
  972. },
  973. })
  974. ads.run_once = true
  975. end