init.lua 12 KB

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