diff --git a/lwcomponents/dropper.lua b/lwcomponents/dropper.lua new file mode 100644 index 0000000..6030eb4 --- /dev/null +++ b/lwcomponents/dropper.lua @@ -0,0 +1,608 @@ +local utils = ... +local S = utils.S + + + +if utils.digilines_supported or utils.mesecon_supported then + + + +local function drop_pos (pos, node) + if node.param2 == 0 then + return { x = pos.x, y = pos.y, z = pos.z - 1 } + elseif node.param2 == 1 then + return { x = pos.x - 1, y = pos.y, z = pos.z } + elseif node.param2 == 2 then + return { x = pos.x, y = pos.y, z = pos.z + 1 } + elseif node.param2 == 3 then + return { x = pos.x + 1, y = pos.y, z = pos.z } + else + return { x = pos.x, y = pos.y, z = pos.z } + end +end + + + +local function send_drop_message (pos, slot, name, qty) + 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 + utils.digilines_receptor_send (pos, + utils.digilines_default_rules, + channel, + { action = "drop", + name = name, + slot = slot, + qty = qty }) + end + end + end +end + + + +-- slot: +-- nil or "nil"- next item, no drop if empty, max qty or less of first found item +-- number - qty items from slot, no drop if empty, max qty or less +-- string - name of item to drop, no drop if none, max qty or less +local function drop_item (pos, node, slot, qty) + local meta = minetest.get_meta (pos) + + if meta then + local inv = meta:get_inventory () + + if inv then + local item + + if qty then + qty = tonumber (qty) + end + + if not qty then + qty = tonumber (meta:get_string ("quantity")) or 1 + end + + qty = math.max (qty, 1) + + if not slot or (type (slot) == "string" and slot == "nil") then + local slots = inv:get_size ("main") + + for i = 1, slots do + local stack = inv:get_stack ("main", i) + + if not stack:is_empty () and stack:get_count () > 0 then + item = stack:get_name () + break + end + end + + slot = nil + + elseif type (slot) == "string" then + item = slot + slot = nil + + else + slot = tonumber (slot) + end + + if slot then + local stack = inv:get_stack ("main", slot) + + if not stack:is_empty () and stack:get_count () > 0 then + item = stack:get_name () + local drop + + if stack:get_count () >= qty then + drop = qty + stack:set_count (stack:get_count () - qty) + inv:set_stack ("main", slot, stack) + else + drop = stack:get_count () + inv:set_stack ("main", slot, nil) + end + + if drop > 0 then + utils.item_drop (ItemStack (item.." "..drop), nil, drop_pos (pos, node)) + + send_drop_message (pos, slot, item, drop) + + return true, slot, item + end + end + + elseif item then + local slots = inv:get_size ("main") + local drop = 0 + + for i = 1, slots do + local stack = inv:get_stack ("main", i) + + if not stack:is_empty () and stack:get_count () > 0 then + if item == stack:get_name () then + local remain = qty - drop + + slot = (slot and -1) or i + + if stack:get_count () > remain then + stack:set_count (stack:get_count () - remain) + drop = qty + inv:set_stack ("main", i, stack) + else + drop = drop + stack:get_count () + inv:set_stack ("main", i, nil) + end + end + end + + if drop == qty then + break + end + end + + if drop > 0 then + utils.item_drop (ItemStack (item.." "..drop), nil, drop_pos (pos, node)) + + send_drop_message (pos, slot, item, drop) + + return true, slot, item + end + end + end + end + + return false +end + + + +local function get_formspec () + return + "formspec_version[3]\n".. + "size[11.75,13.75;true]\n".. + "field[1.0,1.0;4.0,0.8;channel;Channel;${channel}]".. + "button[5.5,1.0;2.0,0.8;setchannel;Set]".. + "list[context;main;1.0,2.5;4,4;]".. + "list[current_player;main;1.0,8.0;8,4;]".. + "listring[]".. + "field[6.5,2.9;2.75,0.8;quantity;Qty;${quantity}]".. + "button[9.25,2.9;1.5,0.8;setquantity;Set]" +end + + + +local function after_place_base (pos, placer, itemstack, pointed_thing) + local meta = minetest.get_meta (pos) + + meta:set_string ("inventory", "{ main = { } }") + meta:set_string ("quantity", "1") + meta:set_string ("formspec", get_formspec ()) + + local inv = meta:get_inventory () + + inv:set_size ("main", 16) + inv:set_width ("main", 4) +end + + + +local function after_place_node (pos, placer, itemstack, pointed_thing) + 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 after_place_node_locked (pos, placer, itemstack, pointed_thing) + 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", "Dropper (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 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.setquantity then + local meta = minetest.get_meta (pos) + + if meta then + local qty = math.max (tonumber (fields.quantity or 1) or 1, 1) + meta:set_string ("quantity", tostring (qty)) + end + end +end + + + +local function 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 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 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 + local meta = minetest.get_meta (pos) + + if meta then + if meta:get_string ("quantity") == "" then + meta:set_string ("quantity", "1") + meta:set_string ("formspec", get_formspec ()) + end + end + 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] == "drop" then + if m[2] and tonumber (m[2]) then + m[2] = tonumber (m[2]) + end + + if m[3] and tonumber (m[3]) then + m[3] = tonumber (m[3]) + else + m[3] = nil + end + + drop_item (pos, node, m[2], m[3]) + end + end + end + end, + } + } + end + + return nil +end + + + +local function mesecon_support () + if utils.mesecon_supported then + return + { + effector = + { + rules = utils.mesecon_flat_rules, + + action_on = function (pos, node) + drop_item (pos, node) + end + } + } + end + + return nil +end + + + +local function pipeworks_support () + if utils.pipeworks_supported then + return + { + priority = 100, + input_inventory = "main", + connect_sides = { left = 1, right = 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 + return inv:add_item ("main", 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 ("main", 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 ("main") + + for i = 1, slots, 1 do + local s = inv:get_stack ("main", 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 ("main") + + for i = 1, slots, 1 do + local s = inv:get_stack ("main", 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 ("main", i, s) + left = 0 + else + left = left - s:get_count () + inv:set_stack ("main", 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 dropper_groups = { cracky = 3, wires_connect = 1 } +if utils.pipeworks_supported then + dropper_groups.tubedevice = 1 + dropper_groups.tubedevice_receiver = 1 +end + + + +minetest.register_node("lwcomponents:dropper", { + description = S("Dropper"), + tiles = { "lwdropper.png", "lwdropper.png", "lwdropper.png", + "lwdropper.png", "lwdropper.png", "lwdropper_face.png"}, + is_ground_content = false, + groups = table.copy (dropper_groups), + --sounds = default.node_sound_stone_defaults (), + paramtype = "none", + param1 = 0, + paramtype2 = "facedir", + param2 = 1, + floodable = false, + _digistuff_channelcopier_fieldname = "channel", + + mesecons = mesecon_support (), + digiline = digilines_support (), + tube = pipeworks_support (), + + on_receive_fields = on_receive_fields, + after_place_node = after_place_node, + can_dig = can_dig, + after_dig_node = utils.pipeworks_after_dig, + on_blast = on_blast, + on_rightclick = on_rightclick +}) + + + +minetest.register_node("lwcomponents:dropper_locked", { + description = S("Dropper (locked)"), + tiles = { "lwdropper.png", "lwdropper.png", "lwdropper.png", + "lwdropper.png", "lwdropper.png", "lwdropper_face.png"}, + is_ground_content = false, + groups = table.copy (dropper_groups), + --sounds = default.node_sound_stone_defaults (), + paramtype = "none", + param1 = 0, + paramtype2 = "facedir", + param2 = 1, + floodable = false, + _digistuff_channelcopier_fieldname = "channel", + + mesecons = mesecon_support (), + digiline = digilines_support (), + tube = pipeworks_support (), + + on_receive_fields = on_receive_fields, + after_place_node = after_place_node_locked, + can_dig = can_dig, + after_dig_node = utils.pipeworks_after_dig, + on_blast = on_blast, + on_rightclick = on_rightclick +}) + + + +utils.hopper_add_container({ + {"top", "lwcomponents:dropper", "main"}, -- take items from above into hopper below + {"bottom", "lwcomponents:dropper", "main"}, -- insert items below from hopper above + {"side", "lwcomponents:dropper", "main"}, -- insert items from hopper at side +}) + + + +utils.hopper_add_container({ + {"top", "lwcomponents:dropper_locked", "main"}, -- take items from above into hopper below + {"bottom", "lwcomponents:dropper_locked", "main"}, -- insert items below from hopper above + {"side", "lwcomponents:dropper_locked", "main"}, -- insert items from hopper at side +}) + + +end -- utils.digilines_supported or utils.mesecon_supported + + + +-- diff --git a/lwcomponents/dummy_player.lua b/lwcomponents/dummy_player.lua new file mode 100644 index 0000000..5d83cee --- /dev/null +++ b/lwcomponents/dummy_player.lua @@ -0,0 +1,437 @@ + + +local function get_dummy_player (as_player, name, pos, look_dir, controls, velocity, + hp, armor_groups, properties, nametag, breath) + local obj_as_player = as_player ~= false + local obj_name = name or "" + local obj_pos = vector.new (pos or { x = 0, y = 0, z = 0 }) + local obj_look_dir = vector.new (look_dir or { x = 0, y = 0, z = 0 }) + local obj_controls = table.copy (controls or { }) + local obj_velocity = vector.new (velocity or { x = 0, y = 0, z = 0 }) + local obj_hp = hp or 20 + local obj_armor_groups = table.copy (armor_groups or { }) + local obj_properties = table.copy (properties or { }) + local obj_nametag = table.copy (nametag or { }) + local obj_breath = breath or 20 + + local object = { } + + -- common + object.get_pos = function (self) + return vector.new (obj_pos) + end + + + object.set_pos = function (self, _pos) + obj_pos = vector.new (_pos) + end + + + object.get_velocity = function (self) + return vector.new (obj_velocity) + end + + + object.add_velocity = function (self, vel) + obj_velocity = vector.add (obj_velocity, vel) + end + + + object.move_to = function (self, _pos, continuous) + obj_pos = vector.new (_pos) + end + + + object.punch = function (self, puncher, time_from_last_punch, tool_capabilities, direction) + end + + + object.right_click = function (self, clicker) + end + + + object.get_hp = function (self) + return obj_hp + end + + + object.set_hp = function (self, _hp, reason) + obj_hp = _hp + end + + + object.get_inventory = function (self) + return nil + end + + + object.get_wield_list = function (self) + return nil + end + + + object.get_wield_index = function (self) + return nil + end + + + object.get_wielded_item = function (self) + return nil + end + + + object.set_wielded_item = function (self, item) + end + + + object.set_armor_groups = function (self, groups) + obj_armor_groups = groups + end + + + object.get_armor_groups = function (self) + return table.copy (obj_armor_groups) + end + + + object.set_animation = function (self, frame_range, frame_speed, frame_blend, frame_loop) + end + + + object.get_animation = function (self) + return { x = 1, y = 1 }, 15.0, 0.0, true + end + + + object.set_animation_frame_speed = function (self, frame_speed) + end + + + object.set_attach = function (self, parent, bone, position, rotation, forced_visible) + end + + + object.get_attach = function (self) + return nil + end + + + object.get_children = function (self) + return { } + end + + + object.set_detach = function (self) + end + + + object.set_bone_position = function (self, bone, position, rotation) + end + + + object.get_bone_position = function (self) + return nil + end + + + object.set_properties = function (self, _properties) + obj_properties = table.copy (_properties or { }) + end + + + object.get_properties = function (self) + return table.copy (obj_properties) + end + + + object.is_player = function (self) + return obj_as_player + end + + + object.get_nametag_attributes = function (self) + return obj_nametag + end + + + object.set_nametag_attributes = function (self, attributes) + obj_nametag = table.copy (attributes) + end + + + + -- player + object.get_player_name = function (self) + return obj_name + end + + + object.get_player_velocity = function (self) + return table.copy (obj_velocity) + end + + + object.add_player_velocity = function (self, vel) + obj_velocity = vector.add (obj_velocity, vel) + end + + + object.get_look_dir = function (self) + return table.copy (obj_look_dir) + end + + + object.get_look_vertical = function (self) + return vector.dir_to_rotation (obj_look_dir, { x = 0, y = 1, z = 0 }).x + end + + + object.get_look_horizontal = function (self) + return vector.dir_to_rotation (obj_look_dir, { x = 0, y = 1, z = 0 }).y + end + + + object.set_look_vertical = function (self, radians) + obj_look_dir = vector.new ({ x = radians, y = obj_look_dir.y, z = obj_look_dir.z }) + end + + + object.set_look_horizontal = function (self, radians) + obj_look_dir = vector.new ({ x = obj_look_dir.x, y = radians, z = obj_look_dir.z }) + end + + + object.get_look_pitch = function (self) + return vector.dir_to_rotation (obj_look_dir, { x = 0, y = 1, z = 0 }).x + end + + + object.get_look_yaw = function (self) + return vector.dir_to_rotation (obj_look_dir, { x = 0, y = 1, z = 0 }).y + end + + + object.set_look_pitch = function (self, radians) + obj_look_dir = vector.new ({ x = radians, y = obj_look_dir.y, z = obj_look_dir.z }) + end + + + object.set_look_yaw = function (self, radians) + obj_look_dir = vector.new ({ x = obj_look_dir.x, y = radians, z = obj_look_dir.z }) + end + + + object.get_breath = function (self) + return obj_breath + end + + + object.set_breath = function (self, value) + obj_breath = value + end + + + object.get_fov = function (self) + return 0, false, 0 + end + + + object.set_fov = function (self, fov, is_multiplier, transition_time) + end + + + object.get_attribute = function (self, attribute) + return nil + end + + + object.set_attribute = function (self, attribute, value) + end + + + object.get_meta = function (self) + return nil + end + + + object.set_inventory_formspec = function (self, formspec) + end + + + object.get_inventory_formspec = function (self) + return "" + end + + + object.set_formspec_prepend = function (self, formspec) + end + + + object.get_formspec_prepend = function (self) + return "" + end + + + object.get_player_control = function (self) + return table.copy (obj_controls) + end + + + object.set_physics_override = function (self, override_table) + end + + + object.get_physics_override = function (self) + return { } + end + + + object.hud_add = function (self, definition) + return nil + end + + + object.hud_remove = function (self, id) + end + + + object.hud_change = function (self, id, stat, value) + end + + + object.hud_get = function (self, id) + return nil + end + + + object.hud_set_flags = function (self, flags) + end + + + object.hud_get_flags = function (self) + return { } + end + + + object.hud_set_hotbar_itemcount = function (self, count) + end + + + object.hud_get_hotbar_itemcount = function (self) + return 0 + end + + + object.hud_set_hotbar_image = function (self, texturename) + end + + + object.hud_get_hotbar_image = function (self) + return "" + end + + + object.hud_set_hotbar_selected_image = function (self, texturename) + end + + + object.hud_get_hotbar_selected_image = function (self) + return "" + end + + + object.set_minimap_modes = function (self, modes, selected_mode) + end + + + object.set_sky = function (self, sky_parameters) + end + + + object.get_sky = function (self) + return nil + end + + + object.get_sky_color = function (self) + return nil + end + + + object.set_sun = function (self, sun_parameters) + end + + + object.get_sun = function (self) + return { } + end + + + object.set_moon = function (self, moon_parameters) + end + + + object.get_moon = function (self) + return { } + end + + + object.set_stars = function (self, star_parameters) + end + + + object.get_stars = function (self) + return { } + end + + + object.set_clouds = function (self, cloud_parameters) + end + + + object.get_clouds = function (self) + return { } + end + + + object.override_day_night_ratio = function (self, ratio) + end + + + object.get_day_night_ratio = function (self) + return nil + end + + + object.set_local_animation = function (self, idle, walk, dig, walk_while_dig, frame_speed) + end + + + object.get_local_animation = function (self) + return { x = 0, y = 0 }, { x = 0, y = 0 }, { x = 0, y = 0 }, { x = 0, y = 0 }, 30 + end + + + object.set_eye_offset = function (self, firstperson, thirdperson) + end + + + object.get_eye_offset = function (self) + return { x = 0, y = 0, z = 0 }, { x = 0, y = 0, z = 0 } + end + + + object.send_mapblock = function (self, blockpos) + return false + end + + + return object +end + + + +return get_dummy_player + + +-- diff --git a/lwcomponents/explode.lua b/lwcomponents/explode.lua new file mode 100644 index 0000000..cedca02 --- /dev/null +++ b/lwcomponents/explode.lua @@ -0,0 +1,517 @@ +local utils = ... +local S = utils.S + + + + +local explode = { } + + + +if minetest.global_exists ("fire") then + + +explode.fire_supported = true + + +function explode.set_fire (pos, burn_all) + local node = utils.get_far_node (pos) + + if not node then + return + end + + if node.name ~= "air" then + local def = minetest.registered_nodes[node.name] + + if not def or not def.buildable_to then + return + end + end + + local dirs = + { + { x = 0, y = -1, z = 0 }, + { x = -1, y = 0, z = 0 }, + { x = 0, y = 0, z = -1 }, + { x = 1, y = 0, z = 0 }, + { x = 0, y = 0, z = 1 } + } + + for i = 1, #dirs do + node = utils.get_far_node (vector.add (pos, dirs[i])) + + if node and node.name ~= "air" and node.name ~= "fire:basic_flame" then + local def = minetest.registered_nodes[node.name] + + if def and def.liquidtype == "none" then + if (def.groups and def.groups.flammable) or burn_all then + minetest.set_node (pos, { name = "fire:basic_flame" }) + + return + end + end + end + end +end + + +else + + +explode.fire_supported = false + + +function explode.set_fire (pos, burn_all) +end + + +end + + + +local function dig_node (pos, toolname) + local node = utils.get_far_node (pos) + local dig = false + local drops = nil + + if toolname == true then + dig = true + toolname = nil + end + + if node and node.name ~= "air" then + local def = utils.find_item_def (node.name) + + if not dig then + if def and def.can_dig then + local result, can_dig = pcall (def.can_dig, pos) + + dig = ((not result) or (result and (can_dig == nil or can_dig == true))) + else + dig = true + end + end + + if dig then + local items = minetest.get_node_drops (node, toolname) + + if items then + drops = { } + + for i = 1, #items do + drops[i] = ItemStack (items[i]) + end + + if def and def.preserve_metadata then + def.preserve_metadata (pos, node, minetest.get_meta (pos), drops) + end + end + + minetest.remove_node (pos) + end + end + + return drops +end + + + +local function add_drops (drops, drop) + if drops and drop then + for i = 1, #drop do + local item = ItemStack (drop[i]) + + if item and not item:is_empty () then + local existing = drops[item:get_name ()] + + if existing and utils.is_same_item (item, existing) then + existing:set_count (existing:get_count () + item:get_count ()) + else + drops[item:get_name ()] = item + end + end + end + end +end + + + +local function explode_node (pos, dig_chance, intensity, drops, filter) + if not utils.is_protected (pos, nil) then + dig_chance = math.min (math.max (dig_chance, 0), 100) + + if math.random (100) <= dig_chance then + local node = utils.get_far_node (pos) + local blasted = false + + if node and node.name ~= "air" then + local def = minetest.registered_nodes[node.name] + + if def then + if def.diggable == false then + return false + end + + for k, v in pairs (filter) do + if def[k] == nil then + if filter[k.."_undefined"] == false then + return false + end + elseif def[k] ~= v then + return false + end + end + + if def.on_blast then + def.on_blast (pos, intensity) + blasted = true + end + end + + if not blasted then + local drop = dig_node (pos, true) + + add_drops (drops, drop) + end + + minetest.check_for_falling ({ x = pos.x, y = pos.y + 1, z = pos.z }) + + return true + end + end + end + + return false +end + + + +local function burn_node (pos, fire_chance, burn_all) + if not utils.is_protected (pos, nil) then + fire_chance = math.min (math.max (fire_chance, 0), 100) + + if math.random (100) <= fire_chance then + explode.set_fire (pos, burn_all) + end + end +end + + + +local function entity_is_drop (obj) + return obj.get_luaentity and obj:get_luaentity () and + obj:get_luaentity ().name and + obj:get_luaentity ().name == "__builtin:item" +end + + + +local function explode_entities (pos, radius, damage, drops) + local objs = minetest.get_objects_inside_radius (pos, radius) + + for _, obj in ipairs (objs) do + -- could be detached player from controller + if obj.get_pos and obj:get_pos () then + local obj_pos = obj:get_pos () + local dir = vector.direction (pos, obj_pos) + local dist = vector.length (vector.subtract (obj_pos, pos)) + local vel = vector.multiply (dir, ((radius + 1) - dist) / (radius + 1) * damage * 5) + + if entity_is_drop (obj) then + obj:add_velocity (vel) + + elseif not obj:get_armor_groups ().immortal then + + local ent_damage = ((radius - dist) / radius * damage / 2) + (damage / 2) + local reason = { type = "set_hp", from = "lwcomponents" } + + if obj:is_player() then + local parent = obj:get_attach () + + if parent then + obj:set_detach () + end + + obj:add_velocity (vel) + obj:set_hp (obj:get_hp () - ent_damage, reason) + + else + local luaobj = obj:get_luaentity () + + -- object might have disappeared somehow + if luaobj then + if luaobj.name == "digistuff:controller_entity" then + for _, child in ipairs (obj:get_children ()) do + if child:is_player () then + local def = utils.find_item_def ("digistuff:controller_programmed") + + if def and def.on_rightclick then + def.on_rightclick (obj:get_pos (), ItemStack (), child) + + child:add_velocity (vel) + child:set_hp (child:get_hp () - ent_damage, reason) + end + end + end + else + local do_damage = true + local do_knockback = true + local entity_drops = {} + local objdef = minetest.registered_entities[luaobj.name] + + if objdef and objdef.on_blast then + do_damage, do_knockback, entity_drops = objdef.on_blast (luaobj, ent_damage) + end + + if do_knockback then + obj:add_velocity (vel) + end + + if do_damage then + obj:set_hp (obj:get_hp() - ent_damage, reason) + end + + add_drops (drops, entity_drops) + end + end + end + end + end + end +end + + + +local function spray_drops (pos, drops, damage) + local max_vel = damage * 2.5 + + for k, stack in pairs (drops) do + local vel = + { + x = math.random (max_vel) - (max_vel / 2), + y = math.random (max_vel) - (max_vel / 2), + z = math.random (max_vel) - (max_vel / 2) + } + + local drop = minetest.add_item (pos, stack) + + if drop then + drop:set_velocity (vel) + end + end +end + + + +local function add_effects (pos, radius, drops) + minetest.add_particle ({ + pos = pos, + velocity = vector.new (), + acceleration = vector.new (), + expirationtime = 0.4, + size = 30, -- radius * 10, + collisiondetection = false, + vertical = false, + texture = "lwcomponents_boom.png", + glow = 14, + }) + + minetest.add_particlespawner ({ + amount = 64, + time = 0.5, + minpos = vector.subtract (pos, radius / 2), + maxpos = vector.add (pos, radius / 2), + minvel = {x = -10, y = -10, z = -10}, + maxvel = {x = 10, y = 10, z = 10}, + minacc = vector.new (), + maxacc = vector.new (), + minexptime = 1, + maxexptime = 2.5, + minsize = 9, -- radius * 3, + maxsize = 15, -- radius * 5, + texture = "lwcomponents_smoke.png", + }) + + -- we just dropped some items. Look at the items entities and pick + -- one of them to use as texture + local texture = "lwcomponents_blast.png" --fallback texture + local node + local most = 0 + + if drops then + for name, stack in pairs (drops) do + local count = stack:get_count() + if count > most then + most = count + local def = minetest.registered_nodes[name] + if def then + node = { name = name } + end + if def and def.tiles and def.tiles[1] then + if type (def.tiles[1]) == "table" then + texture = def.tiles[1].name or "lwcomponents_blast.png" + elseif type (def.tiles[1]) == "string" then + texture = def.tiles[1] + end + end + end + end + end + + minetest.add_particlespawner ({ + amount = 64, + time = 0.1, + minpos = vector.subtract (pos, radius / 2), + maxpos = vector.add (pos, radius / 2), + minvel = {x = -3, y = 0, z = -3}, + maxvel = {x = 3, y = 5, z = 3}, + minacc = {x = 0, y = -10, z = 0}, + maxacc = {x = 0, y = -10, z = 0}, + minexptime = 0.8, + maxexptime = 2.0, + minsize = 1, -- radius * 0.33, + maxsize = 3, -- radius, + texture = texture, + -- ^ only as fallback for clients without support for `node` parameter + node = node, + collisiondetection = true, + }) +end + + + +function utils.boom (pos, -- center of explosion + node_radius, node_chance, -- radius and chance in 100 + fire_radius, fire_chance, -- radius and chance in 100 + entity_radius, entity_damage, -- radius and max damage applied + disable_drops, -- true to disable drops + node_filter, -- node filter table as { buildable_to = true, buildable_to_undefined = false, ... } + burn_all, -- true to set fire to anything, otherwise only flammable + sound) -- sound on blast, if nil plays default + + pos = vector.round (pos) + node_radius = math.floor (node_radius or 1) + fire_radius = math.floor (fire_radius or node_radius) + entity_radius = math.floor (entity_radius or node_radius * 2) + node_chance = node_chance or 80 + fire_chance = fire_chance or 30 + entity_damage = math.floor (entity_damage or entity_radius) + disable_drops = disable_drops == true + node_filter = node_filter or { } + burn_all = burn_all == true + sound = sound or "lwcannon" + + local drops = { } + local effects_radius = (node_radius > 0 and node_radius) or entity_radius + local center_free = false + + if not utils.is_protected (pos, nil) then + local center_node = utils.get_far_node (pos) + + if not center_node or center_node.name == "air" then + center_free = true + end + end + + if node_radius > 0 and node_chance > 0 then + local extents = node_radius * 2 + + for y = -extents, extents, 1 do + for z = -extents, extents, 1 do + for x = -extents, extents, 1 do + local node_pos = { x = x + pos.x, y = y + pos.y, z = z + pos.z } + local length = vector.length ({ x = x, y = y, z = z }) + + if node_chance > 0 and length <= node_radius then + if explode_node (node_pos, node_chance, 1.0, drops, node_filter) then + if vector.equals (pos, node_pos) then + center_free = true + end + end + end + end + end + end + end + + if fire_radius > 0 and fire_chance > 0 then + local extents = fire_radius * 2 + + for y = -extents, extents, 1 do + for z = -extents, extents, 1 do + for x = -extents, extents, 1 do + local node_pos = { x = x + pos.x, y = y + pos.y, z = z + pos.z } + local length = vector.length ({ x = x, y = y, z = z }) + + if fire_chance > 0 and length <= fire_radius then + burn_node (node_pos, fire_chance, burn_all) + end + end + end + end + end + + minetest.sound_play (sound, + { + pos = pos, + gain = 2.5, + max_hear_distance = math.min (effects_radius * 20, 128) + }, + true) + + if center_free then + minetest.set_node (pos, { name = "lwcomponents:boom" }) + end + + explode_entities (pos, entity_radius, entity_damage, drops) + + if not disable_drops then + spray_drops (pos, drops, entity_damage) + end + + add_effects (pos, effects_radius, drops) + + + minetest.log ("action", "A Shell explosion occurred at " .. minetest.pos_to_string (pos) .. + " with radius " .. entity_radius) +end + + + +minetest.register_node ("lwcomponents:boom", { + description = S("Boom"), + drawtype = "airlike", + tiles = { "lwcomponents_boom.png" }, + inventory_image = "lwcomponents_boom.png", + wield_image = "lwcomponents_boom.png", + light_source = 15, + use_texture_alpha = "blend", + sunlight_propagates = true, + walkable = false, + pointable = false, + diggable = false, + climbable = false, + buildable_to = true, + floodable = true, + is_ground_content = false, + drop = "", + paramtype = "light", + param1 = 255, + post_effect_color = { a = 128, r = 255, g = 0, b = 0 }, + groups = { dig_immediate = 3, not_in_creative_inventory = 1 }, + on_construct = function (pos) + minetest.get_node_timer (pos):start (0.5) + end, + on_timer = function (pos, elapsed) + minetest.remove_node (pos) + + return false + end, + -- unaffected by explosions + on_blast = function() end, +}) + + + +-- diff --git a/lwcomponents/extras.lua b/lwcomponents/extras.lua new file mode 100644 index 0000000..d5d8106 --- /dev/null +++ b/lwcomponents/extras.lua @@ -0,0 +1,107 @@ +local utils = ... +local S = utils.S + + + +local touchscreen = minetest.registered_nodes["digistuff:touchscreen"] +if touchscreen then + local touchblock = table.copy (touchscreen) + + touchblock.description = S("LWComponents Touchscreen") + + touchblock.node_box = { + type = "fixed", + fixed = { + { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 } + } + } + + touchblock.selection_box = { + type = "fixed", + fixed = { + { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 } + } + } + + touchblock.collision_box = { + type = "fixed", + fixed = { + { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 } + } + } + + minetest.register_node ("lwcomponents:touchscreen", touchblock) + if core.get_modpath("mcl_core") then + minetest.register_craft({ + output = "lwcomponents:touchscreen", + recipe = { + {"basic_materials:ic","mcl_core:glass","mcl_core:glass"}, + {"mcl_core:glass","digilines:lcd","mcl_core:glass"}, + {"mcl_core:glass","mcl_core:glass","mcl_core:stone"} + } + }) + else + minetest.register_craft({ + output = "lwcomponents:touchscreen", + recipe = { + {"basic_materials:ic","mcl_core:glass","mcl_core:glass"}, + {"mcl_core:glass","digilines:lcd","mcl_core:glass"}, + {"mcl_core:glass","mcl_core:glass","mcl_core:stone"} + } + }) + end +end + + +local panel = minetest.registered_nodes["digistuff:panel"] +if panel then + local panelblock = table.copy (panel) + + panelblock.description = S("LWComponents Control Panel") + + panelblock.node_box = { + type = "fixed", + fixed = { + { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 } + } + } + + panelblock.selection_box = { + type = "fixed", + fixed = { + { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 } + } + } + + panelblock.collision_box = { + type = "fixed", + fixed = { + { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 } + } + } + + minetest.register_node ("lwcomponents:panel", panelblock) + if core.get_modpath("mcl_core") then + minetest.register_craft({ + output = "lwcomponents:panel", + recipe = { + {"","mesecons_button:button",""}, + {"mesecons_button:button","digilines:lcd","mesecons_button:button"}, + {"","mesecons_button:button","mcl_core:stone"} + } + }) + else + minetest.register_craft({ + output = "lwcomponents:panel", + recipe = { + {"","digistuff:button",""}, + {"digistuff:button","digilines:lcd","digistuff:button"}, + {"","digistuff:button","default:stone"} + } + }) + end +end + + + +-- diff --git a/lwcomponents/fan.lua b/lwcomponents/fan.lua new file mode 100644 index 0000000..8fa2909 --- /dev/null +++ b/lwcomponents/fan.lua @@ -0,0 +1,453 @@ +local utils = ... +local S = utils.S + + + +if utils.digilines_supported or utils.mesecon_supported then + + + +local fan_interval = 0.2 +local fan_force = 15.0 + + + +local function direction_vector (node) + local axis = math.floor (node.param2 / 4) + local rotate = node.param2 % 4 + local vec = { x = 0, y = 0, z = 0 } + + if rotate == 0 then + vec = { x = 0, y = 0, z = -1 } + elseif rotate == 1 then + vec = { x = -1, y = 0, z = 0 } + elseif rotate == 2 then + vec = { x = 0, y = 0, z = 1 } + elseif rotate == 3 then + vec = { x = 1, y = 0, z = 0 } + end + + if axis == 1 then + vec = vector.rotate (vec, { x = math.pi / -2, y = 0, z = 0 }) + elseif axis == 2 then + vec = vector.rotate (vec, { x = math.pi / 2, y = 0, z = 0 }) + elseif axis == 3 then + vec = vector.rotate (vec, { x = 0, y = 0, z = math.pi / 2 }) + elseif axis == 4 then + vec = vector.rotate (vec, { x = 0, y = 0, z = math.pi / -2 }) + elseif axis == 5 then + vec = vector.rotate (vec, { x = math.pi, y = 0, z = 0 }) + end + + return vec +end + + + +local function blow (pos) + local node = minetest.get_node (pos) + local dir = direction_vector (node) + local reach = 5 + + for r = 1, reach, 1 do + local tpos = vector.add (pos, vector.multiply (dir, r)) + local tnode = minetest.get_node_or_nil (tpos) + + if tnode and tnode.name ~= "air" then + local def = utils.find_item_def (tnode.name) + + if def and def.walkable then + return + end + end + + local object = minetest.get_objects_inside_radius (tpos, 1.5) + local vel = vector.multiply (dir, (dir.y > 0 and fan_force / 2) or fan_force) + + for i = 1, #object do + if object[i].add_velocity then + local opos = object[i]:get_pos () + + if opos.x >= (tpos.x - 0.5) and opos.x <= (tpos.x + 0.5) and + opos.z >= (tpos.z - 0.5) and opos.z <= (tpos.z + 0.5) and + opos.y >= (tpos.y - 0.5) and opos.y <= (tpos.y + 0.5) then + + if object[i].get_luaentity and object[i]:get_luaentity () and + object[i]:get_luaentity ().name and + object[i]:get_luaentity ().name == "__builtin:item" then + + object[i]:add_velocity (vector.multiply (vel, 5)) + else + object[i]:add_velocity (vel) + end + end + end + end + end +end + + + +local function fan_off (pos) + local node = minetest.get_node (pos) + + if node then + if node.name == "lwcomponents:fan_on" then + node.name = "lwcomponents:fan" + + minetest.get_node_timer (pos):stop () + minetest.swap_node (pos, node) + + elseif node.name == "lwcomponents:fan_locked_on" then + node.name = "lwcomponents:fan_locked" + + minetest.get_node_timer (pos):stop () + minetest.swap_node (pos, node) + + end + end +end + + + +local function fan_on (pos) + local node = minetest.get_node (pos) + + if node then + if node.name == "lwcomponents:fan" then + node.name = "lwcomponents:fan_on" + + minetest.swap_node (pos, node) + minetest.get_node_timer (pos):start (fan_interval) + + elseif node.name == "lwcomponents:fan_locked" then + node.name = "lwcomponents:fan_locked_on" + + minetest.swap_node (pos, node) + minetest.get_node_timer (pos):start (fan_interval) + + end + end +end + + + +local function on_place (itemstack, placer, pointed_thing) + local param2 = 0 + + if placer and placer:is_player () then + param2 = minetest.dir_to_facedir (placer:get_look_dir (), true) + elseif pointed_thing and pointed_thing.type == "node" then + param2 = minetest.dir_to_facedir (vector.subtract (pointed_thing.under, pointed_thing.above), true) + end + + return minetest.item_place (itemstack, placer, pointed_thing, param2) +end + + + + +local function after_place_node (pos, placer, itemstack, pointed_thing) + local meta = minetest.get_meta (pos) + local spec = + "size[7.5,3]".. + "field[1,1;6,2;channel;Channel;${channel}]".. + "button_exit[2.5,2;3,1;submit;Set]" + + meta:set_string ("formspec", spec) + + -- 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", "Fan (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 + + local meta = minetest.get_meta(pos) + + if fields.submit then + meta:set_string ("channel", fields.channel) + end +end + + + +local function on_blast (pos, intensity) + local meta = minetest.get_meta (pos) + + if meta then + if intensity >= 1.0 then + + 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) + minetest.remove_node (pos) + end + end + 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_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 + + + +local function on_timer (pos, elapsed) + blow (pos) + + return true +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 then + if type (msg) == "string" then + local m = { } + for w in string.gmatch(msg, "[^%s]+") do + m[#m + 1] = w + end + + if m[1] == "start" then + fan_on (pos) + + elseif m[1] == "stop" then + fan_off (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 + fan_on (pos) + end, + + action_off = function (pos, node) + -- do something to turn the effector off + fan_off (pos) + end, + } + } + end + + return nil +end + + + +minetest.register_node("lwcomponents:fan", { + description = S("Fan"), + tiles = { "lwfan.png", "lwfan.png", "lwfan.png", + "lwfan.png", "lwfan.png", "lwfan_face.png"}, + is_ground_content = false, + groups = { cracky = 3, wires_connect = 1 }, + --sounds = default.node_sound_stone_defaults (), + paramtype = "none", + param1 = 0, + paramtype2 = "facedir", + param2 = 0, + floodable = false, + drop = "lwcomponents:fan", + _digistuff_channelcopier_fieldname = "channel", + + mesecons = mesecon_support (), + digiline = digilines_support (), + + on_place = on_place, + on_receive_fields = on_receive_fields, + can_dig = can_dig, + after_place_node = after_place_node, + on_blast = on_blast, + on_rightclick = on_rightclick, +}) + + + +minetest.register_node("lwcomponents:fan_locked", { + description = S("Fan (locked)"), + tiles = { "lwfan.png", "lwfan.png", "lwfan.png", + "lwfan.png", "lwfan.png", "lwfan_face.png"}, + is_ground_content = false, + groups = { cracky = 3, wires_connect = 1 }, + --sounds = default.node_sound_stone_defaults (), + paramtype = "none", + param1 = 0, + paramtype2 = "facedir", + param2 = 0, + floodable = false, + drop = "lwcomponents:fan_locked", + _digistuff_channelcopier_fieldname = "channel", + + mesecons = mesecon_support (), + digiline = digilines_support (), + + on_place = on_place, + on_receive_fields = on_receive_fields, + can_dig = can_dig, + after_place_node = after_place_node_locked, + on_blast = on_blast, + on_rightclick = on_rightclick, +}) + + + +minetest.register_node("lwcomponents:fan_on", { + description = S("Fan"), + tiles = { "lwfan.png", "lwfan.png", "lwfan.png", + "lwfan.png", "lwfan.png", "lwfan_face_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, + paramtype2 = "facedir", + param2 = 0, + floodable = false, + drop = "lwcomponents:fan", + _digistuff_channelcopier_fieldname = "channel", + + mesecons = mesecon_support (), + digiline = digilines_support (), + + on_place = on_place, + 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:fan_locked_on", { + description = S("Fan (locked)"), + tiles = { "lwfan.png", "lwfan.png", "lwfan.png", + "lwfan.png", "lwfan.png", "lwfan_face_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, + paramtype2 = "facedir", + param2 = 0, + floodable = false, + drop = "lwcomponents:fan_locked", + _digistuff_channelcopier_fieldname = "channel", + + mesecons = mesecon_support (), + digiline = digilines_support (), + + on_place = on_place, + 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