diff --git a/lwcomponents/api.lua b/lwcomponents/api.lua new file mode 100644 index 0000000..e615100 --- /dev/null +++ b/lwcomponents/api.lua @@ -0,0 +1,12 @@ +local utils = ... + + + +-- function (spawn_pos, itemstack, owner, spawner_pos, spawner_dir, force) +function lwcomponents.register_spawner (itemname, spawn_func) + return utils.register_spawner (itemname, spawn_func) +end + + + +-- diff --git a/lwcomponents/breaker.lua b/lwcomponents/breaker.lua new file mode 100644 index 0000000..ae91a04 --- /dev/null +++ b/lwcomponents/breaker.lua @@ -0,0 +1,891 @@ +local utils = ... +local S = utils.S + + + +if utils.digilines_supported or utils.mesecon_supported then + + + +local break_interval = 1.0 + + + +local function get_breaker_side (pos, param2, side) + local base + + if side == "left" then + base = { x = -1, y = pos.y, z = 0 } + elseif side == "right" then + base = { x = 1, y = pos.y, z = 0 } + elseif side == "back" then + base = { x = 0, y = pos.y, z = -1 } + else -- "front" + base = { x = 0, y = pos.y, z = 1 } + end + + if param2 == 3 then -- +x + return { x = base.z + pos.x, y = base.y, z = (base.x * -1) + pos.z } + elseif param2 == 0 then -- -z + return { x = (base.x * -1) + pos.x, y = base.y, z = (base.z * -1) + pos.z } + elseif param2 == 1 then -- -x + return { x = (base.z * -1) + pos.x, y = base.y, z = base.x + pos.z } + else -- param2 == 2 +z + return { x = base.x + pos.x, y = base.y, z = base.z + pos.z } + end +end + + + +local function get_break_pos (pos, param2, range) + local breakpos = { x = pos.x, y = pos.y, z = pos.z } + + for i = 1, range do + breakpos = get_breaker_side (breakpos, param2, "front") + + if i < range then + local node = minetest.get_node_or_nil (breakpos) + + if not node or node.name ~= "air" then + return nil + end + end + end + + return breakpos +end + + + +local function send_break_message (pos, action, name, range) + 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 = action, + name = name, + range = range }) + end + end + end +end + + + +local function get_tool (pos) + local meta = minetest.get_meta (pos) + + if meta then + local inv = meta:get_inventory () + + if inv then + local stack = inv:get_stack ("tool", 1) + + if stack and not stack:is_empty () then + return stack + end + end + end + + return nil +end + + + +local function add_wear (pos, wear) + if wear > 0 then + local meta = minetest.get_meta (pos) + + if meta then + local inv = meta:get_inventory () + + if inv then + local stack = inv:get_stack ("tool", 1) + + if stack and not stack:is_empty () then + local cur_wear = stack:get_wear () + + if (cur_wear + wear) >= 65535 then + inv:set_stack ("tool", 1, nil) + send_break_message (pos, "tool", stack:get_name ()) + else + stack:set_wear (cur_wear + wear) + inv:set_stack ("tool", 1, stack) + end + end + end + end + end +end + + + +local function can_break_node (pos, breakpos) + local node = minetest.get_node (pos) + + if node then + local dig_node = minetest.get_node_or_nil (breakpos) + + if dig_node and dig_node.name ~= "air" then + local node_def = minetest.registered_nodes[dig_node.name] + + if node_def then + -- try tool first + local tool = get_tool (pos) + local dig_params + + if tool then + local tool_def = minetest.registered_items[tool:get_name ()] + + if tool_def then + dig_params = + minetest.get_dig_params (node_def.groups, + tool_def.tool_capabilities) + + if dig_params.diggable then + return true, tool:get_name (), dig_params.wear + end + end + end + + -- then try hand + dig_params = + minetest.get_dig_params (node_def.groups, + minetest.registered_items[""].tool_capabilities) + + if dig_params.diggable then + return true, nil, 0 + end + end + end + end + + return false +end + + + +local function dig_node (pos, toolname) + local node = minetest.get_node_or_nil (pos) + local dig = false + local drops = nil + + if toolname == true then + dig = true + toolname = nil + end + + if node and node.name ~= "air" and node.name ~= "ignore" 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 + + if def and def.sounds and def.sounds.dug then + pcall (minetest.sound_play, def.sounds.dug, { pos = pos }) + end + + minetest.remove_node (pos) + end + end + + return drops +end + + + +local function eject_item (pos, stack, eject_pos) + if utils.pipeworks_supported then + local node = utils.get_far_node (eject_pos) + + if node and minetest.get_item_group (node.name, "tube") > 0 then + local owner = nil + local meta = minetest.get_meta (pos) + + if meta then + local o = meta:get_string ("owner") + + if o ~= "" then + owner = o + end + end + + local vel = vector.subtract (eject_pos, pos) + + pipeworks.tube_inject_item (pos, pos, vel, stack, owner) + + return + end + end + + utils.item_drop (stack, nil, eject_pos) +end + + + +local function break_node (pos, range) + local node = minetest.get_node_or_nil (pos) + local meta = minetest.get_meta (pos) + + if node then + if meta and minetest.is_protected (pos, meta:get_string ("owner")) then + return + end + + local breakpos = get_break_pos (pos, node.param2, range) + + if breakpos then + local diggable, toolname, wear = can_break_node (pos, breakpos) + + if diggable then + local breaknode = minetest.get_node_or_nil (breakpos) + + if breaknode and breaknode.name ~= "air" then + local drops = dig_node (breakpos, toolname) + + if drops then + local break_name = breaknode.name + local eject_pos = get_breaker_side (pos, node.param2, "back") + + for i = 1, #drops do + eject_item (pos, drops[i], eject_pos) + end + + add_wear (pos, wear) + send_break_message (pos, "break", break_name, range) + end + end + end + end + end +end + + + +local function breaker_off (pos) + local node = minetest.get_node (pos) + + if node then + if node.name == "lwcomponents:breaker_on" then + node.name = "lwcomponents:breaker" + + minetest.get_node_timer (pos):stop () + minetest.swap_node (pos, node) + + elseif node.name == "lwcomponents:breaker_locked_on" then + node.name = "lwcomponents:breaker_locked" + + minetest.get_node_timer (pos):stop () + minetest.swap_node (pos, node) + + end + end +end + + + +local function breaker_on (pos, range) + local node = minetest.get_node (pos) + + range = tonumber (range) or 1 + + if node and range < 6 and range > 0 then + if node.name == "lwcomponents:breaker" then + node.name = "lwcomponents:breaker_on" + + minetest.swap_node (pos, node) + break_node (pos, range) + minetest.get_node_timer (pos):start (break_interval) + + elseif node.name == "lwcomponents:breaker_locked" then + node.name = "lwcomponents:breaker_locked_on" + + minetest.swap_node (pos, node) + break_node (pos, range) + minetest.get_node_timer (pos):start (break_interval) + + end + end +end + + + +local function eject_tool (pos, side) + local node = minetest.get_node (pos) + local meta = minetest.get_meta (pos) + + if meta and node then + local inv = meta:get_inventory () + + if inv then + local stack = inv:get_stack ("tool", 1) + + if stack and not stack:is_empty () then + utils.item_drop (stack, nil, get_breaker_side (pos, node.param2, side)) + inv:set_stack ("tool", 1, nil) + end + end + end +end + + + +local function after_place_base (pos, placer, itemstack, pointed_thing) + local meta = minetest.get_meta (pos) + local spec = + "formspec_version[3]\n".. + "size[11.75,10.75;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".. + "list[context;tool;5.0,2.75;1,1;]\n".. + "list[current_player;main;1.0,5.0;8,4;]\n".. + "listring[]" + + meta:set_string ("inventory", "{ tool = { } }") + meta:set_string ("formspec", spec) + + local inv = meta:get_inventory () + + inv:set_size ("tool", 1) + inv:set_width ("tool", 1) +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", "Breaker (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 +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 ("tool") then + return false + end + end + 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_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 ("tool") + + for slot = 1, slots do + local stack = inv:get_stack ("tool", 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 ("tool") + + for slot = 1, slots do + local stack = inv:get_stack ("tool", 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_timer (pos, elapsed) + breaker_off (pos) +end + + + +local function allow_metadata_inventory_put (pos, listname, index, stack, player) + if listname == "tool" then + if stack and not stack:is_empty () then + local def = utils.find_item_def (stack:get_name ()) + + if def and def.tool_capabilities then + return 1 + end + end + end + + return 0 +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] == "break" then + breaker_on (pos, m[2]) + + elseif m[1] == "eject" then + eject_tool (pos, m[2]) + + 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 + breaker_on (pos, 1) + end, + } + } + end + + return nil +end + + + +local function pipeworks_support () + if utils.pipeworks_supported then + return + { + priority = 100, + input_inventory = "tool", + 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 ("tool", stack) + end + + return stack + end, + + can_insert = function (pos, node, stack, direction) + if stack and not stack:is_empty () then + local def = utils.find_item_def (stack:get_name ()) + + if def and def.tool_capabilities then + local meta = minetest.get_meta (pos) + local inv = (meta and meta:get_inventory ()) or nil + + if inv then + return inv:room_for_item ("tool", stack) + end + end + 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 ("tool") + + for i = 1, slots, 1 do + local s = inv:get_stack ("tool", 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 ("tool") + + for i = 1, slots, 1 do + local s = inv:get_stack ("tool", 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 ("tool", i, s) + left = 0 + else + left = left - s:get_count () + inv:set_stack ("tool", 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 breaker_groups = { cracky = 3, wires_connect = 1 } +if utils.pipeworks_supported then + breaker_groups.tubedevice = 1 + breaker_groups.tubedevice_receiver = 1 +end + + + +local breaker_on_groups = { cracky = 3, not_in_creative_inventory = 1, wires_connect = 1 } +if utils.pipeworks_supported then + breaker_on_groups.tubedevice = 1 + breaker_on_groups.tubedevice_receiver = 1 +end + + + +minetest.register_node("lwcomponents:breaker", { + description = S("Breaker"), + tiles = { "lwbreaker.png", "lwbreaker.png", "lwbreaker.png", + "lwbreaker.png", "lwbreaker_rear.png", "lwbreaker_face.png"}, + is_ground_content = false, + groups = table.copy (breaker_groups), + --sounds = default.node_sound_stone_defaults (), + paramtype = "none", + param1 = 0, + paramtype2 = "facedir", + param2 = 1, + floodable = false, + drop = "lwcomponents:breaker", + _digistuff_channelcopier_fieldname = "channel", + + mesecons = mesecon_support (), + digiline = digilines_support (), + tube = pipeworks_support (), + + on_receive_fields = on_receive_fields, + can_dig = can_dig, + after_dig_node = utils.pipeworks_after_dig, + after_place_node = after_place_node, + on_blast = on_blast, + on_timer = on_timer, + on_rightclick = on_rightclick, + allow_metadata_inventory_put = allow_metadata_inventory_put +}) + + + +minetest.register_node("lwcomponents:breaker_locked", { + description = S("Breaker (locked)"), + tiles = { "lwbreaker.png", "lwbreaker.png", "lwbreaker.png", + "lwbreaker.png", "lwbreaker_rear.png", "lwbreaker_face.png"}, + is_ground_content = false, + groups = table.copy (breaker_groups), + --sounds = default.node_sound_stone_defaults (), + paramtype = "none", + param1 = 0, + paramtype2 = "facedir", + param2 = 1, + floodable = false, + drop = "lwcomponents:breaker_locked", + _digistuff_channelcopier_fieldname = "channel", + + mesecons = mesecon_support (), + digiline = digilines_support (), + tube = pipeworks_support (), + + on_receive_fields = on_receive_fields, + can_dig = can_dig, + after_dig_node = utils.pipeworks_after_dig, + after_place_node = after_place_node_locked, + on_blast = on_blast, + on_timer = on_timer, + on_rightclick = on_rightclick, + allow_metadata_inventory_put = allow_metadata_inventory_put +}) + + + + +minetest.register_node("lwcomponents:breaker_on", { + description = S("Breaker"), + tiles = { "lwbreaker.png", "lwbreaker.png", "lwbreaker.png", + "lwbreaker.png", "lwbreaker_rear.png", "lwbreaker_face_on.png"}, + is_ground_content = false, + groups = table.copy (breaker_on_groups), + --sounds = default.node_sound_stone_defaults (), + paramtype = "none", + param1 = 0, + paramtype2 = "facedir", + param2 = 1, + light_source = 3, + floodable = false, + drop = "lwcomponents:breaker", + _digistuff_channelcopier_fieldname = "channel", + + mesecons = mesecon_support (), + digiline = digilines_support (), + tube = pipeworks_support (), + + on_receive_fields = on_receive_fields, + can_dig = can_dig, + after_dig_node = utils.pipeworks_after_dig, + after_place_node = after_place_node, + on_blast = on_blast, + on_timer = on_timer, + on_rightclick = on_rightclick, + allow_metadata_inventory_put = allow_metadata_inventory_put +}) + + + +minetest.register_node("lwcomponents:breaker_locked_on", { + description = S("Breaker (locked)"), + tiles = { "lwbreaker.png", "lwbreaker.png", "lwbreaker.png", + "lwbreaker.png", "lwbreaker_rear.png", "lwbreaker_face_on.png"}, + is_ground_content = false, + groups = table.copy (breaker_on_groups), + --sounds = default.node_sound_stone_defaults (), + paramtype = "none", + param1 = 0, + paramtype2 = "facedir", + param2 = 1, + light_source = 3, + floodable = false, + drop = "lwcomponents:breaker_locked", + _digistuff_channelcopier_fieldname = "channel", + + mesecons = mesecon_support (), + digiline = digilines_support (), + tube = pipeworks_support (), + + on_receive_fields = on_receive_fields, + can_dig = can_dig, + after_dig_node = utils.pipeworks_after_dig, + after_place_node = after_place_node_locked, + on_blast = on_blast, + on_timer = on_timer, + on_rightclick = on_rightclick, + allow_metadata_inventory_put = allow_metadata_inventory_put +}) + + + +utils.hopper_add_container({ + {"bottom", "lwcomponents:breaker", "tool"}, -- insert items below from hopper above + {"side", "lwcomponents:breaker", "tool"}, -- insert items from hopper at side +}) + + + +utils.hopper_add_container({ + {"bottom", "lwcomponents:breaker_locked", "tool"}, -- insert items below from hopper above + {"side", "lwcomponents:breaker_locked", "tool"}, -- insert items from hopper at side +}) + + + +utils.hopper_add_container({ + {"bottom", "lwcomponents:breaker_on", "tool"}, -- insert items below from hopper above + {"side", "lwcomponents:breaker_on", "tool"}, -- insert items from hopper at side +}) + + + +utils.hopper_add_container({ + {"bottom", "lwcomponents:breaker_locked_on", "tool"}, -- insert items below from hopper above + {"side", "lwcomponents:breaker_locked_on", "tool"}, -- insert items from hopper at side +}) + + + +end -- utils.digilines_supported or utils.mesecon_supported diff --git a/lwcomponents/camera.lua b/lwcomponents/camera.lua new file mode 100644 index 0000000..e55e089 --- /dev/null +++ b/lwcomponents/camera.lua @@ -0,0 +1,439 @@ +local utils = ... +local S = utils.S + + + +if utils.digilines_supported then + + + +local function get_entity_dims (obj) + local dims = { -0.5, 0, -0.5, 0.5, 2, 0.5 } + local found = false + + if obj.get_luaentity then + local entity = obj:get_luaentity () + + if entity and entity.name then + local def = minetest.registered_entities[entity.name] + + if def and type (def.collisionbox) == "table" then + + dims = { def.collisionbox[1] or -0.5, + def.collisionbox[2] or -0.5, + def.collisionbox[3] or -0.5, + def.collisionbox[4] or 0.5, + def.collisionbox[5] or 0.5, + def.collisionbox[6] or 0.5 } + + found = true + end + end + end + + if not found then + local props = obj:get_properties () + if props and props.collisionbox and type (props.collisionbox) == "table" then + + dims = { props.collisionbox[1] or -0.5, + props.collisionbox[2] or -0.5, + props.collisionbox[3] or -0.5, + props.collisionbox[4] or 0.5, + props.collisionbox[5] or 0.5, + props.collisionbox[6] or 0.5 } + end + end + + dims[1] = math.min (dims[1], dims[3]) + dims[3] = dims[1] + dims[4] = math.max (dims[4], dims[6]) + dims[6] = dims[4] + + if (dims[3] - dims[1]) < 1 then + dims[1] = -0.5 + dims[3] = -0.5 + dims[4] = 0.5 + dims[6] = 0.5 + end + + return dims +end + + + +local function get_entity (pos) + local objects = minetest.get_objects_inside_radius (pos, 2.0) + + if #objects > 0 then + for _, obj in ipairs (objects) do + if obj.get_pos then + if obj:is_player () then + local epos = vector.round (obj:get_pos ()) + + if epos.x == pos.x and epos.z == pos.z and + (epos.y == pos.y or epos.y == pos.y - 1) then + return 1 + end + end + + if not utils.is_drop (obj) then + local epos = vector.new (obj:get_pos ()) + local dims = get_entity_dims (obj) + + if pos.x >= (epos.x + dims[1]) and pos.x <= (epos.x + dims[4]) and + pos.y >= (epos.y + dims[2]) and pos.y <= (epos.y + dims[5]) and + pos.z >= (epos.z + dims[3]) and pos.z <= (epos.z + dims[6]) then + return 2 + end + end + end + end + end + + return nil +end + + + +local function camera_scan (pos, resolution, distance) + local node = utils.get_far_node (pos) + local image = { } + + for y = 1, resolution, 1 do + image[y] = { } + + for x = 1, resolution, 1 do + image[y][x] = "000000" + end + end + + if node then + local dir = vector.multiply (minetest.facedir_to_dir (node.param2), -1) + local last_pos = nil + local last_color = "000000" + local view = (distance * 1.414213562) / resolution + + for dist = distance, 1, -1 do + local scale = dist / distance + + for y = 1, resolution, 1 do + for x = 1, resolution, 1 do + local horz = (x - (resolution / 2)) * scale * view + local vert = (y - (resolution / 2)) * scale * view + + local tpos + if dir.x ~= 0 then + tpos = vector.round ({ x = (dist * dir.x) + pos.x, y = pos.y - vert, z = horz + pos.z }) + else + tpos = vector.round ({ x = horz + pos.x, y = pos.y - vert, z = (dist * dir.z) + pos.z }) + end + + if last_pos and vector.equals (last_pos, tpos) then + if last_color then + image[y][x] = last_color + end + else + local entity = get_entity (tpos) + + if entity == 1 then + local color = (((distance - dist) / distance) * 98) + 30 + + last_color = string.format ("00%02X00", color) + image[y][x] = last_color + elseif entity == 2 then + local color = (((distance - dist) / distance) * 98) + 30 + + last_color = string.format ("0000%02X", color) + image[y][x] = last_color + else + local tnode = utils.get_far_node (tpos) + + if tnode and tnode.name ~= "air" then + local color = (((distance - dist) / distance) * 98) + 30 + + last_color = string.format ("%02X%02X%02X", color, color, color) + image[y][x] = last_color + else + last_color = nil + end + end + end + + last_pos = tpos + end + end + end + end + + return image +end + + + +local function send_scan (pos) + local meta = minetest.get_meta (pos) + + if meta then + local channel = meta:get_string ("channel") + + if channel:len () > 0 then + local image = camera_scan (pos, + tonumber (meta:get_string ("resolution")), + tonumber (meta:get_string ("distance"))) + + utils.digilines_receptor_send (pos, + utils.digilines_default_rules, + channel, + image) + end + end +end + + + +local function after_place_node (pos, placer, itemstack, pointed_thing) + local meta = minetest.get_meta (pos) + local spec = + "formspec_version[3]".. + "size[8.5,5.5;true]".. + "field[1.0,1.0;4.0,0.8;channel;Channel;${channel}]".. + "button[5.5,1.0;2.0,0.8;setchannel;Set]".. + "field[1.0,2.5;4.0,0.8;distance;Distance;${distance}]".. + "button[5.5,2.5;2.0,0.8;setdistance;Set]".. + "field[1.0,4.0;4.0,0.8;resolution;Resolution;${resolution}]".. + "button[5.5,4.0;2.0,0.8;setresolution;Set]" + + meta:set_string ("formspec", spec) + meta:set_string ("distance", "5") + meta:set_string ("resolution", "16") + + -- 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", "Camera (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.setdistance then + local meta = minetest.get_meta (pos) + + if meta then + local distance = math.min (math.max (tonumber (fields.distance) or 1, 1), 16) + fields.distance = tostring (distance) + + meta:set_string ("distance", tostring (distance)) + end + end + + if fields.setresolution then + local meta = minetest.get_meta (pos) + + if meta then + local resolution = math.min (math.max (tonumber (fields.resolution) or 1, 1), 128) + fields.resolution = tostring (resolution) + + meta:set_string ("resolution", tostring (resolution)) + 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 + 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 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 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] == "scan" then + send_scan (pos) + + elseif m[1] == "distance" then + local distance = math.min (math.max (tonumber (m[2] or 5) or 5, 1), 16) + meta:set_string ("distance", tostring (distance)) + + elseif m[1] == "resolution" then + local resolution = math.min (math.max (tonumber (m[2] or 16) or 16, 1), 128) + meta:set_string ("resolution", tostring (resolution)) + + end + end + end + end, + } + } + end + + return nil +end + + + +minetest.register_node("lwcomponents:camera", { + description = S("Camera"), + tiles = { "lwcamera.png", "lwcamera.png", "lwcamera.png", + "lwcamera.png", "lwcamera.png", "lwcamera_lens.png"}, + is_ground_content = false, + groups = { cracky = 3 }, + --sounds = default.node_sound_stone_defaults (), + paramtype = "none", + param1 = 0, + paramtype2 = "facedir", + param2 = 0, + floodable = false, + drop = "lwcomponents:camera", + _digistuff_channelcopier_fieldname = "channel", + + digiline = digilines_support (), + + 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:camera_locked", { + description = S("Camera (locked)"), + tiles = { "lwcamera.png", "lwcamera.png", "lwcamera.png", + "lwcamera.png", "lwcamera.png", "lwcamera_lens.png"}, + is_ground_content = false, + groups = { cracky = 3 }, + --sounds = default.node_sound_stone_defaults (), + paramtype = "none", + param1 = 0, + paramtype2 = "facedir", + param2 = 0, + floodable = false, + drop = "lwcomponents:camera_locked", + _digistuff_channelcopier_fieldname = "channel", + + digiline = digilines_support (), + + 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 +}) + + + +end -- utils.digilines_supported + + + +-- diff --git a/lwcomponents/cannon.lua b/lwcomponents/cannon.lua new file mode 100644 index 0000000..5fc0d9a --- /dev/null +++ b/lwcomponents/cannon.lua @@ -0,0 +1,1165 @@ +local utils = ... +local S = utils.S + + + +local cannon_force = 20 +local min_pitch = -20 +local max_pitch = 70 +local min_rotation = -60 +local max_rotation = 60 + + + +local function get_cannon_barrel (pos) + local barrel_pos = { x = pos.x, y = pos.y + 0.65, z = pos.z } + local objects = minetest.get_objects_inside_radius (barrel_pos, 0.1) + + for i = 1, #objects, 1 do + if not objects[i]:is_player () then + if objects[i].get_luaentity and objects[i]:get_luaentity () and + objects[i]:get_luaentity ().name and + objects[i]:get_luaentity ().name == "lwcomponents:cannon_barrel" then + + return objects[i] + end + end + end +end + + + +local function get_barrel_angle (pos) + local node = minetest.get_node_or_nil (pos) + local barrel = get_cannon_barrel (pos) + + if barrel and node then + local cur = barrel:get_rotation () + local node_rot = vector.dir_to_rotation (minetest.facedir_to_dir (node.param2)) + local rot = (cur.y - node_rot.y) * 180 / math.pi + + if (node.param2 % 2) == 0 then + rot = -rot + end + + return { + x = cur.x * -180 / math.pi, + y = rot, + z = 0 + } + end + + return nil +end + + + +local function set_barrel_rotation (pos, angle) + local node = minetest.get_node_or_nil (pos) + local barrel = get_cannon_barrel (pos) + + angle = tonumber (angle) + + if angle and barrel and node then + local cur = barrel:get_rotation () + local node_rot = vector.dir_to_rotation (minetest.facedir_to_dir (node.param2)) + + angle = math.max (math.min (angle, max_rotation), min_rotation) + + if (node.param2 % 2) == 0 then + angle = -angle + end + + cur.y = node_rot.y + (angle * math.pi / 180) + cur.z = 0 + + barrel:set_rotation (cur) + end +end + + + +local function set_barrel_pitch (pos, pitch) + local node = minetest.get_node_or_nil (pos) + local barrel = get_cannon_barrel (pos) + + pitch = tonumber (pitch) + + if pitch and barrel and node then + local cur = barrel:get_rotation () + + cur.x = (math.max (math.min (pitch, max_pitch), min_pitch) / -180 * math.pi) + cur.z = 0 + + barrel:set_rotation (cur) + end +end + + + +local function start_flash (pos) + local blank_pos = { x = pos.x, y = pos.y + 1, z = pos.z } + local blank = minetest.get_node_or_nil (blank_pos) + local node_timer = minetest.get_node_timer (pos) + + if node_timer and blank and blank.name == "lwcomponents:cannon_blank" then + node_timer:stop () + minetest.set_node (blank_pos, { name = "lwcomponents:cannon_blank_fire" }) + node_timer:start (0.5) + end +end + + + +local function cancel_flash (pos) + local blank_pos = { x = pos.x, y = pos.y + 1, z = pos.z } + local blank = minetest.get_node_or_nil (blank_pos) + + if blank and blank.name == "lwcomponents:cannon_blank_fire" then + minetest.set_node (blank_pos, { name = "lwcomponents:cannon_blank" }) + end +end + + + +local function set_barrel_angle_delayed (pos, pitch, rotation) + pitch = pitch and tonumber (pitch) + rotation = rotation and tonumber (rotation) + local node = minetest.get_node_or_nil (pos) + + if node and pitch or rotation then + local angle = get_barrel_angle (pos) + local node_timer = minetest.get_node_timer (pos) + local meta = minetest.get_meta (pos) + local was_set = false + + if angle and node_timer and meta then + cancel_flash (pos) + + if pitch then + pitch = math.floor (math.max (math.min (pitch, max_pitch), min_pitch) - angle.x) + + if pitch ~= 0 then + meta:set_int ("barrel_pitch", pitch) + was_set = true + end + end + + if rotation then + if (node.param2 % 2) == 1 then + rotation = -rotation + end + + rotation = math.floor (math.max (math.min (rotation, max_rotation), min_rotation) - angle.y) + + if rotation ~= 0 then + meta:set_int ("barrel_rotate", rotation) + was_set = true + end + end + + if was_set then + node_timer:stop () + node_timer:start (0.1) + end + end + end +end + + + +local function aim_barrel_delayed (pos, aimpos) + if (tonumber (aimpos.z) or 0) < 1 then + return + end + + local angle = vector.dir_to_rotation (aimpos) + + local rot = math.floor (math.deg (-angle.y) + 0.5) + local pitch = math.floor (math.deg (angle.x) + 0.5) + + set_barrel_angle_delayed (pos, pitch, rot) +end + + + +local function fire_cannon (pos) + local meta = minetest.get_meta (pos) + + if meta then + if meta:get_int ("barrel_pitch") ~= 0 or + meta:get_int ("barrel_rotate") ~= 0 then + + return false + end + + local inv = meta:get_inventory () + + if inv then + local stack = inv:get_stack ("main", 1) + + if not stack:is_empty () and stack:get_count () > 0 then + local name = stack:get_name () + local item = ItemStack (stack) + + if item then + item:set_count (1) + + local barrel = get_cannon_barrel (pos) + + if barrel then + local ammo_pos = barrel:get_pos () + local ammo_angle = barrel:get_rotation (pos) + + if ammo_pos and ammo_angle then + local dir = vector.rotate ({ x = 0, y = 0, z = -1 }, ammo_angle) + local owner = meta:get_string ("owner") + local obj = nil + local cancel + local spawn_pos = { x = ammo_pos.x + dir.x, + y = ammo_pos.y + dir.y, + z = ammo_pos.z + dir.z } + + if utils.settings.spawn_mobs then + obj, cancel = utils.spawn_registered (name, + spawn_pos, + item, + owner, + pos, + dir, + cannon_force) + + if obj == nil and cancel then + return false + end + end + + if not obj then + obj = minetest.add_item (spawn_pos, item) + + if obj then + local vel = vector.multiply (dir, cannon_force) + + obj:set_velocity (vel) + end + end + + if obj then + stack:set_count (stack:get_count () - 1) + inv:set_stack ("main", 1, stack) + + start_flash (pos) + + minetest.sound_play ("lwcannon", + { + pos = pos, + gain = 1.0, + max_hear_distance = 20 + }, + true) + + --send_fired_message (pos, slot, name) + + return true, name + end + end + end + end + end + end + end +end + + + +local function process_controller_input (pos, input) + local node = minetest.get_node_or_nil (pos) + local meta = minetest.get_meta (pos) + + if meta and node then + local owner = meta:get_string ("owner") + + if owner:len () < 1 or owner == input.name then + local pitch = input.pitch * -180 / math.pi + local node_rot = vector.dir_to_rotation (minetest.facedir_to_dir ((node.param2 + 2) % 4)) + local rot = (input.yaw - node_rot.y) * 180 / math.pi + local sensitivity = (meta:get_string ("sensitive") == "true" and 3) or 1 + + while rot > 180 do + rot = rot - 360 + end + + while rot < -180 do + rot = rot + 360 + end + + if (node.param2 % 2) == 0 then + rot = -rot + end + + set_barrel_pitch (pos, pitch * sensitivity) + set_barrel_rotation (pos, rot * sensitivity) + + if input.dig then + fire_cannon (pos) + end + end + end +end + + + +local function get_formspec (pos) + local meta = minetest.get_meta (pos) + local sensitive = (meta and meta:get_string ("sensitive")) or "false" + + return + "formspec_version[3]\n".. + "size[11.75,10.75;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.5,1.0;2.0,0.8;hide;Hide]\n".. + "field[1.0,2.6;4.0,0.8;controller;Controller;${controller}]\n".. + "button[5.5,2.6;2.0,0.8;setcontroller;Set]\n".. + "checkbox[1.3,3.8;sensitive;Sensitive;"..sensitive.."]\n".. + "list[context;main;9.0,2.75;1,1;]\n".. + "list[current_player;main;1.0,5.0;8,4;]\n".. + "listring[]" +end + + + +local function can_place (pos, player) + local above = { x = pos.x, y = pos.y + 1, z = pos.z } + + return utils.can_place (pos) and utils.can_place (above) and + not utils.is_protected (pos, player) and + not utils.is_protected (above, player) +end + + + +local function on_construct (pos) + local barrel_pos = { x = pos.x, y = pos.y + 0.65, z = pos.z } + local blank_pos = { x = pos.x, y = pos.y + 1, z = pos.z } + local staticdata = + { + base_pos = { x = pos.x, y = pos.y, z = pos.z }, + blank_pos = blank_pos, + } + + local barrel = minetest.add_entity (barrel_pos, + "lwcomponents:cannon_barrel", + minetest.serialize (staticdata)) + + if barrel then + set_barrel_rotation (pos, 0) + set_barrel_pitch (pos, 0) + barrel:set_armor_groups ({ immortal = 1 }) + end + + minetest.set_node (blank_pos, { name = "lwcomponents:cannon_blank" }) +end + + + +local function on_destruct (pos) + local blank_pos = { x = pos.x, y = pos.y + 1, z = pos.z } + local blank = minetest.get_node_or_nil (blank_pos) + local barrel = get_cannon_barrel (pos) + + if barrel then + barrel:remove () + end + + if blank and (blank.name == "lwcomponents:cannon_blank" or + blank.name == "lwcomponents:cannon_blank_fire") then + minetest.remove_node (blank_pos) + end +end + + + +local function after_place_base (pos, placer, itemstack, pointed_thing) + local meta = minetest.get_meta (pos) + + meta:set_string ("sensitive", "true") + meta:set_string ("inventory", "{ main = { } }") + + local inv = meta:get_inventory () + + inv:set_size ("main", 1) + inv:set_width ("main", 1) + + meta:set_string ("formspec", get_formspec (pos)) +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", "Cannon (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_place (itemstack, placer, pointed_thing) + if pointed_thing and pointed_thing.type == "node" and placer and placer:is_player () then + local param2 = 0 + local pos = pointed_thing.under + + local on_rightclick = utils.get_on_rightclick (pos, placer) + if on_rightclick then + return on_rightclick (pos, minetest.get_node (pos), placer, itemstack, pointed_thing) + end + + if not can_place (pos, placer) then + pos = pointed_thing.above + + if not can_place (pos, placer) then + return itemstack + end + end + + if placer and placer:is_player () then + param2 = (minetest.dir_to_facedir (placer:get_look_dir (), false) + 2) % 4 + end + + minetest.set_node (pos, { name = "lwcomponents:cannon", param1 = 0, param2 = param2 }) + after_place_node (pos, placer, itemstack, pointed_thing) + + if not utils.is_creative (placer) then + itemstack:set_count (itemstack:get_count () - 1) + end + end + + return itemstack +end + + + +local function on_place_locked (itemstack, placer, pointed_thing) + if pointed_thing and pointed_thing.type == "node" and placer and placer:is_player () then + local param2 = 0 + local pos = pointed_thing.under + + local on_rightclick = utils.get_on_rightclick (pos, placer) + if on_rightclick then + return on_rightclick (pos, minetest.get_node (pos), placer, itemstack, pointed_thing) + end + + if not can_place (pos, placer) then + pos = pointed_thing.above + + if not can_place (pos, placer) then + return itemstack + end + end + + if placer and placer:is_player () then + param2 = (minetest.dir_to_facedir (placer:get_look_dir (), false) + 2) % 4 + end + + minetest.set_node (pos, { name = "lwcomponents:cannon_locked", param1 = 0, param2 = param2 }) + after_place_node_locked (pos, placer, itemstack, pointed_thing) + + if not utils.is_creative (placer) then + itemstack:set_count (itemstack:get_count () - 1) + end + end + + return itemstack +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_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.setcontroller then + local meta = minetest.get_meta (pos) + + if meta then + meta:set_string ("controller", fields.controller) + end + end + + if fields.hide then + local meta = minetest.get_meta (pos) + + if meta then + meta:set_string ("formspec", "") + end + end + + if fields.sensitive ~= nil then + local meta = minetest.get_meta (pos) + + if meta then + meta:set_string ("sensitive", fields.sensitive) + meta:set_string ("formspec", get_formspec (pos)) + end + end +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 + + on_destruct (pos) + 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) + on_destruct (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 + local formspec = meta:get_string ("formspec") + + if formspec == "" then + local hit = minetest.pointed_thing_to_face_pos (clicker, pointed_thing) + hit.x = hit.x - pos.x + hit.y = hit.y - pos.y + hit.z = hit.z - pos.z + + local hx = hit.x + local hy = hit.y + local hz = hit.z + local inc = (clicker:get_player_control ().aux1 and 1) or 10 + + if node.param2 == 1 then + hx = hit.z + hz = hit.x + elseif node.param2 == 2 then + hx = -hit.x + hz = -hit.z + elseif node.param2 == 3 then + hx = -hit.z + hz = -hit.x + end + + if hz == 0.5 and hy >= -0.5 and hy <= 0.2 then + local angle = get_barrel_angle (pos) + + if angle then + if hx >= -0.5 and hx <= -0.25 and hy >= -0.25 and hy <= -0.0625 then + -- left + set_barrel_rotation (pos, angle.y + inc) + elseif hx >= 0.25 and hx <= 0.5 and hy >= -0.25 and hy <= -0.0625 then + -- right + set_barrel_rotation (pos, angle.y - inc) + elseif hx >= -0.125 and hx <= 0.125 and hy >= 0.0 and hy <= 0.1875 then + -- up + set_barrel_pitch (pos, angle.x + inc) + elseif hx >= -0.125 and hx <= 0.125 and hy >= -0.5 and hy <= -0.3125 then + -- down + set_barrel_pitch (pos, angle.x - inc) + elseif hx >= -0.125 and hx <= 0.125 and hy >= -0.25 and hy <= -0.0625 then + -- fire + fire_cannon (pos) + end + end + else + meta:set_string ("formspec", get_formspec (pos)) + end + end + end + end + + return itemstack +end + + + +local function on_timer (pos, elapsed) + local meta = minetest.get_meta (pos) + + if meta then + local barrel_pitch = meta:get_int ("barrel_pitch") + local barrel_rotate = meta:get_int ("barrel_rotate") + + if barrel_pitch ~= 0 or barrel_rotate ~= 0 then + local angle = get_barrel_angle (pos) + + if angle then + if barrel_pitch < -10 then + angle.x = angle.x - 10 + barrel_pitch = barrel_pitch + 10 + elseif barrel_pitch > 10 then + angle.x = angle.x + 10 + barrel_pitch = barrel_pitch - 10 + else + angle.x = angle.x + barrel_pitch + barrel_pitch = 0 + end + + if barrel_rotate < -10 then + angle.y = angle.y - 10 + barrel_rotate = barrel_rotate + 10 + elseif barrel_rotate > 10 then + angle.y = angle.y + 10 + barrel_rotate = barrel_rotate - 10 + else + angle.y = angle.y + barrel_rotate + barrel_rotate = 0 + end + + set_barrel_pitch (pos, angle.x) + set_barrel_rotation (pos, angle.y) + + meta:set_int ("barrel_pitch", barrel_pitch) + meta:set_int ("barrel_rotate", barrel_rotate) + end + + return barrel_pitch ~= 0 or barrel_rotate ~= 0 + end + end + + cancel_flash (pos) + + return false +end + + + +local function digilines_support () + if utils.digilines_supported then + return + { + 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] == "pitch" then + set_barrel_angle_delayed (pos, m[2], nil) + + elseif m[1] == "rotation" then + set_barrel_angle_delayed (pos, nil, m[2]) + + elseif m[1] == "fire" then + fire_cannon (pos) + + end + end + + elseif type (msg) == "table" then + local controller = meta:get_string ("controller") + + if controller:len () > 0 and controller == channel and + msg.pitch and msg.yaw and msg.name and msg.look_vector then + + process_controller_input (pos, msg) + + elseif type (msg.action) == "string" and msg.action == "aim" and + type (msg.aim) == "table" then + + aim_barrel_delayed (pos, msg.aim) + + end + + 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) + fire_cannon (pos) + 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, front = 1, back = 1, bottom = 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 cannon_groups = { cracky = 3, wires_connect = 1 } +if utils.pipeworks_supported then + cannon_groups.tubedevice = 1 + cannon_groups.tubedevice_receiver = 1 +end + + + +minetest.register_node("lwcomponents:cannon_blank", { + description = S("Cannon blank"), + drawtype = "airlike", + light_source = 0, + sunlight_propagates = true, + walkable = false, + pointable = false, + diggable = false, + climbable = false, + buildable_to = false, + floodable = false, + is_ground_content = false, + drop = "", + groups = { not_in_creative_inventory = 1 }, + paramtype = "light", + -- unaffected by explosions + on_blast = function() end, +}) + + + +minetest.register_node("lwcomponents:cannon_blank_fire", { + description = S("Cannon blank"), + drawtype = "airlike", + light_source = 7, + sunlight_propagates = true, + walkable = false, + pointable = false, + diggable = false, + climbable = false, + buildable_to = false, + floodable = false, + is_ground_content = false, + drop = "", + groups = { not_in_creative_inventory = 1 }, + paramtype = "light", + -- unaffected by explosions + on_blast = function() end, +}) + + + +minetest.register_node("lwcomponents:cannon", { + description = S("Cannon"), + tiles = { + "lwcannon_top.png", + "lwcannon_bottom.png", + "lwcannon.png", + "lwcannon.png", + "lwcannon_face.png", + "lwcannon.png" + }, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + { -0.09, 0, -0.09, 0.09, 0.5, 0.09 }, + { -0.5, -0.25, -0.5, 0.5, 0.125, 0.5 }, + { -0.4375, -0.1875, -0.4375, 0.4375, 0.1875, 0.5 }, + { -0.5, -0.5, 0.3125, 0.5, 0.125, 0.5 }, + { -0.5, -0.5, -0.5, -0.3125, 0.125, -0.3125 }, + { 0.3125, -0.5, -0.5, 0.5, 0.125, -0.3125 }, + } + }, + collision_box = { + type = "fixed", + fixed = { + { -0.5, -0.5, -0.5, 0.5, 0.85, 0.5 } + } + }, + selection_box = { + type = "fixed", + fixed = { + { -0.5, -0.5, -0.5, 0.5, 0.1875, 0.5 } + } + }, + wield_image = "lwcannon_item.png", + inventory_image = "lwcannon_item.png", + is_ground_content = false, + groups = table.copy (cannon_groups), + --sounds = default.node_sound_stone_defaults (), + paramtype = "light", + paramtype2 = "facedir", + param2 = 0, + floodable = false, + drop = "lwcomponents:cannon", + _digistuff_channelcopier_fieldname = "channel", + + mesecons = mesecon_support (), + digiline = digilines_support (), + tube = pipeworks_support (), + + on_construct = on_construct, + on_destruct = on_destruct, + on_place = on_place, + on_receive_fields = on_receive_fields, + can_dig = can_dig, + after_dig_node = utils.pipeworks_after_dig, + after_place_node = after_place_node, + on_blast = on_blast, + on_rightclick = on_rightclick, + on_timer = on_timer, +}) + + + +minetest.register_node("lwcomponents:cannon_locked", { + description = S("Cannon (locked)"), + tiles = { + "lwcannon_top.png", + "lwcannon_bottom.png", + "lwcannon.png", + "lwcannon.png", + "lwcannon_face.png", + "lwcannon.png" + }, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + { -0.09, 0, -0.09, 0.09, 0.5, 0.09 }, + { -0.5, -0.25, -0.5, 0.5, 0.125, 0.5 }, + { -0.4375, -0.1875, -0.4375, 0.4375, 0.1875, 0.5 }, + { -0.5, -0.5, 0.3125, 0.5, 0.125, 0.5 }, + { -0.5, -0.5, -0.5, -0.3125, 0.125, -0.3125 }, + { 0.3125, -0.5, -0.5, 0.5, 0.125, -0.3125 }, + } + }, + collision_box = { + type = "fixed", + fixed = { + { -0.5, -0.5, -0.5, 0.5, 0.85, 0.5 } + } + }, + selection_box = { + type = "fixed", + fixed = { + { -0.5, -0.5, -0.5, 0.5, 0.1875, 0.5 } + } + }, + wield_image = "lwcannon_item.png", + inventory_image = "lwcannon_item.png", + is_ground_content = false, + groups = table.copy (cannon_groups), + --sounds = default.node_sound_stone_defaults (), + paramtype = "light", + paramtype2 = "facedir", + param2 = 0, + floodable = false, + drop = "lwcomponents:cannon_locked", + _digistuff_channelcopier_fieldname = "channel", + + mesecons = mesecon_support (), + digiline = digilines_support (), + tube = pipeworks_support (), + + on_construct = on_construct, + on_destruct = on_destruct, + on_place = on_place_locked, + on_receive_fields = on_receive_fields, + can_dig = can_dig, + after_dig_node = utils.pipeworks_after_dig, + after_place_node = after_place_node_locked, + on_blast = on_blast, + on_rightclick = on_rightclick, + on_timer = on_timer, +}) + + + +minetest.register_entity ("lwcomponents:cannon_barrel", { + initial_properties = { + physical = false, + collide_with_objects = false, + collisionbox = { -0.5, -0.35, -0.5, 0.5, 0.35, 0.5 }, + selectionbox = { -0.5, -0.35, -0.5, 0.5, 0.35, 0.5 }, + pointable = false, + visual = "mesh", + visual_size = { x = 1, y = 1, z = 1 }, + mesh = "lwcomponents_cannon_barrel.obj", + textures = { "lwcomponents_cannon_barrel.png" }, + use_texture_alpha = false, + is_visible = true, + makes_footstep_sound = false, + automatic_rotate = 0, + backface_culling = true, + damage_texture_modifier = "", + glow = 0, + static_save = true, + shaded = true, + show_on_minimap = false, + }, + + on_activate = function (self, staticdata, dtime_s) + self.staticdata = staticdata + end, + + get_staticdata = function (self) + return self.staticdata + end, + + on_step = function (self, dtime, moveresult) + end, + + on_punch = function (self, puncher, time_from_last_punch, tool_capabilities, dir) + return true + end, + + on_blast = function (self, damage) + return false, false, nil + end, +}) + + + +utils.hopper_add_container({ + {"top", "lwcomponents:cannon", "main"}, -- take items from above into hopper below + {"bottom", "lwcomponents:cannon", "main"}, -- insert items below from hopper above + {"side", "lwcomponents:cannon", "main"}, -- insert items from hopper at side +}) + + + +utils.hopper_add_container({ + {"top", "lwcomponents:cannon_locked", "main"}, -- take items from above into hopper below + {"bottom", "lwcomponents:cannon_locked", "main"}, -- insert items below from hopper above + {"side", "lwcomponents:cannon_locked", "main"}, -- insert items from hopper at side +}) + + + +-- diff --git a/lwcomponents/cannon_shell.lua b/lwcomponents/cannon_shell.lua new file mode 100644 index 0000000..f771169 --- /dev/null +++ b/lwcomponents/cannon_shell.lua @@ -0,0 +1,297 @@ +local utils = ... +local S = utils.S + + + +--[[ +on_step info + +info.touching_ground = bool +info.standing_on_object = bool +info.collides = bool + + +info.collisions[n].type = "node" +info.collisions[n].node_pos = vector +info.collisions[n].old_velocity = vector +info.collisions[n].now_velocity = vector +info.collisions[n].axis = "x" | "y" | "z" - axis hit + +or + +info.collisions[n].type = "object" +info.collisions[n].object = userdata +info.collisions[n].old_velocity = vector +info.collisions[n].now_velocity = vector +info.collisions[n].axis = "x" | "y" | "z" - axis hit +]] + + + +local function get_adjacent_node (collision_info, spawn_pos) + if vector.equals (collision_info.node_pos, spawn_pos) then + return collision_info.node_pos + end + + local adj = { x = 0, y = 0, z = 0 } + + if collision_info.axis == "x" then + adj.x = (collision_info.old_velocity.x > 0 and -1) or 1 + elseif collision_info.axis == "y" then + adj.y = (collision_info.old_velocity.y > 0 and -1) or 1 + elseif collision_info.axis == "z" then + adj.z = (collision_info.old_velocity.z > 0 and -1) or 1 + end + + local pos = vector.new (collision_info.node_pos) + local node = utils.get_far_node (pos) + local def = minetest.registered_nodes[node and node.name or nil] + + while (node and node.name ~= "air") and (def and not def.buildable_to) do + local next_pos = vector.add (pos, adj) + + if vector.equals (next_pos, spawn_pos) then + return pos + end + + pos = next_pos + node = utils.get_far_node (pos) + def = minetest.registered_nodes[node and node.name or nil] + end + + return pos +end + + + +local function register_shell (name, description, texture, inventory_image, + stack_max, shell_speed, explode_func) + + minetest.register_entity (name.."_entity", { + initial_properties = { + physical = true, + collide_with_objects = true, + collisionbox = { -0.25, -0.125, -0.25, 0.25, 0.125, 0.25 }, + pointable = false, + visual_size = { x = 0.7, y = 0.7, z = 0.7 }, + visual = "mesh", + mesh = "lwcomponents_shell.obj", + textures = { texture }, + use_texture_alpha = false, + is_visible = true, + makes_footstep_sound = false, + automatic_face_movement_dir = false, + automatic_face_movement_max_rotation_per_sec = false, + automatic_rotate = 0, + backface_culling = true, + damage_texture_modifier = "", + glow = 0, + static_save = false, + shaded = true, + show_on_minimap = false, + }, + + on_activate = function (self, staticdata, dtime_s) + if not self.spawn_pos then + self.spawn_pos = vector.new (self.object:get_pos ()) + end + + if not self.time_lived then + self.time_lived = 0 + end + + if not self.shell_speed then + self.shell_speed = shell_speed + end + + self.staticdata = staticdata + end, + + get_staticdata = function (self) + return self.staticdata + end, + + on_step = function (self, dtime, info) + local explode_pos = nil + + self.object:set_rotation (vector.dir_to_rotation (self.object:get_velocity ())) + + if self.time_lived then + self.time_lived = self.time_lived + dtime + + if self.time_lived > self.shell_speed then + self.object:remove () + + return + end + end + + if info.collides then + --For each collision that was found in reverse order + for i = #info.collisions, 1, -1 do + local c = info.collisions[i] + + if c.type == "node" then + local node = utils.get_far_node (c.node_pos) + + if node and node.name ~= "air" then + local def = minetest.registered_nodes[node.name] + + if def and def.walkable then + -- adjacent for explosion + explode_pos = get_adjacent_node (c, self.spawn_pos) + +--minetest.log ("action", "Shell on node "..node.name.." at "..minetest.pos_to_string (explode_pos).. + --" node at "..minetest.pos_to_string (c.node_pos)) + + break + end + end + + if not explode_pos then + self.object:set_velocity (c.old_velocity) + end + + elseif c.type == "object" then + local c_name = (c.object.get_luaentity and + c.object:get_luaentity () and + c.object:get_luaentity ().name) or "" + local s_name = (self.name) or "" + + -- explode at this pos + if c.object:get_armor_groups ().immortal or s_name == c_name then + self.object:set_velocity (c.old_velocity) + else + explode_pos = vector.new (c.object:get_pos ()) + +--minetest.log ("action", "Shell on entity "..c.object:get_luaentity ().name.." at "..minetest.pos_to_string (explode_pos)) + + break + end + end + end + end + + if explode_pos then + self.object:remove () + + explode_func (explode_pos) + end + end, + + on_punch = function (self, puncher, time_from_last_punch, tool_capabilities, dir) + return true + end, + }) + + minetest.register_craftitem (name, { + description = description, + short_description = description, + groups = { }, + inventory_image = inventory_image, + wield_image = inventory_image, + stack_max = stack_max, + }) + + + lwcomponents.register_spawner (name, + function (spawn_pos, itemstack, owner, spawner_pos, spawner_dir, force) + if not itemstack:is_empty() then + local def = minetest.registered_entities[name.."_entity"] + + if def then + local obj = minetest.add_entity (spawn_pos, name.."_entity") + + if obj then + obj:set_acceleration ({ x = 0, y = -9.81, z = 0 }) + obj:set_rotation (vector.dir_to_rotation (vector.multiply (spawner_dir, shell_speed))) + obj:set_velocity (vector.multiply (spawner_dir, shell_speed)) + + local luaent = obj:get_luaentity () + + if luaent then + luaent.spawn_pos = { x = spawn_pos.x, y = spawn_pos.y, z = spawn_pos.z } + luaent.time_lived = 0 + luaent.shell_speed = shell_speed + end + + return obj, false + end + end + end + + return nil, false + end) +end + + +register_shell ("lwcomponents:cannon_shell", + S("Shell"), + "lwcannon_shell.png", + "lwcannon_shell_item.png", + 99, + 25, + function (pos) + utils.boom (pos, + 2, -- node_radius + 70, -- node_chance in 100 + 2, -- fire_radius + 5, -- fire_chance in 100 + 4, -- entity_radius + 20, -- entity_damage + false, -- disable_drops + nil, -- node_filter + false, -- burn_all + nil) -- sound + end) + + +register_shell ("lwcomponents:cannon_soft_shell", + S("Soft Shell"), + "lwcannon_soft_shell.png", + "lwcannon_soft_shell_item.png", + 99, + 25, + function (pos) + utils.boom (pos, + 2, -- node_radius + 50, -- node_chance in 100 + 2, -- fire_radius + 5, -- fire_chance in 100 + 4, -- entity_radius + 20, -- entity_damage + false, -- disable_drops + { + buildable_to = true, + buildable_to_undefined = false, + }, -- node_filter + false, -- burn_all + nil) -- sound + end) + + +if minetest.global_exists ("fire") then +register_shell ("lwcomponents:cannon_fire_shell", + S("Fire Shell"), + "lwcannon_fire_shell.png", + "lwcannon_fire_shell_item.png", + 99, + 25, + function (pos) + utils.boom (pos, + 2, -- node_radius + 0, -- node_chance in 100 + 2, -- fire_radius + 70, -- fire_chance in 100 + 4, -- entity_radius + 20, -- entity_damage + false, -- disable_drops + nil, -- node_filter + true, -- burn_all + nil) -- sound + end) +end + + + +--