Compare commits

...

5 Commits

1 changed files with 171 additions and 79 deletions

188
init.lua
View File

@ -1,14 +1,16 @@
--[[
basic_shop by rnd, gui design by Jozet, 2018
TODO:
buying: /shop opens gui OK
gui basics OK
filter OK
buy button action OK
sorting
selling: /sell price adds items in hand to shop OK
INSTRUCTIONS:
use /sell command to sell items (make shop).
admin can set up shops using negative price to make shop buy item from players - this way players can get money, but only up to 100.
TODO:
- ability to reverse money - item in admin shop OK
meaning: shop owner(admin) buys item and gives money to players. admin shop has infinite money
TODO: other players can do this too. problem: need additional storage for items if player not online
rating of shops: more money player gives for item more his rating is worth
--]]
modname = "basic_shop";
@ -16,18 +18,41 @@ basic_shop = {};
basic_shop.data = {}; -- {"item name", quantity, price, time_left, seller, minimal sell quantity}
basic_shop.guidata = {}; -- [name] = {idx = idx, filter = filter, sort = sort } (start index on cur. page, filter item name, sort_coloumn)
basic_shop.bank = {}; -- bank for offline players, [name] = {balance, deposit_time},
basic_shop.version = "20190929a"
basic_shop.version = "20201129a"
-------------------------
-- CONFIGURATION SETTINGS
-------------------------
basic_shop.items_on_page = 8 -- gui setting
basic_shop.maxprice = 1000000 -- maximum price players can set for 1 item
basic_shop.max_noob_money = 100 -- after this player no longer allowed to sell his goods to admin shop to prevent inflation
basic_shop.time_left = 60*60*24*7*2; -- 2 week before shop removed/bank account reset
basic_shop.allowances = { -- how much money players need to make more shops and levels
{5, 1}, -- noob: 5$ allows to make 1 shop
{100,5}, -- medium
{3000,25} -- pro
}
-- 2 initial admin shops to get items
basic_shop.admin_shops = { --{"item name", quantity, price, time_left, seller, minimal sell quantity}
[1] = {"default:dirt",1,-1,10^15,"*server*",1}, -- for skyblock
[2] = {"default:tree",1,-0.1,10^15,"*server*",1},
}
---------------------
-- END OF SETTINGS
---------------------
basic_shop.items_on_page = 8
basic_shop.maxprice = 1000000
basic_shop.time_left = 60*60*24*7; -- 1 week before shop removed/bank account reset
local filepath = minetest.get_worldpath()..'/' .. modname;
minetest.mkdir(filepath) -- create if non existent
save_shops = function()
local file,err = io.open(filepath..'/shops', 'wb');
if err then minetest.log("#basic_shop: error cant save data") return end
@ -36,14 +61,27 @@ end
local player_shops = {}; --[name] = count
load_shops = function()
local file,err = io.open(filepath..'/shops', 'rb')
if err then minetest.log("#basic_shop: error cant load data") return end
local told = minetest.get_gametime() - basic_shop.time_left; -- time of oldest shop before timeout
local data = minetest.deserialize(file:read("*a")) or {};file:close()
local data = {}
local file,err = io.open(filepath..'/shops', 'rb')
if err then
minetest.log("#basic_shop: error cant load data. creating new shops.")
else
data = minetest.deserialize(file:read("*a")) or {};file:close()
end
local out = {}
if not data[1] then -- no shops yet in file
for i = 1,#basic_shop.admin_shops do -- add admin shops first
out[#out+1]= basic_shop.admin_shops[i]
end
end
for i = 1,#data do
if data[i][4]>told then -- shop is recent, not too old
if data[i][4]>told then -- shop is recent, not too old, otherwise (skip) remove it
out[#out+1] = data[i]
player_shops[data[i][5]] = (player_shops[data[i][5]] or 0) + 1 -- how many shops player has
end
@ -86,7 +124,7 @@ local check_toplist = function(name,balance) -- too small to be on toplist -- at
toplist[name] = balance
if n+1>10 then toplist[mink] = nil end --remove minimal
--more than 10, have to throw out smallest one
--more than 10 entries, have to throw out smallest one
minb = 10^9; mink = "" -- find new minimal element
@ -96,7 +134,7 @@ local check_toplist = function(name,balance) -- too small to be on toplist -- at
toplist["_min"] = mink
end
local display_toplist = function()
local display_toplist = function(name)
local out = {};
for k,v in pairs(toplist) do
if k ~= "_min" then
@ -104,11 +142,14 @@ local display_toplist = function()
end
end
table.sort(out, function(a,b) return a[2]>b[2] end)
local ret = {"TOP RICHEST"};
local ret = {};
for i = 1,#out do
ret[#ret+1] = i .. ". " .. out[i][1] .. " " .. out[i][2]
end
minetest.chat_send_all(table.concat(ret,"\n"))
local form = "size [6,7] textarea[0,0.1;6.6,8.5;TOP SHOPS;TOP RICHEST;".. table.concat(ret,"\n").."]"
minetest.show_formspec(name, "basic_shop:toplist", form)
--minetest.chat_send_all(table.concat(ret,"\n"))
end
@ -118,7 +159,7 @@ save_bank = function()
file:write(minetest.serialize(basic_shop.bank)); file:close()
end
minetest.after(0, function() -- problem: before minetest.get_gametime() is nil
minetest.after(0, function() -- problem: before this minetest.get_gametime() is nil
load_shops()
load_bank()
end)
@ -157,18 +198,21 @@ basic_shop.show_shop_gui = function(name)
local idx = guidata.idx;
local sort = guidata.sort;
local filter = guidata.filter;
if string.find(filter,"%%") then filter = "" end
local data = basic_shop.data; -- whole list of items for sale
local idxdata = {}; -- list of idx of items for sale
if filter == "" then
for i = 1,#data do idxdata[i] = i end
guidata.count = #data
else
for i = 1,#data do
if string.find(data[i][1],filter) then
idxdata[#idxdata+1] = i
end
end
guidata.count = #idxdata
end
if guidata.sort>0 then
@ -183,6 +227,8 @@ basic_shop.show_shop_gui = function(name)
local m = basic_shop.items_on_page; -- default 8 items per page
local n = #idxdata; -- how many items in current selection
local pricesort = "";
if guidata.sort == 1 then pricesort = "+" elseif guidata.sort == 2 then pricesort = "-" end
local form = "size[10,8]".. -- width, height
"bgcolor[#222222cc; true]" ..
@ -193,7 +239,7 @@ basic_shop.show_shop_gui = function(name)
"label[0.4,0.7;" .. minetest.colorize("#aaa", "item") .. "]" ..
--"label[3,0.7;" .. minetest.colorize("#aaa", "price") .. "]" ..
"button[3,0.7;1,0.5;price;" .. minetest.colorize("#aaa", "price") .. "]" ..
"button[3,0.7;1.2,0.5;price;" .. minetest.colorize("#aaa", "price"..pricesort) .. "]" ..
"label[5,0.7;" .. minetest.colorize("#aaa", "time left") .. "]" ..
"label[6.5,0.7;" .. minetest.colorize("#aaa", "seller") .. "]" ..
@ -202,7 +248,7 @@ basic_shop.show_shop_gui = function(name)
"field[0.65,7.9;2,0.5;search;;".. guidata.filter .."] button[2.5,7.6;1.5,0.5;filter;refresh]"..
"button[4,7.6;1,0.5;help;help]"..
"button[6.6,7.6;1,0.5;left;<] button[8.6,7.6;1,0.5;right;>]" ..
"label[7.6,7.6; " .. math.floor(idx/m)+1 .." / " .. math.floor(n/m)+1 .."]";
"label[7.6,7.6; " .. math.ceil(idx/(m+1)) .." / " .. math.ceil(n/(m+1)) .."]";
local tabdata = {};
@ -211,7 +257,7 @@ basic_shop.show_shop_gui = function(name)
local t = basic_shop.time_left-minetest.get_gametime();
for i = idx, idxhigh do
local id = idxdata[i];
local id = idxdata[i] or 1;
local y = 1.3+(i-idx)*0.65
local ti = tonumber(data[id][4]) or 0;
local time_left = ""
@ -231,6 +277,8 @@ basic_shop.show_shop_gui = function(name)
end
end
local price = data[id][3]
if price>=0 then -- shop buys money and sells item
tabdata[i-idx+1] =
"item_image[0.4,".. y-0.1 .. ";0.7,0.7;".. data[id][1] .. "]" .. -- image
"label[1.1,".. y .. ";x ".. data[id][2] .. "/" .. data[id][6] .. "]" .. -- total_quantity
@ -239,6 +287,16 @@ basic_shop.show_shop_gui = function(name)
"label[6.5," .. y .. ";" .. minetest.colorize("#EE0", data[id][5]) .."]" .. -- seller
"image_button[8.5," .. y .. ";1.25,0.7;wool_black.png;buy".. id ..";buy ".. id .."]" -- buy button
.."tooltip[buy".. id ..";".. data[id][1] .. "]"
else -- shop buys item and sells money
tabdata[i-idx+1] =
"item_image[3.0,".. y-0.1 .. ";0.7,0.7;".. data[id][1] .. "]" .. -- image
"label[3.7,".. y .. ";x ".. data[id][2] .. "]" .. -- total_quantity
"label[0.4,".. y .. ";" .. minetest.colorize("#00ff36", -data[id][3].." $") .."]" .. -- price
"label[5,".. y ..";" .. time_left .."]" .. -- time left
"label[6.5," .. y .. ";" .. minetest.colorize("#EE0", data[id][5]) .."]" .. -- seller
"image_button[8.5," .. y .. ";1.25,0.7;wool_black.png;buy".. id ..";buy ".. id .."]" -- buy button
.."tooltip[buy".. id ..";".. data[id][1] .. "]"
end
end
minetest.show_formspec(name, "basic_shop", form .. table.concat(tabdata,""))
@ -246,12 +304,21 @@ end
local dout = minetest.chat_send_all;
local make_table_copy = function(tab)
local out = {};
for i = 1,#tab do out[i] = tab[i] end
return out
end
local remove_shop = function(idx)
local data = {};
for i = 1,idx-1 do data[i] = make_table_copy(basic_shop.data[i]) end -- expensive, but ok for 'small'<1000 number of shops
for i = idx+1,#basic_shop.data do data[i-1] = make_table_copy(basic_shop.data[i]) end
basic_shop.data = data;
end
minetest.register_on_player_receive_fields(
function(player, formname, fields)
if formname~="basic_shop" then return end
@ -278,14 +345,15 @@ minetest.register_on_player_receive_fields(
if fields.help then
local name = player:get_player_name();
local text = "Make a shop using /sell command while holding item to sell in hands. "..
"As your basic income you get 1 money for each 12 minutes of play, but only up to 100.\n\n"..
"Players get money by selling goods into admin made shops, but only up to ".. basic_shop.allowances[2][1] .. "$.\n\n"..
"Depending on how much money you have (/shop_money command) you get ability to create " ..
"more shops with variable life span:\n\n"..
" balance 0-4 : new player, can't create shops yet\n"..
" balance 0-99 : new trader, 1 shop\n"..
" balance 100-999 : medium trader, 5 shops\n"..
" balance 1000+ : pro trader, 25 shops\n\n"..
"All trader shop lifetime is one week ( after that shop closes down), for pro traders unlimited lifetime."
" balance 0-" .. basic_shop.allowances[1][1]-1 .. " : new player, can't create shops yet\n"..
" balance ".. basic_shop.allowances[1][1] .."-".. basic_shop.allowances[2][1]-1 .. " : new trader, " .. basic_shop.allowances[1][2] .. " shop\n"..
" balance " .. basic_shop.allowances[2][1] .. "-" .. basic_shop.allowances[3][1]-1 .. ": medium trader, " .. basic_shop.allowances[2][2] .. " shops\n"..
" balance " .. basic_shop.allowances[3][1] .. "+ : pro trader, " .. basic_shop.allowances[3][2] .. " shops\n\n"..
"All trader shop lifetime is one week ( after that shop closes down), for pro traders unlimited lifetime.\n\n"..
"Admin can set up shop that buys items and gives money by setting negative price when using /sell."
local form = "size [6,7] textarea[0,0;6.5,8.5;help;SHOP HELP;".. text.."]"
minetest.show_formspec(name, "basic_shop:help", form)
return
@ -297,7 +365,8 @@ minetest.register_on_player_receive_fields(
local n = guidata.count;
local m = basic_shop.items_on_page;
idx = idx - m-1;
if idx<0 then idx = math.max(n - m,0)+1 end
if idx<0 then idx = math.max(n - n%(m+1),0)+1 end
if idx>n then idx = math.max(n-m,1) end
guidata.idx = idx;
basic_shop.show_shop_gui(name)
return
@ -334,6 +403,8 @@ minetest.register_on_player_receive_fields(
local price = shop_item[3];
local seller = shop_item[5]
if price >=0 then -- normal mode, sell items, buy money
if seller ~= name then -- owner buys for free
if balance<price then
minetest.chat_send_player(name,"#basic_shop : you need " .. price .. " money to buy item " .. sel .. ", you only have " .. balance)
@ -349,6 +420,7 @@ minetest.register_on_player_receive_fields(
local bank_balance = bank_account[1] or 0;
basic_shop.bank[seller] = {bank_balance + price, minetest.get_gametime()} -- balance, time of deposit.
end
end
local inv = player:get_inventory();
@ -359,14 +431,32 @@ minetest.register_on_player_receive_fields(
shop_item[4] = minetest.get_gametime() -- time refresh
if shop_item[6]<=0 then --remove shop
player_shops[seller] = (player_shops[seller] or 1) - 1;
local data = {};
-- expensive, but ok for 'small'<1000 number of shops
for i = 1,sel-1 do data[i] = make_table_copy(basic_shop.data[i]) end
for i = sel+1,#basic_shop.data do data[i-1] = make_table_copy(basic_shop.data[i]) end
basic_shop.data = data;
remove_shop(sel)
end
minetest.chat_send_player(name,"#basic_shop : you bought " .. shop_item[1] .." x " .. shop_item[2] .. ", price " .. price .." $")
else -- price<0 -> admin shop buys item, gives money to player
-- TODO: if shop owner not admin only allow sell if he online so that he can receive items.
local balance = get_money(player);
if balance>=basic_shop.max_noob_money then
minetest.chat_send_player(name,"#basic_shop: you can no longer get more money by selling goods to admin shop (to prevent inflation) but you can still get money by selling to other players.")
return
end
local inv = player:get_inventory(); -- buyer, his name = name
if inv:contains_item("main",ItemStack(shop_item[1] .. " " .. shop_item[2])) then
inv:remove_item("main",ItemStack(shop_item[1] .. " " .. shop_item[2]));
balance = math.min(basic_shop.max_noob_money, balance-price)
set_money(player,balance)
minetest.chat_send_player(name,"#basic_shop : you sold " .. shop_item[1] .." x " .. shop_item[2] .. " for price " .. -price .." $")
if balance>=basic_shop.max_noob_money then
minetest.chat_send_player(name,"#basic_shop : CONGRATULATIONS! you are no longer noob merchant. now you can make more shops - look in help in /shop screen.")
end
end
end
basic_shop.show_shop_gui(name)
end
end
@ -387,6 +477,7 @@ minetest.register_on_joinplayer( -- if player has money from bank, give him the
end
)
--[[
local ts = 0
minetest.register_globalstep(function(dtime) -- time based income
ts = ts + dtime
@ -401,7 +492,7 @@ minetest.register_globalstep(function(dtime) -- time based income
end
end)
--]]
-- CHATCOMMANDS
@ -421,7 +512,7 @@ minetest.register_chatcommand("shop_top", {
privs = interact
},
func = function(name, param)
display_toplist()
display_toplist(name)
end
});
@ -437,13 +528,13 @@ minetest.register_chatcommand("sell", {
for word in param:gmatch("%S+") do words[#words+1]=word end
local price, count, total_count
if #words == 0 then
minetest.chat_send_player(name,"#basic_shop " .. basic_shop.version .. " : /sell price, where price must be between 0 and " .. basic_shop.maxprice .."\nadvanced: /sell price count total_sell_count")
minetest.chat_send_player(name,"#basic_shop " .. basic_shop.version .. " : /sell price, where price must be between 0 and " .. basic_shop.maxprice .."\nadvanced: /sell price count total_sell_count.")
return
end
price = tonumber(words[1]) or 0
if price<0 or price>basic_shop.maxprice then
minetest.chat_send_player(name,"#basic_shop: /sell price, where price must be between 0 and " .. basic_shop.maxprice .."\nadvanced: /sell price count total_sell_count")
price = tonumber(words[1]) or 0; price = (price>=0) and math.floor(price+0.5) or -math.floor(-price+0.5);
if price>basic_shop.maxprice then
minetest.chat_send_player(name,"#basic_shop: /sell price, where price must be less than " .. basic_shop.maxprice .."\nadvanced: /sell price count total_sell_count .")
return
end
count = tonumber(words[2])
@ -451,6 +542,8 @@ minetest.register_chatcommand("sell", {
local player = minetest.get_player_by_name(name); if not player then return end
if price<0 and not minetest.get_player_privs(name).kick then price = - price end -- non admin players can not make shops that give money for items
local stack = player:get_wielded_item()
local itemname = stack:get_name();
@ -470,13 +563,13 @@ minetest.register_chatcommand("sell", {
return
elseif balance<100 then -- noob
if shop_count>1 then allow = false end -- 1 shop for noob
elseif balance<1000 then -- medium
elseif balance<3000 then -- medium
if shop_count>5 then allow = false end -- 5 shop for medium
else -- pro
if shop_count>25 then allow = false end -- 25 shop for pro
end
if not allow then
minetest.chat_send_player(name,"#basic_shop: you need more money if you want more shops (100 for 5, 1000+ for 25).")
minetest.chat_send_player(name,"#basic_shop: you need more money if you want more shops (100 for 5, 3000+ for 25). Currently " .. shop_count .. " shops and " .. balance .. " money.")
return
end
@ -503,11 +596,10 @@ minetest.register_chatcommand("sell", {
--{"item name", quantity, price, time_left, seller}
data[#data+1 ] = { itemname, count, price, minetest.get_gametime(), name, total_count};
if balance>= 1000 then data[#data][4] = 10^15; end -- if player is 'pro' then remove time limit, shop will never be too old
if balance>= basic_shop.allowances[3][1] then data[#data][4] = 10^15; end -- if player is 'pro' then remove time limit, shop will never be too old
minetest.chat_send_player(name,"#basic_shop : " .. itemname .. " x " .. count .."/"..total_count .." put on sale for the price " .. price .. ". To remove item simply go /shop and buy it (for free).")
end
})
@ -531,7 +623,7 @@ minetest.register_chatcommand("shop_set_money", {
},
func = function(name, param)
local pname, amount
pname,amount = string.match(param,"(%a+) (%d+)");
pname,amount = string.match(param,"^([%w_]+)%s+(.+)");
if not pname or not amount then minetest.chat_send_player(name,"usage: shop_set_money NAME AMOUNT") return end
amount = tonumber(amount) or 0;
local player = minetest.get_player_by_name(pname); if not player then return end