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 = "<unknown>"
			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
})



--
