--[[
Shooter Crossbow [shooter_crossbow]
Copyright (C) 2013-2019 stujones11, Stuart Jones

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
]]--

local config = {
	crossbow_uses = 50,
	arrow_lifetime = 180,
	arrow_fleshy = 2,
	arrow_object_attach = false,
}

-- Legacy Config Support

for name, _ in pairs(config) do
	local global = "SHOOTER_"..name:upper()
	if minetest.global_exists(global) then
		config[name] = _G[global]
	end
end

-- Load configuration

config = shooter.get_configuration(config)

local arrow_tool_caps = {damage_groups={fleshy=config.arrow_fleshy, ranged=1, crossbow=1}}
if minetest.global_exists("SHOOTER_ARROW_TOOL_CAPS") then
	arrow_tool_caps = table.copy(SHOOTER_ARROW_TOOL_CAPS)
end

local dye_basecolors = (dye and dye.basecolors) or
	{"white", "grey", "black", "red", "yellow", "green", "cyan", "blue", "magenta"}

-- name is the overlay texture name, colour is used to select the wool texture
local function get_texture(name, colour)
	return "wool_"..colour..".png^shooter_"..name..".png^[makealpha:255,126,126"
end

local function get_animation_frame(dir)
	local angle = math.atan(dir.y)
	local frame = 90 - math.floor(angle * 360 / math.pi)
	if frame < 1 then
		frame = 1
	elseif frame > 180 then
		frame = 180
	end
	return frame
end

local function get_pointed_thing(pos, dir, dist)
	local p1 = vector.add(pos, dir)
	local p2 = vector.add(pos, vector.multiply(dir, dist))
	local ray = minetest.raycast(p1, p2, true, true)
	return ray:next()
end

local function strike(arrow, pointed_thing, name)
	local puncher = minetest.get_player_by_name(name)
	if not puncher then
		return
	end
	local object = arrow.object
	local hit_pos = pointed_thing.intersection_point or object:get_pos()
	local dir = vector.normalize(object:get_velocity())
	if pointed_thing.type == "object" then
		local target = pointed_thing.ref
		if shooter.is_valid_object(target) then
			if puncher and puncher ~= target then
				local groups = target:get_armor_groups() or {}
				if groups.fleshy then
					shooter.spawn_particles(hit_pos)
				end
				target:punch(puncher, nil, arrow_tool_caps, dir)
				if config.arrow_object_attach then
					local pos = vector.multiply(vector.subtract(target:get_pos(),
						hit_pos), -10)
					local rot = vector.new()
					rot.y = (target:get_yaw() - object:get_yaw()) * 57.2958
					object:set_attach(target, "", pos, rot)
					arrow.state = "stuck"
				else
					arrow.state = "dropped"
				end
			end
		end
	elseif pointed_thing.type == "node" then
		local pos = minetest.get_pointed_thing_position(pointed_thing, false)
		local node = minetest.get_node(pos)
		hit_pos = vector.subtract(hit_pos, vector.multiply(dir, 0.25))
		arrow.node_pos = pos
		arrow.state = "stuck"
		shooter.play_node_sound(node, pos)
	else
		return
	end
	arrow:stop(hit_pos)
end

minetest.register_entity("shooter_crossbow:arrow_entity", {
	physical = false,
	visual = "mesh",
	mesh = "shooter_arrow.b3d",
	visual_size = {x=1, y=1},
	textures = {
		get_texture("arrow_uv", "white"),
	},
	color = "white",
	timer = 0,
	lifetime = config.arrow_lifetime,
	user = nil,
	state = "init",
	node_pos = nil,
	collisionbox = {0,0,0, 0,0,0},
	stop = function(self, pos)
		local acceleration = {x=0, y=-10, z=0}
		if self.state == "stuck" then
			pos = pos or self.object:get_pos()
			acceleration = {x=0, y=0, z=0}
		end
		if pos then
			self.object:move_to(pos)
		end
		self.object:set_properties({
			physical = true,
			collisionbox = {-1/8,-1/8,-1/8, 1/8,1/8,1/8},
		})
		self.object:set_velocity({x=0, y=0, z=0})
		self.object:set_acceleration(acceleration)
	end,
	on_activate = function(self, staticdata)
		self.object:set_armor_groups({immortal=1})
		if staticdata == "expired" then
			self.object:remove()
		end
	end,
	on_punch = function(self, puncher)
		if puncher then
			if puncher:is_player() then
				local stack = "shooter_crossbow:arrow_"..self.color
				local inv = puncher:get_inventory()
				if inv:room_for_item("main", stack) then
					inv:add_item("main", stack)
					self.object:remove()
				end
			end
		end
	end,
	on_step = function(self, dtime)
		if self.state == "init" then
			return
		end
		self.timer = self.timer + dtime
		self.lifetime = self.lifetime - dtime
		if self.lifetime < 0 then
			self.object:remove()
			return
		elseif self.state == "dropped" then
			return
		elseif self.state == "stuck" then
			if self.timer > 1 then
				if self.node_pos then
					local node = minetest.get_node(self.node_pos)
					if node.name then
						local item = minetest.registered_items[node.name]
						if item then
							if not item.walkable then
								self.state = "dropped"
								self:stop()
								return
							end
						end
					end
				end
				self.timer = 0
			end
			return
		end
		if self.timer > 0.2 then
			local dir = vector.normalize(self.object:get_velocity())
			local frame = get_animation_frame(dir)
			local pos = self.object:get_pos()
			local pointed_thing = get_pointed_thing(pos, dir, 5)
			if pointed_thing then
				strike(self, pointed_thing, self.user)
			end
			self.object:set_animation({x=frame, y=frame}, 0)
			self.timer = 0
		end
	end,
	get_staticdata = function()
		return "expired"
	end,
})

for _, color in pairs(dye_basecolors) do
	minetest.register_craftitem("shooter_crossbow:arrow_"..color, {
		description = color:gsub("%a", string.upper, 1).." Arrow",
		inventory_image = get_texture("arrow_inv", color),
	})
	minetest.register_tool("shooter_crossbow:crossbow_loaded_"..color, {
		description = "Crossbow",
		inventory_image = get_texture("crossbow_loaded", color),
		groups = {not_in_creative_inventory=1},
		on_use = function(itemstack, user)
			minetest.sound_play("shooter_click", {object=user})
			if not minetest.settings:get_bool("creative_mode") then
				itemstack:add_wear(65535 / config.crossbow_uses)
			end
			itemstack = "shooter_crossbow:crossbow 1 "..itemstack:get_wear()
			local pos = user:get_pos()
			local dir = user:get_look_dir()
			local yaw = user:get_look_horizontal()
			if pos and dir and yaw then
				pos.y = pos.y + user:get_properties().eye_height
				local obj = minetest.add_entity(pos,
					"shooter_crossbow:arrow_entity")
				local ent = nil
				if obj then
					ent = obj:get_luaentity()
				end
				if ent then
					ent.user = user:get_player_name()
					ent.state = "flight"
					ent.color = color
					obj:set_properties({
						textures = {get_texture("arrow_uv", color)}
					})
					minetest.sound_play("shooter_throw", {object=obj})
					local frame = get_animation_frame(dir)
					obj:set_yaw(yaw - math.pi / 2)
					obj:set_animation({x=frame, y=frame}, 0)
					obj:set_velocity({x=dir.x * 14, y=dir.y * 14, z=dir.z * 14})
					obj:set_acceleration({x=dir.x * -3, y=-5, z=dir.z * -3})
					local pointed_thing = get_pointed_thing(pos, dir, 5)
					if pointed_thing then
						strike(ent, pointed_thing, ent.user)
					end
				end
			end
			return itemstack
		end,
	})
end

minetest.register_tool("shooter_crossbow:crossbow", {
	description = "Crossbow",
	inventory_image = "shooter_crossbow.png",
	on_use = function(itemstack, user)
		local inv = user:get_inventory()
		local stack = inv:get_stack("main", user:get_wield_index() + 1)
		local color = string.match(stack:get_name(), "shooter_crossbow:arrow_(%a+)")
		if color then
			minetest.sound_play("shooter_reload", {object=user})
			if not minetest.settings:get_bool("creative_mode") then
				inv:remove_item("main", "shooter_crossbow:arrow_"..color.." 1")
			end
			return "shooter_crossbow:crossbow_loaded_"..color.." 1 "..itemstack:get_wear()
		end
		for _, clr in pairs(dye_basecolors) do
			if inv:contains_item("main", "shooter_crossbow:arrow_"..clr) then
				minetest.sound_play("shooter_reload", {object=user})
				if not minetest.settings:get_bool("creative_mode") then
					inv:remove_item("main", "shooter_crossbow:arrow_"..clr.." 1")
				end
				return "shooter_crossbow:crossbow_loaded_"..clr.." 1 "..itemstack:get_wear()
			end
		end
		minetest.sound_play("shooter_click", {object=user})
	end,
})

if shooter.config.enable_crafting == true then
	minetest.register_craft({
		output = "shooter_crossbow:crossbow",
		recipe = {
			{"default:stick", "default:stick", "default:stick"},
			{"default:stick", "default:stick", ""},
			{"default:stick", "", "default:bronze_ingot"},
		},
	})
	minetest.register_craft({
		output = "shooter_crossbow:arrow_white",
		recipe = {
			{"default:steel_ingot", "", ""},
			{"", "default:stick", "default:paper"},
			{"", "default:paper", "default:stick"},
		},
	})
	if minetest.get_modpath("dye") then
		for _, color in pairs(dye_basecolors) do
			if color ~= "white" then
				minetest.register_craft({
					output = "shooter_crossbow:arrow_"..color,
					recipe = {
						{"", "dye:"..color, "shooter_crossbow:arrow_white"},
					},
				})
			end
		end
	end
end

--Backwards compatibility
minetest.register_alias("shooter:crossbow", "shooter_crossbow:crossbow")
for _, color in pairs(dye_basecolors) do
	minetest.register_alias("shooter:arrow_"..color, "shooter_crossbow:arrow_"..color)
	minetest.register_alias("shooter:crossbow_loaded_"..color, "shooter_crossbow:crossbow_loaded_"..color)
end
