a9cd80063a14f30e280a4470e9549c41873de46b
[smartshop.git] / init.lua
1 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={
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}},
3 {{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}},
4 {{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}},
5 {{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}}}
6 }
7
8 -- table with itemname: number of items being traded
9 smartshop.itemstats = {}
10 smartshop.itemprices = {}
11
12
13
14 smartshop.itemsatpos = function(pos, item, count)
15 -- set number of items of type 'item' sold at position 'pos'
16 if smartshop.itemstats[item] == nil then
17 smartshop.itemstats[item] = {}
18 end
19 smartshop.itemstats[item][pos] = count
20 local file = io.open(minetest.get_worldpath().."/smartshop_itemcounts.txt", "w")
21 if file then
22 file:write(minetest.serialize(smartshop.itemstats))
23 file:close()
24 end
25 end
26
27 smartshop.itempriceatpos = function(pos, item, price)
28 -- set number of items of type 'item' sold at position 'pos'
29 if smartshop.itemprices[item] == nil then
30 smartshop.itemprices[item] = {}
31 end
32 local file = io.open(minetest.get_worldpath().."/smartshop_itemprices.txt", "w")
33 if file then
34 file:write(minetest.serialize(smartshop.itemprices))
35 file:close()
36 end
37 smartshop.itemprices[item][pos] = price
38 end
39
40 smartshop.minegeldtonumber = function(stack)
41 -- return number of minegeld in stack, returns nil if stack is not composed of minegeld
42 count = stack:get_count()
43 if count == 0 then
44 return 0
45 end
46 if stack:get_name() == "currency:minegeld" then
47 return count
48 elseif stack:get_name() == "currency:minegeld_5" then
49 return count * 5
50 elseif stack:get_name() == "currency:minegeld_10" then
51 return count * 10
52 else
53 return nil
54 end
55 end
56
57
58 minetest.register_craft({
59 output = "smartshop:shop",
60 recipe = {
61 {"default:chest_locked", "default:chest_locked", "default:chest_locked"},
62 {"default:sign_wall_wood", "default:chest_locked", "default:sign_wall_wood"},
63 {"default:sign_wall_wood", "default:torch", "default:sign_wall_wood"},
64 }
65 })
66 smartshop.get_human_name = function(item)
67 if core.registered_items[item] then
68 return core.registered_items[item].description
69 else
70 return "Unknown Item"
71 end
72 end
73
74 smartshop.use_offer=function(pos,player,n)
75 local pressed={}
76 pressed["buy" .. n]=true
77 smartshop.user[player:get_player_name()]=pos
78 smartshop.receive_fields(player,pressed)
79 smartshop.user[player:get_player_name()]=nil
80 smartshop.update(pos)
81 end
82
83 smartshop.get_offer=function(pos)
84 if not pos or not minetest.get_node(pos) then return end
85 if minetest.get_node(pos).name~="smartshop:shop" then return end
86 local meta=minetest.get_meta(pos)
87 local inv=meta:get_inventory()
88 local offer={}
89 for i=1,4,1 do
90 offer[i]={
91 give=inv:get_stack("give" .. i,1):get_name(),
92 give_count=inv:get_stack("give" .. i,1):get_count(),
93 pay=inv:get_stack("pay" .. i,1):get_name(),
94 pay_count=inv:get_stack("pay" .. i,1):get_count(),
95 }
96 end
97 return offer
98 end
99
100 smartshop.send_mail=function(owner, pos, item)
101 if not minetest.get_modpath( "mail" ) then
102 return
103 end
104 local spos = "("..pos.x..", "..pos.y..", "..pos.z..")"
105 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")
106 end
107
108
109 smartshop.receive_fields=function(player,pressed)
110 if pressed.customer then
111 return smartshop.showform(smartshop.user[player:get_player_name()],player,true)
112 elseif pressed.tooglelime then
113 local pos=smartshop.user[player:get_player_name()]
114 local meta=minetest.get_meta(pos)
115 local pname=player:get_player_name()
116 if meta:get_int("type")==0 then
117 meta:set_int("type",1)
118 minetest.chat_send_player(pname, "Your stock is limited")
119 else
120 meta:set_int("type",0)
121 minetest.chat_send_player(pname, "Your stock is unlimited")
122 end
123 elseif not pressed.quit then
124 local n=1
125 for i=1,4,1 do
126 n=i
127 if pressed["buy" .. i] then break end
128 end
129 local pos=smartshop.user[player:get_player_name()]
130 if not pos then
131 return
132 end
133 local meta=minetest.get_meta(pos)
134 local type=meta:get_int("type")
135 local inv=meta:get_inventory()
136 local pinv=player:get_inventory()
137 local pname=player:get_player_name()
138 if pressed["buy" .. n] then
139 local name=inv:get_stack("give" .. n,1):get_name()
140 local stack=name .." ".. inv:get_stack("give" .. n,1):get_count()
141 local pay=inv:get_stack("pay" .. n,1):get_name() .." ".. inv:get_stack("pay" .. n,1):get_count()
142 if name~="" then
143 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
144 if meta:get_int("ghost") ~=1 then
145 -- transition shops to ghost inventory.
146 for i=1,4 do
147 if inv:room_for_item("main", "pay"..i) and inv:room_for_item("main", "give"..i) then
148 meta:set_int("ghost", 1)
149 inv:add_item("main", inv:get_stack("pay"..i,1))
150 inv:add_item("main", inv:get_stack("give"..i,1))
151 end
152 end
153 end
154 if type==1 and inv:contains_item("main", stack)==false then
155 minetest.chat_send_player(pname, "Error: "..smartshop.get_human_name(name).." is sold out.")
156 if not meta:get_int("alerted") or meta:get_int("alerted") == 0 then
157 meta:set_int("alerted",1) -- Do not alert twice
158 smartshop.send_mail(meta:get_string("owner"), pos, name)
159 end
160 return
161 end
162 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
163 if not pinv:room_for_item("main", stack) then minetest.chat_send_player(pname, "Error: Your inventory is full, exchange aborted.") return end
164 pinv:remove_item("main", pay)
165 pinv:add_item("main", stack)
166 if type==1 then
167 inv:remove_item("main", stack)
168 inv:add_item("main", pay)
169 if not inv:contains_item("main", stack) and (not meta:get_int("alerted") or meta:get_int("alerted") == 0) then
170 meta:set_int("alerted",1) -- Do not alert twice
171 smartshop.send_mail(meta:get_string("owner"), pos, name)
172 end
173 end
174 end
175 end
176 else
177 local pos=smartshop.user[player:get_player_name()]
178 smartshop.update_info(pos)
179 if smartshop.user[player:get_player_name()] or minetest.check_player_privs(player:get_player_name(), {protection_bypass=true}) then
180 local meta=minetest.get_meta(smartshop.user[player:get_player_name()])
181 if meta:get_string("owner")==player:get_player_name() then
182 smartshop.update(smartshop.user[player:get_player_name()],"update")
183 end
184 end
185 smartshop.user[player:get_player_name()]=nil
186 end
187 end
188
189 minetest.register_on_player_receive_fields(function(player, form, pressed)
190 if form=="smartshop.showform" then
191 smartshop.receive_fields(player,pressed)
192 end
193 end)
194
195
196
197
198 smartshop.update_info=function(pos)
199 if not pos then
200 return
201 end
202 local meta=minetest.get_meta(pos)
203 local spos=minetest.pos_to_string(pos)
204 local inv = meta:get_inventory()
205 local owner=meta:get_string("owner")
206 if meta:get_int("type")==0 then
207 meta:set_string("infotext","(Smartshop by " .. owner ..") Stock is unlimited")
208 return false
209 end
210 local name=""
211 local count=0
212 local stuff={}
213 for i=1,4,1 do
214 stuff["count" ..i]=inv:get_stack("give" .. i,1):get_count()
215 stuff["name" ..i]=inv:get_stack("give" .. i,1):get_name()
216 stuff["stock" ..i]=0 -- stuff["count" ..i]
217 stuff["pay"..i] = smartshop.minegeldtonumber(inv:get_stack("pay" .. i,1))/stuff["count" ..i]
218 stuff["buy" ..i]=0
219 for ii=1,32,1 do
220 name=inv:get_stack("main",ii):get_name()
221 count=inv:get_stack("main",ii):get_count()
222 if name==stuff["name" ..i] then
223 stuff["stock" ..i]=stuff["stock" ..i]+count
224 end
225 end
226 local nstr=(stuff["stock" ..i]/stuff["count" ..i]) ..""
227 nstr=nstr.split(nstr, ".")
228 stuff["buy" ..i]=tonumber(nstr[1])
229 if stuff["name" ..i]~="" and stuff["buy" ..i]==0 then
230 smartshop.itemsatpos(spos, stuff["name"..i], stuff["buy"..i]*stuff["count" ..i])
231 smartshop.itempriceatpos(spos, stuff["name"..i], nil)
232 end
233 if stuff["name" ..i]=="" or stuff["buy" ..i]==0 then
234 stuff["buy" ..i]=""
235 stuff["name" ..i]=""
236 else
237 smartshop.itemsatpos(spos, stuff["name"..i], stuff["buy"..i]*stuff["count" ..i])
238 smartshop.itempriceatpos(spos, stuff["name"..i], stuff["pay"..i])
239 stuff["name"..i] = smartshop.get_human_name(stuff["name"..i])
240 stuff["buy" ..i]="(" ..stuff["buy" ..i] ..") "
241 stuff["name" ..i]=stuff["name" ..i] .."\n"
242 end
243 end
244 meta:set_string("infotext",
245 "(Smartshop by " .. owner ..") Purchases left:\n"
246 .. stuff.buy1 .. stuff.name1
247 .. stuff.buy2 .. stuff.name2
248 .. stuff.buy3 .. stuff.name3
249 .. stuff.buy4 .. stuff.name4
250 )
251 end
252
253
254
255
256 smartshop.update=function(pos,stat)
257 --clear
258 local spos=minetest.pos_to_string(pos)
259 for _, ob in ipairs(minetest.env:get_objects_inside_radius(pos, 2)) do
260 if ob and ob:get_luaentity() and ob:get_luaentity().smartshop and ob:get_luaentity().pos==spos then
261 ob:remove()
262 end
263 end
264 if stat=="clear" then return end
265 --update
266 local meta=minetest.get_meta(pos)
267 local inv = meta:get_inventory()
268 local node=minetest.get_node(pos)
269 local dp = smartshop.dir[node.param2+1]
270 if not dp then return end
271 pos.x = pos.x + dp.x*0.01
272 pos.y = pos.y + dp.y*6.5/16
273 pos.z = pos.z + dp.z*0.01
274 for i=1,4,1 do
275 local item=inv:get_stack("give" .. i,1):get_name()
276 local pos2=smartshop.dpos[node.param2+1][i]
277 if item~="" then
278 smartshop.tmp.item=item
279 smartshop.tmp.pos=spos
280 local e = minetest.env:add_entity({x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z},"smartshop:item")
281 e:setyaw(math.pi*2 - node.param2 * math.pi/2)
282 end
283 end
284 end
285
286
287 minetest.register_entity("smartshop:item",{
288 hp_max = 1,
289 visual="wielditem",
290 visual_size={x=.20,y=.20},
291 collisionbox = {0,0,0,0,0,0},
292 physical=false,
293 textures={"air"},
294 smartshop=true,
295 type="",
296 on_activate = function(self, staticdata)
297 if smartshop.tmp.item ~= nil then
298 self.item=smartshop.tmp.item
299 self.pos=smartshop.tmp.pos
300 smartshop.tmp={}
301 else
302 if staticdata ~= nil and staticdata ~= "" then
303 local data = staticdata:split(';')
304 if data and data[1] and data[2] then
305 self.item = data[1]
306 self.pos = data[2]
307 end
308 end
309 end
310 if self.item ~= nil then
311 self.object:set_properties({textures={self.item}})
312 else
313 self.object:remove()
314 end
315 end,
316 get_staticdata = function(self)
317 if self.item ~= nil and self.pos ~= nil then
318 return self.item .. ';' .. self.pos
319 end
320 return ""
321 end,
322 })
323
324
325 smartshop.showform=function(pos,player,re)
326 local meta=minetest.get_meta(pos)
327 local creative=meta:get_int("creative")
328 local inv = meta:get_inventory()
329 local gui=""
330 local spos=pos.x .. "," .. pos.y .. "," .. pos.z
331 local owner=meta:get_string("owner")==player:get_player_name()
332 if minetest.check_player_privs(player:get_player_name(), {protection_bypass=true}) then owner=true end
333 if re then owner=false end
334 smartshop.user[player:get_player_name()]=pos
335 if owner then
336 meta:set_int("alerted",0) -- Player has been there to refill
337 gui=""
338 .."size[8,10]"
339 .."button_exit[6,0;1.5,1;customer;Customer]"
340 .."label[0,0.2;Item:]"
341 .."label[0,1.2;Price:]"
342 .."list[nodemeta:" .. spos .. ";give1;2,0;1,1;]"
343 .."list[nodemeta:" .. spos .. ";pay1;2,1;1,1;]"
344 .."list[nodemeta:" .. spos .. ";give2;3,0;1,1;]"
345 .."list[nodemeta:" .. spos .. ";pay2;3,1;1,1;]"
346 .."list[nodemeta:" .. spos .. ";give3;4,0;1,1;]"
347 .."list[nodemeta:" .. spos .. ";pay3;4,1;1,1;]"
348 .."list[nodemeta:" .. spos .. ";give4;5,0;1,1;]"
349 .."list[nodemeta:" .. spos .. ";pay4;5,1;1,1;]"
350 if creative==1 then
351 gui=gui .."label[0.5,-0.4;Your stock is unlimited becaouse you have creative or give]"
352 .."button[6,1;2.2,1;tooglelime;Toggle limit]"
353 end
354 gui=gui
355 .."list[nodemeta:" .. spos .. ";main;0,2;8,4;]"
356 .."list[current_player;main;0,6.2;8,4;]"
357 .."listring[nodemeta:" .. spos .. ";main]"
358 .."listring[current_player;main]"
359 else
360 gui=""
361 .."size[8,6]"
362 .."list[current_player;main;0,2.2;8,4;]"
363 .."label[0,0.2;Item:]"
364 .."label[0,1.2;Price:]"
365 .."list[nodemeta:" .. spos .. ";give1;2,0;1,1;]"
366 .."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() .."]"
367 .."list[nodemeta:" .. spos .. ";give2;3,0;1,1;]"
368 .."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() .."]"
369 .."list[nodemeta:" .. spos .. ";give3;4,0;1,1;]"
370 .."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() .."]"
371 .."list[nodemeta:" .. spos .. ";give4;5,0;1,1;]"
372 .."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() .."]"
373 end
374 minetest.after((0.1), function(gui)
375 return minetest.show_formspec(player:get_player_name(), "smartshop.showform",gui)
376 end, gui)
377 end
378
379 minetest.register_node("smartshop:shop", {
380 description = "Smartshop",
381 tiles = {"default_chest_top.png^[colorize:#ffffff77^default_obsidian_glass.png"},
382 groups = {choppy = 2, oddly_breakable_by_hand = 1,tubedevice = 1, tubedevice_receiver = 1},
383 drawtype="nodebox",
384 node_box = {type="fixed",fixed={-0.5,-0.5,-0.0,0.5,0.5,0.5}},
385 paramtype2="facedir",
386 paramtype = "light",
387 sunlight_propagates = true,
388 light_source = 10,
389 tube = {insert_object = function(pos, node, stack, direction)
390 local meta = minetest.get_meta(pos)
391 local inv = meta:get_inventory()
392 local added = inv:add_item("main", stack)
393 smartshop.update_info(pos)
394 return added
395 end,
396 can_insert = function(pos, node, stack, direction)
397 local meta = minetest.get_meta(pos)
398 local inv = meta:get_inventory()
399 for i=1,4 do
400 local sellitem = inv:get_stack("give"..i,1):get_name()
401 if sellitem == stack:get_name() then
402 return inv:room_for_item("main", stack)
403 end
404 -- minetest.chat_send_all(sellitem)
405 end
406 --
407 return false
408 end,
409 input_inventory = "main",
410 connect_sides = {left = 1, right = 1, front = 1, back = 1, top = 1, bottom = 1}},
411 after_place_node = function(pos, placer)
412 local meta=minetest.get_meta(pos)
413 meta:set_string("owner",placer:get_player_name())
414 meta:set_string("infotext", "Shop by: " .. placer:get_player_name())
415 meta:set_int("type",1)
416 if minetest.check_player_privs(placer:get_player_name(), {creative=true}) or minetest.check_player_privs(placer:get_player_name(), {give=true}) then
417 meta:set_int("creative",1)
418 meta:set_int("type",0)
419 end
420 end,
421 on_construct = function(pos)
422 local meta=minetest.get_meta(pos)
423 meta:set_int("state", 0)
424 meta:get_inventory():set_size("main", 32)
425 meta:get_inventory():set_size("give1", 1)
426 meta:get_inventory():set_size("pay1", 1)
427 meta:get_inventory():set_size("give2", 1)
428 meta:get_inventory():set_size("pay2", 1)
429 meta:get_inventory():set_size("give3", 1)
430 meta:get_inventory():set_size("pay3", 1)
431 meta:get_inventory():set_size("give4", 1)
432 meta:get_inventory():set_size("pay4", 1)
433 meta:set_int("ghost", 1)
434 end,
435 on_rightclick = function(pos, node, player, itemstack, pointed_thing)
436 smartshop.showform(pos,player)
437 end,
438 allow_metadata_inventory_put = function(pos, listname, index, stack, player)
439 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
440 local meta = minetest.get_meta(pos)
441 if meta:get_int("ghost") == 1 and (string.find(listname, "pay") or string.find(listname, "give")) then
442 local inv = minetest.get_inventory({type="node", pos=pos})
443 -- minetest.chat_send_all( inv:get_stack(listname, index):get_name()..stack:get_name())
444 if inv:get_stack(listname, index):get_name() == stack:get_name() then
445 inv:add_item(listname, stack)
446 else
447 inv:set_stack(listname, index, stack)
448 end
449 return 0
450 end
451 return stack:get_count()
452 end
453 return 0
454 end,
455 allow_metadata_inventory_take = function(pos, listname, index, stack, player)
456 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
457 local meta = minetest.get_meta(pos)
458 if meta:get_int("ghost") == 1 and (string.find(listname, "pay") or string.find(listname, "give")) then
459 local inv = minetest.get_inventory({type="node", pos=pos})
460 inv:set_stack(listname, index, ItemStack(""))
461 return 0
462 end
463 return stack:get_count()
464 end
465 return 0
466 end,
467 allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
468 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
469 local meta = minetest.get_meta(pos)
470 local inv = minetest.get_inventory({type="node", pos=pos})
471 if meta:get_int("ghost") ~= 1 then
472 return count
473 end
474 if (string.find(from_list, "pay") or string.find(from_list, "give")) and to_list == "main" then
475 inv:set_stack(from_list, from_index, ItemStack(""))
476 return 0
477 elseif (string.find(to_list, "pay") or string.find(to_list, "give")) and from_list == "main" then
478 if inv:get_stack(to_list, to_index):get_name() == inv:get_stack(from_list, from_index):get_name() then
479 inv:add_item(to_list, inv:get_stack(from_list, from_index))
480 else
481 inv:set_stack(to_list, to_index, inv:get_stack(from_list, from_index))
482 inv:set_stack(from_list, from_index, inv:get_stack(from_list, from_index))
483 end
484 return 0
485 else
486 return count
487 end
488 end
489 return 0
490 end,
491 can_dig = function(pos, player)
492 local meta=minetest.get_meta(pos)
493 local inv=meta:get_inventory()
494 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
495 smartshop.update(pos,"clear")
496 return true
497 end
498 end,
499 })
500
501 minetest.register_chatcommand("smstats", {
502 description = "Get number of items sold",
503 params = "<item_name>",
504 func = function(plname, params)
505 local name = params:match("(%S+)")
506 if not (name) then
507 return false, "Usage: /smstats <itemname>"
508 end
509 if not smartshop.itemstats[name] then
510 return false, "No stats on "..name
511 end
512 sum = 0
513 for i, k in pairs(smartshop.itemstats[name]) do
514 sum = sum + k
515 end
516 minetest.chat_send_player(plname, "Number of items: "..sum)
517 psum = 0
518 for i, k in pairs(smartshop.itemprices[name]) do
519 psum = psum + k*smartshop.itemstats[name][i]
520 end
521 minetest.chat_send_player(plname, "Average price: "..psum/sum)
522 return true
523 -- local ok, e = xban.ban_player(plname, name, nil, reason)
524 -- return ok, ok and ("Banned %s."):format(plname) or e
525 end,
526 })
527
528
529 -- load itemstats
530 local file = io.open(minetest.get_worldpath().."/smartshop_itemcounts.txt", "r")
531 if file then
532 local table = minetest.deserialize(file:read("*all"))
533 if type(table) == "table" then
534 smartshop.itemstats = table
535 end
536 end
537 local file = io.open(minetest.get_worldpath().."/smartshop_itemprices.txt", "r")
538 if file then
539 local table = minetest.deserialize(file:read("*all"))
540 if type(table) == "table" then
541 smartshop.itemprices = table
542 end
543 end