
function cannons.destroy(pos,range)
	for x=-range,range do
	for y=-range,range do
	for z=-range,range do
		if x*x+y*y+z*z <= range * range + range then
			local np={x=pos.x+x,y=pos.y+y,z=pos.z+z}
			local n = minetest.env:get_node(np)
			if n.name ~= "air" then
				minetest.env:remove_node(np)
			end
		end
	end
	end
	end
end

function cannons.sound_defaults(table)
	table = table or {}
	table.footstep = table.footstep or
			{name="cannons_walk", gain=1.0}
	table.dig = table.dig or
			{name="cannons_dig", gain=0.5}
	table.dug = table.dug or
			{name="default_dug_node", gain=0.5}
	table.place = table.place or
			{name="default_place_node_hard", gain=1.0}
	return table
end
function cannons.inventory_modified(pos)
	local meta = minetest.get_meta(pos)
	local inv = meta:get_inventory()
	local muni = inv:get_stack("muni", 1):to_table()
	local gunpowder = inv:get_stack("gunpowder", 1):to_table();
	local addition = ""
	if  meta:get_string("owner") ~="" then
		addition = " (owned by "..meta:get_string("owner")..")"
	end
	if muni == nil then
		muni = false
	else
		muni = cannons.is_muni(muni.name)
	end
	if gunpowder == nil then
		gunpowder = false;
	else
		gunpowder = cannons.is_gunpowder(gunpowder.name)
	end
	
	if not muni and not gunpowder then
		meta:set_string("infotext","Cannon has no muni and no gunpowder"..addition)
	
	elseif not muni then
		meta:set_string("infotext","Cannon has no muni"..addition)
	
	elseif not gunpowder then
		meta:set_string("infotext","Cannon has no gunpowder"..addition)
		
	else
		meta:set_string("infotext","Cannon is ready"..addition)
	end		
end

cannons.allow_metadata_inventory_put = function(pos, listname, index, stack, player)
		
		local meta = minetest.get_meta(pos)
		if(meta:get_string("owner") ~="" and not( locks:lock_allow_use( pos, player ))) then
		   return 0;
		end
		local inv = meta:get_inventory()
		stack = stack:to_table()
		if listname == "gunpowder" and cannons.is_gunpowder(stack.name) then	
			return stack.count
		elseif listname == "muni" and cannons.is_muni(stack.name) then	
			return stack.count
		else return 0
		end

	end
	
cannons.allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)

		local meta = minetest.get_meta(pos)
		if(meta:get_string("owner") ~="" and not( locks:lock_allow_use( pos, player ))) then
		   return 0;
		end
		local inv = meta:get_inventory()
		local stack = inv:get_stack(from_list, from_index)
		stack = stack:to_table()
		if to_list == "gunpowder" and cannons.is_gunpowder(stack.name) then
			return count
		
		elseif to_list == "muni" and  cannons.is_muni(stack.name) then
			return count
		else
			return 0
		end
	end
	
cannons.can_dig = function(pos,player)
		local meta = minetest.get_meta(pos);
		local inv = meta:get_inventory()
		if not inv:is_empty("gunpowder") then
			return false
		elseif not inv:is_empty("muni") then
			return false
		else
			return true
		end
	end
	
cannons.formspec =
	"size[8,9]"..
	"list[current_name;muni;0,1;1,1;] label[0,0.5;Muni:]"..
	"list[current_name;gunpowder;0,3;1,1;] label[0,2.5;Gunpowder:]"..
	"list[current_player;main;0,5;8,4;]"
if default and default.gui_bg then 
	cannons.formspec = cannons.formspec..default.gui_bg;
end

if default and default.gui_bg_img then 
	cannons.formspec = cannons.formspec..default.gui_bg_img;
end

if default and default.gui_slots then 
	cannons.formspec = cannons.formspec..default.gui_slots;
end

cannons.disabled_formspec =
	"size[8,9]"..
	"label[1,0.5;Cannon is Disabled. Place it on a cannonstand to activate it]"..
	"list[current_player;main;0,5;8,4;]"
	
if default and default.gui_bg then 
	cannons.disabled_formspec = cannons.disabled_formspec..default.gui_bg;
end

if default and default.gui_bg_img then 
	cannons.disabled_formspec = cannons.disabled_formspec..default.gui_bg_img;
end

if default and default.gui_slots then 
	cannons.disabled_formspec = cannons.disabled_formspec..default.gui_slots;
end
	
cannons.on_construct = function(pos)
	local node = minetest.get_node({x = pos.x ,y = pos.y, z = pos.z})
		if minetest.registered_items[node.name].cannons then
		local meta = minetest.get_meta(pos)
		meta:set_string("formspec", cannons.formspec)
		meta:set_string("infotext", "Cannon has no muni and no gunpowder")
		local inv = meta:get_inventory()
		inv:set_size("gunpowder", 1)
		inv:set_size("muni", 1)
	else
		local meta = minetest.get_meta(pos)
		meta:set_string("formspec", cannons.disabled_formspec)
		meta:set_string("infotext", "Cannon is out of order")
	end
end

cannons.stand_on_rightclick = function(pos, node, player, itemstack, pointed_thing)
	if  minetest.get_item_group(itemstack:get_name(), "cannon")>=1 then --if rightclicked with a cannon
		local item = string.split(itemstack:get_name(),":")[2];
		node.name=node.name.."_with_"..item
		---print(node.name);
		minetest.swap_node(pos, node)
		local meta = minetest.get_meta(pos)
		meta:set_string("formspec", cannons.formspec)
		meta:set_string("infotext", "Cannon has no muni and no gunpowder")
		local inv = meta:get_inventory()
		inv:set_size("gunpowder", 1)
		inv:set_size("muni", 1)
		itemstack:take_item(1)
		return itemstack
	end
end

cannons.dug = function(pos, node, digger)
	if not node or not type(node)=="table" then
	  return
	end
	if not digger or not digger:is_player() then
	  return
	end
	local cannons = minetest.registered_nodes[node.name].cannons
	if cannons and cannons.stand and cannons.cannon then --node dug
		node.name = cannons.stand
		minetest.swap_node(pos, node)--replace node with the stand
		local meta = minetest.get_meta(pos)
		meta:set_string("formspec","")
		meta:set_string("infotext", "place a cannon on this stand")
		local inv =  digger:get_inventory()
		local stack = inv:add_item("main", ItemStack(cannons.cannon))--add the cannon to the ineentory
		minetest.item_drop(stack, digger, pos)
	end
end

cannons.on_construct_locks = function(pos)
	local node = minetest.get_node({x = pos.x ,y = pos.y-1, z = pos.z})
	if minetest.registered_nodes[node.name].groups.cannonstand then
		local meta = minetest.get_meta(pos)
		meta:set_string("formspec", cannons.formspec..
									"field[2,1.3;6,0.7;locks_sent_lock_command;Locked Cannon. Type /help for help:;]"..
									"button[6,2;1.7,0.7;locks_sent_input;Proceed]")
		meta:set_string("infotext", "Cannon has no muni and no gunpowder")
		local inv = meta:get_inventory()
		inv:set_size("gunpowder", 1)
		inv:set_size("muni", 1)
	else
		local meta = minetest.get_meta(pos)
		meta:set_string("formspec", cannons.disabled_formspec)
		meta:set_string("infotext", "Cannon is out of order")
	end
end

function cannons.nodehitparticles(pos,node)
if type(minetest.registered_nodes[node.name]) == "table" and type(minetest.registered_nodes[node.name].tiles) == "table" and type(minetest.registered_nodes[node.name].tiles[1])== "string" then
local texture = minetest.registered_nodes[node.name].tiles[1]
	minetest.add_particlespawner(
        30, --amount
        0.5, --time
        {x=pos.x-0.3, y=pos.y+0.3, z=pos.z-0.3}, --minpos
        {x=pos.x+0.3, y=pos.y+0.5, z=pos.z+0.3}, --maxpos
        {x=0, y=2, z=0}, --minvel
        {x=0, y=3, z=0}, --maxvel
        {x=-4,y=-4,z=-4}, --minacc
        {x=4,y=-4,z=4}, --maxacc
        0.1, --minexptime
        1, --maxexptime
        1, --minsize
        3, --maxsize
        false, --collisiondetection
        texture --texture
    )
	end
end
function cannons.fire(pos,node,puncher)
	local meta = minetest.get_meta(pos)
	local inv = meta:get_inventory()
	local muni = inv:get_stack("muni", 1):to_table();
	local gunpowder = inv:get_stack("gunpowder", 1):to_table();
	local dir = {}
	--print(muni ~= nil,cannons.is_muni(muni.name),inv:contains_item("muni",muni.name.." 1"),gunpowder ~= nil ,cannons.is_gunpowder(gunpowder.name),inv:contains_item("gunpowder",gunpowder.name.." 1"))
	if  muni ~= nil 
		and cannons.is_muni(muni.name) 
		and inv:contains_item("muni",muni.name.." 1")
		and gunpowder ~= nil
		and cannons.is_gunpowder(gunpowder.name)
		and inv:contains_item("gunpowder",gunpowder.name.." 1")
	
	then 
		if puncher ~= nil then
			dir=puncher:get_look_dir()
			meta:set_string("dir", minetest.serialize(dir))
		else			
			dir = minetest.deserialize(meta:get_string("dir"));
			if dir == nil then
				return
			end
		end	
		minetest.sound_play("cannons_shot",
			{pos = pos, gain = 1.0, max_hear_distance = 32,})
		

		inv:remove_item("muni", muni.name.." 1")
		inv:remove_item("gunpowder", gunpowder.name.." 1")
		cannons.inventory_modified(pos)


		local settings = cannons.get_settings(muni.name)
		local obj=minetest.env:add_entity(pos, cannons.get_entity(muni.name))
		obj:setvelocity({x=dir.x*settings.velocity, y=-1, z=dir.z*settings.velocity})
		obj:setacceleration({x=dir.x*-3, y=-settings.gravity, z=dir.z*-3})
		minetest.add_particlespawner(50,0.5,
    pos, pos,
    {x=dir.x*settings.velocity, y=-1, z=dir.z*settings.velocity}, {x=dir.x*settings.velocity/2, y=-1, z=dir.z*settings.velocity/2},
    {x=dir.x*-3/4, y=-settings.gravity*2, z=dir.z*-3/4}, {x=dir.x*-3/2, y=-settings.gravity, z=dir.z*-3/2},
    0.1, 0.5,--time
    0.5, 1,
    false, "cannons_gunpowder.png")
	end
end

function cannons.punched(pos, node, puncher)
	if not puncher or not node then
		return
	end
	local wield = puncher:get_wielded_item()
	if not wield then
		return
	end
	wield = wield:get_name()
	if wield and wield == 'default:torch' then
		cannons.fire(pos,node,puncher)
	end
end

--++++++++++++++++++++++++++++++++++++
--+ cannons.register_muni            +
--++++++++++++++++++++++++++++++++++++

cannons.registered_muni = {}

function cannons.register_muni(node,entity)
	cannons.registered_muni[node] = {}
	cannons.registered_muni[node].entity = entity
	local name = node:split(":")
	cannons.registered_muni[node].entity.name = minetest.get_current_modname()..":entity_"..name[1].."_"..name[2]
	cannons.registered_muni[node].entity.on_step = function(self, dtime)
		self.timer=self.timer+dtime
		if self.timer >= 0.3 then --easiesst less laggiest way to find out that it left his start position
			local pos = self.object:getpos()
			local node = minetest.env:get_node(pos)

			if node.name == "air" then
				local objs = minetest.get_objects_inside_radius({x=pos.x,y=pos.y,z=pos.z}, self.range)
				for k, obj in pairs(objs) do
				if obj:get_luaentity() ~= nil then
					if obj:get_luaentity().name ~= self.name and obj:get_luaentity().name ~= "__builtin:item" then --something other found
						local mob = obj
						self.on_mob_hit(self,pos,mob)
						end
					elseif obj:is_player() then --player found
					local player = obj
						self.on_player_hit(self,pos,player)
					end		
				end
			elseif node.name ~= "air"  then
				self.on_node_hit(self,pos,node)
			end	
			self.lastpos={x=pos.x, y=pos.y, z=pos.z}
		end	
	end
	cannons.registered_muni[node].obj = entity.name
	minetest.register_entity(entity.name, cannons.registered_muni[node].entity)
end

function cannons.is_muni(node)
	return cannons.registered_muni[node] ~= nil
end
function cannons.get_entity(node)
	return cannons.registered_muni[node].obj
end
function cannons.get_settings(node)
	return cannons.registered_muni[node].entity	
end

--++++++++++++++++++++++++++++++++++++
--+ cannons.register_gunpowder       +
--++++++++++++++++++++++++++++++++++++
cannons.registered_gunpowder = {}
function cannons.register_gunpowder(node)
	cannons.registered_gunpowder[node] = true;
end

function cannons.is_gunpowder(node)
	return cannons.registered_gunpowder[node] ~= nil
end


--++++++++++++++++++++++++++++++++++++
--+ cannons ball stack               +
--++++++++++++++++++++++++++++++++++++
function cannons.on_ball_punch(pos, node, puncher, pointed_thing)
	if not puncher or not puncher:is_player() then
	  return
	end
	local nodearr = string.split(node.name,"_stack_");
	local item = nodearr[1];
	local level = tonumber(nodearr[2]);
	puncher:get_inventory():add_item('main', item)
	if level > 1 then
		node.name = item.."_stack_"..level-1
		minetest.swap_node(pos,node);
	else 
		minetest.remove_node(pos);
	end
end

function cannons.on_ball_rightclick(pos, node, player, itemstack, pointed_thing)
	if not player or not player:is_player() then
	  return
	end
	local basename = string.split(itemstack:get_name(),"_stack_")[1];
	local nodearr = string.split(node.name,"_stack_");
	local item = nodearr[1];
	local level = tonumber(nodearr[2]);
	if basename == item then
		if level < 5 then
			itemstack:take_item(1)
			node.name = item.."_stack_"..level+1
			minetest.swap_node(pos,node);
			return itemstack;
		else 
			return itemstack
		end
	end
	
end

function cannons.generate_and_register_ball_node(name,nodedef)
	minetest.register_alias(name, name.."_stack_1");
	nodedef.drawtype = "nodebox";
	nodedef.selection_box = nil;
	nodedef.on_punch = cannons.on_ball_punch;
	nodedef.on_rightclick = cannons.on_ball_rightclick;
	local nodebox = {
		type = "fixed",
		fixed = {},
		}; --initialize empty nodebox
		
	for number = 1, 5, 1 do 
		nodebox.fixed[number] = cannons.nodeboxes.ball_stack.fixed[number];--copy a part to nodebox
		nodedef.node_box = nodebox;--add nodebox to nodedef
		nodedef.selection_box = table.copy(nodebox);
		nodedef["drop"] =  name.."_stack_1 "..number;--set drop
		nodedef.name = name.."_stack_"..number;--set name
		
		minetest.register_node(nodedef.name, table.copy(nodedef))--register node
	end
	--register craft, to allow craft 5-stacks
	minetest.register_craft({ 
		type = "shapeless", 
		output = name.."_stack_5", 
		recipe = { name,name,name,name,name},
	})
end

--++++++++++++++++++++++++++++++++++++
--+ mesecons stuff                   +
--++++++++++++++++++++++++++++++++++++
cannons.rules ={
	{x = 1, y = 0, z = 0},
	{x =-1, y = 0, z = 0},
	{x = 0, y = 0, z = 1},
	{x = 0, y = 0, z =-1}
 }
 
function cannons.meseconsfire(pos,node)
	cannons.fire(pos,node)
end

cannons.supportMesecons = {
	effector = {
		rules = cannons.rules,
		action_on = cannons.meseconsfire,
		}
}
	

--++++++++++++++++++++++++++++++++++++
--+ cannons.nodeboxes                +
--++++++++++++++++++++++++++++++++++++
cannons.nodeboxes = {}
cannons.nodeboxes.ball = {
		type = "fixed",
		fixed = {
			{-0.2, -0.5, -0.2, 0.2, -0.1, 0.2},
			
			-- side , top , side , side , bottom, side,
				
		},
	}

cannons.nodeboxes.ball_stack = {
		type = "fixed",
		fixed = {
			{-0.4375, -0.5, 0.0625, -0.0625, -0.125, 0.4375}, -- unten_hinten_links
			{0.125, -0.5, 0.125, 0.5, -0.125, 0.5}, -- unten_hinten_rechts
			{-0.4375, -0.5, -0.375, -0.0625, -0.125, 0}, -- unten_vorne_links
			{0.0625, -0.5, -0.4375, 0.4375, -0.125, -0.0625}, -- unten_vorne_rechts
			{-0.1875, -0.125, -0.125, 0.1875, 0.25, 0.25}, -- oben_mitte
		},
	}

cannons.nodeboxes.cannon = {
		type = "fixed",
		fixed = {
			{-0.2, 0.2, -0.7, 0.2, -0.2, 0.9}, -- barrle --
			{0.53, -0.1, 0.1, -0.53, 0.1, -0.1}, -- plinth --
			
			-- side , top hight , depth , side , bottom, side,
				
		}
	}
cannons.nodeboxes.stand = {
		type = "fixed",
		fixed = {
			{-0.5, -0.5, -0.5, 0.5, -0.45, 0.5}, -- bottom --
			{-0.5, -0.5, -0.5, -0.35, 0.0, 0.5}, -- side left --
			{0.35, -0.5, -0.5, 0.5, 0.0, 0.5}, -- side right --
			{0.35, -0.5, -0.2, 0.5, 0.2, 0.5}, -- side right --
			{-0.5, -0.5, -0.2, -0.35, 0.2, 0.5}, -- side left --
			
			-- side , top , side , side , bottom, side,
				
		},
	}

local apple={
	physical = false,
	timer=0,
	textures = {"default_apple.png"},
	lastpos={},
	damage=-10,
	range=2,
	gravity=10,
	velocity=30,
	collisionbox = {-0.25,-0.25,-0.25, 0.25,0.25,0.25},
	on_player_hit = function(self,pos,player)
		local playername = player:get_player_name()
		player:punch(self.object, 1.0, {
			full_punch_interval=1.0,
			damage_groups={fleshy=self.damage},
			}, nil)
		self.object:remove()
		minetest.chat_send_player(playername ," this is not an easter egg!")
	end,
	on_mob_hit = function(self,pos,mob)
		self.object:remove()
	end,
	on_node_hit = function(self,pos,node)
		pos = self.lastpos
		minetest.env:set_node({x=pos.x, y=pos.y, z=pos.z},{name="default:apple"})
		minetest.sound_play("canons_hit",
			{pos = pos, gain = 1.0, max_hear_distance = 32,})
		self.object:remove()
	end,

}
cannons.register_muni("default:apple",apple)
