Added chest management buttons and ability to name chest
[protector.git] / init.lua
1 minetest.register_privilege("delprotect","Ignore player protection")
2
3 protector = {}
4 protector.radius = 5
5
6 protector.get_member_list = function(meta)
7 local s = meta:get_string("members")
8 local list = s:split(" ")
9 return list
10 end
11
12 protector.set_member_list = function(meta, list)
13 meta:set_string("members", table.concat(list, " "))
14 end
15
16 protector.is_member = function (meta, name)
17 local list = protector.get_member_list(meta)
18 for _, n in ipairs(list) do
19 if n == name then
20 return true
21 end
22 end
23 return false
24 end
25
26 protector.add_member = function(meta, name)
27 if protector.is_member(meta, name) then return end
28 local list = protector.get_member_list(meta)
29 table.insert(list,name)
30 protector.set_member_list(meta,list)
31 end
32
33 protector.del_member = function(meta,name)
34 local list = protector.get_member_list(meta)
35 for i, n in ipairs(list) do
36 if n == name then
37 table.remove(list, i)
38 break
39 end
40 end
41 protector.set_member_list(meta,list)
42 end
43
44 -- Protector Interface
45
46 protector.generate_formspec = function(meta)
47
48 local formspec = "size[8,7]"..default.gui_bg..default.gui_bg_img..default.gui_slots
49 .."label[2.5,0;-- Protector interface --]"
50 .."label[0,1;PUNCH node to show protected area or USE for area check]"
51 .."label[0,2;Members: (type player name then press Enter to add)]"
52
53 local members = protector.get_member_list(meta)
54 local npp = 12
55 local i = 0
56
57 for _, member in ipairs(members) do
58 if i < npp then
59 formspec = formspec .. "button["..(i%4*2)..","
60 ..math.floor(i/4+3)..";1.5,.5;protector_member;"..member.."]"
61 formspec = formspec .. "button["..(i%4*2+1.25)..","
62 ..math.floor(i/4+3)..";.75,.5;protector_del_member_"..member..";X]"
63 end
64 i = i +1
65 end
66
67 if i < npp then
68 formspec = formspec
69 .."field["..(i%4*2+1/3)..","..(math.floor(i/4+3)+1/3)..";1.433,.5;protector_add_member;;]"
70 end
71
72 formspec = formspec.."button_exit[2.5,6.2;3,0.5;close_me;Close]"
73
74 return formspec
75 end
76
77 -- ACTUAL PROTECTION SECTION
78
79 -- Infolevel:
80 -- 0 for no info
81 -- 1 for "This area is owned by <owner> !" if you can't dig
82 -- 2 for "This area is owned by <owner>.
83 -- 3 for checking protector overlaps
84
85 protector.can_dig = function(r,pos,digger,onlyowner,infolevel)
86
87 if not digger then
88 return false
89 end
90
91 local whois = digger
92
93 -- Delprotect privileged users can override protections
94
95 if minetest.check_player_privs(whois, {delprotect=true}) and infolevel == 1 then
96 return true
97 end
98
99 if infolevel == 3 then infolevel = 1 end
100
101 -- Find the protector nodes
102
103 local positions = minetest.find_nodes_in_area(
104 {x=pos.x-r, y=pos.y-r, z=pos.z-r},
105 {x=pos.x+r, y=pos.y+r, z=pos.z+r},
106 {"protector:protect", "protector:protect2"})
107
108 for _, pos in ipairs(positions) do
109 local meta = minetest.env:get_meta(pos)
110 local owner = meta:get_string("owner")
111
112 if owner ~= whois then
113 if onlyowner or not protector.is_member(meta, whois) then
114 if infolevel == 1 then
115 minetest.chat_send_player(whois, "This area is owned by "..owner.." !")
116 elseif infolevel == 2 then
117 minetest.chat_send_player(whois,"This area is owned by "..meta:get_string("owner")..".")
118 if meta:get_string("members") ~= "" then
119 minetest.chat_send_player(whois,"Members: "..meta:get_string("members")..".")
120 end
121 end
122 return false
123 end
124 end
125 end
126
127 if infolevel == 2 then
128 if #positions < 1 then
129 minetest.chat_send_player(whois,"This area is not protected.")
130 else
131 local meta = minetest.env:get_meta(positions[1])
132 minetest.chat_send_player(whois,"This area is owned by "..meta:get_string("owner")..".")
133 if meta:get_string("members") ~= "" then
134 minetest.chat_send_player(whois,"Members: "..meta:get_string("members")..".")
135 end
136 end
137 minetest.chat_send_player(whois,"You can build here.")
138 end
139 return true
140 end
141
142 -- Can node be added or removed, if so return node else true (for protected)
143
144 protector.old_is_protected = minetest.is_protected
145 minetest.is_protected = function(pos, digger)
146
147 if protector.can_dig(protector.radius, pos, digger, false, 1) then
148 return protector.old_is_protected(pos, digger)
149 else
150 return true
151 end
152 end
153
154 -- Make sure protection block doesn't overlap another protector's area
155
156 protector.old_node_place = minetest.item_place
157 function minetest.item_place(itemstack, placer, pointed_thing)
158
159 if itemstack:get_name() == "protector:protect" or itemstack:get_name() == "protector:protect2" then
160 local pos = pointed_thing.above
161 local user = placer:get_player_name()
162 if not protector.can_dig(protector.radius * 2, pos, user, true, 3) then
163 minetest.chat_send_player(placer:get_player_name(),"Overlaps into another protected area")
164 return protector.old_node_place(itemstack, placer, pos)
165 end
166 end
167
168 return protector.old_node_place(itemstack, placer, pointed_thing)
169 end
170
171 -- END
172
173 --= Protection Block
174
175 minetest.register_node("protector:protect", {
176 description = "Protection Block",
177 tiles = {"protector_top.png","protector_top.png","protector_side.png"},
178 sounds = default.node_sound_stone_defaults(),
179 groups = {dig_immediate=2},
180 drawtype = "nodebox",
181 node_box = {
182 type="fixed",
183 fixed = { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 },
184 },
185 selection_box = { type="regular" },
186 paramtype = "light",
187 light_source = 2,
188
189 after_place_node = function(pos, placer)
190 local meta = minetest.env:get_meta(pos)
191 meta:set_string("owner", placer:get_player_name() or "")
192 meta:set_string("infotext", "Protection (owned by "..meta:get_string("owner")..")")
193 meta:set_string("members", "")
194 end,
195
196 on_use = function(itemstack, user, pointed_thing)
197 if pointed_thing.type ~= "node" then
198 return
199 end
200
201 protector.can_dig(protector.radius,pointed_thing.under,user:get_player_name(),false,2)
202 end,
203
204 on_rightclick = function(pos, node, clicker, itemstack)
205 local meta = minetest.env:get_meta(pos)
206 if protector.can_dig(1,pos,clicker:get_player_name(),true,1) then
207 minetest.show_formspec(clicker:get_player_name(),
208 "protector:node_"..minetest.pos_to_string(pos), protector.generate_formspec(meta))
209 end
210 end,
211
212 on_punch = function(pos, node, puncher)
213 if not protector.can_dig(1,pos,puncher:get_player_name(),true,1) then
214 return
215 end
216
217 minetest.env:add_entity(pos, "protector:display")
218 minetest.env:get_node_timer(pos):start(10)
219 end,
220
221 on_timer = function(pos)
222 local objs = minetest.env:get_objects_inside_radius(pos,.5)
223 for _, o in pairs(objs) do
224 if (not o:is_player()) and o:get_luaentity().name == "protector:display" then
225 o:remove()
226 end
227 end
228 end,
229 })
230
231 minetest.register_craft({
232 output = "protector:protect 4",
233 recipe = {
234 {"default:stone","default:stone","default:stone"},
235 {"default:stone","default:steel_ingot","default:stone"},
236 {"default:stone","default:stone","default:stone"},
237 }
238 })
239
240 --= Protection Logo
241
242 minetest.register_node("protector:protect2", {
243 description = "Protection Logo",
244 tiles = {"protector_logo.png"},
245 wield_image = "protector_logo.png",
246 inventory_image = "protector_logo.png",
247 sounds = default.node_sound_stone_defaults(),
248 groups = {dig_immediate=2},
249 paramtype = 'light',
250 paramtype2 = "wallmounted",
251 light_source = 2,
252 drawtype = "nodebox",
253 sunlight_propagates = true,
254 walkable = true,
255 node_box = {
256 type = "wallmounted",
257 wall_top = {-0.375, 0.4375, -0.5, 0.375, 0.5, 0.5},
258 wall_bottom = {-0.375, -0.5, -0.5, 0.375, -0.4375, 0.5},
259 wall_side = {-0.5, -0.5, -0.375, -0.4375, 0.5, 0.375},
260 },
261 selection_box = {type = "wallmounted"},
262
263 after_place_node = function(pos, placer)
264 local meta = minetest.env:get_meta(pos)
265 meta:set_string("owner", placer:get_player_name() or "")
266 meta:set_string("infotext", "Protection (owned by "..meta:get_string("owner")..")")
267 meta:set_string("members", "")
268 end,
269
270 on_use = function(itemstack, user, pointed_thing)
271 if pointed_thing.type ~= "node" then
272 return
273 end
274
275 protector.can_dig(protector.radius,pointed_thing.under,user:get_player_name(),false,2)
276 end,
277
278 on_rightclick = function(pos, node, clicker, itemstack)
279 local meta = minetest.env:get_meta(pos)
280 if protector.can_dig(1,pos,clicker:get_player_name(),true,1) then
281 minetest.show_formspec(clicker:get_player_name(),
282 "protector:node_"..minetest.pos_to_string(pos), protector.generate_formspec(meta))
283 end
284 end,
285
286 on_punch = function(pos, node, puncher)
287 if not protector.can_dig(1,pos,puncher:get_player_name(),true,1) then
288 return
289 end
290
291 minetest.env:add_entity(pos, "protector:display")
292 minetest.env:get_node_timer(pos):start(10)
293 end,
294
295 on_timer = function(pos)
296 local objs = minetest.env:get_objects_inside_radius(pos,.5)
297 for _, o in pairs(objs) do
298 if (not o:is_player()) and o:get_luaentity().name == "protector:display" then
299 o:remove()
300 end
301 end
302 end,
303 })
304
305 minetest.register_craft({
306 output = "protector:protect2 4",
307 recipe = {
308 {"default:stone","default:stone","default:stone"},
309 {"default:stone","default:copper_ingot","default:stone"},
310 {"default:stone","default:stone","default:stone"},
311 }
312 })
313
314 -- If name entered or button press
315 minetest.register_on_player_receive_fields(function(player,formname,fields)
316
317 if string.sub(formname,0,string.len("protector:node_")) == "protector:node_" then
318
319 local pos_s = string.sub(formname,string.len("protector:node_")+1)
320 local pos = minetest.string_to_pos(pos_s)
321 local meta = minetest.env:get_meta(pos)
322
323 if not protector.can_dig(1,pos,player:get_player_name(),true,1) then
324 return
325 end
326
327 if fields.protector_add_member then
328 for _, i in ipairs(fields.protector_add_member:split(" ")) do
329 protector.add_member(meta,i)
330 end
331 end
332
333 for field, value in pairs(fields) do
334 if string.sub(field,0,string.len("protector_del_member_"))=="protector_del_member_" then
335 protector.del_member(meta, string.sub(field,string.len("protector_del_member_")+1))
336 end
337 end
338
339 if not fields.close_me then
340 minetest.show_formspec(player:get_player_name(), formname, protector.generate_formspec(meta))
341 end
342
343 end
344
345 end)
346
347 minetest.register_entity("protector:display", {
348 physical = false,
349 collisionbox = {0,0,0,0,0,0},
350 visual = "wielditem",
351 visual_size = {x=1.0/1.5,y=1.0/1.5}, -- wielditem seems to be scaled to 1.5 times original node size
352 textures = {"protector:display_node"},
353 on_step = function(self, dtime)
354 local nam = minetest.get_node(self.object:getpos()).name
355 if nam ~= "protector:protect" and nam ~= "protector:protect2" then
356 self.object:remove()
357 return
358 end
359 end,
360 })
361
362 -- Display-zone node, Do NOT place the display as a node, it is made to be used as an entity (see above)
363 local x = protector.radius
364 minetest.register_node("protector:display_node", {
365 tiles = {"protector_display.png"},
366 use_texture_alpha = true,
367 walkable = false,
368 drawtype = "nodebox",
369 node_box = {
370 type = "fixed",
371 fixed = {
372 -- sides
373 {-(x+.55), -(x+.55), -(x+.55), -(x+.45), (x+.55), (x+.55)},
374 {-(x+.55), -(x+.55), (x+.45), (x+.55), (x+.55), (x+.55)},
375 {(x+.45), -(x+.55), -(x+.55), (x+.55), (x+.55), (x+.55)},
376 {-(x+.55), -(x+.55), -(x+.55), (x+.55), (x+.55), -(x+.45)},
377 -- top
378 {-(x+.55), (x+.45), -(x+.55), (x+.55), (x+.55), (x+.55)},
379 -- bottom
380 {-(x+.55), -(x+.55), -(x+.55), (x+.55), -(x+.45), (x+.55)},
381 -- middle (surround protector)
382 {-.55,-.55,-.55, .55,.55,.55},
383 },
384 },
385 selection_box = {
386 type = "regular",
387 },
388 paramtype = "light",
389 groups = {dig_immediate=3,not_in_creative_inventory=1},
390 drop = "",
391 })
392
393 -- Register Protected Doors
394
395 local function on_rightclick(pos, dir, check_name, replace, replace_dir, params)
396 pos.y = pos.y+dir
397 if not minetest.get_node(pos).name == check_name then
398 return
399 end
400 local p2 = minetest.get_node(pos).param2
401 p2 = params[p2+1]
402
403 minetest.swap_node(pos, {name=replace_dir, param2=p2})
404
405 pos.y = pos.y-dir
406 minetest.swap_node(pos, {name=replace, param2=p2})
407
408 local snd_1 = "door_close"
409 local snd_2 = "door_open"
410 if params[1] == 3 then
411 snd_1 = "door_open"
412 snd_2 = "door_close"
413 end
414
415 if minetest.get_meta(pos):get_int("right") ~= 0 then
416 minetest.sound_play(snd_1, {pos = pos, gain = 0.3, max_hear_distance = 10})
417 else
418 minetest.sound_play(snd_2, {pos = pos, gain = 0.3, max_hear_distance = 10})
419 end
420 end
421
422 -- Protected Wooden Door
423
424 local name = "protector:door_wood"
425
426 doors.register_door(name, {
427 description = "Protected Wooden Door",
428 inventory_image = "door_wood.png^protector_logo.png",
429 groups = {snappy=1,choppy=2,oddly_breakable_by_hand=2,flammable=2,door=1},
430 tiles_bottom = {"door_wood_b.png^protector_logo.png", "door_brown.png"},
431 tiles_top = {"door_wood_a.png", "door_brown.png"},
432 sounds = default.node_sound_wood_defaults(),
433 sunlight = false,
434 })
435
436 minetest.override_item(name.."_b_1", {
437 on_rightclick = function(pos, node, clicker)
438 if not minetest.is_protected(pos, clicker:get_player_name()) then
439 on_rightclick(pos, 1, name.."_t_1", name.."_b_2", name.."_t_2", {1,2,3,0})
440 end
441 end,
442 })
443
444 minetest.override_item(name.."_t_1", {
445 on_rightclick = function(pos, node, clicker)
446 if not minetest.is_protected(pos, clicker:get_player_name()) then
447 on_rightclick(pos, -1, name.."_b_1", name.."_t_2", name.."_b_2", {1,2,3,0})
448 end
449 end,
450 })
451
452 minetest.override_item(name.."_b_2", {
453 on_rightclick = function(pos, node, clicker)
454 if not minetest.is_protected(pos, clicker:get_player_name()) then
455 on_rightclick(pos, 1, name.."_t_2", name.."_b_1", name.."_t_1", {3,0,1,2})
456 end
457 end,
458 })
459
460 minetest.override_item(name.."_t_2", {
461 on_rightclick = function(pos, node, clicker)
462 if not minetest.is_protected(pos, clicker:get_player_name()) then
463 on_rightclick(pos, -1, name.."_b_2", name.."_t_1", name.."_b_1", {3,0,1,2})
464 end
465 end,
466 })
467
468 minetest.register_craft({
469 output = name,
470 recipe = {
471 {"group:wood", "group:wood"},
472 {"group:wood", "default:copper_ingot"},
473 {"group:wood", "group:wood"}
474 }
475 })
476
477 minetest.register_craft({
478 output = name,
479 recipe = {
480 {"doors:door_wood", "default:copper_ingot"}
481 }
482 })
483
484 -- Protected Steel Door
485
486 local name = "protector:door_steel"
487
488 doors.register_door(name, {
489 description = "Protected Steel Door",
490 inventory_image = "door_steel.png^protector_logo.png",
491 groups = {snappy=1,bendy=2,cracky=1,melty=2,level=2,door=1},
492 tiles_bottom = {"door_steel_b.png^protector_logo.png", "door_grey.png"},
493 tiles_top = {"door_steel_a.png", "door_grey.png"},
494 sounds = default.node_sound_wood_defaults(),
495 sunlight = false,
496 })
497
498 minetest.override_item(name.."_b_1", {
499 on_rightclick = function(pos, node, clicker)
500 if not minetest.is_protected(pos, clicker:get_player_name()) then
501 on_rightclick(pos, 1, name.."_t_1", name.."_b_2", name.."_t_2", {1,2,3,0})
502 end
503 end,
504 })
505
506 minetest.override_item(name.."_t_1", {
507 on_rightclick = function(pos, node, clicker)
508 if not minetest.is_protected(pos, clicker:get_player_name()) then
509 on_rightclick(pos, -1, name.."_b_1", name.."_t_2", name.."_b_2", {1,2,3,0})
510 end
511 end,
512 })
513
514 minetest.override_item(name.."_b_2", {
515 on_rightclick = function(pos, node, clicker)
516 if not minetest.is_protected(pos, clicker:get_player_name()) then
517 on_rightclick(pos, 1, name.."_t_2", name.."_b_1", name.."_t_1", {3,0,1,2})
518 end
519 end,
520 })
521
522 minetest.override_item(name.."_t_2", {
523 on_rightclick = function(pos, node, clicker)
524 if not minetest.is_protected(pos, clicker:get_player_name()) then
525 on_rightclick(pos, -1, name.."_b_2", name.."_t_1", name.."_b_1", {3,0,1,2})
526 end
527 end,
528 })
529
530 minetest.register_craft({
531 output = name,
532 recipe = {
533 {"default:steel_ingot", "default:steel_ingot"},
534 {"default:steel_ingot", "default:copper_ingot"},
535 {"default:steel_ingot", "default:steel_ingot"}
536 }
537 })
538
539 minetest.register_craft({
540 output = name,
541 recipe = {
542 {"doors:door_steel", "default:copper_ingot"}
543 }
544 })
545
546 local function get_locked_chest_formspec(pos)
547 local spos = pos.x .. "," .. pos.y .. "," ..pos.z
548 local formspec =
549 "size[8,9]"..
550 default.gui_bg..
551 default.gui_bg_img..
552 default.gui_slots..
553 "list[nodemeta:".. spos .. ";main;0,0.3;8,4;]"..
554 "button[0,4.5;2,0.25;toup;To Chest]"..
555 "field[2.3,4.8;4,0.25;chestname;;]"..
556 "button[6,4.5;2,0.25;todn;To Inventory]"..
557 "list[current_player;main;0,5;8,1;]"..
558 "list[current_player;main;0,6.08;8,3;8]"..
559 default.get_hotbar_bg(0,5)
560 return formspec
561 end
562
563 -- Protected Chest
564
565 minetest.register_node("protector:chest", {
566 description = "Protected Chest",
567 tiles = {"default_chest_top.png", "default_chest_top.png", "default_chest_side.png",
568 "default_chest_side.png", "default_chest_side.png", "default_chest_front.png^protector_logo.png"},
569 paramtype2 = "facedir",
570 groups = {choppy=2,oddly_breakable_by_hand=2},
571 legacy_facedir_simple = true,
572 is_ground_content = false,
573 sounds = default.node_sound_wood_defaults(),
574 on_construct = function(pos)
575 local meta = minetest.get_meta(pos)
576 meta:set_string("infotext", "Protected Chest")
577 local inv = meta:get_inventory()
578 inv:set_size("main", 8*4)
579 end,
580 can_dig = function(pos,player)
581 local meta = minetest.get_meta(pos)
582 local inv = meta:get_inventory()
583 if inv:is_empty("main") then
584 if not minetest.is_protected(pos, player:get_player_name()) then
585 return true
586 end
587 end
588 end,
589 allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
590 return count
591 end,
592 allow_metadata_inventory_put = function(pos, listname, index, stack, player)
593 return stack:get_count()
594 end,
595 allow_metadata_inventory_take = function(pos, listname, index, stack, player)
596 return stack:get_count()
597 end,
598 on_metadata_inventory_put = function(pos, listname, index, stack, player)
599 minetest.log("action", player:get_player_name()..
600 " moves stuff to protected chest at "..minetest.pos_to_string(pos))
601 end,
602 on_metadata_inventory_take = function(pos, listname, index, stack, player)
603 minetest.log("action", player:get_player_name()..
604 " takes stuff from protected chest at "..minetest.pos_to_string(pos))
605 end,
606 on_rightclick = function(pos, node, clicker)
607 local meta = minetest.get_meta(pos)
608 if not minetest.is_protected(pos, clicker:get_player_name()) then
609 minetest.show_formspec(
610 clicker:get_player_name(),
611 "protector:chest_"..minetest.pos_to_string(pos),
612 get_locked_chest_formspec(pos)
613 )
614 end
615 end,
616 })
617
618 -- Proteted Chest formspec buttons
619
620 minetest.register_on_player_receive_fields(function(player,formname,fields)
621
622 if string.sub(formname,0,string.len("protector:chest_")) == "protector:chest_" then
623
624 local pos_s = string.sub(formname,string.len("protector:chest_")+1)
625 local pos = minetest.string_to_pos(pos_s)
626 local meta = minetest.env:get_meta(pos)
627
628 local chest_inv = meta:get_inventory()
629 local player_inv = player:get_inventory()
630
631 if fields.toup then
632
633 -- copy contents of players inventory to chest
634 for i,v in ipairs( player_inv:get_list( "main" ) or {}) do
635 if( chest_inv and chest_inv:room_for_item('main', v)) then
636 local leftover = chest_inv:add_item( 'main', v )
637 player_inv:remove_item( "main", v )
638 if( leftover and not( leftover:is_empty() )) then
639 player_inv:add_item( "main", v )
640 end
641 end
642 end
643
644 elseif fields.todn then
645
646 -- copy contents of chest to players inventory
647 for i,v in ipairs( chest_inv:get_list( 'main' ) or {}) do
648 if( player_inv:room_for_item( "main", v)) then
649 local leftover = player_inv:add_item( "main", v )
650 chest_inv:remove_item( 'main', v )
651 if( leftover and not( leftover:is_empty() )) then
652 chest_inv:add_item( 'main', v )
653 end
654 end
655 end
656
657 elseif fields.chestname then
658
659 -- change chest infotext to display name
660 if fields.chestname ~= "" then
661 meta:set_string("infotext", "Protected Chest ("..fields.chestname..")")
662 else
663 meta:set_string("infotext", "Protected Chest")
664 end
665
666 end
667 end
668
669 end)
670
671 -- Protected Chest recipe
672
673 minetest.register_craft({
674 output = 'protector:chest',
675 recipe = {
676 {'group:wood', 'group:wood', 'group:wood'},
677 {'group:wood', 'default:copper_ingot', 'group:wood'},
678 {'group:wood', 'group:wood', 'group:wood'},
679 }
680 })
681
682 minetest.register_craft({
683 output = 'protector:chest',
684 recipe = {
685 {'default:chest', 'default:copper_ingot', ''},
686 }
687 })