init.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  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. -- 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. minetest.log("error", "Failed to open " .. depositor.datafile .. " for reading: " .. err)
  215. else
  216. local datastring = file:read("*all")
  217. if datastring and datastring ~= "" then
  218. local records = string.split(datastring, "\n")
  219. for record_number, record in ipairs(records) do
  220. local data = string.split(record, ",")
  221. if type(data) == "table" and #data >= 10 then
  222. local x = tonumber(data[1])
  223. local y = tonumber(data[2])
  224. local z = tonumber(data[3])
  225. local o = tostring(data[4])
  226. local i = tostring(data[5])
  227. local c = tonumber(data[6])
  228. local t = tonumber(data[7])
  229. local a = tonumber(data[8])
  230. local n = tonumber(data[9])
  231. local r = tostring(data[10])
  232. if x and y and z and o and i and c and t and a and n and r then
  233. local act = false
  234. if a == 0 then
  235. act = false
  236. elseif a == 1 then
  237. act = true
  238. end
  239. 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})
  240. else
  241. minetest.log("error", "Could not deserialize record #" .. record_number .. " from shops.txt! Data: " .. record)
  242. end
  243. else
  244. minetest.log("error", "Could not load record #" .. record_number .. " from shops.txt! Data: " .. record)
  245. end
  246. end
  247. end
  248. file:close()
  249. end
  250. depositor.drops = {}
  251. local file, err = io.open(depositor.dropfile, "r")
  252. if err then
  253. minetest.log("error", "Failed to open " .. depositor.dropfile .. " for reading: " .. err)
  254. else
  255. local datastring = file:read("*all")
  256. if datastring and datastring ~= "" then
  257. local drops = minetest.deserialize(datastring)
  258. if drops and type(drops) == "table" then
  259. depositor.drops = drops
  260. end
  261. end
  262. file:close()
  263. end
  264. depositor.dirty = false
  265. end
  266. function depositor.save()
  267. -- Custom file format. minetest.serialize() is unusable for large tables.
  268. local datastring = ""
  269. for k, v in ipairs(depositor.shops) do
  270. if v.pos then
  271. local x = v.pos.x
  272. local y = v.pos.y
  273. local z = v.pos.z
  274. local t = v.type
  275. local o = v.owner
  276. local i = v.item
  277. local n = v.number
  278. local r = v.currency
  279. local c = v.cost
  280. local a = v.active
  281. if a then
  282. a = 1
  283. else
  284. a = 0
  285. end
  286. if x and y and z and t and o and i and c and a and r and n then
  287. -- x,y,z,owner,item,cost,type,active,number,currency
  288. datastring = datastring ..
  289. x .. "," .. y .. "," .. z .. "," .. o .. "," .. i .. "," .. c .. "," .. t .. "," .. a .. "," .. n .. "," .. r .. "\n"
  290. end
  291. end
  292. end
  293. local file, err = io.open(depositor.datafile, "w")
  294. if err then
  295. minetest.log("error", "Failed to open " .. depositor.datafile .. " for writing: " .. err)
  296. else
  297. file:write(datastring)
  298. file:close()
  299. end
  300. local file, err = io.open(depositor.dropfile, "w")
  301. if err then
  302. minetest.log("error", "Failed to open " .. depositor.dropfile .. " for writing: " .. err)
  303. else
  304. local datastring = minetest.serialize(depositor.drops)
  305. if datastring then
  306. file:write(datastring)
  307. end
  308. file:close()
  309. end
  310. end
  311. -- Called for vending & delivery booths.
  312. function depositor.check_machine(pos)
  313. pos = vector_round(pos)
  314. for i, dep in ipairs(depositor.shops) do
  315. if vector.equals(dep.pos, pos) then
  316. return
  317. end
  318. end
  319. table.insert(depositor.shops, {pos={x=pos.x, y=pos.y, z=pos.z}})
  320. depositor.dirty = true
  321. --depositor.save()
  322. end
  323. -- Called for vending & delivery booths.
  324. function depositor.on_construct(pos)
  325. pos = vector_round(pos)
  326. table.insert(depositor.shops, {pos={x=pos.x, y=pos.y, z=pos.z}})
  327. depositor.dirty = true
  328. end
  329. -- Called for vending & delivery booths.
  330. function depositor.on_destruct(pos)
  331. pos = vector_round(pos)
  332. for i=1, #(depositor.shops), 1 do
  333. local dep = depositor.shops[i]
  334. if vector.equals(dep.pos, pos) then
  335. -- If this was the active drop point, then we must remove it.
  336. local meta = minetest.get_meta(pos)
  337. local owner = meta:get_string("owner")
  338. if depositor.drops[owner] then
  339. if vector.equals(depositor.drops[owner].pos, pos) then
  340. depositor.drops[owner] = nil
  341. end
  342. end
  343. table.remove(depositor.shops, i)
  344. depositor.dirty = true
  345. return
  346. end
  347. end
  348. end
  349. function depositor.update_info(pos, owner, itemname, number, cost, currency, bsb, active)
  350. pos = vector_round(pos)
  351. local needsave = false
  352. for k, dep in ipairs(depositor.shops) do
  353. if vector.equals(dep.pos, pos) then
  354. dep.owner = owner or "server"
  355. dep.item = itemname or "none"
  356. dep.cost = cost or 0
  357. dep.number = number or 0
  358. dep.currency = currency or "none"
  359. dep.active = active
  360. dep.type = 0
  361. if bsb == "sell" then
  362. dep.type = 1
  363. elseif bsb == "buy" then
  364. dep.type = 2
  365. elseif bsb == "info" then
  366. dep.type = 3
  367. end
  368. needsave = true
  369. break
  370. end
  371. end
  372. if needsave then
  373. depositor.dirty = true
  374. end
  375. end
  376. function depositor.on_mapsave()
  377. if depositor.dirty then
  378. depositor.save()
  379. end
  380. depositor.dirty = false
  381. end
  382. if not depositor.run_once then
  383. depositor.load()
  384. minetest.register_on_shutdown(function() depositor.on_mapsave() end)
  385. minetest.register_on_mapsave(function() depositor.on_mapsave() end)
  386. local c = "depositor:core"
  387. local f = depositor.modpath .. "/init.lua"
  388. reload.register_file(c, f, false)
  389. depositor.run_once = true
  390. end