function vg_mobs.microtasks.dogfight(attack_range, attack_toolcaps, attack_time) return vg_mobs.create_microtask({
	label = "dogfight",
	start_animation = "punch",
	on_start = function(self, mob)
		self.statedata.attack_target = mob._temp_custom_state.angry_at
		-- For attack timer; initialize at attack_time + 1 to guarantee an instant attack
		self.statedata.attack_timer = attack_time + 1
		-- Time of last punch for punch() function
		self.statedata.last_punch = nil
	end,
	on_step = function(self, mob, dtime)
		if self.statedata.last_punch then
			self.statedata.last_punch = self.statedata.last_punch + dtime
		end
		if not self.statedata.attack_target then
			return
		elseif self.statedata.attack_target:get_hp() == 0 then
			return
		end
		local mpos = mob.object:get_pos()
		local tpos = self.statedata.attack_target:get_pos()
		local mpos_h = vector.copy(mpos)
		local tpos_h = vector.copy(tpos)
		mpos_h.y = 0
		tpos_h.y = 0
		local dir = vector.direction(mpos_h, tpos_h)
		local yaw = core.dir_to_yaw(dir)
		mob.object:set_yaw(yaw)
		if not tpos then return end
		local dist = vector.distance(mpos, tpos)
		if dist <= attack_range then
			self.statedata.attack_timer = self.statedata.attack_timer + dtime
			if self.statedata.attack_timer > attack_time then
				local time_from_last_punch = self.statedata.last_punch or 1000000
				local dir = vector.direction(mpos, tpos)
				vg_mobs.default_mob_sound(mob, "attack")
				self.statedata.attack_target:punch(mob.object, time_from_last_punch, attack_toolcaps, dir)
				self.statedata.attack_timer = 0
			end
		else
			self.statedata.attack_timer = 0
		end
	end,
	is_finished = function(self, mob)
		if self.statedata.attack_target == nil then
			return true
		elseif self.statedata.attack_target:get_hp() == 0 then
			return true
		elseif self.statedata.attack_target._dying then
			return true
		else
			local mpos = mob.object:get_pos()
			local tpos = self.statedata.attack_target:get_pos()
			if not tpos then
				return true
			end
			local dist = vector.distance(mpos, tpos)
			if dist > attack_range then
				return true
			end
		end
		return false
	end,
}) end

function vg_mobs.create_dogfight_decider(attack_range, attack_toolcaps, attack_time) return function(task_queue, mob)
	local mt_dogfight = vg_mobs.microtasks.dogfight
	local task = vg_mobs.create_task({label="dogfight"})
	vg_mobs.add_microtask_to_task(mob, mt_dogfight, task)
	vg_mobs.add_task_to_task_queue(task_queue, task)
end end

function vg_mobs.microtasks.shoot(attack_range, shoot_callback, attack_time) return vg_mobs.create_microtask({
	label = "shoot",
	start_animation = "shoot",
	on_start = function(self, mob)
		self.statedata.attack_target = mob._temp_custom_state.angry_at
		-- For attack timer; initialize at attack_time + 1 to guarantee an instant attack
		self.statedata.attack_timer = attack_time + 1
	end,
	on_step = function(self, mob, dtime)
		if not self.statedata.attack_target then
			return
		elseif self.statedata.attack_target:get_hp() == 0 then
			return
		end
		local mpos = mob.object:get_pos()
		local tpos = self.statedata.attack_target:get_pos()
		local mpos_h = vector.copy(mpos)
		local tpos_h = vector.copy(tpos)
		mpos_h.y = 0
		tpos_h.y = 0
		local dir = vector.direction(mpos_h, tpos_h)
		local yaw = core.dir_to_yaw(dir)
		mob.object:set_yaw(yaw)
		if not tpos then return end
		local dist = vector.distance(mpos, tpos)
		if dist <= attack_range then
			self.statedata.attack_timer = self.statedata.attack_timer + dtime
			if self.statedata.attack_timer > attack_time then
				local dir = vector.direction(mpos, tpos)
				vg_mobs.default_mob_sound(mob, "shoot")
				shoot_callback(mob, dir)
				self.statedata.attack_timer = 0
			end
		else
			self.statedata.attack_timer = 0
		end
	end,
	is_finished = function(self, mob)
		if self.statedata.attack_target == nil then
			return true
		elseif self.statedata.attack_target:get_hp() == 0 then
			return true
		elseif self.statedata.attack_target._dying then
			return true
		else
			local mpos = mob.object:get_pos()
			local tpos = self.statedata.attack_target:get_pos()
			if not tpos then
				return true
			end
			local dist = vector.distance(mpos, tpos)
			if dist > attack_range then
				return true
			end
		end
		return false
	end,
}) end

function vg_mobs.create_shoot_decider(attack_range, attack_toolcaps, attack_time) return function(task_queue, mob)
	local mt_shoot = vg_mobs.microtasks.shoot
	local task = vg_mobs.create_task({label="shoot"})
	vg_mobs.add_microtask_to_task(mob, mt_shoot, task)
	vg_mobs.add_task_to_task_queue(task_queue, task)
end end

function vg_mobs.create_player_angry_decider() return function(task_queue, mob)
	local mt = vg_mobs.create_microtask({
		label = "mark players as attack target",
		on_step = function(self, mob, dtime)
			if mob._child then
				-- Children are never angry
				return
			end
			-- Closest player becomes target
			if mob._temp_custom_state.closest_player then
				-- Don't change attack target if already set
				if not mob._temp_custom_state.angry_at then
					local player = mob._temp_custom_state.closest_player

					local is_valid = player and player:is_player()
					local is_alive = player:get_hp() > 0
					local has_creative = is_valid and
							core.is_creative_enabled(player:get_player_name())

					if is_valid and is_alive and not has_creative then
						mob._temp_custom_state.angry_at = player
						mob._temp_custom_state.angry_at_timer = 0
					else
						mob._temp_custom_state.angry_at = nil
						mob._temp_custom_state.angry_at_timer = 0
					end
				end
			else
				mob._temp_custom_state.angry_at = nil
				mob._temp_custom_state.angry_at_timer = 0
			end
		end,
		is_finished = function()
			return false
		end,
	})
	local task = vg_mobs.create_task({label="mark players as attack target"})
	vg_mobs.add_microtask_to_task(mob, mt, task)
	vg_mobs.add_task_to_task_queue(task_queue, task)
end end

function vg_mobs.create_angry_cooldown_decider(range, cooldown_time) return function(task_queue, mob)
	local mt = vg_mobs.create_microtask({
		label = "stop being angry at target when out of range for a time",
		on_start = function(self, mob)
			mob._temp_custom_state.angry_at_timer = 0
		end,
		on_step = function(self, mob, dtime)
			if mob._temp_custom_state.angry_at then
				local mobpos = mob.object:get_pos()
				local targetpos = mob._temp_custom_state.angry_at:get_pos()
				if not targetpos then
					mob._temp_custom_state.angry_at = nil
					mob._temp_custom_state.angry_at_timer = 0
					return
				end
				local dist = vector.distance(mobpos, targetpos)
				if dist > range then
					mob._temp_custom_state.angry_at_timer = mob._temp_custom_state.angry_at_timer + dtime
					if mob._temp_custom_state.angry_at_timer > cooldown_time then
						mob._temp_custom_state.angry_at = nil
						mob._temp_custom_state.angry_at_timer = 0
					end
				else
					mob._temp_custom_state.angry_at_timer = 0
				end
			end
		end,
		is_finished = function()
			return false
		end,
	})
	local task = vg_mobs.create_task({label="anger cooldown"})
	vg_mobs.add_microtask_to_task(mob, mt, task)
	vg_mobs.add_task_to_task_queue(task_queue, task)
end end

function vg_mobs.on_punch_make_hostile(mob, puncher, time_from_last_punch, tool_capabilities, dir, damage, ...)
	-- Children are never angry
	if not mob._child and puncher and puncher:is_player() then
		mob._temp_custom_state.angry_at = puncher
		mob._temp_custom_state.angry_at_timer = 0
	end
	return vg_mobs.on_punch_default(mob, puncher, time_from_last_punch, tool_capabilities, dir, damage, ...)
end
