cow_objects = {}

local S = core.get_translator("cow_objects")

local LASER_DAMAGE_TICK = 0.2

local AIR_MINE_DIST_ACTIVATE = 16.0
local AIR_MINE_DIST_EXPLODE = 1.75
local AIR_MINE_SPEED = 4.0

local MISSILE_DIST_EXPLODE = 1.75
local MISSILE_SPEED = 6.0
local HOMING_MISSILE_DIST_FOLLOW = 32.0
local HOMING_MISSILE_STEER_FORCE = 3.0

local MISSILE_LAUNCHER_TIMER = 4
local MISSILE_LAUNCHER_DIST_FOLLOW = 48.0

local COLLECT_RANGE_COLLECTIBLE = 1.850

local LEVEL_OBJECT_STATE_REMEMBERED = 1
local LEVEL_OBJECT_STATE_INVALIDATED = 2

local DAMAGE_TEXTURE_MODIFIER = "^[fill:16x16:#c65046"

local deactivated_level_objects = {}

-- List of all objects that are made for being spawned in levels
cow_objects.level_objects = {
	"cow_objects:missile",
	"cow_objects:homing_missile",
	"cow_objects:missile_launcher",
	"cow_objects:homing_missile_launcher",
	"cow_objects:air_mine",
	"cow_objects:collectible",
	"cow_objects:oil_barrel",
}

local explode_effect = function(pos, size)
	core.sound_play({name="cow_objects_explode", gain=1}, {pos=pos, max_hear_distance=40}, true)
	core.add_particlespawner({
		amount = 48,
		time = 0.001,
		pos = pos,
		vel = {
			min = vector.new(-size-1, -size-1, -size-1),
			max = vector.new(size+1, size+1, size+1),
		},
		drag = vector.new(2,2,2),
		texture = {
			name = "cow_objects_particle_smoke_explosion.png",
			alpha_tween = { start = 0.8, 1, 0 },
		},
		exptime = 1,
		size = { min = 1.5, max = 3.0 },
	})
end

cow_objects.deal_laser_damage = function(player, obj, dtime, impact_pos)
	local armor = obj:get_armor_groups()
	if armor.laser and armor.laser ~= 0 then
		local lua = obj:get_luaentity()
		if not lua then
			return
		end
		if not lua._laser_timer then
			lua._laser_timer = 0
		end
		lua._laser_timer = lua._laser_timer + dtime

		local time_threshold = LASER_DAMAGE_TICK * (math.abs(armor.laser)/100)
		local pos = obj:get_pos()
		core.log("verbose", "[cow_objects] Object hit by laser at:"..core.pos_to_string(pos, 1))

		if lua._laser_timer >= time_threshold then
			local damage = 1
			if armor.laser < 0 then
				damage = -1
			end
			core.log("info", "[cow_objects] Dealt "..damage.." damage to object at "..core.pos_to_string(pos, 1))
			local hp = obj:get_hp() - damage

			core.sound_play({name="cow_objects_metal_impact", gain=1}, {pos=pos}, true)
			-- spawn metal scrap particle
			core.add_particlespawner({
				amount = 12,
				time = 0.001,
				pos = {
					min = vector.offset(impact_pos or pos, -0.1, -0.1, -0.1),
					max = vector.offset(impact_pos or pos, 0.1, 0.1, 0.1),
				},
				vel = {
					min = vector.new(-1.5, -1.5, -1.5),
					max = vector.new(1.5, 1.5, 1.5),
				},
				acc = vector.new(0, -7.5, 0),
				texture = {
					name = "cow_objects_particle_metal_scrap.png",
				},
				exptime = { min = 0.25, max = 0.3 },
				size = { min = 0.8, max = 1.8 },
			})

			local score
			local ent = obj:get_luaentity()
			if ent and ent._score and ent._score ~= 0 then
				score = ent._score
			end
			obj:set_hp(hp)
			if hp <= 0 then
				if score then
					cow_game.add_score(player, ent._score, vector.offset(pos, 0, 0.5, 0))
				end
				core.log("action", "[cow_objects] Destroyed object with laser at: "..core.pos_to_string(pos, 1))
				return
			end
			lua._laser_timer = 0
		end
	end
end

local get_closest_player = function(pos, max_dist)
	local objs = core.get_objects_inside_radius(pos, max_dist)
	local closest_target
	local closest_dist = math.huge
	for o=1, #objs do
		local obj = objs[o]
		if obj:is_player() then
			local dist = vector.distance(obj:get_pos(), pos)
			if dist < closest_dist then
				closest_dist = dist
				closest_target = obj
			end
			break
		end
	end
	return closest_target, closest_dist
end

local forget_level_object = function(self)
	local guid = self.object:get_guid()
	if deactivated_level_objects[guid] == LEVEL_OBJECT_STATE_INVALIDATED then
		self.object:remove()
		core.log("verbose", "[cow_objects] Invalidated level object removed: "..guid)
		return true
	elseif deactivated_level_objects[guid] == LEVEL_OBJECT_STATE_REMEMBERED then
		deactivated_level_objects[guid] = nil
		core.log("verbose", "[cow_objects] Level object forgotten: "..guid)
	end
	return false
end
local remember_level_object = function(self, removal)
	local guid = self.object:get_guid()
	if not removal then
		core.log("verbose", "[cow_objects] Level object remembered: "..guid)
		deactivated_level_objects[guid] = LEVEL_OBJECT_STATE_REMEMBERED
	else
		core.log("verbose", "[cow_objects] Level object removed: "..guid)
		deactivated_level_objects[guid] = nil
	end
end

-- Invisible entity to hold the player in place
-- at the start of the game.
-- To use it, attach the player to this entity.
core.register_entity("cow_objects:player_holder", {
	initial_properties = {
		physical = false,
		collide_with_objects = false,
		pointable = false,
		static_save = false,
		is_visible = true,
		textures = { "blank.png", "blank.png" },
	},
	on_activate = function(self)
		self.object:set_armor_groups({immortal=1})
		core.log("info", "[cow_objects] Player holder spawned @ "..core.pos_to_string(self.object:get_pos()))
	end,
	on_deactivate = function(self)
		core.log("info", "[cow_objects] Player holder despawned @ "..core.pos_to_string(self.object:get_pos()))
	end,
})

-- An object that looks identical to the player
-- but doesn't do anything
core.register_entity("cow_objects:player_dummy", {
	initial_properties = {
		physical = false,
		collide_with_objects = false,
		pointable = false,
		static_save = false,

		collisionbox = { -0.9, 0, -0.9, 0.9, 1.6, 0.9 } ,
		selectionbox = { -0.9, 0, -0.9, 0.9, 1.6, 0.9 } ,
		visual_size = { x = 10, y = 10 },
		visual = "mesh",
		mesh = "cow_player_cow.glb",
		textures = {"cow_player_cow.png"},
		backface_culling = true,
		use_texture_alpha = false,
		is_visible = true,
	},
	on_activate = function(self)
		self.object:set_armor_groups({immortal=1})
	end,
})

-- Oil barrel
core.register_entity("cow_objects:oil_barrel", {
	initial_properties = {
		visual = "cube",
		textures = {
			"cow_objects_oil_barrel_top.png",
			"cow_objects_oil_barrel_bottom.png",
			"cow_objects_oil_barrel.png",
			"cow_objects_oil_barrel.png",
			"cow_objects_oil_barrel.png",
			"cow_objects_oil_barrel.png",
		},
		damage_texture_modifier = DAMAGE_TEXTURE_MODIFIER,
		collisionbox = { -4/16, -0.5, -4/16, 4/16, 0.5, 4/16 },
		selectionbox = { -4/16, -0.5, -4/16, 4/16, 0.5, 4/16 },
		visual_size = { x = 10/16, y = 1, z = 10/16 },
		physical = true,
		collide_with_objects = true,
		hp_max = 1,
	},
	_score = 25,
	on_activate = function(self)
		if forget_level_object(self) then
			return
		end
		self.object:set_armor_groups({laser=100})
	end,
	on_deactivate = remember_level_object,
	on_death = function(self)
		core.log("action", "[cow_objects] Oil barrel at "..core.pos_to_string(self.object:get_pos(), 1).." explodes")
		explode_effect(self.object:get_pos(), 4)
	end,
})


-- Air mine
core.register_entity("cow_objects:air_mine", {
	initial_properties = {
		visual = "cube",
		textures = {
			"cow_objects_air_mine.png",
			"cow_objects_air_mine.png",
			"cow_objects_air_mine.png",
			"cow_objects_air_mine.png",
			"cow_objects_air_mine.png",
			"cow_objects_air_mine.png",
		},
		damage_texture_modifier = DAMAGE_TEXTURE_MODIFIER,
		collisionbox = { -6/16, -6/16, -6/16, 6/16, 6/16, 6/16 },
		selectionbox = { -6/16, -6/16, -6/16, 6/16, 6/16, 6/16 },
		visual_size = { x = 14/16, y = 14/16, z = 14/16 },
		physical = false,
		collide_with_objects = false,
		hp_max = 1,
		automatic_rotate = 1,
	},
	-- if true, will follow closest player.
	-- if false, will not follow.
	_follow = true,
	_score = 250,
	on_activate = function(self)
		if forget_level_object(self) then
			return
		end
		self.object:set_armor_groups({laser=100})
	end,
	on_deactivate = remember_level_object,
	on_step = function(self)
		if cow_game.get_state() ~= cow_game.STATE_PLAY then
			return
		end

		local pos = self.object:get_pos()
		local objs = core.get_objects_inside_radius(pos, AIR_MINE_DIST_ACTIVATE)
		local target, dist = get_closest_player(pos, AIR_MINE_DIST_ACTIVATE)
		if not target then
			self.object:set_velocity(vector.zero())
		else
			if dist < AIR_MINE_DIST_EXPLODE then
				if target:is_player() then
					cow_game.die_if_unprotected(target)
				end
				self.object:set_hp(0)
				return
			end
			if self._follow then
				local tpos = vector.add(target:get_pos(), vector.new(0, 1, 0))
				local dir = vector.direction(pos, tpos)
				local vel = vector.multiply(dir, AIR_MINE_SPEED)
				self.object:set_velocity(vel)
			end
		end
	end,
	on_death = function(self)
		core.log("action", "[cow_objects] Air mine at "..core.pos_to_string(self.object:get_pos(), 1).." explodes")
		explode_effect(self.object:get_pos(), 4)
	end,
})

local function spawn_missile_smoke(missile)
	return core.add_particlespawner({
		amount = 24,
		time = 0,
		pos = {
			min = vector.new(-0.1, -0.1, -0.45),
			max = vector.new(0.1, 0.1, -0.45),
		},
		vel = {
			min = vector.new(-0.025, 0, 0.025),
			max = vector.new(0.025, 0.1, 0.025),
		},
		texture = {
			name = "cow_objects_particle_smoke_missile.png",
			alpha_tween = { start = 0.8, 1, 0 },
		},
		exptime = { min = 2.8, max = 4.4 },
		size = { min = 1.5, max = 2.0 },
		attached = missile,
	})
end

-- Missile. Explodes when it collides with something or near player.
-- When spawning this, an initial speed should be set.
core.register_entity("cow_objects:missile", {
	initial_properties = {
		visual = "cube",
		textures = {
			"cow_objects_missile.png",
			"cow_objects_missile.png^[transformR180",
			"cow_objects_missile.png^[transformR270",
			"cow_objects_missile.png^[transformR90",
			"cow_objects_missile_front.png",
			"cow_objects_missile_back.png",
		},
		damage_texture_modifier = DAMAGE_TEXTURE_MODIFIER,
		visual_size = { x = 0.4, y = 0.4, z = 1 },
		collisionbox = { -0.5, -0.25, -0.5, 0.5, 0.25, 0.5 },
		selectionbox = { -0.5, -0.25, -0.5, 0.5, 0.25, 0.5, rotate = false },
		physical = true,
		collide_with_objects = false,
		hp_max = 1,
	},
	_score = 25,
	_particlespawner = nil,
	on_activate = function(self, staticdata, dtime_s)
		if forget_level_object(self) then
			return
		end
		self.object:set_armor_groups({laser=100})
		if dtime_s == 0 then
			local vel = vector.new(0,MISSILE_SPEED,0)
			self.object:set_velocity(vel)

			local dir = vector.normalize(vel)
			local rot = vector.dir_to_rotation(dir)
			self.object:set_rotation(rot)
		end

		local spawner = spawn_missile_smoke(self.object)
		if spawner ~= -1 then
			self._particlespawner = spawner
		end
	end,
	on_deactivate = function(self, removal)
		if self._particlespawner then
			core.delete_particlespawner(self._particlespawner)
		end
		remember_level_object(self, removal)
	end,
	on_step = function(self, dtime, moveresult)
		if cow_game.get_state() ~= cow_game.STATE_PLAY then
			return
		end

		local pos = self.object:get_pos()
		if moveresult and moveresult.collides then
			for c=1, #moveresult.collisions do
				local collision = moveresult.collisions[c]
				if collision.type == "object" and collision.object:is_player() then
					cow_game.die_if_unprotected(collision.object)
				end
			end
			self.object:set_hp(0)
			return
		end
		local target, dist = get_closest_player(pos, MISSILE_DIST_EXPLODE)
		if target then
			explode_effect(pos, 4)
			if target:is_player() then
				cow_game.die_if_unprotected(target)
			end
			self.object:set_hp(0)
			return
		end
	end,
	on_death = function(self)
		core.log("action", "[cow_objects] Missile at "..core.pos_to_string(self.object:get_pos(), 1).." explodes")
		explode_effect(self.object:get_pos(), 4)
	end,
})

-- Homing missile. Follows closest player.
-- Explodes when it collides with something or near player.
-- When spawning this, an initial speed should be set.
core.register_entity("cow_objects:homing_missile", {
	initial_properties = {
		visual = "cube",
		textures = {
			"cow_objects_homing_missile.png",
			"cow_objects_homing_missile.png^[transformR180",
			"cow_objects_homing_missile.png^[transformR270",
			"cow_objects_homing_missile.png^[transformR90",
			"cow_objects_homing_missile_front.png",
			"cow_objects_homing_missile_back.png",
		},
		damage_texture_modifier = DAMAGE_TEXTURE_MODIFIER,
		visual_size = { x = 0.4, y = 0.4, z = 1 },
		collisionbox = { -0.5, -0.25, -0.5, 0.5, 0.25, 0.5 },
		selectionbox = { -0.5, -0.25, -0.5, 0.5, 0.25, 0.5, rotate = false },
		physical = true,
		collide_with_objects = false,
		hp_max = 1,
	},
	_score = 50,
	_particlespawner = nil,
	on_activate = function(self)
		if forget_level_object(self) then
			return
		end
		self.object:set_armor_groups({laser=100})

		local vel = vector.new(0, MISSILE_SPEED, 0)
		self.object:set_velocity(vel)

		local dir = vector.normalize(vel)
		local rot = vector.dir_to_rotation(dir)
		self.object:set_rotation(rot)

		local spawner = spawn_missile_smoke(self.object)
		if spawner ~= -1 then
			self._particlespawner = spawner
		end
	end,
	on_deactivate = function(self, removal)
		if self._particlespawner then
			core.delete_particlespawner(self._particlespawner)
		end
		remember_level_object(self, removal)
	end,
	on_step = function(self, dtime, moveresult)
		local pos = self.object:get_pos()
		if moveresult and moveresult.collides then
			if cow_game.get_state() == cow_game.STATE_PLAY then
				for c=1, #moveresult.collisions do
					local collision = moveresult.collisions[c]
					if collision.type == "object" and collision.object:is_player() then
						cow_game.die_if_unprotected(collision.object)
					end
				end
			end
			self.object:set_hp(0)
			return
		end
		local vel
		if cow_game.get_state() == cow_game.STATE_PLAY then
			local target, dist = get_closest_player(pos, HOMING_MISSILE_DIST_FOLLOW)
			if target and dist <= MISSILE_DIST_EXPLODE then
				if target:is_player() then
					cow_game.die_if_unprotected(target)
				end
				self.object:set_hp(0)
				return
			end
			if target then
				local delta = dtime
				vel = self.object:get_velocity()
				local target_pos = target:get_pos()
				local wanted_vel = vector.multiply(vector.direction(pos, target_pos), MISSILE_SPEED)
				local steer = vector.multiply(vector.normalize(vector.subtract(wanted_vel, vel)), HOMING_MISSILE_STEER_FORCE)
				vel = vector.add(vel, vector.multiply(steer, 3*delta))
				self.object:set_velocity(vel)
			end
		end
		if not vel then
			vel = self.object:get_velocity()
		end

		local rot = vector.dir_to_rotation(vector.normalize(vel))
		self.object:set_rotation(rot)
	end,
	on_death = function(self)
		core.log("action", "[cow_objects] Homing missile at "..core.pos_to_string(self.object:get_pos(), 1).." explodes")
		explode_effect(self.object:get_pos(), 4)
	end,
})

local launchers = {
	-- launcher ID, missile ID, name for logfile, health, score
	{ "missile_launcher", "missile", "Missile launcher", 3, 1500 },
	{ "homing_missile_launcher", "homing_missile", "Homing missile launcher", 4, 2000 },
}

-- Missile launchers. Spawn missiles.
for l=1, #launchers do
	local launcher_id = launchers[l][1]
	local missile_id = "cow_objects:"..launchers[l][2]
	local logname = launchers[l][3]
	local health = launchers[l][4]
	local score = launchers[l][5]
	core.register_entity("cow_objects:"..launcher_id, {
		initial_properties = {
			visual = "cube",
			textures = {
				"cow_objects_"..launcher_id.."_sides.png",
				"cow_objects_"..launcher_id.."_sides.png",
				"cow_objects_"..launcher_id.."_sides.png",
				"cow_objects_"..launcher_id.."_sides.png",
				"cow_objects_"..launcher_id.."_front.png",
				"cow_objects_"..launcher_id.."_sides.png",
			},
			damage_texture_modifier = DAMAGE_TEXTURE_MODIFIER,
			visual_size = { x = 1, y = 1, z = 1 },
			collisionbox = { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 },
			selectionbox = { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5, rotate = false },
			physical = false,
			collide_with_objects = false,
			hp_max = health,
		},
		_score = score,
		_active = true,
		_launcher_timer = 0,
		on_activate = function(self)
			self._launcher_timer = math.random(-MISSILE_LAUNCHER_TIMER*10, 0) / 10
			if forget_level_object(self) then
				return
			end
			self.object:set_armor_groups({laser=100})
		end,
		on_deactivate = remember_level_object,
		on_step = function(self, dtime)
			if cow_game.get_state() ~= cow_game.STATE_PLAY then
				return
			end

			if not self._active then
				return
			end
			local pos = self.object:get_pos()
			local target, dist = get_closest_player(pos, MISSILE_LAUNCHER_DIST_FOLLOW)
			if not target then
				return
			end
			local target_pos = target:get_pos()
			target_pos.y = target_pos.y + 1

			local dir = vector.direction(pos, target_pos)
			dir.y = 0
			local yaw = core.dir_to_yaw(dir)
			self.object:set_yaw(yaw)

			self._launcher_timer = self._launcher_timer + dtime
			if self._launcher_timer < MISSILE_LAUNCHER_TIMER then
				return
			end
			self._launcher_timer = 0
			local pos = self.object:get_pos()
			local obj = core.add_entity(pos, missile_id)
			core.sound_play({name="cow_objects_launch_missile", gain=0.75}, {pos=pos, max_hear_distance=50}, true)

			local rdir = vector.direction(pos, target_pos)
			local rrot = vector.dir_to_rotation(rdir)
			local rvel = table.copy(rdir)
			rvel = vector.multiply(rvel, MISSILE_SPEED)
			obj:set_velocity(rvel)
			obj:set_rotation(rrot)
		end,
		on_death = function(self)
			core.log("action", "[cow_objects] "..logname.." launcher at "..core.pos_to_string(self.object:get_pos(), 1).." explodes")
			explode_effect(self.object:get_pos(), 4)
		end,
	})
end

core.register_entity("cow_objects:collectible", {
	initial_properties = {
		visual = "mesh",
		visual_size = { x = 10, y = 10, z = 10 },
		automatic_rotate = -3,
		mesh = "cow_objects_star.glb",
		textures = { "cow_objects_star.png" },
		collisionbox = { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 },
		selectionbox = { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5, rotate = false },
		physical = false,
		collide_with_objects = false,
		hp_max = 1,
		pointable = false,
	},
	on_activate = forget_level_object,
	on_deactivate = remember_level_object,
	_type = "star",
	_score = 5000,
	_set_type = function(self, ctype)
		if ctype == "star" then
			self._type = ctype
			self.object:set_properties({
				mesh = "cow_objects_star.glb",
				textures = { "cow_objects_star.png" },
			})
			return true
		else
			core.log("error", "[cow_objects] Tried to set collectible object to unknown type: "..tostring(ctype))
			return false
		end
	end,
})


local collect_collectible = function(player, entity)
	local pos = entity.object:get_pos()
	core.log("action", "[cow_objects] Collectible at "..core.pos_to_string(pos, 1).." was collected")

	if cow_game.get_state() == cow_game.STATE_PLAY then
		if entity._score > 0 then
			cow_game.add_score(player, entity._score, vector.offset(pos, 0, 0.5, 0))
		end
		if entity._type == "star" then
			core.sound_play({name="cow_objects_collect_star", gain=1}, {pos=pos}, true)
			core.add_particlespawner({
				amount = 24,
				time = 0.001,
				pos = pos,
				vel = {
					min = vector.new(-5,-5,-5),
					max = vector.new(5,5,5),
				},
				drag = vector.new(2,2,2),
				texture = {
					name = "cow_objects_particle_star.png",
					alpha_tween = { start = 0.8, 1, 0 },
				},
				exptime = 1,
				size = 1.5,
			})
		end
	end

	entity.object:remove()
end

cow_objects.remove_all_level_objects = function()
	for id, ent in pairs(core.luaentities) do
		for n=1, #cow_objects.level_objects do
			if ent.name == cow_objects.level_objects[n] then
				ent.object:remove()
				break
			end
		end
	end
	for guid, state in pairs(deactivated_level_objects) do
		if state == LEVEL_OBJECT_STATE_REMEMBERED then
			deactivated_level_objects[guid] = LEVEL_OBJECT_STATE_INVALIDATED
		end
	end
	core.log("action", "[cow_objects] All level objects removed")
end

cow_objects.register_spawner = function(subname, spawns, desc, img)
	core.register_node("cow_objects:spawner_"..subname, {
		description = desc,
		drawtype = "allfaces",
		paramtype = "light",
		sunlight_propagates = true,
		visual_scale = 14/16,
		selection_box = {
			type = "fixed",
			fixed = { -7/16, -7/16, -7/16, 7/16, 7/16, 7/16 },
		},
		tiles = { img or "cow_objects_spawner.png" },
		walkable = false,
		groups = { spawner = 1 },
		_cow_spawns = spawns,
	})
end

cow_objects.register_spawner("cow_objects_missile_launcher", "cow_objects:missile_launcher", S("Missile Launcher Spawner"), "cow_objects_spawner_missile_launcher.png")
cow_objects.register_spawner("cow_objects_homing_missile_launcher", "cow_objects:homing_missile_launcher", S("Homing Missile Launcher Spawner"), "cow_objects_spawner_homing_missile_launcher.png")
cow_objects.register_spawner("cow_objects_air_mine", "cow_objects:air_mine", S("Air Mine Spawner"), "cow_objects_spawner_air_mine.png")
cow_objects.register_spawner("cow_objects_collectible", "cow_objects:collectible", S("Collectible Spawner"), "cow_objects_spawner_collectible.png")
cow_objects.register_spawner("cow_objects_oil_barrel", "cow_objects:oil_barrel", S("Oil Barrel Spawner"), "cow_objects_spawner_oil_barrel.png")

--[[ ----- START BORROWED CODE ----
-- from Glitch/mods/glitch_entities/level.lua ]]

local disable_far_collect_until = {}

cow_objects.disable_far_collect_until = function(player, ustime)
	local name = player:get_player_name()
	disable_far_collect_until[name] = ustime
end

local get_disable_far_collect_until = function(player)
	local name = player:get_player_name()
	return disable_far_collect_until[name]
end

local last_player_pos = {}
-- Number of positions to check between two position (including
-- the two). Part of "far-collect" to make electron detection much
-- more precise. A higher number increases precision but might
-- reduce performance.
local COLLECT_CHECK_INTERMEDIATE_STEPS = 11


-- Returns a list of `steps` positions on a straight
-- line between pos1 and pos2 (including the two).
-- `steps` must be at least 2.
local get_intermediate_positions = function(pos1, pos2, steps)
	if steps < 2 then
		return nil
	end
	local posses = { pos1 }
	local sx, sy, sz, ex, ey, ez
	sx = math.min(pos1.x, pos2.x)
	sy = math.min(pos1.y, pos2.y)
	sz = math.min(pos1.z, pos2.z)
	ex = math.max(pos1.x, pos2.x)
	ey = math.max(pos1.y, pos2.y)
	ez = math.max(pos1.z, pos2.z)
	local xup, yup, zup
	xup = pos1.x < pos2.x
	yup = pos1.y < pos2.y
	zup = pos1.z < pos2.z
	local x,y,z
	steps = steps - 1
	for s=1, steps-1 do
		if xup then
			x = sx + (ex - sx) * (s/steps)
		else
			x = sx + (ex - sx) * ((steps-s)/steps)
		end
		if yup then
			y = sy + (ey - sy) * (s/steps)
		else
			y = sy + (ey - sy) * ((steps-s)/steps)
		end
		if zup then
			z = sz + (ez - sz) * (s/steps)
		else
			z = sz + (ez - sz) * ((steps-s)/steps)
		end
		table.insert(posses, vector.new(x,y,z))
	end
	table.insert(posses, pos2)
	return posses
end

-- Returns true if player will use the extended collect check,
-- (with multiple position checks).
-- Return false if player can only perform a simple collect check
-- (only checks current position).
local can_collect_far = function(player)
	local meta = player:get_meta()
	local now = core.get_us_time()
	local wait_until = get_disable_far_collect_until(player)
	if wait_until == nil then
		return true
	end
	return now > wait_until
end

-- Helper function to collect a collectible object if in range
local scan_and_collect = function(player_pos, object_pos, collect_range, collect_function, player, luaentity)
	local dist = vector.distance(player_pos, object_pos)
	if (dist <= collect_range) and (object_pos.y > player_pos.y - 0.49) then
		collect_function(player, luaentity)
	end
end

core.register_globalstep(function(dtime)
	if cow_game.get_state() ~= cow_game.STATE_PLAY then
		return
	end
	local players = core.get_connected_players()
	local max_collect_range = COLLECT_RANGE_COLLECTIBLE
	for p=1, #players do
		local player = players[p]
		local name = player:get_player_name()
		local last_pos = last_player_pos[name] -- pos of the previous globalstep
		local ppos = player:get_pos()
		if not last_pos then
			last_pos = ppos
		end

		local check_posses
		--[[ If player did not move or the far-collect is disabled
		(which happens if the player has freshly teleported),
		the player will check multiple positions on a line between
		current pos and last_pos to greatly increase the chance that
		a fast-moving player will still collect all objects.
		This is neccessary because a fast-moving player might sometimes not return
		a position close to an object, thus failing to collect it although
		the player clearly has passed the position.
		We only check a straight line and not accurately simulate the actual path
		between the two positions which might not be the exact player movement,
		but this inaccuracy might not matter since the time between globalsteps
		is very short.

		"far-collect" refers to the multiple position checks.
		]]
		if vector.distance(ppos, last_pos) < 0.2 or (not can_collect_far(player)) then
			-- simple collect:
			-- We only check the check the current player pos.
			check_posses = { ppos }
		else
			-- far-collect:
			-- We check multiple positions (explained above).
			check_posses = get_intermediate_positions(last_pos, ppos, COLLECT_CHECK_INTERMEDIATE_STEPS)
		end

		for c=1, #check_posses do
			local cpos = check_posses[c]
			local objs = core.get_objects_in_area(vector.subtract(cpos, max_collect_range), vector.add(cpos, max_collect_range))
			for o=1, #objs do
				local obj = objs[o]
				local lua = obj:get_luaentity()
				if lua then
					local dist
					local opos = obj:get_pos()
					if lua.name == "cow_objects:collectible" then
						scan_and_collect(cpos, opos, COLLECT_RANGE_COLLECTIBLE, collect_collectible, player, lua)
					end
				end
			end
		end
		-- Remember pos for the next globalstep
		last_player_pos[name] = ppos
	end
end)

--[[ ----- END BORROWED CODE ----- ]]

