From e98a3debe01248b290a65c083f0f83cae323f9bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20P=C3=A9rez-Cerezo?= Date: Wed, 11 Jul 2018 16:30:44 +0200 Subject: added patch from SmallJoker --- init.lua | 1243 +++++++++++++++++++++++++++++++------------------------------- 1 file changed, 619 insertions(+), 624 deletions(-) diff --git a/init.lua b/init.lua index 2c3fc8d..b54edc7 100644 --- a/init.lua +++ b/init.lua @@ -1,624 +1,619 @@ -smartshop={user={},tmp={},dir={{x=0,y=0,z=-1},{x=-1,y=0,z=0},{x=0,y=0,z=1},{x=1,y=0,z=0}},dpos={ -{{x=0.2,y=0.2,z=0},{x=-0.2,y=0.2,z=0},{x=0.2,y=-0.2,z=0},{x=-0.2,y=-0.2,z=0}}, -{{x=0,y=0.2,z=0.2},{x=0,y=0.2,z=-0.2},{x=0,y=-0.2,z=0.2},{x=0,y=-0.2,z=-0.2}}, -{{x=-0.2,y=0.2,z=0},{x=0.2,y=0.2,z=0},{x=-0.2,y=-0.2,z=0},{x=0.2,y=-0.2,z=0}}, -{{x=0,y=0.2,z=-0.2},{x=0,y=0.2,z=0.2},{x=0,y=-0.2,z=-0.2},{x=0,y=-0.2,z=0.2}}} -} - --- table with itemname: number of items being traded -smartshop.itemstats = {} -smartshop.itemprices = {} -smartshop.stuffsold = {} - - - -smartshop.itemsatpos = function(pos, item, count) - -- set number of items of type 'item' sold at position 'pos' - if smartshop.itemstats[item] == nil then - smartshop.itemstats[item] = {} - end - smartshop.itemstats[item][pos] = count - local file = io.open(minetest.get_worldpath().."/smartshop_itemcounts.txt", "w") - if file then - file:write(minetest.serialize(smartshop.itemstats)) - file:close() - end -end - -smartshop.itempriceatpos = function(pos, item, price) - -- set number of items of type 'item' sold at position 'pos' - if smartshop.itemprices[item] == nil then - smartshop.itemprices[item] = {} - end - local file = io.open(minetest.get_worldpath().."/smartshop_itemprices.txt", "w") - if file then - file:write(minetest.serialize(smartshop.itemprices)) - file:close() - end - smartshop.itemprices[item][pos] = price -end - -smartshop.minegeldtonumber = function(stack) - -- return number of minegeld in stack, returns nil if stack is not composed of minegeld - count = stack:get_count() - if count == 0 then - return 0 - end - if stack:get_name() == "currency:minegeld" then - return count - elseif stack:get_name() == "currency:minegeld_5" then - return count * 5 - elseif stack:get_name() == "currency:minegeld_10" then - return count * 10 - else - return nil - end -end - - -minetest.register_craft({ - output = "smartshop:shop", - recipe = { - {"default:chest_locked", "default:chest_locked", "default:chest_locked"}, - {"default:sign_wall_wood", "default:chest_locked", "default:sign_wall_wood"}, - {"default:sign_wall_wood", "default:torch", "default:sign_wall_wood"}, - } -}) -smartshop.get_human_name = function(item) - if core.registered_items[item] then - return core.registered_items[item].description - else - return "Unknown Item" - end -end - -smartshop.use_offer=function(pos,player,n) - local pressed={} - pressed["buy" .. n]=true - smartshop.user[player:get_player_name()]=pos - smartshop.receive_fields(player,pressed) - smartshop.user[player:get_player_name()]=nil - smartshop.update(pos) -end - -smartshop.get_offer=function(pos) - if not pos or not minetest.get_node(pos) then return end - if minetest.get_node(pos).name~="smartshop:shop" then return end - local meta=minetest.get_meta(pos) - local inv=meta:get_inventory() - local offer={} - for i=1,4,1 do - offer[i]={ - give=inv:get_stack("give" .. i,1):get_name(), - give_count=inv:get_stack("give" .. i,1):get_count(), - pay=inv:get_stack("pay" .. i,1):get_name(), - pay_count=inv:get_stack("pay" .. i,1):get_count(), - } - end - return offer -end - -smartshop.send_mail=function(owner, pos, item) - if not minetest.get_modpath( "mail" ) then - return - end - local spos = "("..pos.x..", "..pos.y..", "..pos.z..")" - mail.send("DO NOT REPLY", owner, "Out of "..smartshop.get_human_name(item).." at "..spos, "Your smartshop at "..spos.." is out of "..smartshop.get_human_name(item)..". Please restock") -end - - -smartshop.receive_fields=function(player,pressed) - if pressed.customer then - return smartshop.showform(smartshop.user[player:get_player_name()],player,true) - elseif pressed.tooglelime then - local pos=smartshop.user[player:get_player_name()] - local meta=minetest.get_meta(pos) - local pname=player:get_player_name() - if meta:get_int("type")==0 then - meta:set_int("type",1) - minetest.chat_send_player(pname, "Your stock is limited") - else - meta:set_int("type",0) - minetest.chat_send_player(pname, "Your stock is unlimited") - end - elseif not pressed.quit then - local n=1 - for i=1,4,1 do - n=i - if pressed["buy" .. i] then break end - end - local pos=smartshop.user[player:get_player_name()] - if not pos then - return - end - local meta=minetest.get_meta(pos) - local type=meta:get_int("type") - local inv=meta:get_inventory() - local pinv=player:get_inventory() - local pname=player:get_player_name() - if pressed["buy" .. n] then - local name=inv:get_stack("give" .. n,1):get_name() - local stack=name .." ".. inv:get_stack("give" .. n,1):get_count() - local pay=inv:get_stack("pay" .. n,1):get_name() .." ".. inv:get_stack("pay" .. n,1):get_count() - if name~="" then - if type==1 and inv:room_for_item("main", pay)==false then minetest.chat_send_player(pname, "Error: The owner's stock is full, can't receive, exchange aborted.") return end - if meta:get_int("ghost") ~=1 then - -- transition shops to ghost inventory. - for i=1,4 do - if inv:room_for_item("main", "pay"..i) and inv:room_for_item("main", "give"..i) then - meta:set_int("ghost", 1) - inv:add_item("main", inv:get_stack("pay"..i,1)) - inv:add_item("main", inv:get_stack("give"..i,1)) - end - end - end - if type==1 and inv:contains_item("main", stack)==false then - minetest.chat_send_player(pname, "Error: "..smartshop.get_human_name(name).." is sold out.") - if not meta:get_int("alerted") or meta:get_int("alerted") == 0 then - meta:set_int("alerted",1) -- Do not alert twice - smartshop.send_mail(meta:get_string("owner"), pos, name) - end - return - end - if not pinv:contains_item("main", pay) then minetest.chat_send_player(pname, "Error: You don't have enough in your inventory to buy this, exchange aborted.") return end - if not pinv:room_for_item("main", stack) then minetest.chat_send_player(pname, "Error: Your inventory is full, exchange aborted.") return end - pinv:remove_item("main", pay) - pinv:add_item("main", stack) - if type==1 then - inv:remove_item("main", stack) - inv:add_item("main", pay) - if not inv:contains_item("main", stack) and (not meta:get_int("alerted") or meta:get_int("alerted") == 0) then - meta:set_int("alerted",1) -- Do not alert twice - smartshop.send_mail(meta:get_string("owner"), pos, name) - end - end - end - end - else - local pos=smartshop.user[player:get_player_name()] - smartshop.update_info(pos) - if smartshop.user[player:get_player_name()] or minetest.check_player_privs(player:get_player_name(), {protection_bypass=true}) then - local meta=minetest.get_meta(smartshop.user[player:get_player_name()]) - if meta:get_string("owner")==player:get_player_name() then - smartshop.update(smartshop.user[player:get_player_name()],"update") - end - end - smartshop.user[player:get_player_name()]=nil - end -end - -minetest.register_on_player_receive_fields(function(player, form, pressed) - if form=="smartshop.showform" then - smartshop.receive_fields(player,pressed) - end -end) - - - - -smartshop.update_info=function(pos) - if not pos then - return - end - local meta=minetest.get_meta(pos) - local spos=minetest.pos_to_string(pos) - local inv = meta:get_inventory() - local owner=meta:get_string("owner") - if meta:get_int("type")==0 then - meta:set_string("infotext","(Smartshop by " .. owner ..") Stock is unlimited") - return false - end - local name="" - local count=0 - local stuff={} - for i=1,4,1 do - stuff["count" ..i]=inv:get_stack("give" .. i,1):get_count() - stuff["name" ..i]=inv:get_stack("give" .. i,1):get_name() - stuff["stock" ..i]=0 -- stuff["count" ..i] - local mg_price = smartshop.minegeldtonumber(inv:get_stack("pay" .. i,1)) - if mg_price ~= nil then - stuff["pay"..i] = mg_price/stuff["count" ..i] - end - stuff["buy" ..i]=0 - for ii=1,32,1 do - name=inv:get_stack("main",ii):get_name() - count=inv:get_stack("main",ii):get_count() - if name==stuff["name" ..i] then - stuff["stock" ..i]=stuff["stock" ..i]+count - end - end - local nstr=(stuff["stock" ..i]/stuff["count" ..i]) .."" - nstr=nstr.split(nstr, ".") - stuff["buy" ..i]=tonumber(nstr[1]) - if stuff["name" ..i]=="" or stuff["buy" ..i]==0 then - stuff["buy" ..i]="" - stuff["name" ..i]="" - if smartshop.stuffsold[spos..i] then - smartshop.itemsatpos(spos, smartshop.stuffsold[spos..i], 0) - smartshop.itempriceatpos(spos, smartshop.stuffsold[spos..i], nil) - smartshop.stuffsold[spos..i] = nil - end - else - smartshop.itemsatpos(spos, stuff["name"..i], stuff["buy"..i]*stuff["count" ..i]) - smartshop.itempriceatpos(spos, stuff["name"..i], stuff["pay"..i]) - smartshop.stuffsold[spos..i] = stuff["name"..i] - stuff["name"..i] = smartshop.get_human_name(stuff["name"..i]) - stuff["buy" ..i]="(" ..stuff["buy" ..i] ..") " - stuff["name" ..i]=stuff["name" ..i] .."\n" - end - end - meta:set_string("infotext", - "(Smartshop by " .. owner ..") Purchases left:\n" - .. stuff.buy1 .. stuff.name1 - .. stuff.buy2 .. stuff.name2 - .. stuff.buy3 .. stuff.name3 - .. stuff.buy4 .. stuff.name4 - ) -end - - - - -smartshop.update=function(pos,stat) ---clear - local spos=minetest.pos_to_string(pos) - for _, ob in ipairs(minetest.env:get_objects_inside_radius(pos, 2)) do - if ob and ob:get_luaentity() and ob:get_luaentity().smartshop and ob:get_luaentity().pos==spos then - ob:remove() - end - end - if stat=="clear" then return end ---update - local meta=minetest.get_meta(pos) - local inv = meta:get_inventory() - local node=minetest.get_node(pos) - local dp = smartshop.dir[node.param2+1] - if not dp then return end - pos.x = pos.x + dp.x*0.01 - pos.y = pos.y + dp.y*6.5/16 - pos.z = pos.z + dp.z*0.01 - for i=1,4,1 do - local item=inv:get_stack("give" .. i,1):get_name() - local pos2=smartshop.dpos[node.param2+1][i] - if item~="" then - smartshop.tmp.item=item - smartshop.tmp.pos=spos - local e = minetest.env:add_entity({x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z},"smartshop:item") - e:setyaw(math.pi*2 - node.param2 * math.pi/2) - end - end -end - - -minetest.register_entity("smartshop:item",{ - hp_max = 1, - visual="wielditem", - visual_size={x=.20,y=.20}, - collisionbox = {0,0,0,0,0,0}, - physical=false, - textures={"air"}, - smartshop=true, - type="", - on_activate = function(self, staticdata) - if smartshop.tmp.item ~= nil then - self.item=smartshop.tmp.item - self.pos=smartshop.tmp.pos - smartshop.tmp={} - else - if staticdata ~= nil and staticdata ~= "" then - local data = staticdata:split(';') - if data and data[1] and data[2] then - self.item = data[1] - self.pos = data[2] - end - end - end - if self.item ~= nil then - self.object:set_properties({textures={self.item}}) - else - self.object:remove() - end - end, - get_staticdata = function(self) - if self.item ~= nil and self.pos ~= nil then - return self.item .. ';' .. self.pos - end - return "" - end, -}) - - -smartshop.showform=function(pos,player,re) - local meta=minetest.get_meta(pos) - local creative=meta:get_int("creative") - local inv = meta:get_inventory() - local gui="" - local spos=pos.x .. "," .. pos.y .. "," .. pos.z - local owner=meta:get_string("owner")==player:get_player_name() - if minetest.check_player_privs(player:get_player_name(), {protection_bypass=true}) then owner=true end - if re then owner=false end - smartshop.user[player:get_player_name()]=pos - if owner then - meta:set_int("alerted",0) -- Player has been there to refill - gui="" - .."size[8,10]" - .."button_exit[6,0;1.5,1;customer;Customer]" - .."label[0,0.2;Item:]" - .."label[0,1.2;Price:]" - .."list[nodemeta:" .. spos .. ";give1;2,0;1,1;]" - .."list[nodemeta:" .. spos .. ";pay1;2,1;1,1;]" - .."list[nodemeta:" .. spos .. ";give2;3,0;1,1;]" - .."list[nodemeta:" .. spos .. ";pay2;3,1;1,1;]" - .."list[nodemeta:" .. spos .. ";give3;4,0;1,1;]" - .."list[nodemeta:" .. spos .. ";pay3;4,1;1,1;]" - .."list[nodemeta:" .. spos .. ";give4;5,0;1,1;]" - .."list[nodemeta:" .. spos .. ";pay4;5,1;1,1;]" - if creative==1 then - gui=gui .."label[0.5,-0.4;Your stock is unlimited becaouse you have creative or give]" - .."button[6,1;2.2,1;tooglelime;Toggle limit]" - end - gui=gui - .."list[nodemeta:" .. spos .. ";main;0,2;8,4;]" - .."list[current_player;main;0,6.2;8,4;]" - .."listring[nodemeta:" .. spos .. ";main]" - .."listring[current_player;main]" - else - gui="" - .."size[8,6]" - .."list[current_player;main;0,2.2;8,4;]" - .."label[0,0.2;Item:]" - .."label[0,1.2;Price:]" - .."list[nodemeta:" .. spos .. ";give1;2,0;1,1;]" - .."item_image_button[2,1;1,1;".. inv:get_stack("pay1",1):get_name() ..";buy1;\n\n\b\b\b\b\b" .. inv:get_stack("pay1",1):get_count() .."]" - .."list[nodemeta:" .. spos .. ";give2;3,0;1,1;]" - .."item_image_button[3,1;1,1;".. inv:get_stack("pay2",1):get_name() ..";buy2;\n\n\b\b\b\b\b" .. inv:get_stack("pay2",1):get_count() .."]" - .."list[nodemeta:" .. spos .. ";give3;4,0;1,1;]" - .."item_image_button[4,1;1,1;".. inv:get_stack("pay3",1):get_name() ..";buy3;\n\n\b\b\b\b\b" .. inv:get_stack("pay3",1):get_count() .."]" - .."list[nodemeta:" .. spos .. ";give4;5,0;1,1;]" - .."item_image_button[5,1;1,1;".. inv:get_stack("pay4",1):get_name() ..";buy4;\n\n\b\b\b\b\b" .. inv:get_stack("pay4",1):get_count() .."]" - end - minetest.after((0.1), function(gui) - return minetest.show_formspec(player:get_player_name(), "smartshop.showform",gui) - end, gui) -end - -minetest.register_node("smartshop:shop", { - description = "Smartshop", - tiles = {"default_chest_top.png^[colorize:#ffffff77^default_obsidian_glass.png"}, - groups = {choppy = 2, oddly_breakable_by_hand = 1,tubedevice = 1, tubedevice_receiver = 1}, - drawtype="nodebox", - node_box = {type="fixed",fixed={-0.5,-0.5,-0.0,0.5,0.5,0.5}}, - paramtype2="facedir", - paramtype = "light", - sunlight_propagates = true, - light_source = 10, - tube = {insert_object = function(pos, node, stack, direction) - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - local added = inv:add_item("main", stack) - smartshop.update_info(pos) - return added - end, - can_insert = function(pos, node, stack, direction) - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - for i=1,4 do - local sellitem = inv:get_stack("give"..i,1):get_name() - if sellitem == stack:get_name() then - return inv:room_for_item("main", stack) - end --- minetest.chat_send_all(sellitem) - end - -- - return false - end, - input_inventory = "main", - connect_sides = {left = 1, right = 1, front = 1, back = 1, top = 1, bottom = 1}}, -after_place_node = function(pos, placer) - local meta=minetest.get_meta(pos) - meta:set_string("owner",placer:get_player_name()) - meta:set_string("infotext", "Shop by: " .. placer:get_player_name()) - meta:set_int("type",1) - if minetest.check_player_privs(placer:get_player_name(), {creative=true}) or minetest.check_player_privs(placer:get_player_name(), {give=true}) then - meta:set_int("creative",1) - meta:set_int("type",0) - end - end, -on_construct = function(pos) - local meta=minetest.get_meta(pos) - meta:set_int("state", 0) - meta:get_inventory():set_size("main", 32) - meta:get_inventory():set_size("give1", 1) - meta:get_inventory():set_size("pay1", 1) - meta:get_inventory():set_size("give2", 1) - meta:get_inventory():set_size("pay2", 1) - meta:get_inventory():set_size("give3", 1) - meta:get_inventory():set_size("pay3", 1) - meta:get_inventory():set_size("give4", 1) - meta:get_inventory():set_size("pay4", 1) - meta:set_int("ghost", 1) - end, -on_rightclick = function(pos, node, player, itemstack, pointed_thing) - smartshop.showform(pos,player) - end, -allow_metadata_inventory_put = function(pos, listname, index, stack, player) - if minetest.get_meta(pos):get_string("owner")==player:get_player_name() or minetest.check_player_privs(player:get_player_name(), {protection_bypass=true}) then - local meta = minetest.get_meta(pos) - if meta:get_int("ghost") == 1 and (string.find(listname, "pay") or string.find(listname, "give")) then - local inv = minetest.get_inventory({type="node", pos=pos}) --- minetest.chat_send_all( inv:get_stack(listname, index):get_name()..stack:get_name()) - if inv:get_stack(listname, index):get_name() == stack:get_name() then - inv:add_item(listname, stack) - else - inv:set_stack(listname, index, stack) - end - return 0 - end - return stack:get_count() - end - return 0 -end, -allow_metadata_inventory_take = function(pos, listname, index, stack, player) - if minetest.get_meta(pos):get_string("owner")==player:get_player_name() or minetest.check_player_privs(player:get_player_name(), {protection_bypass=true}) then - local meta = minetest.get_meta(pos) - if meta:get_int("ghost") == 1 and (string.find(listname, "pay") or string.find(listname, "give")) then - local inv = minetest.get_inventory({type="node", pos=pos}) - inv:set_stack(listname, index, ItemStack("")) - return 0 - end - return stack:get_count() - end - return 0 -end, -allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) - if minetest.get_meta(pos):get_string("owner")==player:get_player_name() or minetest.check_player_privs(player:get_player_name(), {protection_bypass=true}) then - local meta = minetest.get_meta(pos) - local inv = minetest.get_inventory({type="node", pos=pos}) - if meta:get_int("ghost") ~= 1 then - return count - end - if (string.find(from_list, "pay") or string.find(from_list, "give")) and to_list == "main" then - inv:set_stack(from_list, from_index, ItemStack("")) - return 0 - elseif (string.find(to_list, "pay") or string.find(to_list, "give")) and from_list == "main" then - if inv:get_stack(to_list, to_index):get_name() == inv:get_stack(from_list, from_index):get_name() then - inv:add_item(to_list, inv:get_stack(from_list, from_index)) - else - inv:set_stack(to_list, to_index, inv:get_stack(from_list, from_index)) - inv:set_stack(from_list, from_index, inv:get_stack(from_list, from_index)) - end - return 0 - else - return count - end - end - return 0 -end, -can_dig = function(pos, player) - local meta=minetest.get_meta(pos) - local inv=meta:get_inventory() - if ((meta:get_string("owner")==player:get_player_name() or minetest.check_player_privs(player:get_player_name(), {protection_bypass=true})) and inv:is_empty("main") and inv:is_empty("pay1") and inv:is_empty("pay2") and inv:is_empty("pay3") and inv:is_empty("pay4") and inv:is_empty("give1") and inv:is_empty("give2") and inv:is_empty("give3") and inv:is_empty("give4")) or meta:get_string("owner")=="" then - smartshop.update(pos,"clear") - return true - end - end, -}) - -smartshop.get_item_count = function(name) - sum = 0 - if smartshop.itemstats[name] == nil then - return 0 - end - for i, k in pairs(smartshop.itemstats[name]) do - sum = sum + k - end - return sum -end - -smartshop.get_shop_count = function(name) - sum = 0 - if smartshop.itemstats[name] == nil then - return 0 - end - for i, k in pairs(smartshop.itemstats[name]) do - sum = sum + 1 - end - return sum -end - -smartshop.get_item_price = function(name) - sum = smartshop.get_item_count(name) - if smartshop.itemprices[name] == nil then - return 0 - end - if sum == 0 then - return 0 - end - psum = 0 - for i, k in pairs(smartshop.itemprices[name]) do - psum = psum + k*smartshop.itemstats[name][i] - end - return psum/sum -end - - -minetest.register_chatcommand("smstats", { - description = "Get number of items sold", - params = "", - func = function(plname, params) - local name = params:match("(%S+)") - if not (name) then - return false, "Usage: /smstats " - end - if not smartshop.itemstats[name] then - return false, "No stats on "..name - end - sum = smartshop.get_item_count(name) - minetest.chat_send_player(plname, "Number of items: "..sum) - minetest.chat_send_player(plname, "Number of shops offering item: "..smartshop.get_shop_count(name)) - if sum == 0 then - return - end - price = smartshop.get_item_price(name) - minetest.chat_send_player(plname, "Average price: "..string.format("%.3f",price)) - return true --- local ok, e = xban.ban_player(plname, name, nil, reason) --- return ok, ok and ("Banned %s."):format(plname) or e - end, -}) - -smartshop.report = function () - local file = io.open(minetest.get_worldpath().."/smartshop_report.txt", "w") - if not file then - return false, "could not write to file" - end - for i,k in pairs(smartshop.itemstats) do - local count = smartshop.get_item_count(i) - local price = smartshop.get_item_price(i) - file:write(i.." "..count.." "..string.format("%.3f", price).." "..smartshop.get_shop_count(i).."\n") - end - file:close() -end - -minetest.register_chatcommand("smreport", { - description = "Get number of items sold", - func = function(plname, params) - smartshop.report() - end, -}) - -local timer = 0 -minetest.register_globalstep(function(dtime) - timer = timer + dtime; - if timer >= 100 then - smartshop.report() - timer = 0 - end -end) - - -if false then -- This lbm is used to add pre-update smartshops to the price database. Activate with care! Warning: very slow. - minetest.register_lbm({ - name = "smartshop:update", - nodenames = {"smartshop:shop"}, - action = function(pos, node) - smartshop.update_info(pos) - end, - }) -end - --- load itemstats -local file = io.open(minetest.get_worldpath().."/smartshop_itemcounts.txt", "r") -if file then - local table = minetest.deserialize(file:read("*all")) - if type(table) == "table" then - smartshop.itemstats = table - end -end -local file = io.open(minetest.get_worldpath().."/smartshop_itemprices.txt", "r") -if file then - local table = minetest.deserialize(file:read("*all")) - if type(table) == "table" then - smartshop.itemprices = table - end -end +smartshop={user={},tmp={},dir={{x=0,y=0,z=-1},{x=-1,y=0,z=0},{x=0,y=0,z=1},{x=1,y=0,z=0}},dpos={ +{{x=0.2,y=0.2,z=0},{x=-0.2,y=0.2,z=0},{x=0.2,y=-0.2,z=0},{x=-0.2,y=-0.2,z=0}}, +{{x=0,y=0.2,z=0.2},{x=0,y=0.2,z=-0.2},{x=0,y=-0.2,z=0.2},{x=0,y=-0.2,z=-0.2}}, +{{x=-0.2,y=0.2,z=0},{x=0.2,y=0.2,z=0},{x=-0.2,y=-0.2,z=0},{x=0.2,y=-0.2,z=0}}, +{{x=0,y=0.2,z=-0.2},{x=0,y=0.2,z=0.2},{x=0,y=-0.2,z=-0.2},{x=0,y=-0.2,z=0.2}}} +} + +-- table with itemname: number of items being traded +smartshop.itemstats = {} +smartshop.itemprices = {} +smartshop.stuffsold = {} + + + +smartshop.itemsatpos = function(pos, item, count) + -- set number of items of type 'item' sold at position 'pos' + if smartshop.itemstats[item] == nil then + smartshop.itemstats[item] = {} + end + smartshop.itemstats[item][pos] = count + local file = io.open(minetest.get_worldpath().."/smartshop_itemcounts.txt", "w") + if file then + file:write(minetest.serialize(smartshop.itemstats)) + file:close() + end +end + +smartshop.itempriceatpos = function(pos, item, price) + -- set number of items of type 'item' sold at position 'pos' + if smartshop.itemprices[item] == nil then + smartshop.itemprices[item] = {} + end + local file = io.open(minetest.get_worldpath().."/smartshop_itemprices.txt", "w") + if file then + file:write(minetest.serialize(smartshop.itemprices)) + file:close() + end + smartshop.itemprices[item][pos] = price +end + +smartshop.minegeldtonumber = function(stack) + -- return number of minegeld in stack, returns nil if stack is not composed of minegeld + count = stack:get_count() + if count == 0 then + return 0 + end + if stack:get_name() == "currency:minegeld" then + return count + elseif stack:get_name() == "currency:minegeld_5" then + return count * 5 + elseif stack:get_name() == "currency:minegeld_10" then + return count * 10 + else + return nil + end +end + + +minetest.register_craft({ + output = "smartshop:shop", + recipe = { + {"default:chest_locked", "default:chest_locked", "default:chest_locked"}, + {"default:sign_wall_wood", "default:chest_locked", "default:sign_wall_wood"}, + {"default:sign_wall_wood", "default:torch", "default:sign_wall_wood"}, + } +}) +smartshop.get_human_name = function(item) + if core.registered_items[item] then + return core.registered_items[item].description + else + return "Unknown Item" + end +end + +smartshop.use_offer=function(pos,player,n) + local pressed={} + pressed["buy" .. n]=true + smartshop.user[player:get_player_name()]=pos + smartshop.receive_fields(player,pressed) + smartshop.user[player:get_player_name()]=nil + smartshop.update(pos) +end + +smartshop.get_offer=function(pos) + if not pos or not minetest.get_node(pos) then return end + if minetest.get_node(pos).name~="smartshop:shop" then return end + local meta=minetest.get_meta(pos) + local inv=meta:get_inventory() + local offer={} + for i=1,4,1 do + offer[i]={ + give=inv:get_stack("give" .. i,1):get_name(), + give_count=inv:get_stack("give" .. i,1):get_count(), + pay=inv:get_stack("pay" .. i,1):get_name(), + pay_count=inv:get_stack("pay" .. i,1):get_count(), + } + end + return offer +end + +smartshop.send_mail=function(owner, pos, item) + if not minetest.get_modpath( "mail" ) then + return + end + local spos = "("..pos.x..", "..pos.y..", "..pos.z..")" + mail.send("DO NOT REPLY", owner, "Out of "..smartshop.get_human_name(item).." at "..spos, "Your smartshop at "..spos.." is out of "..smartshop.get_human_name(item)..". Please restock") +end + + +smartshop.receive_fields=function(player,pressed) + local pname = player:get_player_name() + local pos = smartshop.user[pname] + if not pos then + return + end + if pressed.customer then + return smartshop.showform(pos, player, true) + elseif pressed.tooglelime then + local meta=minetest.get_meta(pos) + if meta:get_int("type")==0 then + meta:set_int("type",1) + minetest.chat_send_player(pname, "Your stock is limited") + else + meta:set_int("type",0) + minetest.chat_send_player(pname, "Your stock is unlimited") + end + elseif not pressed.quit then + local n=1 + for i=1,4,1 do + n=i + if pressed["buy" .. i] then break end + end + local meta=minetest.get_meta(pos) + local type=meta:get_int("type") + local inv=meta:get_inventory() + local pinv=player:get_inventory() + if pressed["buy" .. n] then + local name=inv:get_stack("give" .. n,1):get_name() + local stack=name .." ".. inv:get_stack("give" .. n,1):get_count() + local pay=inv:get_stack("pay" .. n,1):get_name() .." ".. inv:get_stack("pay" .. n,1):get_count() + if name~="" then + if type==1 and inv:room_for_item("main", pay)==false then minetest.chat_send_player(pname, "Error: The owner's stock is full, can't receive, exchange aborted.") return end + if meta:get_int("ghost") ~=1 then + -- transition shops to ghost inventory. + for i=1,4 do + if inv:room_for_item("main", "pay"..i) and inv:room_for_item("main", "give"..i) then + meta:set_int("ghost", 1) + inv:add_item("main", inv:get_stack("pay"..i,1)) + inv:add_item("main", inv:get_stack("give"..i,1)) + end + end + end + if type==1 and inv:contains_item("main", stack)==false then + minetest.chat_send_player(pname, "Error: "..smartshop.get_human_name(name).." is sold out.") + if not meta:get_int("alerted") or meta:get_int("alerted") == 0 then + meta:set_int("alerted",1) -- Do not alert twice + smartshop.send_mail(meta:get_string("owner"), pos, name) + end + return + end + if not pinv:contains_item("main", pay) then minetest.chat_send_player(pname, "Error: You don't have enough in your inventory to buy this, exchange aborted.") return end + if not pinv:room_for_item("main", stack) then minetest.chat_send_player(pname, "Error: Your inventory is full, exchange aborted.") return end + pinv:remove_item("main", pay) + pinv:add_item("main", stack) + if type==1 then + inv:remove_item("main", stack) + inv:add_item("main", pay) + if not inv:contains_item("main", stack) and (not meta:get_int("alerted") or meta:get_int("alerted") == 0) then + meta:set_int("alerted",1) -- Do not alert twice + smartshop.send_mail(meta:get_string("owner"), pos, name) + end + end + end + end + else + smartshop.update_info(pos) + local meta = minetest.get_meta(pos) + if meta:get_string("owner") == pname then + smartshop.update(pos, "update") + end + smartshop.user[pname] = nil + end +end + +minetest.register_on_player_receive_fields(function(player, form, pressed) + if form=="smartshop.showform" then + smartshop.receive_fields(player,pressed) + end +end) + + + + +smartshop.update_info=function(pos) + if not pos then + return + end + local meta=minetest.get_meta(pos) + local spos=minetest.pos_to_string(pos) + local inv = meta:get_inventory() + local owner=meta:get_string("owner") + if meta:get_int("type")==0 then + meta:set_string("infotext","(Smartshop by " .. owner ..") Stock is unlimited") + return false + end + local name="" + local count=0 + local stuff={} + for i=1,4,1 do + stuff["count" ..i]=inv:get_stack("give" .. i,1):get_count() + stuff["name" ..i]=inv:get_stack("give" .. i,1):get_name() + stuff["stock" ..i]=0 -- stuff["count" ..i] + local mg_price = smartshop.minegeldtonumber(inv:get_stack("pay" .. i,1)) + if mg_price ~= nil then + stuff["pay"..i] = mg_price/stuff["count" ..i] + end + stuff["buy" ..i]=0 + for ii=1,32,1 do + name=inv:get_stack("main",ii):get_name() + count=inv:get_stack("main",ii):get_count() + if name==stuff["name" ..i] then + stuff["stock" ..i]=stuff["stock" ..i]+count + end + end + local nstr=(stuff["stock" ..i]/stuff["count" ..i]) .."" + nstr=nstr.split(nstr, ".") + stuff["buy" ..i]=tonumber(nstr[1]) + if stuff["name" ..i]=="" or stuff["buy" ..i]==0 then + stuff["buy" ..i]="" + stuff["name" ..i]="" + if smartshop.stuffsold[spos..i] then + smartshop.itemsatpos(spos, smartshop.stuffsold[spos..i], 0) + smartshop.itempriceatpos(spos, smartshop.stuffsold[spos..i], nil) + smartshop.stuffsold[spos..i] = nil + end + else + smartshop.itemsatpos(spos, stuff["name"..i], stuff["buy"..i]*stuff["count" ..i]) + smartshop.itempriceatpos(spos, stuff["name"..i], stuff["pay"..i]) + smartshop.stuffsold[spos..i] = stuff["name"..i] + stuff["name"..i] = smartshop.get_human_name(stuff["name"..i]) + stuff["buy" ..i]="(" ..stuff["buy" ..i] ..") " + stuff["name" ..i]=stuff["name" ..i] .."\n" + end + end + meta:set_string("infotext", + "(Smartshop by " .. owner ..") Purchases left:\n" + .. stuff.buy1 .. stuff.name1 + .. stuff.buy2 .. stuff.name2 + .. stuff.buy3 .. stuff.name3 + .. stuff.buy4 .. stuff.name4 + ) +end + + + + +smartshop.update=function(pos,stat) +--clear + local spos=minetest.pos_to_string(pos) + for _, ob in ipairs(minetest.env:get_objects_inside_radius(pos, 2)) do + if ob and ob:get_luaentity() and ob:get_luaentity().smartshop and ob:get_luaentity().pos==spos then + ob:remove() + end + end + if stat=="clear" then return end +--update + local meta=minetest.get_meta(pos) + local inv = meta:get_inventory() + local node=minetest.get_node(pos) + local dp = smartshop.dir[node.param2+1] + if not dp then return end + pos.x = pos.x + dp.x*0.01 + pos.y = pos.y + dp.y*6.5/16 + pos.z = pos.z + dp.z*0.01 + for i=1,4,1 do + local item=inv:get_stack("give" .. i,1):get_name() + local pos2=smartshop.dpos[node.param2+1][i] + if item~="" then + smartshop.tmp.item=item + smartshop.tmp.pos=spos + local e = minetest.env:add_entity({x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z},"smartshop:item") + e:setyaw(math.pi*2 - node.param2 * math.pi/2) + end + end +end + + +minetest.register_entity("smartshop:item",{ + hp_max = 1, + visual="wielditem", + visual_size={x=.20,y=.20}, + collisionbox = {0,0,0,0,0,0}, + physical=false, + textures={"air"}, + smartshop=true, + type="", + on_activate = function(self, staticdata) + if smartshop.tmp.item ~= nil then + self.item=smartshop.tmp.item + self.pos=smartshop.tmp.pos + smartshop.tmp={} + else + if staticdata ~= nil and staticdata ~= "" then + local data = staticdata:split(';') + if data and data[1] and data[2] then + self.item = data[1] + self.pos = data[2] + end + end + end + if self.item ~= nil then + self.object:set_properties({textures={self.item}}) + else + self.object:remove() + end + end, + get_staticdata = function(self) + if self.item ~= nil and self.pos ~= nil then + return self.item .. ';' .. self.pos + end + return "" + end, +}) + + +smartshop.showform=function(pos,player,re) + local meta=minetest.get_meta(pos) + local creative=meta:get_int("creative") + local inv = meta:get_inventory() + local gui="" + local spos=pos.x .. "," .. pos.y .. "," .. pos.z + local owner=meta:get_string("owner")==player:get_player_name() + if minetest.check_player_privs(player:get_player_name(), {protection_bypass=true}) then owner=true end + if re then owner=false end + smartshop.user[player:get_player_name()]=pos + if owner then + meta:set_int("alerted",0) -- Player has been there to refill + gui="" + .."size[8,10]" + .."button_exit[6,0;1.5,1;customer;Customer]" + .."label[0,0.2;Item:]" + .."label[0,1.2;Price:]" + .."list[nodemeta:" .. spos .. ";give1;2,0;1,1;]" + .."list[nodemeta:" .. spos .. ";pay1;2,1;1,1;]" + .."list[nodemeta:" .. spos .. ";give2;3,0;1,1;]" + .."list[nodemeta:" .. spos .. ";pay2;3,1;1,1;]" + .."list[nodemeta:" .. spos .. ";give3;4,0;1,1;]" + .."list[nodemeta:" .. spos .. ";pay3;4,1;1,1;]" + .."list[nodemeta:" .. spos .. ";give4;5,0;1,1;]" + .."list[nodemeta:" .. spos .. ";pay4;5,1;1,1;]" + if creative==1 then + gui=gui .."label[0.5,-0.4;Your stock is unlimited becaouse you have creative or give]" + .."button[6,1;2.2,1;tooglelime;Toggle limit]" + end + gui=gui + .."list[nodemeta:" .. spos .. ";main;0,2;8,4;]" + .."list[current_player;main;0,6.2;8,4;]" + .."listring[nodemeta:" .. spos .. ";main]" + .."listring[current_player;main]" + else + gui="" + .."size[8,6]" + .."list[current_player;main;0,2.2;8,4;]" + .."label[0,0.2;Item:]" + .."label[0,1.2;Price:]" + .."list[nodemeta:" .. spos .. ";give1;2,0;1,1;]" + .."item_image_button[2,1;1,1;".. inv:get_stack("pay1",1):get_name() ..";buy1;\n\n\b\b\b\b\b" .. inv:get_stack("pay1",1):get_count() .."]" + .."list[nodemeta:" .. spos .. ";give2;3,0;1,1;]" + .."item_image_button[3,1;1,1;".. inv:get_stack("pay2",1):get_name() ..";buy2;\n\n\b\b\b\b\b" .. inv:get_stack("pay2",1):get_count() .."]" + .."list[nodemeta:" .. spos .. ";give3;4,0;1,1;]" + .."item_image_button[4,1;1,1;".. inv:get_stack("pay3",1):get_name() ..";buy3;\n\n\b\b\b\b\b" .. inv:get_stack("pay3",1):get_count() .."]" + .."list[nodemeta:" .. spos .. ";give4;5,0;1,1;]" + .."item_image_button[5,1;1,1;".. inv:get_stack("pay4",1):get_name() ..";buy4;\n\n\b\b\b\b\b" .. inv:get_stack("pay4",1):get_count() .."]" + end + minetest.after((0.1), function(gui) + return minetest.show_formspec(player:get_player_name(), "smartshop.showform",gui) + end, gui) +end + +minetest.register_node("smartshop:shop", { + description = "Smartshop", + tiles = {"default_chest_top.png^[colorize:#ffffff77^default_obsidian_glass.png"}, + groups = {choppy = 2, oddly_breakable_by_hand = 1,tubedevice = 1, tubedevice_receiver = 1}, + drawtype="nodebox", + node_box = {type="fixed",fixed={-0.5,-0.5,-0.0,0.5,0.5,0.5}}, + paramtype2="facedir", + paramtype = "light", + sunlight_propagates = true, + light_source = 10, + tube = {insert_object = function(pos, node, stack, direction) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + local added = inv:add_item("main", stack) + smartshop.update_info(pos) + return added + end, + can_insert = function(pos, node, stack, direction) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + for i=1,4 do + local sellitem = inv:get_stack("give"..i,1):get_name() + if sellitem == stack:get_name() then + return inv:room_for_item("main", stack) + end +-- minetest.chat_send_all(sellitem) + end + -- + return false + end, + input_inventory = "main", + connect_sides = {left = 1, right = 1, front = 1, back = 1, top = 1, bottom = 1}}, +after_place_node = function(pos, placer) + local meta=minetest.get_meta(pos) + meta:set_string("owner",placer:get_player_name()) + meta:set_string("infotext", "Shop by: " .. placer:get_player_name()) + meta:set_int("type",1) + if minetest.check_player_privs(placer:get_player_name(), {creative=true}) or minetest.check_player_privs(placer:get_player_name(), {give=true}) then + meta:set_int("creative",1) + meta:set_int("type",0) + end + end, +on_construct = function(pos) + local meta=minetest.get_meta(pos) + meta:set_int("state", 0) + meta:get_inventory():set_size("main", 32) + meta:get_inventory():set_size("give1", 1) + meta:get_inventory():set_size("pay1", 1) + meta:get_inventory():set_size("give2", 1) + meta:get_inventory():set_size("pay2", 1) + meta:get_inventory():set_size("give3", 1) + meta:get_inventory():set_size("pay3", 1) + meta:get_inventory():set_size("give4", 1) + meta:get_inventory():set_size("pay4", 1) + meta:set_int("ghost", 1) + end, +on_rightclick = function(pos, node, player, itemstack, pointed_thing) + smartshop.showform(pos,player) + end, +allow_metadata_inventory_put = function(pos, listname, index, stack, player) + if minetest.get_meta(pos):get_string("owner")==player:get_player_name() or minetest.check_player_privs(player:get_player_name(), {protection_bypass=true}) then + local meta = minetest.get_meta(pos) + if meta:get_int("ghost") == 1 and (string.find(listname, "pay") or string.find(listname, "give")) then + local inv = minetest.get_inventory({type="node", pos=pos}) +-- minetest.chat_send_all( inv:get_stack(listname, index):get_name()..stack:get_name()) + if inv:get_stack(listname, index):get_name() == stack:get_name() then + inv:add_item(listname, stack) + else + inv:set_stack(listname, index, stack) + end + return 0 + end + return stack:get_count() + end + return 0 +end, +allow_metadata_inventory_take = function(pos, listname, index, stack, player) + if minetest.get_meta(pos):get_string("owner")==player:get_player_name() or minetest.check_player_privs(player:get_player_name(), {protection_bypass=true}) then + local meta = minetest.get_meta(pos) + if meta:get_int("ghost") == 1 and (string.find(listname, "pay") or string.find(listname, "give")) then + local inv = minetest.get_inventory({type="node", pos=pos}) + inv:set_stack(listname, index, ItemStack("")) + return 0 + end + return stack:get_count() + end + return 0 +end, +allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) + if minetest.get_meta(pos):get_string("owner")==player:get_player_name() or minetest.check_player_privs(player:get_player_name(), {protection_bypass=true}) then + local meta = minetest.get_meta(pos) + local inv = minetest.get_inventory({type="node", pos=pos}) + if meta:get_int("ghost") ~= 1 then + return count + end + if (string.find(from_list, "pay") or string.find(from_list, "give")) and to_list == "main" then + inv:set_stack(from_list, from_index, ItemStack("")) + return 0 + elseif (string.find(to_list, "pay") or string.find(to_list, "give")) and from_list == "main" then + if inv:get_stack(to_list, to_index):get_name() == inv:get_stack(from_list, from_index):get_name() then + inv:add_item(to_list, inv:get_stack(from_list, from_index)) + else + inv:set_stack(to_list, to_index, inv:get_stack(from_list, from_index)) + inv:set_stack(from_list, from_index, inv:get_stack(from_list, from_index)) + end + return 0 + else + return count + end + end + return 0 +end, +can_dig = function(pos, player) + local meta=minetest.get_meta(pos) + local inv=meta:get_inventory() + if ((meta:get_string("owner")==player:get_player_name() or minetest.check_player_privs(player:get_player_name(), {protection_bypass=true})) and inv:is_empty("main") and inv:is_empty("pay1") and inv:is_empty("pay2") and inv:is_empty("pay3") and inv:is_empty("pay4") and inv:is_empty("give1") and inv:is_empty("give2") and inv:is_empty("give3") and inv:is_empty("give4")) or meta:get_string("owner")=="" then + smartshop.update(pos,"clear") + return true + end + end, +}) + +smartshop.get_item_count = function(name) + sum = 0 + if smartshop.itemstats[name] == nil then + return 0 + end + for i, k in pairs(smartshop.itemstats[name]) do + sum = sum + k + end + return sum +end + +smartshop.get_shop_count = function(name) + sum = 0 + if smartshop.itemstats[name] == nil then + return 0 + end + for i, k in pairs(smartshop.itemstats[name]) do + sum = sum + 1 + end + return sum +end + +smartshop.get_item_price = function(name) + sum = smartshop.get_item_count(name) + if smartshop.itemprices[name] == nil then + return 0 + end + if sum == 0 then + return 0 + end + psum = 0 + for i, k in pairs(smartshop.itemprices[name]) do + psum = psum + k*smartshop.itemstats[name][i] + end + return psum/sum +end + + +minetest.register_chatcommand("smstats", { + description = "Get number of items sold", + params = "", + func = function(plname, params) + local name = params:match("(%S+)") + if not (name) then + return false, "Usage: /smstats " + end + if not smartshop.itemstats[name] then + return false, "No stats on "..name + end + sum = smartshop.get_item_count(name) + minetest.chat_send_player(plname, "Number of items: "..sum) + minetest.chat_send_player(plname, "Number of shops offering item: "..smartshop.get_shop_count(name)) + if sum == 0 then + return + end + price = smartshop.get_item_price(name) + minetest.chat_send_player(plname, "Average price: "..string.format("%.3f",price)) + return true +-- local ok, e = xban.ban_player(plname, name, nil, reason) +-- return ok, ok and ("Banned %s."):format(plname) or e + end, +}) + +smartshop.report = function () + local file = io.open(minetest.get_worldpath().."/smartshop_report.txt", "w") + if not file then + return false, "could not write to file" + end + for i,k in pairs(smartshop.itemstats) do + local count = smartshop.get_item_count(i) + local price = smartshop.get_item_price(i) + file:write(i.." "..count.." "..string.format("%.3f", price).." "..smartshop.get_shop_count(i).."\n") + end + file:close() +end + +minetest.register_chatcommand("smreport", { + description = "Get number of items sold", + func = function(plname, params) + smartshop.report() + end, +}) + +local timer = 0 +minetest.register_globalstep(function(dtime) + timer = timer + dtime; + if timer >= 100 then + smartshop.report() + timer = 0 + end +end) + + +if false then -- This lbm is used to add pre-update smartshops to the price database. Activate with care! Warning: very slow. + minetest.register_lbm({ + name = "smartshop:update", + nodenames = {"smartshop:shop"}, + action = function(pos, node) + smartshop.update_info(pos) + end, + }) +end + +-- load itemstats +local file = io.open(minetest.get_worldpath().."/smartshop_itemcounts.txt", "r") +if file then + local table = minetest.deserialize(file:read("*all")) + if type(table) == "table" then + smartshop.itemstats = table + end +end +local file = io.open(minetest.get_worldpath().."/smartshop_itemprices.txt", "r") +if file then + local table = minetest.deserialize(file:read("*all")) + if type(table) == "table" then + smartshop.itemprices = table + end +end -- cgit v1.2.3