diff --git a/lwcomponents/siren.lua b/lwcomponents/siren.lua new file mode 100644 index 0000000..754a422 --- /dev/null +++ b/lwcomponents/siren.lua @@ -0,0 +1,657 @@ +local utils = ... +local S = utils.S + + + +if utils.digilines_supported or utils.mesecon_supported then + + + +local sound_interval = 5.0 + + + +local siren_sounds = +{ + "lwsiren-buzz", + "lwsiren-horn", + "lwsiren-raid", + "lwsiren-siren", +} + + + +local function start_sound (pos) + local meta = minetest.get_meta (pos) + + if meta then + local handle = meta:get_int ("sound_handle") + + if handle ~= 0 then + minetest.sound_stop (handle) + meta:set_int ("sound_handle", 0) + end + + local sound = siren_sounds[meta:get_int ("sound")] + + if sound then + handle = minetest.sound_play ( + sound, + { + pos = pos, + max_hear_distance = meta:get_int ("distance"), + gain = meta:get_int ("gain") / 100 + }) + + meta:set_int ("sound_handle", handle) + end + end +end + + + +local function stop_sound (pos) + local meta = minetest.get_meta (pos) + + if meta then + local handle = meta:get_int ("sound_handle") + + if handle ~= 0 then + minetest.sound_stop (handle) + meta:set_int ("sound_handle", 0) + end + end +end + + + +local function get_form_spec (is_off, distance, gain, sound) + return + "formspec_version[3]\n".. + "size[11.75,6.0;true]\n".. + "field[1.0,1.0;4.0,0.8;channel;Channel;${channel}]\n".. + "button[5.5,1.0;2.0,0.8;setchannel;Set]\n".. + "button[8.25,1.0;2.5,0.8;"..((is_off and "start;Start") or "stop;Stop").."]\n".. + "label[1.0,2.5;Distance]\n".. + "scrollbaroptions[min=0;max=100;smallstep=10;largestep=10;thumbsize=10]\n".. + "scrollbar[1.0,2.9;6.0,0.5;horizontal;distance;"..tostring (distance).."]\n".. + "label[1.0,4.2;Volume]\n".. + "scrollbaroptions[min=0;max=100;smallstep=10;largestep=10;thumbsize=10]\n".. + "scrollbar[1.0,4.5;6.0,0.5;horizontal;gain;"..tostring (gain).."]\n".. + "textlist[7.75,2.25;3.0,2.75;sound;Buzzer,Horn,Raid,Siren;"..tostring (sound)..";false]" +end + + + +local function update_form_spec (pos) + local node = minetest.get_node (pos) + local meta = minetest.get_meta (pos) + + if node and meta then + local is_off = node.name == "lwcomponents:siren" or + node.name == "lwcomponents:siren_locked" + + meta:set_string ("formspec", + get_form_spec (is_off, + meta:get_int ("distance"), + meta:get_int ("gain"), + meta:get_int ("sound"))) + end +end + + + +local function start_siren (pos) + local node = minetest.get_node (pos) + + if node then + if node.name == "lwcomponents:siren" then + node.name = "lwcomponents:siren_on" + + stop_sound (pos) + minetest.swap_node (pos, node) + update_form_spec (pos) + + elseif node.name == "lwcomponents:siren_locked" then + node.name = "lwcomponents:siren_locked_on" + + stop_sound (pos) + minetest.swap_node (pos, node) + update_form_spec (pos) + + end + end +end + + + +local function stop_siren (pos) + local node = minetest.get_node (pos) + + if node then + if node.name == "lwcomponents:siren_on" or + node.name == "lwcomponents:siren_alarm" then + node.name = "lwcomponents:siren" + + minetest.get_node_timer (pos):stop () + stop_sound (pos) + minetest.swap_node (pos, node) + update_form_spec (pos) + + elseif node.name == "lwcomponents:siren_locked_on" or + node.name == "lwcomponents:siren_locked_alarm" then + node.name = "lwcomponents:siren_locked" + + minetest.get_node_timer (pos):stop () + stop_sound (pos) + minetest.swap_node (pos, node) + update_form_spec (pos) + + end + end +end + + + +local function start_alarm (pos) + local node = minetest.get_node (pos) + + if node then + if node.name == "lwcomponents:siren_on" then + node.name = "lwcomponents:siren_alarm" + + minetest.get_node_timer (pos):start (sound_interval) + start_sound (pos) + minetest.swap_node (pos, node) + + elseif node.name == "lwcomponents:siren_locked_on" then + node.name = "lwcomponents:siren_locked_alarm" + + minetest.get_node_timer (pos):start (sound_interval) + start_sound (pos) + minetest.swap_node (pos, node) + + end + end +end + + + +local function stop_alarm (pos) + local node = minetest.get_node (pos) + + if node then + if node.name == "lwcomponents:siren_alarm" then + node.name = "lwcomponents:siren_on" + + minetest.get_node_timer (pos):stop () + stop_sound (pos) + minetest.swap_node (pos, node) + + elseif node.name == "lwcomponents:siren_locked_alarm" then + node.name = "lwcomponents:siren_locked_on" + + minetest.get_node_timer (pos):stop () + stop_sound (pos) + minetest.swap_node (pos, node) + + end + end +end + + + +local function on_destruct (pos) + minetest.get_node_timer (pos):stop () + stop_sound (pos) +end + + + +local function after_place_node (pos, placer, itemstack, pointed_thing) + local meta = minetest.get_meta (pos) + local is_off = itemstack and (itemstack:get_name () == "lwcomponents:siren" or + itemstack:get_name () == "lwcomponents:siren_locked") + + meta:set_string ("formspec", get_form_spec (is_off, 10, 50, 1)) + + meta:set_int ("sound", 1) + meta:set_int ("distance", 10) + meta:set_int ("gain", 50) + meta:set_int ("sound_handle", 0) + + -- If return true no item is taken from itemstack + return false +end + + + +local function after_place_node_locked (pos, placer, itemstack, pointed_thing) + after_place_node (pos, placer, itemstack, pointed_thing) + + if placer and placer:is_player () then + local meta = minetest.get_meta (pos) + + meta:set_string ("owner", placer:get_player_name ()) + meta:set_string ("infotext", "Siren (owned by "..placer:get_player_name ()..")") + end + + -- If return true no item is taken from itemstack + return false +end + + + +local function on_receive_fields (pos, formname, fields, sender) + if not utils.can_interact_with_node (pos, sender) then + return + end + + if fields.setchannel then + local meta = minetest.get_meta (pos) + + if meta then + meta:set_string ("channel", fields.channel) + end + end + + if fields.start then + start_siren (pos) + end + + if fields.stop then + stop_siren (pos) + end + + if fields.sound then + local event = minetest.explode_textlist_event (fields.sound) + + if event.type == "CHG" then + local meta = minetest.get_meta (pos) + + if meta then + meta:set_int ("sound", event.index) + end + end + end + + if fields.gain then + local event = minetest.explode_scrollbar_event (fields.gain) + + if event.type == "CHG" then + local meta = minetest.get_meta (pos) + + if meta then + meta:set_int ("gain", event.value) + end + end + end + + if fields.distance then + local event = minetest.explode_scrollbar_event (fields.distance) + + if event.type == "CHG" then + local meta = minetest.get_meta (pos) + + if meta then + meta:set_int ("distance", event.value) + end + end + end +end + + + +local function can_dig (pos, player) + if not utils.can_interact_with_node (pos, player) then + return false + end + + return true +end + + + +local function on_blast (pos, intensity) + local meta = minetest.get_meta (pos) + + if meta then + if intensity >= 1.0 then + on_destruct (pos) + minetest.remove_node (pos) + + else -- intensity < 1.0 + local node = minetest.get_node_or_nil (pos) + if node then + local items = minetest.get_node_drops (node, nil) + + if items and #items > 0 then + local stack = ItemStack (items[1]) + + if stack then + utils.item_drop (stack, nil, pos) + on_destruct (pos) + minetest.remove_node (pos) + end + end + end + end + end +end + + + +local function on_timer (pos, elapsed) + start_sound (pos) + + return true +end + + + +local function on_rightclick (pos, node, clicker, itemstack, pointed_thing) + if not utils.can_interact_with_node (pos, clicker) then + if clicker and clicker:is_player () then + local owner = "" + local meta = minetest.get_meta (pos) + + if meta then + owner = meta:get_string ("owner") + end + + local spec = + "formspec_version[3]".. + "size[8.0,4.0,false]".. + "label[1.0,1.0;Owned by "..minetest.formspec_escape (owner).."]".. + "button_exit[3.0,2.0;2.0,1.0;close;Close]" + + minetest.show_formspec (clicker:get_player_name (), + "lwcomponents:component_privately_owned", + spec) + end + + else + update_form_spec (pos) + end + + return itemstack +end + + + +local function digilines_support () + if utils.digilines_supported then + return + { + wire = + { + rules = utils.digilines_default_rules, + }, + + effector = + { + action = function (pos, node, channel, msg) + local meta = minetest.get_meta(pos) + + if meta then + local this_channel = meta:get_string ("channel") + + if this_channel ~= "" and this_channel == channel and + type (msg) == "string" then + + local m = { } + for w in string.gmatch(msg, "[^%s]+") do + m[#m + 1] = w + end + + if m[1] == "start" then + start_siren (pos) + + elseif m[1] == "stop" then + stop_siren (pos) + + elseif m[1] == "siren" then + if m[2] == "on" then + start_alarm (pos) + + elseif m[2] == "off" then + stop_alarm (pos) + + end + + elseif m[1] == "distance" then + local distance = math.min (math.max (tonumber (m[2] or 1) or 1, 1), 100) + + meta:set_int ("distance", distance) + + elseif m[1] == "volume" then + local volume = math.min (math.max (tonumber (m[2] or 1) or 1, 1), 100) + + meta:set_int ("gain", volume) + + elseif m[1] == "sound" then + if m[2] == "buzzer" then + meta:set_int ("sound", 1) + update_form_spec (pos) + + elseif m[2] == "horn" then + meta:set_int ("sound", 2) + update_form_spec (pos) + + elseif m[2] == "raid" then + meta:set_int ("sound", 3) + update_form_spec (pos) + + elseif m[2] == "siren" then + meta:set_int ("sound", 4) + update_form_spec (pos) + + end + end + end + end + end, + } + } + end + + return nil +end + + + +local function mesecon_support () + if utils.mesecon_supported then + return + { + effector = + { + rules = utils.mesecon_default_rules, + + action_on = function (pos, node) + -- do something to turn the effector on + start_alarm (pos) + end, + + action_off = function (pos, node) + -- do something to turn the effector off + stop_alarm (pos) + end, + } + } + end + + return nil +end + + + +minetest.register_node("lwcomponents:siren", { + description = S("Siren"), + tiles = { "lwsiren_base.png", "lwsiren_base.png", "lwsiren.png", + "lwsiren.png", "lwsiren.png", "lwsiren.png"}, + is_ground_content = false, + groups = { cracky = 3, wires_connect = 1 }, + --sounds = default.node_sound_stone_defaults (), + paramtype = "none", + param1 = 0, + floodable = false, + drop = "lwcomponents:siren", + _digistuff_channelcopier_fieldname = "channel", + + mesecons = mesecon_support (), + digiline = digilines_support (), + + on_destruct = on_destruct, + on_receive_fields = on_receive_fields, + can_dig = can_dig, + after_place_node = after_place_node, + on_blast = on_blast, + on_timer = on_timer, + on_rightclick = on_rightclick +}) + + + +minetest.register_node("lwcomponents:siren_locked", { + description = S("Siren (locked)"), + tiles = { "lwsiren_base.png", "lwsiren_base.png", "lwsiren.png", + "lwsiren.png", "lwsiren.png", "lwsiren.png"}, + is_ground_content = false, + groups = { cracky = 3, wires_connect = 1 }, + --sounds = default.node_sound_stone_defaults (), + paramtype = "none", + param1 = 0, + floodable = false, + drop = "lwcomponents:siren_locked", + _digistuff_channelcopier_fieldname = "channel", + + mesecons = mesecon_support (), + digiline = digilines_support (), + + on_destruct = on_destruct, + on_receive_fields = on_receive_fields, + can_dig = can_dig, + after_place_node = after_place_node_locked, + on_blast = on_blast, + on_timer = on_timer, + on_rightclick = on_rightclick +}) + + + +minetest.register_node("lwcomponents:siren_on", { + description = S("Siren"), + tiles = { "lwsiren_base.png", "lwsiren_base.png", "lwsiren_on.png", + "lwsiren_on.png", "lwsiren_on.png", "lwsiren_on.png"}, + is_ground_content = false, + groups = { cracky = 3, not_in_creative_inventory = 1, wires_connect = 1 }, + --sounds = default.node_sound_stone_defaults (), + paramtype = "none", + param1 = 0, + floodable = false, + drop = "lwcomponents:siren", + _digistuff_channelcopier_fieldname = "channel", + + mesecons = mesecon_support (), + digiline = digilines_support (), + + on_destruct = on_destruct, + on_receive_fields = on_receive_fields, + can_dig = can_dig, + after_place_node = after_place_node, + on_blast = on_blast, + on_timer = on_timer, + on_rightclick = on_rightclick +}) + + + +minetest.register_node("lwcomponents:siren_locked_on", { + description = S("Siren (locked)"), + tiles = { "lwsiren_base.png", "lwsiren_base.png", "lwsiren_on.png", + "lwsiren_on.png", "lwsiren_on.png", "lwsiren_on.png"}, + is_ground_content = false, + groups = { cracky = 3, not_in_creative_inventory = 1, wires_connect = 1 }, + --sounds = default.node_sound_stone_defaults (), + paramtype = "none", + param1 = 0, + floodable = false, + drop = "lwcomponents:siren_locked", + _digistuff_channelcopier_fieldname = "channel", + + mesecons = mesecon_support (), + digiline = digilines_support (), + + on_destruct = on_destruct, + on_receive_fields = on_receive_fields, + can_dig = can_dig, + after_place_node = after_place_node_locked, + on_blast = on_blast, + on_timer = on_timer, + on_rightclick = on_rightclick +}) + + + +minetest.register_node("lwcomponents:siren_alarm", { + description = S("Siren"), + tiles = { "lwsiren_base.png", "lwsiren_base.png", "lwsiren_alarm.png", + "lwsiren_alarm.png", "lwsiren_alarm.png", "lwsiren_alarm.png"}, + is_ground_content = false, + groups = { cracky = 3, not_in_creative_inventory = 1, wires_connect = 1 }, + --sounds = default.node_sound_stone_defaults (), + paramtype = "none", + param1 = 0, + light_source = 3, + floodable = false, + drop = "lwcomponents:siren", + _digistuff_channelcopier_fieldname = "channel", + + mesecons = mesecon_support (), + digiline = digilines_support (), + + on_destruct = on_destruct, + on_receive_fields = on_receive_fields, + can_dig = can_dig, + after_place_node = after_place_node, + on_blast = on_blast, + on_timer = on_timer, + on_rightclick = on_rightclick +}) + + + +minetest.register_node("lwcomponents:siren_locked_alarm", { + description = S("Siren (locked)"), + tiles = { "lwsiren_base.png", "lwsiren_base.png", "lwsiren_alarm.png", + "lwsiren_alarm.png", "lwsiren_alarm.png", "lwsiren_alarm.png"}, + is_ground_content = false, + groups = { cracky = 3, not_in_creative_inventory = 1, wires_connect = 1 }, + --sounds = default.node_sound_stone_defaults (), + paramtype = "none", + param1 = 0, + light_source = 3, + floodable = false, + drop = "lwcomponents:siren_locked", + _digistuff_channelcopier_fieldname = "channel", + + mesecons = mesecon_support (), + digiline = digilines_support (), + + on_destruct = on_destruct, + on_receive_fields = on_receive_fields, + can_dig = can_dig, + after_place_node = after_place_node_locked, + on_blast = on_blast, + on_timer = on_timer, + on_rightclick = on_rightclick +}) + + + +end -- utils.digilines_supported or utils.mesecon_supported + + + +-- diff --git a/lwcomponents/solid_conductor.lua b/lwcomponents/solid_conductor.lua new file mode 100644 index 0000000..af9ea28 --- /dev/null +++ b/lwcomponents/solid_conductor.lua @@ -0,0 +1,146 @@ +local utils = ... +local S = utils.S + + + +if utils.unifieddyes_supported and utils.mesecon_supported then + + + +mesecon.register_node (":lwcomponents:solid_conductor", + { + description = S("Solid Color Conductor"), + tiles = { "lwsolid_conductor.png" }, + is_ground_content = false, + sounds = ( default and default.node_sound_wood_defaults() ), + paramtype2 = "color", + palette = "unifieddyes_palette_extended.png", + on_rotate = false, + drop = "lwcomponents:solid_conductor_off", + digiline = { wire = { rules = utils.digilines_default_rules } }, + on_construct = unifieddyes.on_construct, + on_dig = unifieddyes.on_dig, + }, + { + tiles = { "lwsolid_conductor.png" }, + mesecons = + { + conductor = + { + rules = utils.mesecon_default_rules, + state = utils.mesecon_state_off, + onstate = "lwcomponents:solid_conductor_on", + } + }, + groups = { + dig_immediate = 2, + ud_param2_colorable = 1, + wires_connect = 1 + }, + }, + { + tiles = { "lwsolid_conductor.png" }, + mesecons = + { + conductor = + { + rules = utils.mesecon_default_rules, + state = utils.mesecon_state_on, + offstate = "lwcomponents:solid_conductor_off", + } + }, + groups = { + dig_immediate = 2, + ud_param2_colorable = 1, + not_in_creative_inventory = 1, + wires_connect = 1 + }, + } +) + + + +unifieddyes.register_color_craft ({ + output = "lwcomponents:solid_conductor_off 3", + palette = "extended", + type = "shapeless", + neutral_node = "lwcomponents:solid_conductor_off", + recipe = { + "NEUTRAL_NODE", + "NEUTRAL_NODE", + "NEUTRAL_NODE", + "MAIN_DYE" + } +}) + + + +mesecon.register_node (":lwcomponents:solid_horizontal_conductor", + { + description = S("Solid Color Horizontal Conductor"), + tiles = { "lwsolid_conductor.png" }, + is_ground_content = false, + sounds = ( default and default.node_sound_wood_defaults() ), + paramtype2 = "color", + palette = "unifieddyes_palette_extended.png", + on_rotate = false, + drop = "lwcomponents:solid_horizontal_conductor_off", + digiline = { wire = { rules = utils.digilines_flat_rules } }, + on_construct = unifieddyes.on_construct, + on_dig = unifieddyes.on_dig, + }, + { + tiles = { "lwsolid_conductor.png" }, + mesecons = + { + conductor = + { + rules = utils.mesecon_flat_rules, + state = utils.mesecon_state_off, + onstate = "lwcomponents:solid_horizontal_conductor_on", + } + }, + groups = { + dig_immediate = 2, + ud_param2_colorable = 1, + wires_connect = 1 + }, + }, + { + tiles = { "lwsolid_conductor.png" }, + mesecons = + { + conductor = + { + rules = utils.mesecon_flat_rules, + state = utils.mesecon_state_on, + offstate = "lwcomponents:solid_horizontal_conductor_off", + } + }, + groups = { + dig_immediate = 2, + ud_param2_colorable = 1, + not_in_creative_inventory = 1, + wires_connect = 1 + }, + } +) + + + +unifieddyes.register_color_craft ({ + output = "lwcomponents:solid_horizontal_conductor_off 3", + palette = "extended", + type = "shapeless", + neutral_node = "lwcomponents:solid_horizontal_conductor_off", + recipe = { + "NEUTRAL_NODE", + "NEUTRAL_NODE", + "NEUTRAL_NODE", + "MAIN_DYE" + } +}) + + + +end -- utils.unifieddyes_supported and utils.mesecon_supported then diff --git a/lwcomponents/storage.lua b/lwcomponents/storage.lua new file mode 100644 index 0000000..10d9265 --- /dev/null +++ b/lwcomponents/storage.lua @@ -0,0 +1,1495 @@ +local utils = ... +local S = utils.S + + + +local current_formspec_lists = { } + + + +local function unit_after_place_node (pos, placer, itemstack, pointed_thing) + local meta = minetest.get_meta (pos) + local spec = + "formspec_version[3]".. + "size[11.75,12.25,false]".. + "list[context;main;1.0,1.0;8,4;]".. + "list[current_player;main;1.0,6.5;8,4;]".. + "listring[]" + + meta:set_string ("inventory", "{ main = { } }") + meta:set_string ("formspec", spec) + + local inv = meta:get_inventory () + + inv:set_size ("main", 32) + inv:set_width ("main", 8) + + -- If return true no item is taken from itemstack + return false +end + + + +local function unit_after_place_node_locked (pos, placer, itemstack, pointed_thing) + unit_after_place_node (pos, placer, itemstack, pointed_thing) + + if placer and placer:is_player () then + local meta = minetest.get_meta (pos) + + meta:set_string ("owner", placer:get_player_name ()) + meta:set_string ("infotext", "Storage Unit (owned by "..placer:get_player_name ()..")") + end + + -- If return true no item is taken from itemstack + return false +end + + + +local function unit_can_dig (pos, player) + if not utils.can_interact_with_node (pos, player) then + return false + end + + local meta = minetest.get_meta (pos) + + if meta then + local inv = meta:get_inventory () + + if inv then + if not inv:is_empty ("main") then + return false + end + end + end + + return true +end + + + +local function unit_on_blast (pos, intensity) + local meta = minetest.get_meta (pos) + + if meta then + if intensity >= 1.0 then + local inv = meta:get_inventory () + + if inv then + local slots = inv:get_size ("main") + + for slot = 1, slots do + local stack = inv:get_stack ("main", slot) + + if stack and not stack:is_empty () then + if math.floor (math.random (0, 5)) == 3 then + utils.item_drop (stack, nil, pos) + else + utils.on_destroy (stack) + end + end + end + end + + minetest.remove_node (pos) + + else -- intensity < 1.0 + local inv = meta:get_inventory () + + if inv then + local slots = inv:get_size ("main") + + for slot = 1, slots do + local stack = inv:get_stack ("main", slot) + + if stack and not stack:is_empty () then + utils.item_drop (stack, nil, pos) + end + end + end + + local node = minetest.get_node_or_nil (pos) + if node then + local items = minetest.get_node_drops (node, nil) + + if items and #items > 0 then + local stack = ItemStack (items[1]) + + if stack then + utils.item_drop (stack, nil, pos) + minetest.remove_node (pos) + end + end + end + end + end +end + + + +local function unit_on_rightclick (pos, node, clicker, itemstack, pointed_thing) + if not utils.can_interact_with_node (pos, clicker) then + if clicker and clicker:is_player () then + local owner = "" + local meta = minetest.get_meta (pos) + + if meta then + owner = meta:get_string ("owner") + end + + local spec = + "formspec_version[3]".. + "size[8.0,4.0,false]".. + "label[1.0,1.0;Owned by "..minetest.formspec_escape (owner).."]".. + "button_exit[3.0,2.0;2.0,1.0;close;Close]" + + minetest.show_formspec (clicker:get_player_name (), + "lwcomponents:component_privately_owned", + spec) + end + end + + return itemstack +end + + + + +minetest.register_node("lwcomponents:storage_unit", { + description = S("Storage Unit"), + drawtype = "glasslike_framed", + tiles = { "lwcomponents_storage_framed.png", "lwcomponents_storage.png" }, + is_ground_content = false, + groups = { choppy = 2 }, + --sounds = default.node_sound_wood_defaults (), + paramtype = "none", + param1 = 0, + paramtype2 = "none", + param2 = 0, + floodable = false, + + after_place_node = unit_after_place_node, + can_dig = unit_can_dig, + on_blast = unit_on_blast, + on_rightclick = unit_on_rightclick +}) + + + +minetest.register_node("lwcomponents:storage_unit_locked", { + description = S("Storage Unit (locked)"), + drawtype = "glasslike_framed", + tiles = { "lwcomponents_storage_framed.png", "lwcomponents_storage.png" }, + is_ground_content = false, + groups = { choppy = 2 }, + --sounds = default.node_sound_wood_defaults (), + paramtype = "none", + param1 = 0, + paramtype2 = "none", + param2 = 0, + floodable = false, + + after_place_node = unit_after_place_node_locked, + can_dig = unit_can_dig, + on_blast = unit_on_blast, + on_rightclick = unit_on_rightclick +}) + + + +local consolidation_interval = 20 + + + +local function unit_inventory (pos, owner, inv_list) + local node = utils.get_far_node (pos) + + if node and (node.name == "lwcomponents:storage_unit" or + node.name == "lwcomponents:storage_unit_locked") then + + local meta = minetest.get_meta (pos) + local uowner = meta:get_string ("owner") + + if meta and (owner == uowner or uowner == "") then + local inv = meta:get_inventory () + + if inv then + local slots = inv:get_size ("main") + + for slot = 1, slots, 1 do + local stack = inv:get_stack ("main", slot) + + if stack and not stack:is_empty () then + local copy = ItemStack (stack) + copy:set_count (1) + local name = copy:to_string () + local item = inv_list[name] + + if not item then + inv_list[name] = { count = 0 } + item = inv_list[name] + end + + item[#item + 1] = + { + pos = vector.new (pos), + count = stack:get_count (), + slot = slot + } + + item.count = item.count + stack:get_count () + end + end + end + + return true + end + end + + return false +end + + + +local function inventory_searcher (pos, owner, coords, inv_list, check_list) + local spos = minetest.pos_to_string (pos, 0) + + if not check_list[spos] then + check_list[spos] = true + + if unit_inventory (pos, owner, inv_list) then + for _, c in ipairs (coords) do + inventory_searcher (vector.add (pos, c), owner, coords, inv_list, check_list) + end + end + end +end + + + +local function get_inventory_list (pos) + local inv_list = { } + local meta = minetest.get_meta (pos) + + if meta then + local owner = meta:get_string ("owner") + local check_list = { [minetest.pos_to_string (pos, 0)] = true } + local coords = + { + { x = 1, y = 0, z = 0 }, + { x = -1, y = 0, z = 0 }, + { x = 0, y = 1, z = 0 }, + { x = 0, y = -1, z = 0 }, + { x = 0, y = 0, z = 1 }, + { x = 0, y = 0, z = -1 } + } + + for _, c in ipairs (coords) do + inventory_searcher (vector.add (pos, c), owner, coords, inv_list, check_list) + end + end + + return inv_list +end + + + +local function count_table_keys (t) + local count = 0 + + for k, v in pairs (t) do + count = count + 1 + end + + return count +end + + + +local function get_stock_list (pos) + local inv_list = get_inventory_list (pos) + local list = { } + + for k, v in pairs (inv_list) do + local stack = ItemStack (k) + local name = stack:get_name () + local description + local custom = false + local pallet_index = nil + local tstack = stack:to_table () + + if tstack and tstack.meta and count_table_keys (tstack.meta) > 0 then + custom = true + pallet_index = tstack.meta.palette_index + end + + if stack:get_short_description () ~= "" then + description = stack:get_short_description () + elseif stack:get_description () ~= "" then + description = stack:get_description () + else + description = name + + local def = utils.find_item_def (name) + + if def then + if def.short_description then + description = def.short_description + elseif def.description then + description = def.description + end + end + end + + list[#list + 1] = + { + name = stack:get_name (), + description = utils.unescape_description (description), + id = k, + count = v.count, + custom = custom, + pallet_index = pallet_index, + } + end + + return list +end + + + +local function output_items (pos, name, count) + if count < 1 then + return 0 + end + + local meta = minetest.get_meta (pos) + + if not meta then + return 0 + end + + local inv = meta:get_inventory () + + if not inv then + return 0 + end + + local stack = ItemStack (name) + + if stack:get_stack_max () < count then + count = stack:get_stack_max () + end + + stack:set_count (count) + + while stack:get_count () > 0 do + if inv:room_for_item ("output", stack) then + break + else + stack:set_count (stack:get_count () - 1) + end + end + + if stack:get_count () < 1 then + return 0 + end + + local inv_list = get_inventory_list (pos) + local item = inv_list[name] + local left = stack:get_count () + + if item then + for i = #item, 1, -1 do + local tmeta = minetest.get_meta (item[i].pos) + local tinv = (tmeta and tmeta:get_inventory ()) or nil + + if tinv then + local s = tinv:get_stack ("main", item[i].slot) + + if utils.is_same_item (name, s) then + if s:get_count () > left then + s:set_count (s:get_count () - left) + tinv:set_stack ("main", item[i].slot, s) + left = 0 + else + tinv:set_stack ("main", item[i].slot, nil) + left = left - s:get_count () + end + end + + if left == 0 then + break + end + + end + end + end + + if left < count then + local output = ItemStack (name) + + output:set_count (count - left) + inv:add_item ("output", output) + + return count - left + end + + return 0 +end + + + +local function consolidate_itemstacks (item1, item2) + local copy1 = ItemStack (item1) + local copy2 = ItemStack (item2) + + if utils.is_same_item (copy1, copy2) then + local count = copy1:get_stack_max () - copy1:get_count () + + if count > copy2:get_count () then + count = copy2:get_count () + end + + if count > 0 then + copy1:set_count (copy1:get_count () + count) + copy2:set_count (copy2:get_count () - count) + + if copy2:get_count () < 1 then + copy2 = nil + end + end + end + + return copy1, copy2 +end + + + +local function consolidate_stock (pos) + local inv_list = get_inventory_list (pos) + + for k, v in pairs (inv_list) do + if #v > 1 then + for i = #v, 2, -1 do + local smeta = minetest.get_meta (v[i].pos) + local sinv = (smeta and smeta:get_inventory ()) or nil + + if sinv then + local src = sinv:get_stack ("main", v[i].slot) + + if src and not src:is_empty () then + for j = 1, i - 1, 1 do + local dmeta = minetest.get_meta (v[j].pos) + local dinv = (dmeta and dmeta:get_inventory ()) or nil + + if dinv then + local dest = dinv:get_stack ("main", v[j].slot) + + dest, src = consolidate_itemstacks (dest, src) + dinv:set_stack ("main", v[j].slot, dest) + end + + if not src or src:is_empty () then + break + end + end + end + + sinv:set_stack ("main", v[i].slot, src) + end + end + end + end +end + + + +local function check_consolidation (pos) + local meta = minetest.get_meta (pos) + + if meta then + local count = meta:get_int ("input_count") + 1 + + if count >= consolidation_interval then + meta:set_int ("input_count", 0) + + minetest.after (0.1, consolidate_stock, vector.new (pos)) + else + meta:set_int ("input_count", count) + end + end +end + + + +local function check_filter (pos, itemstack) + local stack = ItemStack (itemstack) + + if stack and not stack:is_empty () then + local meta = minetest.get_meta (pos) + + if meta then + local inv = meta:get_inventory () + + if inv then + if inv:is_empty ("filter") then + return true + end + + local slots = inv:get_size ("filter") + for i = 1, slots, 1 do + local s = inv:get_stack ("filter", i) + + if s and not s:is_empty () and + s:get_name () == stack:get_name () then + + return true + end + end + end + end + end + + return false +end + + + +local function unit_placer (pos, owner, stack) + local node = utils.get_far_node (pos) + + if node and (node.name == "lwcomponents:storage_unit" or + node.name == "lwcomponents:storage_unit_locked") then + + local meta = minetest.get_meta (pos) + local uowner = meta:get_string ("owner") + + if meta and (owner == uowner or uowner == "") then + local inv = meta:get_inventory () + + if inv then + local left = inv:add_item ("main", stack) + + if not left or left:is_empty () or left:get_count () == 0 then + return false, nil + end + + return true, left + end + end + end + + return false, stack +end + + + +local function inventory_placer (pos, owner, coords, stack, check_list) + local spos = minetest.pos_to_string (pos, 0) + + if not check_list[spos] then + check_list[spos] = true + + local continue, left = unit_placer (pos, owner, stack) + + if continue and left then + for _, c in ipairs (coords) do + left = inventory_placer (vector.add (pos, c), owner, coords, left, check_list) + + if not left then + break + end + end + end + + return left + end + + return stack +end + + + +local function input_item (pos, itemstack) + local stack = ItemStack (itemstack) + + if stack and not stack:is_empty () and + check_filter (pos, itemstack) then + + local meta = minetest.get_meta (pos) + + if meta then + local owner = meta:get_string ("owner") + local check_list = { [minetest.pos_to_string (pos, 0)] = true } + local coords = + { + { x = 1, y = 0, z = 0 }, + { x = -1, y = 0, z = 0 }, + { x = 0, y = 1, z = 0 }, + { x = 0, y = -1, z = 0 }, + { x = 0, y = 0, z = 1 }, + { x = 0, y = 0, z = -1 } + } + + for _, c in ipairs (coords) do + stack = inventory_placer (vector.add (pos, c), owner, coords, stack, check_list) + + if not stack then + break + end + end + end + + check_consolidation (pos) + end + + return stack +end + + + +local function store_input (pos) + local meta = minetest.get_meta (pos) + + if meta then + local inv = meta:get_inventory () + + if inv then + local slots = inv:get_size ("input") + + for slot = 1, slots, 1 do + local stack = inv:get_stack ("input", slot) + + if stack and not stack:is_empty () then + local left = input_item (pos, stack) + + if left then + left = inv:add_item ("output", left) + end + + inv:set_stack ("input", slot, left) + end + end + end + end +end + + + +local function store_input_delayed (pos) + minetest.after (0.1, store_input, pos) +end + + + +local function search_filter (name, terms) + if terms then + for _, t in ipairs (terms) do + if (name:lower ():find (t, 1, true)) then + return true + end + end + + return false + end + + return true +end + + + +local function indexer_get_blank_formspec (pos) + local meta = minetest.get_meta (pos) + + if meta and meta:get_string ("owner"):len () > 0 then + return "formspec_version[3]".. + "size[8.0,4.0,false]".. + "label[1.0,1.0;Owned by "..minetest.formspec_escape (meta:get_string ("owner")).."]" + else + return "formspec_version[3]".. + "size[8.0,4.0,false]".. + "label[1.0,1.0;Preparing form ...]" + end +end + + + +local function get_formspec_list (pos) + local inv_list = get_inventory_list (pos) + local list = { } + + for k, v in pairs (inv_list) do + local description = k + local stack = ItemStack (k) + + if stack:get_short_description () ~= "" then + description = stack:get_short_description () + elseif stack:get_description () ~= "" then + description = stack:get_description () + else + local def = utils.find_item_def (stack:get_name ()) + + if def then + if def.short_description then + description = def.short_description + elseif def.description then + description = def.description + end + end + end + + list[#list + 1] = + { + item = k, + description = utils.unescape_description (description), + count = v.count + } + end + + table.sort (list , function (e1, e2) + return (e1.description:lower () < e2.description:lower ()) + end) + + return list +end + + + +local function item_to_string_stripped (stack) + local t = ItemStack (stack):to_table () + local palette_index = (t.meta and t.meta.palette_index) + + t.meta = nil + if palette_index then + t.meta = { palette_index = palette_index } + end + + return ItemStack (t):to_string () +end + + + +local function indexer_get_formspec (pos, search) + local inv_list = get_formspec_list (pos) + + search = search or "" + + local terms = { } + for w in string.gmatch(search, "[^%s]+") do + terms[#terms + 1] = string.lower (w) + end + + if #terms < 1 then + terms = nil + end + + local formspec_list = { } + + local index = "" + local count = 0 + local top = 0 + for _, v in ipairs (inv_list) do + if search_filter (v.description, terms) then + local stack = ItemStack (v.item) + local max_stack = stack:get_stack_max () + local descr_esc = minetest.formspec_escape (v.description) + + local item_index = #formspec_list + 1 + formspec_list[item_index] = v + + local item = + string.format ("item_image_button[0.0,%0.2f;1.0,1.0;%s;01_%d;]", + top, minetest.formspec_escape (item_to_string_stripped (v.item)), + item_index) + + if max_stack >= 10 then + item = item.. + string.format ("button[1.0,%0.2f;1.0,1.0;10_%d;10]", + top, item_index) + end + + if max_stack > 1 then + item = item.. + string.format ("button[2.0,%0.2f;1.0,1.0;ST_%d_%d;%d]", + top, max_stack, item_index, max_stack) + end + + item = item.. + string.format ("label[3.1,%0.2f;%d]".. + "label[4.4,%0.2f;%s]", + top + 0.5, v.count, + top + 0.5, descr_esc) + + index = index..item + + top = top + 1.0 + count = count + 1 + end + end + + local scroll_height = ((count < 12 and 0) or (count - 11)) * 10 + local thumb_size = (count < 12 and 11) or (scroll_height * (11 / count)) + + if thumb_size < (scroll_height / 10) then + thumb_size = scroll_height / 10 + end + + local spec = + string.format ("formspec_version[3]".. + "size[21.75,14.5,false]".. + "field[1.0,1.21;7.5,0.8;search_field;;%s]\n".. + "field_close_on_enter[search_field;false]".. + "button[8.5,1.21;2.0,0.8;search;Search]\n".. + "scrollbaroptions[min=0;max=%d;smallstep=10;largestep=100;thumbsize=%d;arrows=default]".. + "scrollbar[10.0,2.5;0.5,11.0;vertical;index_scrollbar;0-%d]".. + "scroll_container[1.0,2.5;9.0,11.0;index_scrollbar;vertical;0.1]".. + "%s".. + "scroll_container_end[]".. + "field[11.0,1.21;3.0,0.8;channel;Channel;${channel}]\n".. + "field_close_on_enter[channel;false]".. + "button[14.0,1.21;1.5,0.8;setchannel;Set]\n".. + "label[11.0,3.6;Input]".. + "list[context;input;11.0,3.8;2,2;]".. + "listring[context;output]".. + "label[16.0,1.0;Output]".. + "list[context;output;16.0,1.25;4,4;]".. + "listring[current_player;main]".. + "label[11.0,6.75;Filter]".. + "list[context;filter;11.0,7.0;8,1;]".. + "list[current_player;main;11.0,8.75;8,4;]".. + "listring[context;input]", + search, + scroll_height, + thumb_size, + scroll_height, + index) + + current_formspec_lists[minetest.pos_to_string (pos, 0)] = formspec_list + + return spec +end + + + +local function indexer_after_place_base (pos, placer, itemstack, pointed_thing) + local meta = minetest.get_meta (pos) + + meta:set_string ("inventory", "{ input = { }, output = { }, filter = { } }") + meta:set_string ("formspec", indexer_get_blank_formspec (pos)) + + local inv = meta:get_inventory () + + inv:set_size ("input", 4) + inv:set_width ("input", 2) + inv:set_size ("output", 16) + inv:set_width ("output", 4) + inv:set_size ("filter", 8) + inv:set_width ("filter", 2) +end + + + +local function indexer_after_place_node (pos, placer, itemstack, pointed_thing) + indexer_after_place_base (pos, placer, itemstack, pointed_thing) + utils.pipeworks_after_place (pos) + + -- If return true no item is taken from itemstack + return false +end + + + +local function indexer_after_place_node_locked (pos, placer, itemstack, pointed_thing) + indexer_after_place_base (pos, placer, itemstack, pointed_thing) + + if placer and placer:is_player () then + local meta = minetest.get_meta (pos) + + meta:set_string ("owner", placer:get_player_name ()) + meta:set_string ("infotext", "Storage Indexer (owned by "..placer:get_player_name ()..")") + end + + utils.pipeworks_after_place (pos) + + -- If return true no item is taken from itemstack + return false +end + + + +local function indexer_can_dig (pos, player) + if not utils.can_interact_with_node (pos, player) then + return false + end + + local meta = minetest.get_meta (pos) + + if meta then + local inv = meta:get_inventory () + + if inv then + if not inv:is_empty ("input") then + return false + end + + if not inv:is_empty ("output") then + return false + end + end + end + + return true +end + + + +local function indexer_on_blast (pos, intensity) + local meta = minetest.get_meta (pos) + + if meta then + if intensity >= 1.0 then + local inv = meta:get_inventory () + + if inv then + local slots = inv:get_size ("input") + + for slot = 1, slots do + local stack = inv:get_stack ("input", slot) + + if stack and not stack:is_empty () then + if math.floor (math.random (0, 5)) == 3 then + utils.item_drop (stack, nil, pos) + else + utils.on_destroy (stack) + end + end + end + + slots = inv:get_size ("output") + + for slot = 1, slots do + local stack = inv:get_stack ("output", slot) + + if stack and not stack:is_empty () then + if math.floor (math.random (0, 5)) == 3 then + utils.item_drop (stack, nil, pos) + else + utils.on_destroy (stack) + end + end + end + end + + minetest.remove_node (pos) + + else -- intensity < 1.0 + local inv = meta:get_inventory () + + if inv then + local slots = inv:get_size ("input") + + for slot = 1, slots do + local stack = inv:get_stack ("input", slot) + + if stack and not stack:is_empty () then + utils.item_drop (stack, nil, pos) + end + end + + slots = inv:get_size ("output") + + for slot = 1, slots do + local stack = inv:get_stack ("output", slot) + + if stack and not stack:is_empty () then + utils.item_drop (stack, nil, pos) + end + end + end + + local node = minetest.get_node_or_nil (pos) + if node then + local items = minetest.get_node_drops (node, nil) + + if items and #items > 0 then + local stack = ItemStack (items[1]) + + if stack then + utils.item_drop (stack, nil, pos) + minetest.remove_node (pos) + end + end + end + end + end +end + + + +local function indexer_on_rightclick (pos, node, clicker, itemstack, pointed_thing) + local meta = minetest.get_meta (pos) + + if meta then + if not utils.can_interact_with_node (pos, clicker) then + if clicker and clicker:is_player () then + local owner = meta:get_string ("owner") + + local spec = + "formspec_version[3]".. + "size[8.0,4.0,false]".. + "label[1.0,1.0;Owned by "..minetest.formspec_escape (owner).."]".. + "button_exit[3.0,2.0;2.0,1.0;close;Close]" + + minetest.show_formspec (clicker:get_player_name (), + "lwcomponents:component_privately_owned", + spec) + end + + return itemstack + end + + meta:set_string ("formspec", indexer_get_formspec (pos)) + end + + return itemstack +end + + + +local function indexer_on_receive_fields (pos, formname, fields, sender) + if not utils.can_interact_with_node (pos, sender) then + return + end + + if fields.setchannel or (fields.key_enter_field and + fields.key_enter_field == "channel") then + local meta = minetest.get_meta (pos) + + if meta then + meta:set_string ("channel", fields.channel) + end + + elseif fields.search or (fields.key_enter_field and + fields.key_enter_field == "search_field") then + local meta = minetest.get_meta (pos) + + if meta then + meta:set_string ("formspec", indexer_get_formspec (pos, fields.search_field)) + end + + elseif fields.quit then + local meta = minetest.get_meta (pos) + + if meta and meta:get_string ("owner"):len () > 0 then + meta:set_string ("formspec", indexer_get_blank_formspec (pos)) + current_formspec_lists[minetest.pos_to_string (pos, 0)] = nil + end + + else + local formspec_list = current_formspec_lists[minetest.pos_to_string (pos, 0)] + + if formspec_list then + for k, v in pairs (fields) do + if k:sub (1, 3) == "01_" then + local index = tonumber (k:sub (4, -1)) + + if index and formspec_list[index] then + output_items (pos, formspec_list[index].item, 1) + end + + break + elseif k:sub (1, 3) == "10_" then + local index = tonumber (k:sub (4, -1)) + + if index and formspec_list[index] then + output_items (pos, formspec_list[index].item, 10) + end + + break + elseif k:sub (1, 3) == "ST_" then + local marker = k:find ("_", 4, true) + + if marker then + local qty = tonumber (k:sub (4, marker - 1) or 1) + local index = tonumber (k:sub (marker + 1, -1)) + + if index and formspec_list[index] then + output_items (pos, formspec_list[index].item, qty) + end + end + end + end + end + end +end + + + +local function indexer_on_metadata_inventory_put (pos, listname, index, stack, player) + if listname == "input" then + store_input_delayed (pos) + end +end + + + +local function indexer_on_metadata_inventory_move (pos, from_list, from_index, + to_list, to_index, count, player) + if from_list == "output" and to_list == "input" then + store_input_delayed (pos) + end +end + + + +local function indexer_allow_metadata_inventory_put (pos, listname, index, stack, player) + if listname == "filter" then + local meta = minetest.get_meta (pos) + + if meta then + local inv = meta:get_inventory () + + if inv then + inv:set_stack ("filter", index, ItemStack (stack:get_name ())) + end + end + + return 0 + end + + return stack:get_stack_max () +end + + + +local function indexer_allow_metadata_inventory_take (pos, listname, index, stack, player) + if listname == "filter" then + local meta = minetest.get_meta (pos) + + if meta then + local inv = meta:get_inventory () + + if inv then + inv:set_stack ("filter", index, nil) + end + end + + return 0 + end + + return stack:get_stack_max () +end + + + +local function indexer_allow_metadata_inventory_move (pos, from_list, from_index, + to_list, to_index, count, player) + local meta = minetest.get_meta (pos) + + if meta then + local inv = meta:get_inventory () + + if inv then + if from_list == "filter" then + if to_list == "filter" then + return 1 + end + + inv:set_stack ("filter", from_index, nil) + + return 0 + + elseif to_list == "filter" then + local stack = inv:get_stack (from_list, from_index) + + if stack and not stack:is_empty () then + inv:set_stack ("filter", to_index, ItemStack (stack:get_name ())) + end + + return 0 + + else + local stack = inv:get_stack (from_list, from_index) + + if stack and not stack:is_empty () then + return stack:get_stack_max () + end + end + end + end + + return utils.settings.default_stack_max +end + + + +local function send_stock_message (pos) + if utils.digilines_supported then + local meta = minetest.get_meta (pos) + + if meta then + local channel = meta:get_string ("channel") + + if channel:len () > 0 then + local msg = + { + action = "inventory", + inventory = get_stock_list (pos) + } + + utils.digilines_receptor_send (pos, + utils.digilines_default_rules, + channel, + msg) + end + end + end +end + + + +local function digilines_support () + if utils.digilines_supported then + return + { + wire = + { + rules = utils.digilines_default_rules, + }, + + effector = + { + action = function (pos, node, channel, msg) + local meta = minetest.get_meta(pos) + + if meta then + local this_channel = meta:get_string ("channel") + + if this_channel ~= "" then + if type (msg) == "string" then + local m = { } + for w in string.gmatch(msg, "[^%s]+") do + m[#m + 1] = w + end + + if this_channel == channel then + if m[1] == "output" then + if m[2] then + output_items (pos, m[2], tonumber (m[3] or 1) or 1) + end + + elseif m[1] == "inventory" then + send_stock_message (pos) + + end + end + + elseif type (msg) == "table" then + if this_channel == channel then + if msg.action and msg.action == "output" and + type (msg.item) == "string" then + + output_items (pos, msg.item, tonumber (msg.count or 1) or 1) + end + end + + end + end + end + end, + } + } + end + + return nil +end + + + +local function pipeworks_support () + if utils.pipeworks_supported then + return + { + priority = 100, + input_inventory = "output", + connect_sides = { left = 1, right = 1, front = 1, back = 1, bottom = 1, top = 1 }, + + insert_object = function (pos, node, stack, direction) + local meta = minetest.get_meta (pos) + local inv = (meta and meta:get_inventory ()) or nil + + if inv then + store_input_delayed (pos) + + return inv:add_item ("input", stack) + end + + return stack + end, + + can_insert = function (pos, node, stack, direction) + local meta = minetest.get_meta (pos) + local inv = (meta and meta:get_inventory ()) or nil + + if inv then + return inv:room_for_item ("input", stack) + end + + return false + end, + + can_remove = function (pos, node, stack, dir) + -- returns the maximum number of items of that stack that can be removed + local meta = minetest.get_meta (pos) + local inv = (meta and meta:get_inventory ()) or nil + + if inv then + local slots = inv:get_size ("output") + + for i = 1, slots, 1 do + local s = inv:get_stack ("output", i) + + if s and not s:is_empty () and utils.is_same_item (stack, s) then + return s:get_count () + end + end + end + + return 0 + end, + + remove_items = function (pos, node, stack, dir, count) + -- removes count items and returns them + local meta = minetest.get_meta (pos) + local inv = (meta and meta:get_inventory ()) or nil + local left = count + + if inv then + local slots = inv:get_size ("output") + + for i = 1, slots, 1 do + local s = inv:get_stack ("output", i) + + if s and not s:is_empty () and utils.is_same_item (s, stack) then + if s:get_count () > left then + s:set_count (s:get_count () - left) + inv:set_stack ("output", i, s) + left = 0 + else + left = left - s:get_count () + inv:set_stack ("output", i, nil) + end + end + + if left == 0 then + break + end + end + end + + local result = ItemStack (stack) + result:set_count (count - left) + + return result + end + } + end + + return nil +end + + + +local indexer_groups = { choppy = 2 } +if utils.pipeworks_supported then + indexer_groups.tubedevice = 1 + indexer_groups.tubedevice_receiver = 1 +end + + + +minetest.register_node("lwcomponents:storage_indexer", { + description = S("Storage Indexer"), + drawtype = "normal", + tiles = { "lwcomponents_storage_framed.png", "lwcomponents_storage_framed.png", + "lwcomponents_storage_indexer.png", "lwcomponents_storage_indexer.png", + "lwcomponents_storage_indexer.png", "lwcomponents_storage_indexer.png",}, + is_ground_content = false, + groups = table.copy (indexer_groups), + --sounds = default.node_sound_wood_defaults (), + paramtype = "none", + param1 = 0, + paramtype2 = "none", + param2 = 0, + floodable = false, + _digistuff_channelcopier_fieldname = "channel", + + digiline = digilines_support (), + tube = pipeworks_support (), + + on_receive_fields = indexer_on_receive_fields, + after_place_node = indexer_after_place_node, + can_dig = indexer_can_dig, + after_dig_node = utils.pipeworks_after_dig, + on_blast = indexer_on_blast, + on_rightclick = indexer_on_rightclick, + on_metadata_inventory_put = indexer_on_metadata_inventory_put, + on_metadata_inventory_move = indexer_on_metadata_inventory_move, + allow_metadata_inventory_take = indexer_allow_metadata_inventory_take, + allow_metadata_inventory_put = indexer_allow_metadata_inventory_put, + allow_metadata_inventory_move = indexer_allow_metadata_inventory_move +}) + + + +minetest.register_node("lwcomponents:storage_indexer_locked", { + description = S("Storage Indexer (locked)"), + drawtype = "normal", + tiles = { "lwcomponents_storage_framed.png", "lwcomponents_storage_framed.png", + "lwcomponents_storage_indexer.png", "lwcomponents_storage_indexer.png", + "lwcomponents_storage_indexer.png", "lwcomponents_storage_indexer.png",}, + is_ground_content = false, + groups = table.copy (indexer_groups), + --sounds = default.node_sound_wood_defaults (), + paramtype = "none", + param1 = 0, + paramtype2 = "none", + param2 = 0, + floodable = false, + _digistuff_channelcopier_fieldname = "channel", + + digiline = digilines_support (), + tube = pipeworks_support (), + + on_receive_fields = indexer_on_receive_fields, + after_place_node = indexer_after_place_node_locked, + can_dig = indexer_can_dig, + after_dig_node = utils.pipeworks_after_dig, + on_blast = indexer_on_blast, + on_rightclick = indexer_on_rightclick, + on_metadata_inventory_put = indexer_on_metadata_inventory_put, + on_metadata_inventory_move = indexer_on_metadata_inventory_move, + allow_metadata_inventory_take = indexer_allow_metadata_inventory_take, + allow_metadata_inventory_put = indexer_allow_metadata_inventory_put, + allow_metadata_inventory_move = indexer_allow_metadata_inventory_move +}) + + + +utils.hopper_add_container({ + {"top", "lwcomponents:storage_indexer", "output"}, -- take items from above into hopper below + {"bottom", "lwcomponents:storage_indexer", "input"}, -- insert items below from hopper above + {"side", "lwcomponents:storage_indexer", "input"}, -- insert items from hopper at side +}) + + + +utils.hopper_add_container({ + {"top", "lwcomponents:storage_indexer_locked", "output"}, -- take items from above into hopper below + {"bottom", "lwcomponents:storage_indexer_locked", "input"}, -- insert items below from hopper above + {"side", "lwcomponents:storage_indexer_locked", "input"}, -- insert items from hopper at side +}) + + + +-- diff --git a/lwcomponents/through_wire.lua b/lwcomponents/through_wire.lua new file mode 100644 index 0000000..c3c5837 --- /dev/null +++ b/lwcomponents/through_wire.lua @@ -0,0 +1,80 @@ +local utils = ... +local S = utils.S + + + +if utils.mesecon_supported then + + + +local through_wire_get_rules = function (node) + local rules = { {x = -1, y = 0, z = 0}, + {x = 2, y = 0, z = 0}, + {x = 3, y = 0, z = 0} } + + if node.param2 == 2 then + rules = mesecon.rotate_rules_left(rules) + elseif node.param2 == 3 then + rules = mesecon.rotate_rules_right(mesecon.rotate_rules_right(rules)) + elseif node.param2 == 0 then + rules = mesecon.rotate_rules_right(rules) + end + + return rules +end + + + +mesecon.register_node ("lwcomponents:through_wire", { + description = S("Mesecons Through Wire"), + drawtype = "nodebox", + paramtype = "light", + paramtype2 = "facedir", + is_ground_content = false, + sunlight_propagates = true, + walkable = false, + on_rotate = false, + selection_box = { + type = "fixed", + fixed = { -3/16, -8/16, -8/16, 3/16, 3/16, 8/16 } + }, + node_box = { + type = "fixed", + fixed = { + { -3/16, -3/16, 13/32 , 3/16, 3/16 , 8/16 }, -- the smaller bump + { -1/32, -1/32, 1/2 , 1/32, 1/32 , 3/2 }, -- the wire through the block + { -2/32, -1/2 , 0.5002-3/32 , 2/32, 0 , 0.5 }, -- the vertical wire bit + { -2/32, -1/2 , -16/32+0.001 , 2/32, -14/32, 7/16+0.002 } -- the horizontal wire + } + }, + drop = "lwcomponents:through_wire_off", + --sounds = default.node_sound_defaults(), +}, { + tiles = { "mesecons_wire_off.png" }, + groups = { dig_immediate = 3 }, + mesecons = { + conductor = { + state = mesecon.state.off, + rules = through_wire_get_rules, + onstate = "lwcomponents:through_wire_on" + } + } +}, { + tiles = { "mesecons_wire_on.png" }, + groups = { dig_immediate = 3, not_in_creative_inventory = 1 }, + mesecons = { + conductor = { + state = mesecon.state.on, + rules = through_wire_get_rules, + offstate = "lwcomponents:through_wire_off" + } + } +}) + + + +end -- utils.mesecon_supported + + + +-- diff --git a/lwcomponents/utils.lua b/lwcomponents/utils.lua new file mode 100644 index 0000000..e7a4003 --- /dev/null +++ b/lwcomponents/utils.lua @@ -0,0 +1,471 @@ +local utils = ... + + + +if minetest.get_translator and minetest.get_translator ("lwcomponents") then + utils.S = minetest.get_translator ("lwcomponents") +elseif minetest.global_exists ("intllib") then + if intllib.make_gettext_pair then + utils.S = intllib.make_gettext_pair () + else + utils.S = intllib.Getter () + end +else + utils.S = function (s) return s end +end + + + +-- check for mesecon +if minetest.global_exists ("mesecon") then + utils.mesecon_supported = true + utils.mesecon_state_on = mesecon.state.on + utils.mesecon_state_off = mesecon.state.off + utils.mesecon_receptor_on = mesecon.receptor_on + utils.mesecon_receptor_off = mesecon.receptor_off + utils.mesecon_default_rules = mesecon.rules.default + utils.mesecon_flat_rules = mesecon.rules.flat + +else + utils.mesecon_supported = false + utils.mesecon_state_on = "on" + utils.mesecon_state_off = "off" + utils.mesecon_default_rules = { } + utils.mesecon_flat_rules = { } + + -- dummies + utils.mesecon_receptor_on = function (pos, rules) + end + + utils.mesecon_receptor_off = function (pos, rules) + end + +end + + + +-- check for digilines +if minetest.global_exists ("digilines") then + utils.digilines_supported = true + utils.digilines_default_rules = digiline.rules.default + utils.digilines_flat_rules = { + { x = 1, y = 0, z = 0 }, + { x = -1, y = 0, z = 0 }, + { x = 0, y = 0, z = 1 }, + { x = 0, y = 0, z = -1 }, + } + utils.digilines_receptor_send = digilines.receptor_send +else + utils.digilines_supported = false + utils.digilines_default_rules = { } + utils.digilines_flat_rules = { } + + -- dummy + utils.digilines_receptor_send = function (pos, rules, channel, msg) + end +end + + + +-- check for unifieddyes +if minetest.global_exists ("unifieddyes") then + utils.unifieddyes_supported = true +else + utils.unifieddyes_supported = false +end + + + +-- check for hopper +if minetest.global_exists ("hopper") then + utils.hopper_supported = true + + utils.hopper_add_container = function (list) + hopper:add_container (list) + end +else + utils.hopper_supported = false + + utils.hopper_add_container = function (list) + end +end + + + +-- check for digistuff +if minetest.global_exists ("digistuff") then + utils.digistuff_supported = true +else + utils.digistuff_supported = false +end + + + +-- check for pipeworks +if minetest.global_exists ("pipeworks") then + utils.pipeworks_supported = true + utils.pipeworks_after_place = pipeworks.after_place + utils.pipeworks_after_dig = pipeworks.after_dig +else + utils.pipeworks_supported = false + utils.pipeworks_after_place = function (pos) + end + utils.pipeworks_after_dig = function (pos) + end +end + + + +function utils.on_destroy (itemstack) + local stack = ItemStack (itemstack) + + if stack and stack:get_count () > 0 then + local def = utils.find_item_def (stack:get_name ()) + + if def and def.on_destroy then + def.on_destroy (stack) + end + end +end + + + +function utils.item_pickup (entity, cleanup) + local stack = nil + + if entity and entity.name and entity.name == "__builtin:item" and + entity.itemstring and entity.itemstring ~= "" then + + stack = ItemStack (entity.itemstring) + + if cleanup ~= false then + entity.itemstring = "" + entity.object:remove () + end + end + + return stack +end + + + +function utils.item_drop (itemstack, dropper, pos) + if itemstack then + local def = utils.find_item_def (itemstack:get_name ()) + + if def and def.on_drop then + return def.on_drop (itemstack, dropper, pos) + end + end + + return minetest.item_drop (itemstack, dropper, pos) +end + + + +function utils.can_interact_with_node (pos, player) + if not player or not player:is_player () then + return false + end + + if minetest.check_player_privs (player, "protection_bypass") then + return true + end + + local meta = minetest.get_meta (pos) + if meta then + local owner = meta:get_string ("owner") + local name = player:get_player_name () + + if not owner or owner == "" or owner == name then + return true + end + end + + return false +end + + + +function utils.get_far_node (pos) + local node = minetest.get_node (pos) + + if node.name == "ignore" then + minetest.get_voxel_manip ():read_from_map (pos, pos) + + node = minetest.get_node (pos) + + if node.name == "ignore" then + return nil + end + end + + return node +end + + + +function utils.find_item_def (name) + local def = minetest.registered_items[name] + + if not def then + def = minetest.registered_craftitems[name] + end + + if not def then + def = minetest.registered_nodes[name] + end + + if not def then + def = minetest.registered_tools[name] + end + + return def +end + + + +function utils.table_equal (t1, t2) + for k, v in pairs (t1) do + if type (t2[k]) ~= type (v) then + return false + end + + if type (v) == "table" then + if not utils.table_equal (v, t2[k]) then + return false + end + elseif v ~= t2[k] then + return false + end + end + + for k, v in pairs (t2) do + if type (t1[k]) ~= type (v) then + return false + end + + if type (v) == "table" then + if not utils.table_equal (v, t1[k]) then + return false + end + elseif v ~= t1[k] then + return false + end + end + + return true +end + + + +function utils.is_same_item (item1, item2) + local copy1 = ItemStack (item1) + local copy2 = ItemStack (item2) + + if copy1 and copy2 then + copy1:set_count (1) + copy2:set_count (1) + + return utils.table_equal (copy1:to_table (), copy2:to_table ()) + end + + return false +end + + + +function utils.unescape_description (description) + description = description:gsub (string.char (27, 70), ""): + gsub (string.char (27, 69), ""): + gsub ("\n", " ") + + local first = description:find (string.char (27, 40, 84, 64), 1, true) + while first do + local last = description:find (")", first + 4, true) + + if not last then + last = first + 3 + end + + description = description:sub (1, first - 1)..description:sub (last + 1) + + first = description:find (string.char (27, 40, 84, 64), 1, true) + end + + return description +end + + + +function utils.is_drop (obj) + if obj then + local entity = obj.get_luaentity and obj:get_luaentity () + + return (entity and entity.name and entity.name == "__builtin:item") + end + + return false +end + + + +function utils.destroy_node (pos) + local node = utils.get_far_node (pos) + + if node then + local items = minetest.get_node_drops (node, nil) + + if items then + for i = 1, #items do + local stack = ItemStack (items[i]) + + if stack and not stack:is_empty () then + local name = stack:get_name () + local def = utils.find_item_def (name) + + if def then + if def.preserve_metadata then + def.preserve_metadata (pos, node, minetest.get_meta (pos), { stack }) + end + + utils.on_destroy (stack) + end + end + end + end + + minetest.remove_node (pos) + end +end + + + +utils.registered_spawners = { } +-- each entry [spawner_itemname] = spawner_func + + + +function utils.register_spawner (itemname, spawn_func) + if type (itemname) == "string" and type (spawn_func) == "function" then + if not utils.registered_spawners[itemname] then + utils.registered_spawners[itemname] = spawn_func + + return true + end + end + + return false +end + + + +function utils.spawn_registered (itemname, spawn_pos, itemstack, owner, spawner_pos, spawner_dir, force) + local func = utils.registered_spawners[itemname] + + if func then + local result, obj, cancel = pcall (func, spawn_pos, itemstack, owner, spawner_pos, spawner_dir, force) + + if result and (obj == nil or type (obj) == "userdata" or type (obj) == "table") then + return obj, cancel + end + + minetest.log ("error", "lwcomponents.register_spawner spawner function for "..itemname.." failed ".. + ((type (obj) == "string" and obj) or "")) + + return nil, true + end + + return nil, false +end + + + +function utils.can_place (pos) + local node = minetest.get_node_or_nil (pos) + + if node and node.name ~= "air" then + local def = minetest.registered_nodes[node.name] + + if not def or not def.buildable_to then + return false + end + end + + return true +end + + + +function utils.is_protected (pos, player) + local name = (player and player:get_player_name ()) or "" + + return minetest.is_protected (pos, name) +end + + + +function utils.get_on_rightclick (pos, player) + local node = minetest.get_node_or_nil (pos) + + if node then + local def = minetest.registered_nodes[node.name] + + if def and def.on_rightclick and + not (player and player:is_player () and + player:get_player_control ().sneak) then + + return def.on_rightclick + end + end + + return nil +end + + + +function utils.is_creative (player) + if minetest.settings:get_bool ("creative_mode") then + return true + end + + if player and player:is_player () then + return minetest.is_creative_enabled (player:get_player_name ()) or + minetest.check_player_privs (player, "creative") + end + + return false +end + + + +local crafting_mods = dofile (minetest.get_modpath ("lwcomponents").."/crafting_mods.lua") + + +function utils.get_crafting_mods (item) + return crafting_mods[item] +end + + + +function utils.hex_decode (hex) + return (hex:gsub ("%x%x", function (digits) + return string.char (tonumber (digits, 16)) + end)) +end + + + +function utils.hex_encode (str) + return (str:gsub (".", function (char) + return string.format ("%2x", char:byte ()) + end)) +end + + + + + + + +--