market.lua 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718
  1. local S = minetest.get_translator(minetest.get_current_modname())
  2. commoditymarket.registered_markets = {}
  3. local log_length_limit = 30
  4. -- from http://lua-users.org/wiki/BinaryInsert
  5. --[[
  6. table.bininsert( table, value [, comp] )
  7. Inserts a given value through BinaryInsert into the table sorted by [, comp].
  8. If 'comp' is given, then it must be a function that receives
  9. two table elements, and returns true when the first is less
  10. than the second, e.g. comp = function(a, b) return a > b end,
  11. will give a sorted table, with the biggest value on position 1.
  12. [, comp] behaves as in table.sort(table, value [, comp])
  13. returns the index where 'value' was inserted
  14. ]]--
  15. local comp_default = function(a, b) return a < b end
  16. function table.bininsert(t, value, comp)
  17. -- Initialise compare function
  18. local comp = comp or comp_default
  19. -- Initialise numbers
  20. local iStart, iEnd, iMid, iState = 1, #t, 1, 0
  21. -- Get insert position
  22. while iStart <= iEnd do
  23. -- calculate middle
  24. iMid = math.floor( (iStart+iEnd)/2 )
  25. -- compare
  26. if comp(value, t[iMid]) then
  27. iEnd, iState = iMid - 1, 0
  28. else
  29. iStart, iState = iMid + 1, 1
  30. end
  31. end
  32. local target = iMid+iState
  33. table.insert(t, target, value)
  34. return target
  35. end
  36. -- lowest price first
  37. local buy_comp = function(order1, order2)
  38. local price1 = order1.price
  39. local price2 = order2.price
  40. if price1 < price2 then
  41. return true
  42. elseif price1 == price2 and order1.timestamp < order2.timestamp then
  43. return true
  44. end
  45. return false
  46. end
  47. -- highest price first
  48. local sell_comp = function(order1, order2)
  49. local price1 = order1.price
  50. local price2 = order2.price
  51. if price1 > price2 then
  52. return true
  53. elseif price1 == price2 and order1.timestamp < order2.timestamp then
  54. return true
  55. end
  56. return false
  57. end
  58. ---------------------------------
  59. local get_account = function(market, player_name)
  60. local account = market.player_accounts[player_name]
  61. if account then
  62. return account
  63. end
  64. account = {}
  65. account.search = ""
  66. account.name = player_name
  67. account.balance = 0 -- currency
  68. account.inventory = {} -- items stored in the market inventory that aren't part of sell orders yet. stored as "[item] = count"
  69. account.filter_participating = "false"
  70. account.log = {} -- might want to use a more sophisticated queue, but this isn't going to be a big list so that's more trouble than it's worth right now.
  71. market.player_accounts[player_name] = account
  72. return account
  73. end
  74. -- Caution: the data structures produced by sale logging caused me to discover
  75. -- issue https://github.com/minetest/minetest/issues/8719 with minetest.serialize()
  76. -- I'm working around it by using the code in persistence.lua instead
  77. local log_sale = function(item, quantity, price, purchaser, seller)
  78. local log_entry = {item=item, quantity=quantity, price=price, purchaser=purchaser, seller=seller, timestamp = minetest.get_gametime()}
  79. local purchaser_log = purchaser.log
  80. local seller_log = seller.log
  81. table.insert(purchaser_log, log_entry)
  82. if #purchaser_log > log_length_limit then
  83. table.remove(purchaser_log, 1)
  84. end
  85. if (purchaser ~= seller) then
  86. table.insert(seller_log, log_entry)
  87. if #seller_log > log_length_limit then
  88. table.remove(seller_log, 1)
  89. end
  90. end
  91. end
  92. local remove_orders_by_account = function(orders, account)
  93. if not orders then return end
  94. local i = 1
  95. while i < #orders do
  96. local order = orders[i]
  97. if order.account == account then
  98. table.remove(orders, i)
  99. else
  100. i = i + 1
  101. end
  102. end
  103. end
  104. local remove_account = function(player_name)
  105. local account = player_accounts[player_name]
  106. if account == nil then
  107. return
  108. end
  109. player_accounts[player_name] = nil
  110. for item, lists in pairs(market) do
  111. remove_orders_by_account(lists.buy_orders, account)
  112. remove_orders_by_account(lists.sell_orders, account)
  113. end
  114. end
  115. ------------------------------------------------------------------------------------------
  116. local add_inventory_to_account = function(market, account, item, quantity)
  117. if quantity < 1 then
  118. return false
  119. end
  120. if market.def.currency[item] then
  121. account.balance = account.balance + market.def.currency[item] * quantity
  122. else
  123. account.inventory[item] = (account.inventory[item] or 0) + quantity
  124. end
  125. return true
  126. end
  127. local remove_inventory_from_account = function(account, item, quantity)
  128. if quantity < 1 then
  129. return false
  130. end
  131. local inventory = account.inventory
  132. local current_quantity = inventory[item] or 0
  133. if current_quantity < quantity then
  134. return false
  135. end
  136. local new_quantity = current_quantity - quantity
  137. if new_quantity == 0 then
  138. inventory[item] = nil
  139. else
  140. inventory[item] = new_quantity
  141. end
  142. return true
  143. end
  144. local remove_order = function(order, array)
  145. for i, market_order in ipairs(array) do
  146. if order == market_order then
  147. table.remove(array, i)
  148. return true
  149. end
  150. end
  151. return false
  152. end
  153. -----------------------------------------------------------------------------------------------------------
  154. local add_sell = function(market, account, item, price, quantity)
  155. price = tonumber(price)
  156. quantity = tonumber(quantity)
  157. local sell_limit = market.def.sell_limit
  158. local sell_limit_exceeded
  159. if sell_limit then
  160. local total_sell = 0
  161. for item, orders in pairs(market.orders_for_items) do
  162. for _, order in ipairs(orders.sell_orders) do
  163. if order.account == account then
  164. total_sell = total_sell + order.quantity
  165. end
  166. end
  167. end
  168. sell_limit_exceeded = total_sell + quantity > sell_limit
  169. end
  170. -- validate that this sell order is possible
  171. if sell_limit_exceeded or price < 0 or quantity < 1 or not remove_inventory_from_account(account, item, quantity) then
  172. minetest.sound_play({name = "commoditymarket_error", gain = 0.1}, {to_player=account.name})
  173. if sell_limit_exceeded then
  174. minetest.chat_send_player(account.name, S("You have too many items listed for sale in this market, please cancel some sell orders to make room for new ones."))
  175. elseif price < 0 then
  176. minetest.chat_send_player(account.name, S("You can't sell items for a negative price."))
  177. elseif quantity < 1 then
  178. minetest.chat_send_player(account.name, S("You can't sell fewer than one item."))
  179. else
  180. minetest.chat_send_player(account.name, S("You don't have enough of that item in your inventory to post this sell order."))
  181. end
  182. return false
  183. end
  184. local buy_market = market.orders_for_items[item].buy_orders
  185. local buy_order = buy_market[#buy_market]
  186. local current_buy_volume = market.orders_for_items[item].buy_volume
  187. -- go through existing buy orders that are more expensive than or equal to the price
  188. -- we're demanding, selling them at the order's price until we run out of
  189. -- buy orders or run out of demand
  190. while quantity > 0 and buy_order and buy_order.price >= price do
  191. local quantity_to_sell = math.min(buy_order.quantity, quantity)
  192. quantity = quantity - quantity_to_sell
  193. local earned = quantity_to_sell*buy_order.price
  194. account.balance = account.balance + earned
  195. add_inventory_to_account(market, buy_order.account, item, quantity_to_sell)
  196. buy_order.quantity = buy_order.quantity - quantity_to_sell
  197. current_buy_volume = current_buy_volume - quantity_to_sell
  198. if buy_order.account ~= account then
  199. -- don't update the last price if a player is just buying and selling from themselves
  200. market.orders_for_items[item].last_price = buy_order.price
  201. end
  202. log_sale(item, quantity_to_sell, buy_order.price, buy_order.account, account)
  203. if buy_order.quantity == 0 then
  204. table.remove(buy_market, #buy_market)
  205. end
  206. buy_order = buy_market[#buy_market]
  207. end
  208. market.orders_for_items[item].buy_volume = current_buy_volume
  209. if quantity > 0 then
  210. local sell_market = market.orders_for_items[item].sell_orders
  211. -- create the order and insert it into order arrays
  212. local order = {account=account, price=price, quantity=quantity, timestamp=minetest.get_gametime()}
  213. table.bininsert(sell_market, order, sell_comp)
  214. market.orders_for_items[item].sell_volume = market.orders_for_items[item].sell_volume + quantity
  215. end
  216. minetest.sound_play({name = "commoditymarket_register_opened", gain = 0.1}, {to_player=account.name})
  217. return true
  218. end
  219. local cancel_sell = function(market, item, order)
  220. local account = order.account
  221. local quantity = order.quantity
  222. local sell_market = market.orders_for_items[item].sell_orders
  223. remove_order(order, sell_market)
  224. market.orders_for_items[item].sell_volume = market.orders_for_items[item].sell_volume - quantity
  225. add_inventory_to_account(market, account, item, quantity)
  226. minetest.sound_play({name = "commoditymarket_register_closed", gain = 0.1}, {to_player=account.name})
  227. end
  228. -----------------------------------------------------------------------------------------------------------
  229. local test_buy = function(market, balance, item, price, quantity)
  230. local sell_market = market.orders_for_items[item].sell_orders
  231. local test_quantity = quantity
  232. local test_balance = balance
  233. local i = 0
  234. local sell_order = sell_market[#sell_market]
  235. while test_quantity > 0 and sell_order and sell_order.price <= price do
  236. local quantity_to_buy = math.min(sell_order.quantity, test_quantity)
  237. test_quantity = test_quantity - quantity_to_buy
  238. test_balance = test_balance - quantity_to_buy*sell_order.price
  239. i = i + 1
  240. sell_order = sell_market[#sell_market-i]
  241. end
  242. local spent = balance - test_balance
  243. test_balance = test_balance - test_quantity*price
  244. if test_balance < 0 then
  245. return false, spent, test_quantity
  246. end
  247. return true, spent, test_quantity
  248. end
  249. local add_buy = function(market, account, item, price, quantity)
  250. price = tonumber(price)
  251. quantity = tonumber(quantity)
  252. if price < 0 or quantity < 1 or not test_buy(market, account.balance, item, price, quantity) then
  253. minetest.sound_play({name = "commoditymarket_error", gain = 0.1}, {to_player=account.name})
  254. if price < 0 then
  255. minetest.chat_send_player(account.name, S("You can't pay less than nothing for an item."))
  256. elseif quantity < 1 then
  257. minetest.chat_send_player(account.name, S("You have to buy at least one item."))
  258. else
  259. minetest.chat_send_player(account.name, S("You can't afford that many of this item."))
  260. end
  261. return false
  262. end
  263. local sell_market = market.orders_for_items[item].sell_orders
  264. local sell_order = sell_market[#sell_market]
  265. local current_sell_volume = market.orders_for_items[item].sell_volume
  266. -- go through existing sell orders that are cheaper than or equal to the price
  267. -- we're wanting to offer, buying them up at the offered price until we run out of
  268. -- sell orders or run out of supply
  269. while quantity > 0 and sell_order and sell_order.price <= price do
  270. local quantity_to_buy = math.min(sell_order.quantity, quantity)
  271. quantity = quantity - quantity_to_buy
  272. local spent = quantity_to_buy*sell_order.price
  273. account.balance = account.balance - spent
  274. sell_order.account.balance = sell_order.account.balance + spent
  275. sell_order.quantity = sell_order.quantity - quantity_to_buy
  276. current_sell_volume = current_sell_volume - quantity_to_buy
  277. add_inventory_to_account(market, account, item, quantity_to_buy)
  278. if sell_order.account ~= account then
  279. -- don't update the last price if a player is just buying and selling from themselves
  280. market.orders_for_items[item].last_price = sell_order.price
  281. end
  282. log_sale(item, quantity_to_buy, sell_order.price, account, sell_order.account)
  283. -- Sell order completely used up, remove it
  284. if sell_order.quantity == 0 then
  285. table.remove(sell_market, #sell_market)
  286. end
  287. -- get the next sell order
  288. sell_order = sell_market[#sell_market]
  289. end
  290. market.orders_for_items[item].sell_volume = current_sell_volume
  291. if quantity > 0 then
  292. local buy_market = market.orders_for_items[item].buy_orders
  293. -- create the order for the remainder and insert it into order arrays
  294. local order = {account=account, price=price, quantity=quantity, timestamp=minetest.get_gametime()}
  295. account.balance = account.balance - quantity*price -- buy orders are pre-paid
  296. table.bininsert(buy_market, order, buy_comp)
  297. market.orders_for_items[item].buy_volume = market.orders_for_items[item].buy_volume + quantity
  298. end
  299. minetest.sound_play({name = "commoditymarket_register_opened", gain = 0.1}, {to_player=account.name})
  300. return true
  301. end
  302. local cancel_buy = function(market, item, order)
  303. local account = order.account
  304. local quantity = order.quantity
  305. local price = order.price
  306. local buy_market = market.orders_for_items[item].buy_orders
  307. market.orders_for_items[item].buy_volume = market.orders_for_items[item].buy_volume - quantity
  308. remove_order(order, buy_market)
  309. account.balance = account.balance + price*quantity
  310. minetest.sound_play({name = "commoditymarket_register_closed", gain = 0.1}, {to_player=account.name})
  311. end
  312. local initialize_market_item = function(orders_for_items, item)
  313. if orders_for_items[item] == nil then
  314. local lists = {}
  315. lists.buy_orders = {}
  316. lists.sell_orders = {}
  317. lists.buy_volume = 0
  318. lists.sell_volume = 0
  319. lists.item = item
  320. -- leave last_price nil to indicate it's never been sold before
  321. orders_for_items[item] = lists
  322. end
  323. end
  324. -----------------------------------------------------------------------------------------------------------
  325. -- Chat commands
  326. minetest.register_chatcommand("market.show", {
  327. params = "marketname",
  328. privs = {server=true},
  329. description = S("show market interface"),
  330. func = function(name, param)
  331. local market = commoditymarket.registered_markets[param]
  332. if market == nil then return end
  333. local formspec = market:get_formspec(market:get_account(name))
  334. minetest.show_formspec(name, "commoditymarket:"..param..":"..name, formspec)
  335. end,
  336. })
  337. minetest.register_chatcommand("market.list", {
  338. params = "",
  339. privs = {server=true},
  340. description = S("list all registered markets"),
  341. func = function(name, param)
  342. local list = {}
  343. for marketname, def in pairs(commoditymarket.registered_markets) do
  344. table.insert(list, marketname)
  345. end
  346. table.sort(list)
  347. minetest.chat_send_player(name, "Registered markets: " .. table.concat(list, ", "))
  348. end,
  349. })
  350. local remove_market_item = function(market, item)
  351. local marketitem = market.orders_for_items[item]
  352. if marketitem then
  353. local buy_orders = marketitem.buy_orders
  354. while #buy_orders > 0 do
  355. market:cancel_buy(item, buy_orders[#buy_orders])
  356. end
  357. local sell_orders = marketitem.sell_orders
  358. while #sell_orders > 0 do
  359. market:cancel_sell(item, sell_orders[#sell_orders])
  360. end
  361. market.orders_for_items[item] = nil
  362. end
  363. end
  364. minetest.register_chatcommand("market.removeitem", {
  365. params = "marketname item",
  366. privs = {server=true},
  367. description = S("remove item from market. All existing buys and sells will be canceled."),
  368. func = function(name, param)
  369. local params = param:split(" ")
  370. if #params ~= 2 then
  371. minetest.chat_send_player(name, "Incorrect parameter count")
  372. return
  373. end
  374. local market = commoditymarket.registered_markets[params[1]]
  375. if market == nil then
  376. minetest.chat_send_player(name, "No such market: " .. params[1])
  377. return
  378. end
  379. remove_market_item(market, params[2])
  380. end,
  381. })
  382. minetest.register_chatcommand("market.purge_unknowns", {
  383. params = "",
  384. privs = {server=true},
  385. description = S("removes all unknown items from all markets. All existing buys and sells for those items will be canceled."),
  386. func = function(name, param)
  387. for market_name, market in pairs(commoditymarket.registered_markets) do
  388. local items_to_remove = {}
  389. local items_to_move = {}
  390. for item, orders in pairs(market.orders_for_items) do
  391. local icon = commoditymarket.get_icon(item)
  392. if icon == "unknown_item.png" then
  393. table.insert(items_to_remove, item)
  394. end
  395. end
  396. for _, item in ipairs(items_to_remove) do
  397. minetest.chat_send_player(name, S("Purging item: @1 from market: @2", tostring(item), market_name))
  398. minetest.log("warning", "[commoditymarket] Purging unknown item: " .. tostring(item) .. " from market: " .. market_name)
  399. remove_market_item(market, item)
  400. end
  401. end
  402. end,
  403. })
  404. -- Used during development and debugging to find items that break the market formspecs when added
  405. local debugging_commands = false
  406. if debugging_commands then
  407. minetest.register_chatcommand("market.addeverything", {
  408. params = "marketname",
  409. privs = {server=true},
  410. description = S("Add all registered items to the provided market"),
  411. func = function(name, param)
  412. local params = param:split(" ")
  413. if #params ~= 1 then
  414. minetest.chat_send_player(name, "Incorrect parameter count")
  415. return
  416. end
  417. local market = commoditymarket.registered_markets[params[1]]
  418. if market == nil then
  419. minetest.chat_send_player(name, "No such market: " .. params[1])
  420. return
  421. end
  422. for item_name, def in pairs(minetest.registered_items) do
  423. initialize_market_item(market.orders_for_items, item_name)
  424. end
  425. end,
  426. })
  427. end
  428. -----------------------------------------------------------------------------------------------------------
  429. -- API exposed to the outside world
  430. local add_inventory = function(self, player_name, item, quantity)
  431. return add_inventory_to_account(self, get_account(self, player_name), item, quantity)
  432. end
  433. local remove_inventory = function(self, player_name, item, quantity)
  434. return remove_inventory_from_account(get_account(self, player_name), item, quantity)
  435. end
  436. local sell = function(self, player_name, item, quantity, price)
  437. return add_sell(self, get_account(self, player_name), item, price, quantity)
  438. end
  439. local buy = function(self, player_name, item, quantity, price)
  440. return add_buy(self, get_account(self, player_name), item, price, quantity)
  441. end
  442. -- Using this instead of minetest.serialize because of https://github.com/minetest/minetest/issues/8719
  443. local MP = minetest.get_modpath(minetest.get_current_modname())
  444. local persistence_store, persistence_load = dofile(MP.."/persistence.lua")
  445. local worldpath = minetest.get_worldpath()
  446. local load_market_data = function(marketname)
  447. local filename = worldpath .. "/market_"..marketname..".lua"
  448. return persistence_load(filename)
  449. end
  450. local save_market_data = function(market)
  451. local filename = worldpath .. "/market_"..market.name..".lua"
  452. local data = {}
  453. data.player_accounts = market.player_accounts
  454. data.orders_for_items = market.orders_for_items
  455. persistence_store(filename, data)
  456. return true
  457. end
  458. local make_doc_entry = function() return end
  459. if minetest.get_modpath("doc") then
  460. make_doc_entry = function(market_name, market_def)
  461. local currencies = {}
  462. for _, currency_item in ipairs(market_def.currency_ordered) do
  463. local item_def = minetest.registered_items[currency_item.item]
  464. table.insert(currencies, S("1 @1 = @2@3", item_def.description, market_def.currency_symbol, currency_item.amount))
  465. end
  466. local inventory_limit
  467. if market_def.inventory_limit then
  468. inventory_limit = S("Market inventory is limited to @1 items.", market_def.inventory_limit)
  469. else
  470. inventory_limit = S("Market has unlimited inventory space.")
  471. end
  472. local sell_limit
  473. if market_def.sell_limit then
  474. sell_limit = S("Total pending sell orders are limited to @1 items.", market_def.inventory_limit)
  475. else
  476. sell_limit = S("Market supports unlimited pending sell orders.")
  477. end
  478. doc.add_entry("commoditymarket", "market_"..market_name, {
  479. name = market_def.description,
  480. data = { text = market_def.long_description
  481. .."\n\n"
  482. ..S("Currency item values:") .. "\n " .. table.concat(currencies, "\n ")
  483. .."\n\n"
  484. ..inventory_limit
  485. .."\n"
  486. ..sell_limit
  487. }
  488. })
  489. end
  490. end
  491. commoditymarket.register_market = function(market_name, market_def)
  492. assert(not commoditymarket.registered_markets[market_name])
  493. assert(market_def.currency)
  494. market_def.currency_symbol = market_def.currency_symbol or "¤" -- \u{00A4} -- defaults to the generic currency symbol ("scarab")
  495. market_def.description = market_def.description or S("Market")
  496. market_def.long_description = market_def.long_description or S("A market where orders to buy or sell items can be placed and fulfilled.")
  497. -- Reprocess currency table into a form easier for the withdraw code to work with
  498. market_def.currency_ordered = {}
  499. for item, amount in pairs(market_def.currency) do
  500. table.insert(market_def.currency_ordered, {item=item, amount=amount})
  501. end
  502. table.sort(market_def.currency_ordered, function(currency1, currency2) return currency1.amount > currency2.amount end)
  503. make_doc_entry(market_name, market_def) -- market_def has now been normalized, make documentation for it if doc is installed.
  504. -- Just in case a developer supplied strings that don't work well in formspecs, escape them now so we don't have to do it
  505. -- wherever they're used.
  506. market_def.currency_symbol = minetest.formspec_escape(market_def.currency_symbol)
  507. market_def.description = minetest.formspec_escape(market_def.description)
  508. market_def.long_description = minetest.formspec_escape(market_def.long_description)
  509. local new_market = {}
  510. new_market.def = market_def
  511. commoditymarket.registered_markets[market_name] = new_market
  512. local loaded_data = load_market_data(market_name)
  513. if loaded_data then
  514. new_market.player_accounts = loaded_data.player_accounts
  515. new_market.orders_for_items = loaded_data.orders_for_items
  516. else
  517. new_market.player_accounts = {}
  518. new_market.orders_for_items = {}
  519. end
  520. -- If there's a list of initial items in the market def, initialize them. allow_item can trump this.
  521. local initial_items = market_def.initial_items
  522. if initial_items then
  523. -- defer until after to ensure that all initial items have been registered, so we can guard against invalid items
  524. minetest.after(0,
  525. function()
  526. for _, item in ipairs(initial_items) do
  527. if minetest.registered_items[item] and
  528. ((not market_def.allow_item) or market_def.allow_item(item)) and
  529. not market_def.currency[item] then
  530. initialize_market_item(new_market.orders_for_items, item)
  531. end
  532. end
  533. end)
  534. end
  535. market_def.initial_items = nil -- don't need this any more
  536. new_market.name = market_name
  537. new_market.add_inventory = add_inventory
  538. new_market.remove_inventory = remove_inventory
  539. new_market.sell = sell
  540. new_market.buy = buy
  541. new_market.cancel_sell = cancel_sell
  542. new_market.cancel_buy = cancel_buy
  543. new_market.get_formspec = commoditymarket.get_formspec
  544. new_market.get_account = get_account
  545. new_market.save = save_market_data
  546. -- save markets on shutdown
  547. minetest.register_on_shutdown(function() new_market:save() end)
  548. -- and also every ten minutes, to be on the safe side in case Minetest crashes
  549. -- TODO: a more sophisticated approach that checks whether the market data is "dirty" before actually saving
  550. local until_next_save = 600
  551. minetest.register_globalstep(function(dtime)
  552. until_next_save = until_next_save - dtime
  553. if until_next_save < 0 then
  554. new_market:save()
  555. until_next_save = 600
  556. end
  557. end)
  558. ----------------------------------------------------------------------
  559. -- Detached inventory for adding items into the market
  560. local inv = minetest.create_detached_inventory("commoditymarket:"..market_name, {
  561. allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
  562. return 0
  563. end,
  564. allow_put = function(inv, listname, index, stack, player)
  565. local item = stack:get_name()
  566. -- reject unknown items
  567. if minetest.registered_items[item] == nil then
  568. return 0
  569. end
  570. -- Currency items are always allowed
  571. if new_market.def.currency[item] then
  572. return stack:get_count()
  573. end
  574. -- only new tools, no used tools
  575. if stack:get_wear() ~= 0 then
  576. return 0
  577. end
  578. --nothing with metadata permitted
  579. local meta = stack:get_meta():to_table()
  580. local fields = meta.fields
  581. local inventory = meta.inventory
  582. if (fields and next(fields)) or (inventory and next(inventory)) then
  583. return 0
  584. end
  585. -- If there's no allow_item function defined, allow everything. Otherwise check if the item is allowed
  586. if (not market_def.allow_item) or market_def.allow_item(item) then
  587. local allowed_count = stack:get_count()
  588. if market_def.inventory_limit then
  589. -- limit additions to the inventory_limit, if there is one
  590. local current_count = 0
  591. for _, inventory_quantity in pairs(new_market:get_account(player:get_player_name()).inventory) do
  592. current_count = current_count + inventory_quantity
  593. end
  594. allowed_count = math.min(allowed_count, allowed_count + market_def.inventory_limit - (current_count+allowed_count))
  595. if allowed_count <= 0 then return 0 end
  596. end
  597. --ensures the item is in the market listing if it wasn't before
  598. initialize_market_item(new_market.orders_for_items, item)
  599. return allowed_count
  600. end
  601. return 0
  602. end,
  603. allow_take = function(inv, listname, index, stack, player)
  604. return 0
  605. end,
  606. on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
  607. end,
  608. on_take = function(inv, listname, index, stack, player)
  609. end,
  610. on_put = function(inv, listname, index, stack, player)
  611. if listname == "add" then
  612. local item = stack:get_name()
  613. local count = stack:get_count()
  614. new_market:add_inventory(player:get_player_name(), item, count)
  615. inv:set_list("add", {})
  616. local name = player:get_player_name()
  617. local formspec = new_market:get_formspec(new_market:get_account(name))
  618. minetest.show_formspec(name, "commoditymarket:"..market_name..":"..name, formspec)
  619. end
  620. end
  621. })
  622. inv:set_size("add", 1)
  623. end
  624. commoditymarket.show_market = function(market_name, player_name)
  625. local market = commoditymarket.registered_markets[market_name]
  626. if market == nil then return end
  627. local formspec = market:get_formspec(market:get_account(player_name))
  628. minetest.show_formspec(player_name, "commoditymarket:"..market_name..":"..player_name, formspec)
  629. end