--grab some functions from modutil
local env = {}
require("modutil")["InsertPostInitFunctions"](env, false)
env.modname = KnownModIndex:GetModActualName("New Boat Shapes")
local AddRecipe = env.AddRecipe
local GetModConfigData = env.GetModConfigData

local assets =
{
	Asset("ANIM", "anim/carrot.zip")
}

local item_assets =
{
}

local prefabs =
{
    "mast",
    "burnable_locator_medium",
    "steeringwheel",
    "rudder",
	"boat",
    "boatlip",
	"boatlip_fx",
    "boat_water_fx",
    "boat_leak",
    "fx_boat_crackle",
    "boatfragment03",
    "boatfragment04",
    "boatfragment05",
    "fx_boat_pop",
    "boat_player_collision_common",
    "boat_item_collision_common",
	"boat_hull_collision_common",
	"boat_world_collision_common",
    "walkingplank",
	"ground_support",
	"marker_c"
}

local item_prefabs = {}

local function OnRepaired(inst)    
    inst.SoundEmitter:PlaySound("turnoftides/common/together/boat/repair_with_wood")
end

local function BoatCam_IsEnabledFn()
	return Profile:IsBoatCameraEnabled()
end

local function BoatCam_ActiveFn(params, parent, best_dist_sq)
	local state = params.updater.state
    local tpos = params.target:GetPosition()
	state.last_platform_x, state.last_platform_z = tpos.x, tpos.z

	local pan_gain, heading_gain, distance_gain = TheCamera:GetGains()
	TheCamera:SetGains(1.5, heading_gain, distance_gain)
end

local function BoatCam_UpdateFn(dt, params, parent, best_dist_sq)
    local tpos = params.target:GetPosition()

	local state = params.updater.state
    local platform_x, platform_y, platform_z = tpos:Get()

    local velocity_x = dt == 0 and 0 or ((platform_x - state.last_platform_x) / dt)
	local velocity_z = dt == 0 and 0 or ((platform_z - state.last_platform_z) / dt)
    local velocity_normalized_x, velocity_normalized_z = 0, 0
    local velocity = 0        
    local min_velocity = 0.4
    local velocity_sq = velocity_x * velocity_x + velocity_z * velocity_z

    if velocity_sq >= min_velocity * min_velocity then
        velocity = math.sqrt(velocity_sq)
        velocity_normalized_x = velocity_x / velocity
        velocity_normalized_z = velocity_z / velocity
        velocity = math.max(velocity - min_velocity, 0)
    end

    local look_ahead_max_dist = 5
    local look_ahead_max_velocity = 3
    local look_ahead_percentage = math.min(math.max(velocity / look_ahead_max_velocity, 0), 1)
    local look_ahead_amount = look_ahead_max_dist * look_ahead_percentage

    --Average target_camera_offset to get rid of some of the noise.
    state.target_camera_offset.x = (state.target_camera_offset.x + velocity_normalized_x * look_ahead_amount) / 2
    state.target_camera_offset.z = (state.target_camera_offset.z + velocity_normalized_z * look_ahead_amount) / 2        

    state.last_platform_x, state.last_platform_z = platform_x, platform_z

    local camera_offset_lerp_speed = 0.25
    state.camera_offset.x, state.camera_offset.z = VecUtil_Lerp(state.camera_offset.x, state.camera_offset.z, state.target_camera_offset.x, state.target_camera_offset.z, dt * camera_offset_lerp_speed)

    TheCamera:SetOffset(state.camera_offset + (tpos - parent:GetPosition()))

    local pan_gain, heading_gain, distance_gain = TheCamera:GetGains()
    local pan_lerp_speed = 0.75
    pan_gain = Lerp(pan_gain, state.target_pan_gain, dt * pan_lerp_speed)        

    TheCamera:SetGains(pan_gain, heading_gain, distance_gain)
end

local function StartBoatCamera(inst)
	local camera_settings =
	{
		state = {
			target_camera_offset = Vector3(0,1.5,0),
			camera_offset = Vector3(0,1.5,0),
			last_platform_x = 0, last_platform_z = 0,
			target_pan_gain = 4,
		},
		UpdateFn = BoatCam_UpdateFn,
		ActiveFn = BoatCam_ActiveFn,
		IsEnabled = BoatCam_IsEnabledFn,
	}

	TheFocalPoint.components.focalpoint:StartFocusSource(inst, nil, nil, math.huge, math.huge, -1, camera_settings)
end

local function OnSpawnNewBoatLeak(inst, data)
	if data ~= nil and data.pt ~= nil then
		local leak = SpawnPrefab("boat_leak")
		leak.Transform:SetPosition(data.pt:Get())
		leak.components.boatleak.isdynamic = true
		leak.components.boatleak:SetBoat(inst)
		leak.components.boatleak:SetState(data.leak_size)

		table.insert(inst.components.hullhealth.leak_indicators_dynamic, leak)

		if inst.components.walkableplatform ~= nil then
	        for k,v in pairs(inst.components.walkableplatform:GetEntitiesOnPlatform()) do
	            if v:IsValid() then
	                v:PushEvent("on_standing_on_new_leak")
	            end
	        end
		end

		if data.playsoundfx then
			inst.SoundEmitter:PlaySoundWithParams("turnoftides/common/together/boat/damage", { intensity = 0.8 })
		end
	end
end

local function OnObjGotOnPlatform(inst, obj)    
    if obj == ThePlayer and inst.StartBoatCamera ~= nil then
		inst:StartBoatCamera()
	end
end

local function OnObjGotOffPlatform(inst, obj) 
    if obj == ThePlayer then
		TheFocalPoint.components.focalpoint:StopFocusSource(inst)
	end
end

local function RemoveConstrainedPhysicsObj(physics_obj)
    if physics_obj:IsValid() then
        physics_obj.Physics:ConstrainTo(nil)
        physics_obj:Remove()
    end
end

local function AddConstrainedPhysicsObj(boat, physics_obj)
	physics_obj:ListenForEvent("onremove", function() RemoveConstrainedPhysicsObj(physics_obj) end, boat)

	if boat:IsValid() then
		physics_obj.Transform:SetPosition(boat.Transform:GetWorldPosition())
		physics_obj.Physics:ConstrainTo(boat.entity)
	end
end

local function on_start_steering(inst)
    if ThePlayer and ThePlayer.components.playercontroller ~= nil and ThePlayer.components.playercontroller.isclientcontrollerattached then
        inst.components.reticule:CreateReticule()    
    end
end

local function on_stop_steering(inst)    
    if ThePlayer and ThePlayer.components.playercontroller ~= nil and ThePlayer.components.playercontroller.isclientcontrollerattached then
        inst.lastreticuleangle = nil
        inst.components.reticule:DestroyReticule()        
    end
end

local function ReticuleTargetFn(inst)

    local range = 7
    local pos = Vector3(inst.Transform:GetWorldPosition())   

    local dir = Vector3()
    dir.x = TheInput:GetAnalogControlValue(CONTROL_MOVE_RIGHT) - TheInput:GetAnalogControlValue(CONTROL_MOVE_LEFT)
    dir.y = 0
    dir.z = TheInput:GetAnalogControlValue(CONTROL_MOVE_UP) - TheInput:GetAnalogControlValue(CONTROL_MOVE_DOWN)
    local deadzone = .3    

    if math.abs(dir.x) >= deadzone or math.abs(dir.z) >= deadzone then    
        dir = dir:GetNormalized()             

        inst.lastreticuleangle = dir
    else
        if inst.lastreticuleangle then
            dir = inst.lastreticuleangle
        else 
            return nil
        end
    end

    local Camangle = TheCamera:GetHeading()/180        
    local theta = -PI *(0.5 - Camangle)

    local newx = dir.x * math.cos(theta) - dir.z *math.sin(theta)
    local newz = dir.x * math.sin(theta) + dir.z *math.cos(theta)

    pos.x = pos.x - (newx * range) 
    pos.z = pos.z - (newz * range) 

    return pos
end

-----Attach collision mesh----
local function AttachCollision(boat, collision, offset_x, offset_z)
	boat:ListenForEvent("onremove", function() collision:Remove() end, boat)
	if collision.Update_Mesh ~= nil then
		collision:Update_Mesh(0, true)
	end
	collision.boat = boat
	collision.offset = {offset_x, offset_z}
	collision:AddTag("ignorewalkableplatforms")
    table.insert(boat.fixed_objects, collision)
	
	local ground_support = collision.ground_support
	if ground_support then
		boat:ListenForEvent("onremove", function() ground_support:Remove() end, boat)	
		ground_support:AddTag("ignorewalkableplatforms")
	end
	collision:DoTaskInTime(0, function()
		if boat:IsValid() then
			local x, y, z = boat.Transform:GetWorldPosition()
			local offset_x_r, offset_z_r = VecUtil_RotateDir(offset_x, offset_z , boat.rotation or 0)

			if ground_support then
				ground_support.Physics:ConstrainTo(nil)
			end
			collision.Transform:SetPosition(x + offset_x_r, y, z + offset_z_r)
			if ground_support then
				ground_support.Transform:SetPosition(x + offset_x_r, y, z + offset_z_r)
				ground_support.Physics:ConstrainTo(collision.entity)
			end			
			collision.Physics:SetActive(true)
		end
	end)
end

function FinishRemovingEntity(entity)
    if entity:IsValid() then
        entity:Remove()
    end
end

function AttachEntityToBoat(inst, obj, offset_x, offset_z, parent_to_boat)
	obj:ListenForEvent("onremove", function() FinishRemovingEntity(obj) end, inst)

    if parent_to_boat then
    	obj.entity:SetParent(inst.entity)
    	obj.Transform:SetPosition(offset_x, 0, offset_z)        	
	else
		inst:DoTaskInTime(0, function(boat)
    		local boat_x, boat_y, boat_z = boat.Transform:GetWorldPosition()
			obj.Transform:SetPosition(boat_x + offset_x, boat_y, boat_z + offset_z)
		end)
	end
end

local function OnCollide_hull(inst, other, world_position_on_a_x, world_position_on_a_y, world_position_on_a_z, world_position_on_b_x, world_position_on_b_y, world_position_on_b_z, world_normal_on_b_x, world_normal_on_b_y, world_normal_on_b_z, lifetime_in_frames)
	if inst.boat ~= nil then
		inst.boat.oncollide(inst.boat, other, world_position_on_a_x, world_position_on_a_y, world_position_on_a_z, world_position_on_b_x, world_position_on_b_y, world_position_on_b_z, world_normal_on_b_x, world_normal_on_b_y, world_normal_on_b_z, lifetime_in_frames)
	end
end

local function OnCollide_world(inst, other, world_position_on_a_x, world_position_on_a_y, world_position_on_a_z, world_position_on_b_x, world_position_on_b_y, world_position_on_b_z, world_normal_on_b_x, world_normal_on_b_y, world_normal_on_b_z, lifetime_in_frames)
	--grab the boats instead of the world_collider
	if inst.boat ~= nil then
		if other.boat ~= nil then
			other = other.boat
			if inst.boat == other then
				return
			end
		end
		inst.boat.oncollide(inst.boat, other, world_position_on_a_x, world_position_on_a_y, world_position_on_a_z, world_position_on_b_x, world_position_on_b_y, world_position_on_b_z, world_normal_on_b_x, world_normal_on_b_y, world_normal_on_b_z, lifetime_in_frames)	
	end
end

local function build_boat_collision_mesh(vertices, height, angle, floor)
 	-- vertices must draw a closed polygon

	local triangles = {}
	local y0 = 0
    local y1 = height

	local idx0 = #vertices
    for idx1 = 1, #vertices do

        local x0, z0 = VecUtil_RotateDir(vertices[idx0][1], vertices[idx0][2], angle)
		local x1, z1 = VecUtil_RotateDir(vertices[idx1][1], vertices[idx1][2], angle)
    
		--vertical one    
        table.insert(triangles, x0)
        table.insert(triangles, y0)
        table.insert(triangles, z0)

        table.insert(triangles, x0)
        table.insert(triangles, y1)
        table.insert(triangles, z0)

        table.insert(triangles, x1)
        table.insert(triangles, y0)
        table.insert(triangles, z1)

        table.insert(triangles, x1)
        table.insert(triangles, y0)
        table.insert(triangles, z1)

        table.insert(triangles, x0)
        table.insert(triangles, y1)
        table.insert(triangles, z0)

        table.insert(triangles, x1)
        table.insert(triangles, y1)
        table.insert(triangles, z1)

		--horizontal one
		if floor then
			table.insert(triangles, 0)
		    table.insert(triangles, 0)
		    table.insert(triangles, 0)

		    table.insert(triangles, x0)
		    table.insert(triangles, 0)
		    table.insert(triangles, z0)

		    table.insert(triangles, x1)
		    table.insert(triangles, 0)
		    table.insert(triangles, z1)
		end

		idx0 = idx1
    end

	return triangles
end

-----Rotation-----

function rotate_vertices(vertices, angle)
	--angle in radian
	local new_vertices = {}
	for i = 1, #vertices do
		table.insert(new_vertices, {VecUtil_RotateDir(vertices[i][1], vertices[i][2], angle)})
	end
	return new_vertices
end

local function Update_Mesh(inst, angle, inactive)
	local x, y, z = inst.Transform:GetWorldPosition()
	inst.Physics:SetActive(false)

	if inst.ground_support then
		inst.ground_support.Physics:ConstrainTo(nil)
	end	

	inst.Physics:SetTriangleMesh(build_boat_collision_mesh(inst.vertices, inst.height, angle, inst.floor))

	inst.Transform:SetPosition(x, 0, z)

	if inst.ground_support then
		inst.ground_support.Transform:SetPosition(x, 0, z)
		if inactive == nil then
			inst.ground_support.Physics:ConstrainTo(inst.entity)
		end
	end
	if inactive == nil then
		inst.Physics:SetActive(true)
	end
end

local function SetRotation(inst, angle_d)
	-- angle in Degrees
	local angle = -angle_d * DEGREES

	local camangle = TheCamera:GetHeading()
	if angle ~= inst.rotation or camangle ~= inst.lip_lastcamangle then			
		for k, lip in pairs(inst.lip_fxs) do
			lip:Update(angle_d, camangle)
		end
		inst.lip_lastcamangle = camangle
	end

	if angle ~= inst.rotation then
		inst.vertices = rotate_vertices(inst.base_vertices, angle)
		if inst.itemcollision ~= nil then
			inst.itemcollision:Update_Mesh(angle)
		end 
		if inst.playercollision ~= nil then
			inst.playercollision:Update_Mesh(angle)
		end
		if inst.hullcollision ~= nil then
			inst.hullcollision:Update_Mesh(angle)
		end
		inst.rotation = angle
	end
end

----walkableplatform Util----

local function is_point_in_poly(x, z, poly)
    local num = #poly
    local i = 0
    local j = num
    local c = false
    for i = 1, num do
        if ((poly[i][2] > z) ~= (poly[j][2] > z)) and
            (x < (poly[i][1] + (poly[j][1] - poly[i][1]) * (z - poly[i][2]) /
                    (poly[j][2] - poly[i][2]))) then
            c = not c
        end
        j = i
    end
    return c
end

local function is_point_on_boat(inst, pos_x, pos_y, pos_z)
	local boat_x, boat_y, boat_z = inst.Transform:GetWorldPosition()
	return is_point_in_poly(pos_x - boat_x, pos_z - boat_z, inst.vertices)
end

-----Embarker Util-----

local function closest_point_in_segment(Px, Pz, Ax, Az, Bx, Bz)
    local ABx, ABz = Bx - Ax, Bz - Az
    local APx, APz = Px - Ax, Pz - Az
    local prod = ABx * APx + ABz * APz

    if prod <= 0 then
		return Ax, Az
	end
    local alpha = prod / (ABx * ABx + ABz * ABz)
    if alpha >= 1 then
		return Bx, Bz
	end
    return Ax + alpha * ABx, Az + alpha * ABz
end

local function closest_point_in_poly(x0, z0, offset_x, offset_z, vertices)
	local x, z = x0 - offset_x, z0 - offset_z
	if is_point_in_poly(x, z, vertices) then
		return x0, z0
	end
	local idx1 = #vertices
	local min_dist, min_x, min_z = nil, nil, nil
    for idx0 = 1, #vertices do
        local x0, z0 = vertices[idx0][1], vertices[idx0][2]
		local x1, z1 = vertices[idx1][1], vertices[idx1][2]
		local pt_x, pt_z = closest_point_in_segment(x, z, x0, z0, x1, z1)
		local dist = VecUtil_Length(x - pt_x, z - pt_z)
		if min_dist == nil or (min_dist and dist < min_dist) then
			min_dist, min_x, min_z = dist, pt_x, pt_z
		end
		idx1 = idx0
	end
	return min_x + offset_x, min_z + offset_z
end

function DistanceToBoat(inst, boat)
	local x, y, z = inst.Transform:GetWorldPosition()
	local bx, by, bz = boat.Transform:GetWorldPosition()
	local pos_x, pos_z = closest_point_in_poly(x, z, bx, bz, boat.vertices)
	return VecUtil_Length(pos_x - x, pos_z - z)
end

local function GetEmbarkPosition(inst, x, z)
	local bx, by, bz = inst.Transform:GetWorldPosition()
	local angle = -inst.Transform:GetRotation() * DEGREES
	local pos_x, pos_z = closest_point_in_poly(x, z, bx, bz, rotate_vertices(inst.vertices_embarker, angle))
	return pos_x, pos_z
end

function Calcwaveyjonesposition(x, z, boat)
	local boat_x, boat_y, boat_z = boat.Transform:GetWorldPosition()
	local vertices = rotate_vertices(boat.vertices_waveyjones, -boat.Transform:GetRotation() * DEGREES)
	local min_dist, min_x, min_z, min_angle = nil
	local idx1 = #vertices
	for idx0 = 1, #vertices do
		local x0, z0 = unpack(vertices[idx0])
		local x1, z1 = unpack(vertices[idx1])
		local mid_x, mid_z = VecUtil_Lerp(x0, z0, x1, z1, 0.5)
		local dist = VecUtil_Length(boat_x + mid_x - x, boat_z + mid_z - z)
		local angle = VecUtil_GetAngleInDegrees(x1 - x0, z1 - z0)
		if min_dist == nil or dist < min_dist then
			min_dist, min_x, min_z, min_angle = dist, mid_x, mid_z, angle
		end
		idx1 = idx0		
	end
	return boat_x + min_x, boat_z + min_z, min_angle
end

----- Vertices Util ---------

local function subdivise(vertices, min_step)
	local subdivision = {}
	local idx0 = #vertices
    for idx1 = 1, #vertices do
        local x0, z0 = vertices[idx0][1], vertices[idx0][2]
		local x1, z1 = vertices[idx1][1], vertices[idx1][2]
		local length = VecUtil_Length(x0 - x1, z0 - z1)
		local num = math.max(math.ceil(length / min_step - 1), 1)
		for k = 0, num - 1 do
			local pos_x, pos_z = VecUtil_Lerp(x0, z0, x1, z1, k / num)
			table.insert(subdivision, {pos_x, pos_z})
		end
		idx0 = idx1
	end
	return subdivision
end

local function calc_bissector_vector(xa, za, x0, z0, xb, zb)
    --bisector of angle AOB
    local OAx, OAz = xa - x0, za - z0
    local OBx, OBz = xb - x0, zb - z0
    local a1 = math.atan2(OAz, OAx)
    local a2 = math.atan2(OBz, OBx)
    local a = (a1 + a2) / 2
    if a1 > a2 then
        a = a + math.pi
    end
    local n = 1 / math.abs(math.sin((a2 - a1) / 2))
    return math.cos(a), math.sin(a), n
end

local function vertices_bissectors(vertices)
    local bissectors = {}
    for i = 1, #vertices do
        local xa, za = unpack(vertices[(i - 2) % #vertices + 1]) --because indices start at 1
        local x0, z0 = unpack(vertices[i])
        local xb, zb = unpack(vertices[i % #vertices + 1])
        table.insert(bissectors, {calc_bissector_vector(xa, za, x0, z0, xb, zb)})
    end
    return bissectors
end

local function shift_vertices(vertices, bissectors, d)
	local shifted_vertices = {}	
	for i = 1, #vertices do
		local x, z = unpack(vertices[i])
		local bx, bz, n = unpack(bissectors[i])
		table.insert(shifted_vertices, {x + d * n * bx, z + d * n * bz})
	end
	return shifted_vertices
end

----------------------------
local function CalcRadius(vertices)
	local radius = 0
	for i = 1, #vertices do
		local length = VecUtil_Length(vertices[i][1], vertices[i][2])
		if length > radius then
			radius = length
		end
	end
	return radius
end

-- from map
local DEPLOY_IGNORE_TAGS = { "NOBLOCK", "player", "FX", "INLIMBO", "DECOR", "WALKABLEPLATFORM" }
local WALKABLE_PLATFORM_TAGS = {"walkableplatform"}

local function custom_candeploy_fn(inst, pt, mouseover, deployer)
	local angle = nil
	if inst.placer ~= nil then  --for client server
		angle = inst.placer.inst.Transform:GetRotation()
	elseif inst.deploy_rotation ~= nil then --for master server
		angle = inst.deploy_rotation
	else  --for master server
		return true
	end
 
	local deploy_points = rotate_vertices(inst.vertices_placer, -angle * DEGREES)
	
	--collide with world and boat
	for k, v in pairs(deploy_points) do
		if TheWorld.Map:IsPassableAtPointWithPlatformRadiusBias(pt.x + v[1], pt.y, pt.z + v[2], false, false, 0.5) then
			return false
		end
	end
	--collide with boat, not very optimal but good enough
	for i, v in pairs(TheSim:FindEntities(pt.x, 0, pt.z, inst.deploy_radius, WALKABLE_PLATFORM_TAGS)) do
		if inst.vertices_hull ~= nil and v.hullcollision ~= nil and v.hullcollision.vertices ~= nil then
			local bx, by, bz = v.Transform:GetWorldPosition()
			--test hull overlap
			for _, w in pairs(v.hullcollision.vertices) do
				if is_point_in_poly(bx + v[1] - pt.x, bz + v[2] - pt.z, inst.vertices_hull) then return false end
			end
			for _, w in pairs(inst.vertices_hull) do
				if is_point_in_poly(pt.x + v[1] - bx, pt.z + v[2] - bz, v.hullcollision.vertices) then return false end
			end
		end	
	end

	--collide with items
	for i, v in ipairs(TheSim:FindEntities(pt.x, 0, pt.z, inst.deploy_radius, nil, DEPLOY_IGNORE_TAGS)) do
        if v ~= inst and v.entity:IsVisible() and v.components.placer == nil and v.entity:GetParent() == nil then			
			local x, y, z = v.Transform:GetWorldPosition()
			if is_point_in_poly(x - pt.x, z - pt.z, deploy_points) then
            	return false
			end
        end
    end

	return (mouseover == nil or mouseover:HasTag("player"))
end

local function marker_fn()
	local inst = CreateEntity()

    inst.entity:AddTransform()
	inst.entity:AddNetwork()

	inst.entity:AddAnimState()
    inst.AnimState:SetBank("carrot")
    inst.AnimState:SetBuild("carrot")
    inst.AnimState:PlayAnimation("planted")

	inst.entity:SetPristine()

    if not TheWorld.ismastersim then
        return inst
    end

    inst.persists = false

    return inst
end

local function post_init_placer(inst)
	local function SetBuilder(self, builder, recipe, invobject)		
		self.builder = builder
		invobject.placer = self
		self.recipe = recipe
		self.invobject = invobject
		self.inst:StartWallUpdatingComponent(self)
	end
	inst.components.placer.SetBuilder = SetBuilder
end

local function boat_player_collision_fn()
	local inst = CreateEntity()

	inst.entity:AddTransform()

	local phys = inst.entity:AddPhysics()
	phys:SetMass(0)
	phys:SetFriction(0)
	phys:SetDamping(5)
	phys:SetCollisionGroup(COLLISION.BOAT_LIMITS)
	phys:ClearCollisionMask()
	phys:CollidesWith(COLLISION.CHARACTERS)
	inst.height = 3
	inst.Update_Mesh = Update_Mesh
	phys:SetDontRemoveOnSleep(true)
	inst.Physics:SetActive(false)

	--[[inst.entity:AddAnimState()
    inst.AnimState:SetBank("carrot")
    inst.AnimState:SetBuild("carrot")
    inst.AnimState:PlayAnimation("idle")]]

	inst:AddTag("NOBLOCK")
	inst:AddTag("NOCLICK")
		
	inst.entity:SetPristine()

	if not TheWorld.ismastersim then
	    return inst
	end

	inst.persists = false

	return inst
end

local function boat_item_collision_fn()
	local inst = CreateEntity()

	inst.entity:AddTransform()
  	  
	local phys = inst.entity:AddPhysics()
	phys:SetMass(1000)
	phys:SetFriction(0)
	phys:SetDamping(5)
	phys:SetCollisionGroup(COLLISION.BOAT_LIMITS)
	phys:ClearCollisionMask()
	phys:CollidesWith(COLLISION.ITEMS)
	phys:CollidesWith(COLLISION.FLYERS)
	inst.height = 3
	inst.Update_Mesh = Update_Mesh
	phys:SetDontRemoveOnSleep(true)
	inst.Physics:SetActive(false)

	local ground_support = SpawnPrefab("ground_support")
	inst.ground_support = ground_support

	--[[inst.entity:AddAnimState()
    inst.AnimState:SetBank("carrot")
    inst.AnimState:SetBuild("carrot")
    inst.AnimState:PlayAnimation("idle")]]

	inst:AddTag("NOBLOCK")
	inst:AddTag("NOCLICK")

	inst.entity:SetPristine()

	if not TheWorld.ismastersim then
	    return inst
	end

	inst.persists = false

	return inst
end

local function boat_hull_collision_fn()
	local inst = CreateEntity()

	inst.entity:AddTransform()

	local phys = inst.entity:AddPhysics()
	phys:SetMass(500)
	phys:SetFriction(0)
	phys:SetDamping(5)
	phys:SetCollisionGroup(COLLISION.OBSTACLES)
	phys:ClearCollisionMask()
	phys:CollidesWith(COLLISION.OBSTACLES)
	phys:CollidesWith(COLLISION.WORLD)
	inst.height = 3
	inst.Update_Mesh = Update_Mesh
	phys:SetCollisionCallback(OnCollide_hull)
	phys:SetDontRemoveOnSleep(true)
	inst.Physics:SetActive(false)

	local ground_support = SpawnPrefab("ground_support")
	inst.ground_support = ground_support

	--[[inst.entity:AddAnimState()
    inst.AnimState:SetBank("carrot")
    inst.AnimState:SetBuild("carrot")
    inst.AnimState:PlayAnimation("idle")]]

	inst:AddTag("NOBLOCK") -- it's fine to build things on top of them
	inst:AddTag("NOCLICK")

	inst.entity:SetPristine()

	if not TheWorld.ismastersim then
	    return inst
	end

	inst.persists = false

	return inst
end

local function boat_world_collision_fn()
	local inst = CreateEntity()

	inst.entity:AddTransform()
	inst.entity:AddNetwork()

	local phys = inst.entity:AddPhysics()
	phys:SetMass(500)
	phys:SetFriction(0)
	phys:SetDamping(5)
	phys:SetCollisionGroup(COLLISION.BOAT_LIMITS)
	phys:ClearCollisionMask()
	phys:CollidesWith(COLLISION.LAND_OCEAN_LIMITS)
	phys:CollidesWith(COLLISION.GROUND)
	--phys:CollidesWith(COLLISION.WORLD)
	phys:CollidesWith(COLLISION.OBSTACLES)
	phys:SetCollisionCallback(OnCollide_world)
	phys:SetDontRemoveOnSleep(true)
	phys:SetActive(false)

	--[[inst.entity:AddAnimState()
    inst.AnimState:SetBank("carrot")
    inst.AnimState:SetBuild("carrot")
    inst.AnimState:PlayAnimation("planted")]]

	inst:AddTag("NOBLOCK")
	inst:AddTag("NOCLICK")

	inst.entity:SetPristine()

	if not TheWorld.ismastersim then
	    return inst
	end

	inst.persists = false

	return inst
end

local function ground_support_fn()
	local inst = CreateEntity()

	inst.entity:AddTransform()
	--inst.entity:AddNetwork()

	local phys = inst.entity:AddPhysics()
	phys:SetMass(500)
	phys:SetFriction(0)
	phys:SetDamping(5)
	phys:ClearCollisionMask()
	phys:CollidesWith(COLLISION.GROUND)
	phys:SetCylinder(2, 2)
	phys:SetDontRemoveOnSleep(true)

	--[[inst.entity:AddAnimState()
    inst.AnimState:SetBank("carrot")
    inst.AnimState:SetBuild("carrot")
    inst.AnimState:PlayAnimation("planted")]]

	inst:AddTag("NOBLOCK")
	inst:AddTag("NOCLICK")

	inst.entity:SetPristine()

	if not TheWorld.ismastersim then
	    return inst
	end

	inst.persists = false
	return inst
end

local function bridge_anchor_fn()
    local inst = CreateEntity()
    inst.entity:AddTransform()
    inst.entity:AddNetwork()

	inst:AddTag("NOBLOCK")
	inst:AddTag("NOCLICK")

    inst.entity:SetPristine()

    if not TheWorld.ismastersim then
        return inst
    end

	inst:AddComponent("boatdrag")
	inst.components.boatdrag.drag = TUNING.BOAT.ANCHOR.BASIC.ANCHOR_DRAG
	inst.components.boatdrag.max_velocity_mod = TUNING.BOAT.ANCHOR.BASIC.MAX_VELOCITY_MOD

	return inst
end

local function MakeBoat(data)

	local placer_radius = CalcRadius(data.vertices_placer)
	if placer_radius > TUNING.MAX_WALKABLE_PLATFORM_RADIUS then
		TUNING.MAX_WALKABLE_PLATFORM_RADIUS = placer_radius
	end

	local function fn()
		local inst = CreateEntity()

		inst.entity:AddTransform()
		inst.entity:AddAnimState()
		inst.entity:AddSoundEmitter()
		inst.entity:AddMiniMapEntity()
		inst.MiniMapEntity:SetIcon("boat.png")
		inst.entity:AddNetwork()

		inst:AddTag("ignorewalkableplatforms")
		inst:AddTag("antlion_sinkhole_blocker")
		inst:AddTag("boat")
		inst:AddTag("wood")

		local max_health = data.health

		local phys = inst.entity:AddPhysics()
		phys:SetMass(TUNING.BOAT.MASS)
		phys:SetFriction(0)
		phys:SetDamping(5)
		phys:SetCollisionGroup(COLLISION.OBSTACLES)
		phys:ClearCollisionMask()
		phys:CollidesWith(COLLISION.GROUND)
		phys:SetCylinder(2, 2)
		phys:SetDontRemoveOnSleep(true)

		inst.AnimState:SetBank(data.bank)
		inst.AnimState:SetBuild(data.build)
		inst.AnimState:SetSortOrder(ANIM_SORT_ORDER.OCEAN_BOAT)
		inst.AnimState:SetFinalOffset(1)
		inst.AnimState:SetOrientation(ANIM_ORIENTATION.OnGround)
		inst.AnimState:SetLayer(LAYER_BACKGROUND)

		inst:AddComponent("walkableplatform_common")
		inst.components.walkableplatform = inst.components.walkableplatform_common
		inst.components.walkableplatform.platform_radius = CalcRadius(data.vertices) + 0.01
		inst.components.walkableplatform.radius = CalcRadius(data.vertices)

		inst:AddComponent("healthsyncer")    
		inst.components.healthsyncer.max_health = max_health

		inst.fixed_objects = {}

		inst.is_point_on_boat = is_point_on_boat
		inst.vertices = data.vertices --rotated
		inst.base_vertices = data.vertices --fixed
		inst.vertices_waveyjones = data.vertices_waveyjones

		inst.GetEmbarkPosition = GetEmbarkPosition
		inst.vertices_embarker = data.vertices_embarker

		inst:AddComponent("waterphysics")
		inst.components.waterphysics.restitution = 1.75    

		inst:AddComponent("reticule")
		inst.components.reticule.targetfn = ReticuleTargetFn
		inst.components.reticule.ispassableatallpoints = true
		inst.on_start_steering = on_start_steering
		inst.on_stop_steering = on_stop_steering
		
		inst.SetRotation = SetRotation

		local itemcollision = SpawnPrefab("boat_item_collision_common")
		itemcollision.vertices = data.vertices_item
		AttachCollision(inst, itemcollision, 0, 0)
		inst.itemcollision = itemcollision

		local playercollision = SpawnPrefab("boat_player_collision_common")
		playercollision.vertices = data.vertices_player
		AttachCollision(inst, playercollision, 0, 0)
		playercollision.collisionboat = inst
		inst.playercollision = playercollision

		inst.lip_fxs = {}
		if GetModConfigData("lip") then
			local idx1 = #data.vertices_lip
			for idx0 = 1, #data.vertices_lip do
				local x0, z0 = unpack(data.vertices_lip[idx0])
				local x1, z1 = unpack(data.vertices_lip[idx1])
				local mid_x, mid_z = VecUtil_Lerp(x0, z0, x1, z1, 0.54)
				local length = VecUtil_Length(x1 - x0, z1 - z0)
				local angle = VecUtil_GetAngleInDegrees(x1 - x0, z1 - z0)

				if length > 0.6 then -- hack because SetScale bug when rotating and its too visible with small fx
					local lip = SpawnPrefab("boatlip_fx")
					AttachEntityToBoat(inst, lip, mid_x, mid_z, true)
					lip.Transform:SetScale(length/1.45, 1, 1)
					lip.Transform:SetRotation(-angle)
					lip.AnimState:SetTime(math.random(0, 20) * FRAMES)
					lip.angle = angle
					lip.boat = inst
					table.insert(inst.lip_fxs, lip)
				end
				idx1 = idx0
			end
		end

		if not TheNet:IsDedicated() then
			-- dedicated server doesnt need to handle camera settings
			inst.StartBoatCamera = StartBoatCamera
			inst:ListenForEvent("obj_got_on_platform", OnObjGotOnPlatform)
			inst:ListenForEvent("obj_got_off_platform", OnObjGotOffPlatform)

		    inst:ListenForEvent("endsteeringreticule", function(inst,data)  if ThePlayer and ThePlayer == data.player then inst:on_stop_steering() end end)
		    inst:ListenForEvent("starsteeringreticule", function(inst,data) if ThePlayer and ThePlayer == data.player then inst:on_start_steering() end end)

		    inst:AddComponent("boattrail")

		    local THRESHOLD = 0.2
		    inst:DoPeriodicTask(0.5,function()
		        local pos = Vector3(inst.Transform:GetWorldPosition())
		        if inst.oldpos then
		            local diff  = pos - inst.oldpos
		            local lengthsq = diff:LengthSq()
		            if lengthsq >= THRESHOLD and (not inst.oldspeed or inst.oldspeed < THRESHOLD) then
		                local ents = inst.components.walkableplatform:GetEntitiesOnPlatform()
		                for i,ent in ipairs(ents) do
		                    if ent == ThePlayer then
		                        ThePlayer:PushEvent("boatspedup")
		                    end
		                end
		            end
		            inst.oldspeed = lengthsq
		        end
		        inst.oldpos = pos
		    end)
		end

		inst.entity:SetPristine()

		if not TheWorld.ismastersim then
			if data.postinitprefabfn ~= nil then
				data.postinitprefabfn(inst)
			end
		    return inst
		end

		for k, v in pairs(data.vertices_world) do
			local worldcollider = SpawnPrefab("boat_world_collision_common")
			worldcollider.Physics:SetCylinder(data.world_collider_size, 3)
			worldcollider.Physics:SetActive(false)
			AttachCollision(inst, worldcollider, v[1], v[2])
		end

		local hullcollision = SpawnPrefab("boat_hull_collision_common")
		hullcollision.vertices = data.vertices_hull
		AttachCollision(inst, hullcollision, 0, 0)
		inst.hullcollision = hullcollision

		inst:AddComponent("hull")
		inst.components.hull:SetRadius(CalcRadius(data.vertices))

		if data.walkingplank ~= nil then
			local walking_plank = SpawnPrefab("walkingplank")
			inst.components.hull:AttachEntityToBoat(walking_plank, data.walkingplank[1], data.walkingplank[2], true)
			walking_plank.Transform:SetRotation(data.walkingplank[3])
			inst.components.hull:SetPlank(walking_plank)
		end

		inst.components.hull.OnDeployed = (function (self)
			if self.boat_lip ~= nil then
				self.boat_lip.AnimState:PlayAnimation("place_lip")
				self.boat_lip.AnimState:PushAnimation("lip", true)
			end
			if self.plank ~= nil then
				self.plank:Hide()
				self.plank:DoTaskInTime(1.25, function() self.plank:Show() self.plank.AnimState:PlayAnimation("plank_place") end)
			end
		end)

		inst:AddComponent("repairable")
		inst.components.repairable.repairmaterial = MATERIALS.WOOD
		inst.components.repairable.onrepaired = OnRepaired

		inst:AddComponent("hullhealth_common")
		inst.components.hullhealth = inst.components.hullhealth_common

		inst:AddComponent("boatphysics_common")
		inst.components.boatphysics = inst.components.boatphysics_common

		inst:AddComponent("health")
		inst.components.health:SetMaxHealth(max_health)
		inst.components.health.nofadeout = true
		
		inst.activefires = 0

		for k, v in pairs(data.burnable_locator) do
			local burnable_locator = SpawnPrefab('burnable_locator_medium')
			burnable_locator.boat = inst
			inst.components.hull:AttachEntityToBoat(burnable_locator, v[1], v[2], true)
		end

		inst:SetStateGraph("SGboat")
		inst.loot = data.loot

		inst:ListenForEvent("spawnnewboatleak", OnSpawnNewBoatLeak)

		inst:AddComponent("savedrotation")
		inst.components.savedrotation.OnLoad = (function(self, data)
			self.inst.Transform:SetRotation(data.rotation or 0)
			self.inst:SetRotation(data.rotation or 0)
		end)

		if data.postinitprefabfn ~= nil then
			data.postinitprefabfn(inst)
		end

		return inst
	end

	local function ondeploy(inst, pt, deployer, rot)
		local boat = SpawnPrefab(data.prefabname)
		if boat ~= nil then
		    boat.Physics:SetCollides(false)
		    boat.Physics:Teleport(pt.x, 0, pt.z)
			boat.Transform:SetRotation(rot)
			local boat_physics = boat.components.boatphysics
			if boat_physics ~= nil then
				local angle = -rot * DEGREES
				boat_physics.target_rudder_direction_x = math.cos(angle)
		   		boat_physics.rudder_direction_x = math.cos(angle)
		    	boat_physics.target_rudder_direction_z = math.sin(angle)
		    	boat_physics.rudder_direction_z = math.sin(angle)
			end
		    boat.Physics:SetCollides(true)

		    boat.sg:GoToState("place")
			
			boat.components.hull:OnDeployed()

		    inst:Remove()
		end
	end

	local function item_fn()
		local inst = CreateEntity()

		inst.entity:AddTransform()
		inst.entity:AddAnimState()
		inst.entity:AddNetwork()

		inst:AddTag("boatbuilder")
		inst:AddTag("usedeployspacingasoffset")

		MakeInventoryPhysics(inst)

		inst.AnimState:SetBank(data.itembank)
		inst.AnimState:SetBuild(data.itembuild)
		inst.AnimState:PlayAnimation("IDLE")

		MakeInventoryFloatable(inst, "med", 0.25, 0.83)

		inst._custom_candeploy_fn = custom_candeploy_fn
		inst.deploy_radius = placer_radius
		inst.vertices_placer = data.vertices_placer

		inst:AddComponent("deployable") --used to be master only
		inst.components.deployable.ondeploy = ondeploy
		inst.components.deployable.DeploySpacingRadius = function(self) return placer_radius - 0.5 end
		inst.components.deployable:SetDeployMode(DEPLOYMODE.CUSTOM)

		inst.entity:SetPristine()

		if not TheWorld.ismastersim then
		    return inst
		end

		inst:AddComponent("inspectable")
		inst:AddComponent("inventoryitem")
		if data.itemasset == nil then
			inst.components.inventoryitem.atlasname = "images/inventoryimages/"..data.item_prefabname..".xml"
		end

		inst:AddComponent("fuel")
		inst.components.fuel.fuelvalue = TUNING.LARGE_FUEL

		MakeLargeBurnable(inst)
		MakeLargePropagator(inst)
		MakeHauntableLaunch(inst)

		return inst
	end

	table.insert(item_assets, Asset("ANIM", "anim/"..data.itembuild..".zip"))

	if data.itemasset ~= nil then
		table.insert(item_assets, data.itemasset)
	else
		table.insert(item_assets, Asset("ATLAS", "images/inventoryimages/"..data.item_prefabname..".xml"))
		table.insert(item_assets, Asset("IMAGE", "images/inventoryimages/"..data.item_prefabname..".tex"))
	end

	table.insert(assets, Asset("ANIM", "anim/"..data.build..".zip"))

	return Prefab(data.prefabname, fn, assets, prefabs),
       Prefab(data.item_prefabname, item_fn, item_assets, item_prefabs),
       MakePlacer(data.item_prefabname.."_placer", data.bank, data.build, "idle_full", true,    false, false,     nil,   90 ,               nil,    post_init_placer, placer_radius)
				-- name,                     bank,      build,      anim,        onground,snap,  metersnap, scale, fixedcameraoffset, facing, postinit_fn,      offset,        onfailedplacement
end

local prefs = {
	   Prefab("ground_support", ground_support_fn),
	   Prefab("marker_c", marker_fn),	   
	   Prefab("boat_player_collision_common", boat_player_collision_fn, assets),
       Prefab("boat_item_collision_common", boat_item_collision_fn),
	   Prefab("boat_hull_collision_common", boat_hull_collision_fn),	
	   Prefab("boat_world_collision_common", boat_world_collision_fn),
	   Prefab("bridge_anchor", bridge_anchor_fn)
}

local function Complete_data(data)
	-- calc automatically all collision meshes from vertices, for silly shape compute them yourself
	local vertices = data.vertices
	local bissectors = vertices_bissectors(vertices)
	data.world_collider_size = 0.2
	data.vertices_world = subdivise(vertices, 1) -- collide with world and other boats	
	data.bissectors = vertices_bissectors(data.vertices_world) --used to shift vertices
	data.vertices_player = shift_vertices(vertices, bissectors, 0.1) -- collide with players
	data.vertices_hull = shift_vertices(vertices, bissectors, data.world_collider_size + 0.1) --collide with obstacles (seastack, etc) (0.1 with world_collider seem necessary)
	data.vertices_item = shift_vertices(vertices, bissectors, 0.2) -- collide with items
	data.vertices_placer = subdivise(shift_vertices(vertices, bissectors, (2 * data.world_collider_size + 0.1) + 0.1), 0.1) --shape to have no object inside when deploying
	data.vertices_embarker = shift_vertices(vertices, bissectors, -0.25) --where to land when embarking
	data.vertices_lip = subdivise(shift_vertices(vertices, bissectors, 0.1), 1.25) -- where to place boat_lip
	data.vertices_waveyjones = subdivise(shift_vertices(vertices, bissectors, -0.5), 0.2) --where to spawn waveyjones hand its hands.
end

local function boat() --overwrite classic boat
	local data = {}
	data.prefabname = "boat"
	data.bank = "boat_01"
	data.build = "boat_test"

	data.health = TUNING.BOAT.HEALTH
	data.vertices = { { 4, 0 }, { 3.8042260651806, 1.2360679774998 }, { 3.2360679774998, 2.3511410091699 }, { 2.3511410091699, 3.2360679774998 }, { 1.2360679774998, 3.8042260651806 }, { 2.4492935982947e-16, 4 }, { -1.2360679774998, 3.8042260651806 }, { -2.3511410091699, 3.2360679774998 }, { -3.2360679774998, 2.3511410091699 }, { -3.8042260651806, 1.2360679774998 }, { -4, 4.8985871965894e-16 }, { -3.8042260651806, -1.2360679774998 }, { -3.2360679774998, -2.3511410091699 }, { -2.3511410091699, -3.2360679774998 }, { -1.2360679774998, -3.8042260651806 }, { -7.3478807948841e-16, -4 }, { 1.2360679774998, -3.8042260651806 }, { 2.3511410091699, -3.2360679774998 }, { 3.2360679774998, -2.3511410091699 }, { 3.8042260651806, -1.2360679774998 }, { 4, -9.7971743931788e-16 } }
	data.burnable_locator = {{0, 0}, {0, 2.5}, {0, -2.5}, {2.5, 0}, {-2.5, 0}}   -- where boat takes fire {x, z}
	data.walkingplank = {0, 3.95, 0}  -- x, z, theta

	data.item_prefabname = "boat_item"
	STRINGS.NAMES[string.upper(data.item_prefabname)] = "Boat Kit"
	data.itemasset = Asset("INV_IMAGE", "boat_item")
	data.itembank = "seafarer_boat"
	data.itembuild = "seafarer_boat"
	AddRecipe(data.item_prefabname, {Ingredient("boards", 4)}, RECIPETABS.SEAFARING, TECH.SEAFARING_TWO)
	data.loot = {boards = 4}

	Complete_data(data)
	return data
end

local function rectangle()
	local data = {}
	data.prefabname = "boat_rectangle"
	data.bank = "boat_rectangle"
	data.build = "boat_rectangle"
	data.health = TUNING.BOAT.HEALTH * 1.5
	data.vertices = {{6.25, 5}, {-6.25, 5}, {-6.25, -5}, {6.25, -5}}
	data.burnable_locator = {{-3.75, -3}, {-3.75, 0}, {-3.75, 3}, {0, -3}, {0, 0}, {0, 3}, {3.75, -3}, {3.75, 0}, {3.75, 3}}
	data.walkingplank = {0, 5, 0}

	data.item_prefabname = data.prefabname.."_item"
	STRINGS.NAMES[string.upper(data.item_prefabname)] = "Rectangle Boat Kit"
	data.itembank = "rectangle"
	data.itembuild = "boat_items"
	AddRecipe(data.item_prefabname, {Ingredient("boards", 8)}, RECIPETABS.SEAFARING, TECH.SEAFARING_TWO, nil, nil, nil, nil, nil, "images/inventoryimages/"..data.item_prefabname..".xml")
	data.loot = {boards = 8}

	STRINGS.RECIPE_DESC[string.upper(data.item_prefabname)] = "A wide but slow platform."
	STRINGS.CHARACTERS.GENERIC.DESCRIBE[string.upper(data.item_prefabname)] = "Maybe I'll have enough room to build a base."

	data.postinitprefabfn = (function(inst) 
		if TheWorld.ismastersim and GetModConfigData("rectangle") ~= "classic" then
			inst.maxspeedmultiplier = 0.8
		end
	end)

	Complete_data(data)
	return data
end

local function moon()
	local data = {}
	data.prefabname = "boat_moon"
	data.bank = "boat_moon"
	data.build = "boat_moon"
	data.health = TUNING.BOAT.HEALTH * 0.75
	data.vertices = {{-3.9779005524861875, 2.7255985267034992}, {-4.622467771639042, 1.2338858195211786}, {-4.732965009208103, -0.40515653775322286}, {-4.30939226519337, -2.154696132596685}, {-3.443830570902394, -3.443830570902394}, {-2.0257826887661143, -4.511970534069982}, {-0.4604051565377532, -4.917127071823205}, {1.289134438305709, -4.8802946593001835}, {2.7808471454880297, -4.29097605893186}, {3.756906077348066, -3.4990791896869244}, {4.5488029465930016, -2.4493554327808473}, {4.383057090239411, -2.0626151012891345}, {2.85451197053407, -2.4493554327808473}, {1.3627992633517496, -1.9889502762430937}, {0.5340699815837937, -0.7918968692449356}, {0.49723756906077343, 0.47882136279926335}, {1.252302025782689, 1.8047882136279927}, {2.6519337016574585, 2.3756906077348066}, {4.346224677716391, 2.0810313075506444}, {4.5488029465930016, 2.4861878453038675}, {3.4806629834254146, 3.793738489871086}, {2.044198895027624, 4.677716390423573}, {0.34990791896869244, 4.972375690607735}, {-1.3627992633517496, 4.751381215469613}, {-2.89134438305709, 3.9779005524861875}}
	data.burnable_locator = {}
	data.walkingplank = {0, 5, 0}

	data.item_prefabname = data.prefabname.."_item"
	STRINGS.NAMES[string.upper(data.item_prefabname)] = "Moon Boat Kit"
	data.itembank = "moon"
	data.itembuild = "boat_items"
	AddRecipe(data.item_prefabname, {Ingredient("moonglass", 8), Ingredient("boards", 1)}, RECIPETABS.MOON_ALTAR, TECH.MOON_ALTAR_TWO, nil, nil, true, nil, nil, "images/inventoryimages/"..data.item_prefabname..".xml")
	data.loot = {boards = 1, moonglass = 8}

	STRINGS.RECIPE_DESC[string.upper(data.item_prefabname)] = "Sail under the protection of the Moon."
	STRINGS.CHARACTERS.GENERIC.DESCRIBE[string.upper(data.item_prefabname)] = "It radiates a strange power."

	if GetModConfigData("moon") ~= "classic" then
		data.postinitprefabfn = function(inst) inst:AddTag("NoWaveyjones") end
	end

	Complete_data(data)
	return data
end

local function bridge()
	local data = {}
	data.prefabname = "boat_bridge"
	data.bank = "boat_bridge"
	data.build = "boat_bridge"

	data.health = TUNING.BOAT.HEALTH * 0.5
	data.vertices = {{5, 0.8}, {-5, 0.8}, {-5, -0.8}, {5, -0.8}}
	data.burnable_locator = {{-3, 0}, {0, 0}, {3, 0}}
	data.walkingplank = nil

	data.item_prefabname = data.prefabname.."_item"
	STRINGS.NAMES[string.upper(data.item_prefabname)] = "Bridge Kit"
	data.itembank = "bridge"
	data.itembuild = "boat_items"
	AddRecipe(data.item_prefabname, {Ingredient("boards", 1), Ingredient("rope", 1)}, RECIPETABS.SEAFARING, TECH.SEAFARING_TWO, nil, nil, nil, nil, nil, "images/inventoryimages/"..data.item_prefabname..".xml")
	data.loot = {boards = 1, rope = 1}

	STRINGS.RECIPE_DESC[string.upper(data.item_prefabname)] = "Build bridges, not walls."
	STRINGS.CHARACTERS.GENERIC.DESCRIBE[string.upper(data.item_prefabname)] = "I will attach it to the ground for safety."

	data.postinitprefabfn = (function(inst) 
		if TheWorld.ismastersim  and GetModConfigData("bridge") ~= "classic" then
			local anchor = SpawnPrefab("bridge_anchor")
			inst.components.hull:AttachEntityToBoat(anchor, 0, 0)
			inst.components.boatphysics:AddBoatDrag(anchor)
		end
	end)

	Complete_data(data)
	return data
end

local function raft()
	local data = {}
	data.prefabname = "boat_raft"
	data.bank = "boat_raft"
	data.build = "boat_raft"

	data.health = TUNING.BOAT.HEALTH * 0.4
	data.vertices = {{1.8, 1.3}, {-1.8, 1.3}, {-1.8, -1.3}, {1.8, -1.3}}
	data.burnable_locator = {{0, 0}}
	data.walkingplank = nil

	data.item_prefabname = data.prefabname.."_item"
	STRINGS.NAMES[string.upper(data.item_prefabname)] = "Raft Kit"
	data.itembank = "raft"
	data.itembuild = "boat_items"
	AddRecipe(data.item_prefabname, {Ingredient("log", 4), Ingredient("rope", 1)}, RECIPETABS.SEAFARING, TECH.SEAFARING_TWO, nil, nil, nil, nil, nil, "images/inventoryimages/"..data.item_prefabname..".xml")
	data.loot = {log = 5, rope = 1}

	STRINGS.RECIPE_DESC[string.upper(data.item_prefabname)] = "Boat at your own risk."
	STRINGS.CHARACTERS.GENERIC.DESCRIBE[string.upper(data.item_prefabname)] = "I'm not sure I want to sail with this thing."

	Complete_data(data)
	return data
end

local function caravel()
	local data = {}
	data.prefabname = "boat_caravel"
	data.bank = "boat_caravel"
	data.build = "boat_caravel"

	data.health = TUNING.BOAT.HEALTH
	data.vertices = {{-1.5, -3.7266666666666666}, {0.013333333333333334, -3.7}, {1.5533333333333332, -3.533333333333333}, {3.066666666666667, -3.033333333333333}, {4.34, -2.3066666666666666}, {5.493333333333333, -1.28}, {6.453333333333333, 0.04}, {5.446666666666666, 1.4266666666666667}, {4.253333333333333, 2.42}, {2.9066666666666667, 3.2133333333333334}, {1.3933333333333333, 3.6333333333333333}, {-0.21333333333333335, 3.7533333333333334}, {-1.8, 3.7666666666666666}, {-3.36, 3.58}, {-4.533333333333333, 2.62}, {-4.6066666666666665, 1.0733333333333333}, {-4.62, -0.006666666666666667}, {-4.573333333333333, -1.3733333333333333}, {-4.433333333333334, -2.566666666666667}, {-3.02, -3.6133333333333333}}
	data.burnable_locator = {{-2, -2}, {-2, 2}, {2, -2}, {2, 2}, {0, 0}, {4, 0}}
	data.walkingplank = {-1, 3.85, 0}

	data.item_prefabname = data.prefabname.."_item"
	STRINGS.NAMES[string.upper(data.item_prefabname)] = "Caravel Kit"
	data.itembank = "caravel"
	data.itembuild = "boat_items"
	AddRecipe(data.item_prefabname, {Ingredient("boards", 6)}, RECIPETABS.SEAFARING, TECH.SEAFARING_TWO, nil, nil, nil, nil, nil, "images/inventoryimages/"..data.item_prefabname..".xml")
	data.loot = {boards = 6}

	STRINGS.RECIPE_DESC[string.upper(data.item_prefabname)] = "Sail the ocean with agility."
	STRINGS.CHARACTERS.GENERIC.DESCRIBE[string.upper(data.item_prefabname)] = "It seems easier to navigate, maybe I won't sink this time..."

	data.postinitprefabfn = (function(inst) 
		if TheWorld.ismastersim and GetModConfigData("caravel") ~= "classic" then
			inst.rudderturnspeedmultiplier = 1.3
		end
	end)

	Complete_data(data)
	return data
end

local boats = {   --ordered as in tab
	GetModConfigData("raft") and raft(),
	GetModConfigData("bridge") and bridge(),
	GetModConfigData("boat") and boat(),
	GetModConfigData("caravel") and caravel(),
	GetModConfigData("rectangle") and rectangle(),
	GetModConfigData("moon") and moon(),
}

for k, data in pairs(boats) do
	if data then
		local boat, item, placer = MakeBoat(data)
		table.insert(prefs, boat)
		table.insert(prefs, item)
		table.insert(prefs, placer)
	end
end

return unpack(prefs)