:NAME sharpshooter_aim

:PARAM float32	hone_delay
:PARAM float32	shoot_delay
:PARAM float32	frequency
:PARAM float32	frequency2
:PARAM float32	max_offset


:VAR float32	offset
:VAR int32		state
:VAR float32	state_time
:VAR c_object*	laser
:VAR float32	last_visible_time
:VAR c_node_state* scale_node_state
:VAR c_node_state* end_node_state
:VAR float32	laser_scale_factor
:VAR boolean	blocked
:VAR c_sound*	sound_laser_on
:VAR c_sound*	sound_laser_off




-- -------------------- GLOBALS --------------------

k_game_time_mode= e_value("e_time_mode", "_time_mode_game")

k_state_none= 0
k_state_honing= 1
k_state_ready= 2

k_follow_mode_target= e_value("e_path_follow_look_mode", "_pf_look_mode_target")

k_time_since_visible_delay= 1

k_laser_expand_time= 0.2

k_collision_distance= 50
k_default_max_offset= 3
k_min_offset= 0.5

local debug_color= {}
debug_color.red= 1
debug_color.green= 0
debug_color.blue= 0

-- NOTE: must agree with shoot_control.lua
k_raycast_type_mask= e_value("e_physics_type", "k_physics_type_static_mask") + e_value("e_physics_type", "k_physics_type_dynamic_mask")
k_raycast_group_always_ignored= 31		-- note, must agree with code
k_shoot_distance= 1000

local vectorlib = require "scripts/vector_math"



-- -------------------- INITIALIZATION --------------------

function param_initialize(p)
	p.hone_delay= 3
	p.max_offset= k_default_max_offset
	p.frequency= 3
end

function var_initialize(v)
	v.offset= 0
	v.state= k_state_none
	v.state_time= 0
	v.laser= nil
	v.last_visible_time= -k_time_since_visible_delay
	v.scale_node_state= nil
	v.end_node_state= nil
	v.laser_scale_factor= 1
	v.blocked= false
	v.sound_laser_on= nil
	v.sound_laser_off= nil
end


-- -------------------- CODE --------------------

function hone_start(object)
	object._c.shoot_control.disabled= true
	var.state= k_state_honing
	var.state_time= world_state_get().time.time
	var.offset= param.max_offset
	
	if (var.laser) then
		var.laser.hide(false)
		
		if (var.sound_laser_on) then
			var.sound_laser_on.play(k_game_time_mode)
		end
	end
end

function hone_finished(object)
	-- we successfully honed in on the target
	var.state= k_state_ready
	var.state_time= world_state_get().time.time
	var.offset= 0
end

function hone_end(object)
	-- the target is no longer visible, so stop doing anything
	object._c.shoot_control.disabled= true
	var.state= k_state_none
	var.state_time= world_state_get().time.time
	var.offset= 0
	
	if (var.laser) then
		var.laser.hide(true)
		
		if (var.sound_laser_off) then
			var.sound_laser_off.play(k_game_time_mode)
		end
	end
end


-- -------------------- CODE --------------------

function attach(object)
	local spawn= object.spawn_get("laser_sight")
	if (spawn) then
		local laser= spawn.spawn()
		var.laser= laser
		
		if (laser) then
			laser.hide(true)
			
			local model= laser.model_get()
			if (model) then
				local skeleton= model.skeleton
				if (skeleton) then
					local scale_node= skeleton.node_get_by_name("scale");
					local end_node= skeleton.node_get_by_name("end");
					
					if (scale_node) then
						var.scale_node_state= laser.node_state_from_node(scale_node);
					end
					
					if (end_node) then
						var.end_node_state= laser.node_state_from_node(end_node);
						var.laser_scale_factor= 1/var.end_node_state.transform.offset.x
					end
					
				end
			end
			
			local sound= object.sound_get()	
			if (sound) then
				var.sound_laser_on= sound.get_sound_by_name("laser_on")
				var.sound_laser_off= sound.get_sound_by_name("laser_off")
			end

		end
	end
	
	local shoot_control= object._c.shoot_control
	if (shoot_control) then
		shoot_control.disabled= true
	end
	
	var.offset= param.max_offset
end

function find_block_distance(object, target_object, distance, muzzle_position, vector)

	-- temporarily set my own raycast group to 31, so that I don't hit myself in the raycast below		
	local cached_raycast_group= object.raycast_group
	object.raycast_group= k_raycast_group_always_ignored
	local raycast_result= world_state_get().raycast_type(object.plate, muzzle_position.x, muzzle_position.y, vector.x, vector.y, distance, k_raycast_type_mask)
	local result= k_shoot_distance

	-- reset the raycast group		
	object.raycast_group= cached_raycast_group
	
	local hit_object= raycast_result.object_handle
	if (hit_object~=nil and hit_object~=target_object) then
		result= raycast_result.distance
	end

	return result
end

function update(object)

	local visible_target= false
	local path_follow= object.path_follow_get()
	local perception= object.perception_get()
	local target_cache= nil
	local t= world_state_get().time.time
	
	if (perception) then
		target_cache= perception.target_cache
	end

	if (path_follow and path_follow.look_mode==k_follow_mode_target and target_cache and target_cache.is_visible()) then
		visible_target= true
		var.last_visible_time= t
	else
		if ((t - var.last_visible_time) < k_time_since_visible_delay) then
			visible_target= true
		end
	end

	if (visible_target) then
	
		if (var.state == k_state_none) then
			hone_start(object)
		end
	
		if (var.state == k_state_honing) then
			local time= t - var.state_time
			
			if (time >= param.hone_delay) then
				hone_finished(object)
			else
				var.offset= k_min_offset + (param.max_offset-k_min_offset) * (1 - time/param.hone_delay)				
			end
		end
	else
		if (var.state > k_state_none) then
			hone_end(object)
		end
	end
	
	local target_point= nil
	local target_object= nil
	local angle= 0
	
	if (var.state > k_state_none) then
		local perception = object.perception_get()
		if (perception) then
			local target= perception.get_target()
			if (target) then
				local tpoint= target.transform.offset
				target_point= {}
				target_object= target.target_object
				local time= t - var.state_time
				
				target_point.x= tpoint.x + var.offset * math.cos(param.frequency*time)
				target_point.y= tpoint.y + var.offset * math.sin(param.frequency2*time)
			end
		end
	end

	if (target_point) then
		local laf= object.laf_get()
		if (laf) then
			local point= object.world_position_get()
			
			local v= vectorlib.vector_from_points(point, target_point)
			
			angle= math.atan2(v.y, v.x)
			
			local base_angle= laf.aim_state.target_angle
			laf.set_aim_driving_mode()
			laf.set_target(angle, false);
			laf.set_look_target(base_angle)
		end
	end
	
	if (target_point and var.state > k_state_none and (var.scale_node_state or var.end_node_state)) then
		local laf= object.laf_get()
		local arena= world_get().arena
		if (arena and laf) then
			local arena_state= arena.arena_state_get()
			local v= vectorlib.vector_from_angle(laf.aim_state.angle)
			local muzzle_marker= object.marker_get("muzzle")
			
			if (muzzle_marker) then
				local muzzle_point= muzzle_marker.world_position_get(object)
				local d= vectorlib.distance2d(muzzle_point, target_point)
				
				if (var.state==k_state_honing) then
					local st= t - var.state_time
					if (st < k_laser_expand_time) then
						d= d * st/k_laser_expand_time
					end
				end
				
				local endpoint= vectorlib.offset_point(muzzle_point, v, d)
				local collision_point= arena_state.trace_line_collision(
					0, 
					muzzle_point.x, 
					muzzle_point.y, 
					endpoint.x, 
					endpoint.y)
					
				local distance= vectorlib.distance2d(collision_point, muzzle_point)

				-- consider also the danger of hitting my friends
				local block_distance= find_block_distance(object, target_object, d, muzzle_point, v)
				var.blocked= (block_distance < d)
				
				distance= math.min(distance, block_distance)
				
				if (var.scale_node_state) then
					var.scale_node_state.scale_set(distance*var.laser_scale_factor, 1)
				end
				
				if (var.end_node_state) then
					var.end_node_state.position_set(distance, 0)
				end
				
			end			
		end
	else
		var.blocked= false
	end
	
	if (var.state==k_state_ready) then
		local st= t - var.state_time
		if (st > param.shoot_delay) then
			local shoot_control= object._c.shoot_control
			if (shoot_control) then
				shoot_control.disabled= false
			end
		end
	end
end


component_register(attach, update, nil, nil)
