init.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. if not minetest.global_exists("depositor") then depositor = {} end
  2. depositor.modpath = minetest.get_modpath("depositor")
  3. depositor.datafile = minetest.get_worldpath() .. "/shops.txt"
  4. depositor.dropfile = minetest.get_worldpath() .. "/drops.txt"
  5. depositor.shops = depositor.shops or {} -- Shop data. Indexed array format.
  6. depositor.drops = depositor.drops or {} -- Dropsite data. Indexed by player name.
  7. depositor.dirty = true
  8. -- Localize for performance.
  9. local vector_round = vector.round
  10. local math_random = math.random
  11. function depositor.get_random_vending_or_depositing_machine()
  12. local data
  13. if #depositor.shops > 0 then
  14. local shops = {}
  15. for k, v in ipairs(depositor.shops) do
  16. if (v.type == 2 or v.type == 1) and v.active then
  17. table.insert(shops, v)
  18. end
  19. end
  20. if #shops > 0 then
  21. local v = shops[math_random(1, #shops)]
  22. data = table.copy(v) -- Copy the data so it cannot be modified.
  23. end
  24. end
  25. return data
  26. end
  27. -- Get random depositor shop data or nil.
  28. function depositor.get_random_depositing_machine()
  29. local data
  30. if #depositor.shops > 0 then
  31. local shops = {}
  32. for k, v in ipairs(depositor.shops) do
  33. if v.type == 2 and v.active then -- Is depositing machine.
  34. table.insert(shops, v)
  35. end
  36. end
  37. if #shops > 0 then
  38. local v = shops[math_random(1, #shops)]
  39. data = table.copy(v) -- Copy the data so it cannot be modified.
  40. end
  41. end
  42. return data
  43. end
  44. function depositor.get_random_vending_machine()
  45. local data
  46. if #depositor.shops > 0 then
  47. local shops = {}
  48. for k, v in ipairs(depositor.shops) do
  49. if v.type == 1 and v.active then -- Is vending machine.
  50. table.insert(shops, v)
  51. end
  52. end
  53. if #shops > 0 then
  54. local v = shops[math_random(1, #shops)]
  55. data = table.copy(v) -- Copy the data so it cannot be modified.
  56. end
  57. end
  58. return data
  59. end
  60. -- Returns data for a depositor offering the highest bid for an item, or nil.
  61. -- Will exclude depositors demanding more than the maximum.
  62. function depositor.get_random_depositor_buying_item(item, maximum)
  63. local data
  64. if #depositor.shops > 0 then
  65. local shops = {}
  66. for k, v in ipairs(depositor.shops) do
  67. if v.type == 2 and v.active then -- Is ative depositing machine.
  68. -- Only if depositor is buying item of not more than given max.
  69. if v.item == item and v.number <= maximum and v.number >= 1 then
  70. table.insert(shops, v)
  71. end
  72. end
  73. end
  74. if #shops > 0 then
  75. -- Sort shops, highest bid first.
  76. table.sort(shops,
  77. function(a, b)
  78. local v1 = currency.get_stack_value(a.currency, a.cost)
  79. local v2 = currency.get_stack_value(b.currency, b.cost)
  80. if v1 > v2 then
  81. return true
  82. end
  83. end)
  84. -- If multiple shops have the same highest bid value,
  85. -- then get a random shop from these that are bidding highest.
  86. local last = 0
  87. local highest_bid = shops[1].cost
  88. for k, v in ipairs(shops) do
  89. if v.cost >= highest_bid then
  90. last = last + 1
  91. end
  92. end
  93. local v = shops[math_random(1, last)]
  94. data = table.copy(v) -- Copy the data so it cannot be modified.
  95. end
  96. end
  97. return data
  98. end
  99. -- Returns data for a vendor offering the lowest price for an item, or nil.
  100. -- Will exclude vendors selling less than the minimum.
  101. function depositor.get_random_vendor_selling_item(item, minimum)
  102. local data
  103. if #depositor.shops > 0 then
  104. local shops = {}
  105. for k, v in ipairs(depositor.shops) do
  106. if v.type == 1 and v.active then -- Is ative vending machine.
  107. -- Only if vendor is selling item of at least this minimum amount.
  108. if v.item == item and v.number >= minimum and v.number >= 1 then
  109. table.insert(shops, v)
  110. end
  111. end
  112. end
  113. if #shops > 0 then
  114. -- Sort shops, lowest price first.
  115. table.sort(shops,
  116. function(a, b)
  117. local v1 = currency.get_stack_value(a.currency, a.cost)
  118. local v2 = currency.get_stack_value(b.currency, b.cost)
  119. if v1 < v2 then
  120. return true
  121. end
  122. end)
  123. -- If multiple shops have the same lowest price value,
  124. -- then get a random shop from these that are priced the lowest.
  125. local last = 0
  126. local lowest_price = shops[1].cost
  127. for k, v in ipairs(shops) do
  128. if v.cost >= lowest_price then
  129. last = last + 1
  130. end
  131. end
  132. local v = shops[math_random(1, last)]
  133. data = table.copy(v) -- Copy the data so it cannot be modified.
  134. end
  135. end
  136. return data
  137. end
  138. function depositor.set_drop_location(pos, pname)
  139. pos = vector_round(pos)
  140. depositor.drops[pname] = {
  141. pos = {x=pos.x, y=pos.y, z=pos.z},
  142. }
  143. end
  144. function depositor.unset_drop_location(pname)
  145. depositor.drops[pname] = nil
  146. end
  147. -- Return `pos` or nil.
  148. function depositor.get_drop_location(pname)
  149. if depositor.drops[pname] then
  150. return depositor.drops[pname].pos
  151. end
  152. end
  153. -- Return error string in case of error, otherwise nil.
  154. function depositor.execute_trade(vend_pos, user_name, vendor_name, user_drop, vendor_drop, item, number, cost, tax, currency, type)
  155. local user = minetest.get_player_by_name(user_name)
  156. if not user or not user:is_player() then
  157. return "Invalid user!"
  158. end
  159. if type ~= 1 and type ~= 2 then
  160. return "Unknown vendor type!"
  161. end
  162. -- Do not allow player to trade with themselves.
  163. if vector.equals(user_drop, vendor_drop) or vendor_name == user_name then
  164. return "Vending and user drop-points cannot be the same (are you trying to trade with yourself?)!"
  165. end
  166. -- Security checks and vending use requires map access.
  167. utility.ensure_map_loaded(vector.add(user_drop, {x=-7, y=-7, z=-7}), vector.add(user_drop, {x=7, y=7, z=7}))
  168. utility.ensure_map_loaded(vector.add(vendor_drop, {x=-7, y=-7, z=-7}), vector.add(vendor_drop, {x=7, y=7, z=7}))
  169. if minetest.get_node(user_drop).name ~= "market:booth" or
  170. minetest.get_node(vendor_drop).name ~= "market:booth"
  171. then
  172. return "Error: 0xDEADBEEF 9020 (Please report)."
  173. end
  174. local meta = minetest.get_meta(user_drop)
  175. local inv = meta:get_inventory()
  176. if not inv then
  177. return "Could not obtain user inventory!"
  178. end
  179. local meta2 = minetest.get_meta(vendor_drop)
  180. local inv2 = meta2:get_inventory()
  181. if not inv2 then
  182. return "Could not obtain vendor inventory!"
  183. end
  184. local meta3 = minetest.get_meta(vend_pos)
  185. if meta3:get_string("owner") ~= vendor_name or
  186. meta3:get_string("itemname") ~= item or
  187. meta3:get_string("machine_currency") ~= currency or
  188. meta3:get_int("number") ~= number or
  189. meta3:get_int("cost") ~= cost
  190. then
  191. return "Vendor information unexpectedly changed! Refusing to trade items."
  192. end
  193. -- The trade function requires map access!
  194. utility.ensure_map_loaded(vector.add(vend_pos, {x=-7, y=-7, z=-7}), vector.add(vend_pos, {x=7, y=7, z=7}))
  195. easyvend.execute_trade(vend_pos, user, inv, "storage", inv2, "storage", tax)
  196. local status = meta3:get_string("status")
  197. local msg = meta3:get_string("message")
  198. if status ~= "" and msg ~= "" then
  199. return "Remote status: " .. status .. " Remote message: " .. msg
  200. else
  201. if status ~= "" then
  202. return "Remote status: " .. status
  203. end
  204. if msg ~= "" then
  205. return "Remote message: " .. msg
  206. end
  207. end
  208. end
  209. function depositor.load()
  210. -- Custom file format. minetest.serialize() is unusable for large tables.
  211. depositor.shops = {}
  212. local file, err = io.open(depositor.datafile, "r")
  213. if err then
  214. if not err:find("No such file") then
  215. minetest.log("error", "Failed to open " .. depositor.datafile .. " for reading: " .. err)
  216. end
  217. else
  218. local datastring = file:read("*all")
  219. if datastring and datastring ~= "" then
  220. local records = string.split(datastring, "\n")
  221. for record_number, record in ipairs(records) do
  222. local data = string.split(record, ",")
  223. if type(data) == "table" and #data >= 10 then
  224. local x = tonumber(data[1])
  225. local y = tonumber(data[2])
  226. local z = tonumber(data[3])
  227. local o = tostring(data[4])
  228. local i = tostring(data[5])
  229. local c = tonumber(data[6])
  230. local t = tonumber(data[7])
  231. local a = tonumber(data[8])
  232. local n = tonumber(data[9])
  233. local r = tostring(data[10])
  234. if x and y and z and o and i and c and t and a and n and r then
  235. local act = false
  236. if a == 0 then
  237. act = false
  238. elseif a == 1 then
  239. act = true
  240. end
  241. table.insert(depositor.shops, {pos={x=x, y=y, z=z}, owner=o, item=i, number=n, cost=c, currency=r, type=t, active=act})
  242. else
  243. minetest.log("error", "Could not deserialize record #" .. record_number .. " from shops.txt! Data: " .. record)
  244. end
  245. else
  246. minetest.log("error", "Could not load record #" .. record_number .. " from shops.txt! Data: " .. record)
  247. end
  248. end
  249. end
  250. file:close()
  251. end
  252. depositor.drops = {}
  253. local file, err = io.open(depositor.dropfile, "r")
  254. if err then
  255. if not err:find("No such file") then
  256. minetest.log("error", "Failed to open " .. depositor.dropfile .. " for reading: " .. err)
  257. end
  258. else
  259. local datastring = file:read("*all")
  260. if datastring and datastring ~= "" then
  261. local drops = minetest.deserialize(datastring)
  262. if drops and type(drops) == "table" then
  263. depositor.drops = drops
  264. end
  265. end
  266. file:close()
  267. end
  268. depositor.dirty = false
  269. end
  270. function depositor.save()
  271. -- Custom file format. minetest.serialize() is unusable for large tables.
  272. local datastring = ""
  273. for k, v in ipairs(depositor.shops) do
  274. if v.pos then
  275. local x = v.pos.x
  276. local y = v.pos.y
  277. local z = v.pos.z
  278. local t = v.type
  279. local o = v.owner
  280. local i = v.item
  281. local n = v.number
  282. local r = v.currency
  283. local c = v.cost
  284. local a = v.active
  285. if a then
  286. a = 1
  287. else
  288. a = 0
  289. end
  290. if x and y and z and t and o and i and c and a and r and n then
  291. -- x,y,z,owner,item,cost,type,active,number,currency
  292. datastring = datastring ..
  293. x .. "," .. y .. "," .. z .. "," .. o .. "," .. i .. "," .. c .. "," .. t .. "," .. a .. "," .. n .. "," .. r .. "\n"
  294. end
  295. end
  296. end
  297. local file, err = io.open(depositor.datafile, "w")
  298. if err then
  299. minetest.log("error", "Failed to open " .. depositor.datafile .. " for writing: " .. err)
  300. else
  301. file:write(datastring)
  302. file:close()
  303. end
  304. local file, err = io.open(depositor.dropfile, "w")
  305. if err then
  306. minetest.log("error", "Failed to open " .. depositor.dropfile .. " for writing: " .. err)
  307. else
  308. local datastring = minetest.serialize(depositor.drops)
  309. if datastring then
  310. file:write(datastring)
  311. end
  312. file:close()
  313. end
  314. end
  315. -- Called for vending & delivery booths.
  316. function depositor.check_machine(pos)
  317. pos = vector_round(pos)
  318. for i, dep in ipairs(depositor.shops) do
  319. if vector.equals(dep.pos, pos) then
  320. return
  321. end
  322. end
  323. table.insert(depositor.shops, {pos={x=pos.x, y=pos.y, z=pos.z}})
  324. depositor.dirty = true
  325. --depositor.save()
  326. end
  327. -- Called for vending & delivery booths.
  328. function depositor.on_construct(pos)
  329. pos = vector_round(pos)
  330. table.insert(depositor.shops, {pos={x=pos.x, y=pos.y, z=pos.z}})
  331. depositor.dirty = true
  332. end
  333. -- Called for vending & delivery booths.
  334. function depositor.on_destruct(pos)
  335. pos = vector_round(pos)
  336. for i=1, #(depositor.shops), 1 do
  337. local dep = depositor.shops[i]
  338. if vector.equals(dep.pos, pos) then
  339. -- If this was the active drop point, then we must remove it.
  340. local meta = minetest.get_meta(pos)
  341. local owner = meta:get_string("owner")
  342. if depositor.drops[owner] then
  343. if vector.equals(depositor.drops[owner].pos, pos) then
  344. depositor.drops[owner] = nil
  345. end
  346. end
  347. table.remove(depositor.shops, i)
  348. depositor.dirty = true
  349. return
  350. end
  351. end
  352. end
  353. function depositor.update_info(pos, owner, itemname, number, cost, currency, bsb, active)
  354. pos = vector_round(pos)
  355. local needsave = false
  356. for k, dep in ipairs(depositor.shops) do
  357. if vector.equals(dep.pos, pos) then
  358. dep.owner = owner or "server"
  359. dep.item = itemname or "none"
  360. dep.cost = cost or 0
  361. dep.number = number or 0
  362. dep.currency = currency or "none"
  363. dep.active = active
  364. dep.type = 0
  365. if bsb == "sell" then
  366. dep.type = 1
  367. elseif bsb == "buy" then
  368. dep.type = 2
  369. elseif bsb == "info" then
  370. dep.type = 3
  371. end
  372. needsave = true
  373. break
  374. end
  375. end
  376. if needsave then
  377. depositor.dirty = true
  378. end
  379. end
  380. function depositor.on_mapsave()
  381. if depositor.dirty then
  382. depositor.save()
  383. end
  384. depositor.dirty = false
  385. end
  386. if not depositor.run_once then
  387. depositor.load()
  388. minetest.register_on_shutdown(function() depositor.on_mapsave() end)
  389. minetest.register_on_mapsave(function() depositor.on_mapsave() end)
  390. local c = "depositor:core"
  391. local f = depositor.modpath .. "/init.lua"
  392. reload.register_file(c, f, false)
  393. depositor.run_once = true
  394. end