mirror of
				https://github.com/luanti-org/minetest_game.git
				synced 2025-11-04 09:15:29 +01:00 
			
		
		
		
	Implement drag force according to the equation: drag_force = drag_coefficient * speed ^2 Also add a small constant force to implement friction force.
		
			
				
	
	
		
			295 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			295 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
-- boats/init.lua
 | 
						|
 | 
						|
-- Load support for MT game translation.
 | 
						|
local S = minetest.get_translator("boats")
 | 
						|
 | 
						|
--
 | 
						|
-- Helper functions
 | 
						|
--
 | 
						|
 | 
						|
local function is_water(pos)
 | 
						|
	local nn = minetest.get_node(pos).name
 | 
						|
	return minetest.get_item_group(nn, "water") ~= 0
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
local function get_velocity(v, yaw, y)
 | 
						|
	local x = -math.sin(yaw) * v
 | 
						|
	local z =  math.cos(yaw) * v
 | 
						|
	return {x = x, y = y, z = z}
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
local function get_v(v)
 | 
						|
	return math.sqrt(v.x ^ 2 + v.z ^ 2)
 | 
						|
end
 | 
						|
 | 
						|
--
 | 
						|
-- Boat entity
 | 
						|
--
 | 
						|
 | 
						|
local boat = {
 | 
						|
	initial_properties = {
 | 
						|
		physical = true,
 | 
						|
		-- Warning: Do not change the position of the collisionbox top surface,
 | 
						|
		-- lowering it causes the boat to fall through the world if underwater
 | 
						|
		collisionbox = {-0.5, -0.35, -0.5, 0.5, 0.3, 0.5},
 | 
						|
		visual = "mesh",
 | 
						|
		mesh = "boats_boat.obj",
 | 
						|
		textures = {"default_wood.png"},
 | 
						|
	},
 | 
						|
 | 
						|
	driver = nil,
 | 
						|
	v = 0,
 | 
						|
	last_v = 0,
 | 
						|
	removed = false,
 | 
						|
	auto = false
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
function boat.on_rightclick(self, clicker)
 | 
						|
	if not clicker or not clicker:is_player() then
 | 
						|
		return
 | 
						|
	end
 | 
						|
	local name = clicker:get_player_name()
 | 
						|
	if self.driver and name == self.driver then
 | 
						|
		self.driver = nil
 | 
						|
		self.auto = false
 | 
						|
		clicker:set_detach()
 | 
						|
		player_api.player_attached[name] = false
 | 
						|
		player_api.set_animation(clicker, "stand" , 30)
 | 
						|
		local pos = clicker:get_pos()
 | 
						|
		pos = {x = pos.x, y = pos.y + 0.2, z = pos.z}
 | 
						|
		minetest.after(0.1, function()
 | 
						|
			clicker:set_pos(pos)
 | 
						|
		end)
 | 
						|
	elseif not self.driver then
 | 
						|
		local attach = clicker:get_attach()
 | 
						|
		if attach and attach:get_luaentity() then
 | 
						|
			local luaentity = attach:get_luaentity()
 | 
						|
			if luaentity.driver then
 | 
						|
				luaentity.driver = nil
 | 
						|
			end
 | 
						|
			clicker:set_detach()
 | 
						|
		end
 | 
						|
		self.driver = name
 | 
						|
		clicker:set_attach(self.object, "",
 | 
						|
			{x = 0.5, y = 1, z = -3}, {x = 0, y = 0, z = 0})
 | 
						|
		player_api.player_attached[name] = true
 | 
						|
		minetest.after(0.2, function()
 | 
						|
			player_api.set_animation(clicker, "sit" , 30)
 | 
						|
		end)
 | 
						|
		clicker:set_look_horizontal(self.object:get_yaw())
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
-- If driver leaves server while driving boat
 | 
						|
function boat.on_detach_child(self, child)
 | 
						|
	self.driver = nil
 | 
						|
	self.auto = false
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
function boat.on_activate(self, staticdata, dtime_s)
 | 
						|
	self.object:set_armor_groups({immortal = 1})
 | 
						|
	if staticdata then
 | 
						|
		self.v = tonumber(staticdata)
 | 
						|
	end
 | 
						|
	self.last_v = self.v
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
function boat.get_staticdata(self)
 | 
						|
	return tostring(self.v)
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
function boat.on_punch(self, puncher)
 | 
						|
	if not puncher or not puncher:is_player() or self.removed then
 | 
						|
		return
 | 
						|
	end
 | 
						|
 | 
						|
	local name = puncher:get_player_name()
 | 
						|
	if self.driver and name == self.driver then
 | 
						|
		self.driver = nil
 | 
						|
		puncher:set_detach()
 | 
						|
		player_api.player_attached[name] = false
 | 
						|
	end
 | 
						|
	if not self.driver then
 | 
						|
		self.removed = true
 | 
						|
		local inv = puncher:get_inventory()
 | 
						|
		if not (creative and creative.is_enabled_for
 | 
						|
				and creative.is_enabled_for(name))
 | 
						|
				or not inv:contains_item("main", "boats:boat") then
 | 
						|
			local leftover = inv:add_item("main", "boats:boat")
 | 
						|
			-- if no room in inventory add a replacement boat to the world
 | 
						|
			if not leftover:is_empty() then
 | 
						|
				minetest.add_item(self.object:get_pos(), leftover)
 | 
						|
			end
 | 
						|
		end
 | 
						|
		-- delay remove to ensure player is detached
 | 
						|
		minetest.after(0.1, function()
 | 
						|
			self.object:remove()
 | 
						|
		end)
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
function boat.on_step(self, dtime)
 | 
						|
	self.v = get_v(self.object:get_velocity()) * math.sign(self.v)
 | 
						|
	if self.driver then
 | 
						|
		local driver_objref = minetest.get_player_by_name(self.driver)
 | 
						|
		if driver_objref then
 | 
						|
			local ctrl = driver_objref:get_player_control()
 | 
						|
			if ctrl.up and ctrl.down then
 | 
						|
				if not self.auto then
 | 
						|
					self.auto = true
 | 
						|
					minetest.chat_send_player(self.driver, S("Boat cruise mode on"))
 | 
						|
				end
 | 
						|
			elseif ctrl.down then
 | 
						|
				self.v = self.v - dtime * 2.0
 | 
						|
				if self.auto then
 | 
						|
					self.auto = false
 | 
						|
					minetest.chat_send_player(self.driver, S("Boat cruise mode off"))
 | 
						|
				end
 | 
						|
			elseif ctrl.up or self.auto then
 | 
						|
				self.v = self.v + dtime * 2.0
 | 
						|
			end
 | 
						|
			if ctrl.left then
 | 
						|
				if self.v < -0.001 then
 | 
						|
					self.object:set_yaw(self.object:get_yaw() - dtime * 0.9)
 | 
						|
				else
 | 
						|
					self.object:set_yaw(self.object:get_yaw() + dtime * 0.9)
 | 
						|
				end
 | 
						|
			elseif ctrl.right then
 | 
						|
				if self.v < -0.001 then
 | 
						|
					self.object:set_yaw(self.object:get_yaw() + dtime * 0.9)
 | 
						|
				else
 | 
						|
					self.object:set_yaw(self.object:get_yaw() - dtime * 0.9)
 | 
						|
				end
 | 
						|
			end
 | 
						|
		end
 | 
						|
	end
 | 
						|
	local velo = self.object:get_velocity()
 | 
						|
	if self.v == 0 and velo.x == 0 and velo.y == 0 and velo.z == 0 then
 | 
						|
		self.object:set_pos(self.object:get_pos())
 | 
						|
		return
 | 
						|
	end
 | 
						|
	-- We need to preserve velocity sign to properly apply drag force
 | 
						|
	-- while moving backward
 | 
						|
	local drag = dtime * math.sign(self.v) * (0.01 + 0.0796 * self.v * self.v)
 | 
						|
	-- If drag is larger than velocity, then stop horizontal movement
 | 
						|
	if math.abs(self.v) <= math.abs(drag) then
 | 
						|
		self.v = 0
 | 
						|
	else
 | 
						|
		self.v = self.v - drag
 | 
						|
	end
 | 
						|
 | 
						|
	local p = self.object:get_pos()
 | 
						|
	p.y = p.y - 0.5
 | 
						|
	local new_velo
 | 
						|
	local new_acce = {x = 0, y = 0, z = 0}
 | 
						|
	if not is_water(p) then
 | 
						|
		local nodedef = minetest.registered_nodes[minetest.get_node(p).name]
 | 
						|
		if (not nodedef) or nodedef.walkable then
 | 
						|
			self.v = 0
 | 
						|
			new_acce = {x = 0, y = 1, z = 0}
 | 
						|
		else
 | 
						|
			new_acce = {x = 0, y = -9.8, z = 0}
 | 
						|
		end
 | 
						|
		new_velo = get_velocity(self.v, self.object:get_yaw(),
 | 
						|
			self.object:get_velocity().y)
 | 
						|
		self.object:set_pos(self.object:get_pos())
 | 
						|
	else
 | 
						|
		p.y = p.y + 1
 | 
						|
		if is_water(p) then
 | 
						|
			local y = self.object:get_velocity().y
 | 
						|
			if y >= 5 then
 | 
						|
				y = 5
 | 
						|
			elseif y < 0 then
 | 
						|
				new_acce = {x = 0, y = 20, z = 0}
 | 
						|
			else
 | 
						|
				new_acce = {x = 0, y = 5, z = 0}
 | 
						|
			end
 | 
						|
			new_velo = get_velocity(self.v, self.object:get_yaw(), y)
 | 
						|
			self.object:set_pos(self.object:get_pos())
 | 
						|
		else
 | 
						|
			new_acce = {x = 0, y = 0, z = 0}
 | 
						|
			if math.abs(self.object:get_velocity().y) < 1 then
 | 
						|
				local pos = self.object:get_pos()
 | 
						|
				pos.y = math.floor(pos.y) + 0.5
 | 
						|
				self.object:set_pos(pos)
 | 
						|
				new_velo = get_velocity(self.v, self.object:get_yaw(), 0)
 | 
						|
			else
 | 
						|
				new_velo = get_velocity(self.v, self.object:get_yaw(),
 | 
						|
					self.object:get_velocity().y)
 | 
						|
				self.object:set_pos(self.object:get_pos())
 | 
						|
			end
 | 
						|
		end
 | 
						|
	end
 | 
						|
	self.object:set_velocity(new_velo)
 | 
						|
	self.object:set_acceleration(new_acce)
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
minetest.register_entity("boats:boat", boat)
 | 
						|
 | 
						|
 | 
						|
minetest.register_craftitem("boats:boat", {
 | 
						|
	description = S("Boat"),
 | 
						|
	inventory_image = "boats_inventory.png",
 | 
						|
	wield_image = "boats_wield.png",
 | 
						|
	wield_scale = {x = 2, y = 2, z = 1},
 | 
						|
	liquids_pointable = true,
 | 
						|
	groups = {flammable = 2},
 | 
						|
 | 
						|
	on_place = function(itemstack, placer, pointed_thing)
 | 
						|
		local under = pointed_thing.under
 | 
						|
		local node = minetest.get_node(under)
 | 
						|
		local udef = minetest.registered_nodes[node.name]
 | 
						|
		if udef and udef.on_rightclick and
 | 
						|
				not (placer and placer:is_player() and
 | 
						|
				placer:get_player_control().sneak) then
 | 
						|
			return udef.on_rightclick(under, node, placer, itemstack,
 | 
						|
				pointed_thing) or itemstack
 | 
						|
		end
 | 
						|
 | 
						|
		if pointed_thing.type ~= "node" then
 | 
						|
			return itemstack
 | 
						|
		end
 | 
						|
		if not is_water(pointed_thing.under) then
 | 
						|
			return itemstack
 | 
						|
		end
 | 
						|
		pointed_thing.under.y = pointed_thing.under.y + 0.5
 | 
						|
		boat = minetest.add_entity(pointed_thing.under, "boats:boat")
 | 
						|
		if boat then
 | 
						|
			if placer then
 | 
						|
				boat:set_yaw(placer:get_look_horizontal())
 | 
						|
			end
 | 
						|
			local player_name = placer and placer:get_player_name() or ""
 | 
						|
			if not (creative and creative.is_enabled_for and
 | 
						|
					creative.is_enabled_for(player_name)) then
 | 
						|
				itemstack:take_item()
 | 
						|
			end
 | 
						|
		end
 | 
						|
		return itemstack
 | 
						|
	end,
 | 
						|
})
 | 
						|
 | 
						|
 | 
						|
minetest.register_craft({
 | 
						|
	output = "boats:boat",
 | 
						|
	recipe = {
 | 
						|
		{"",           "",           ""          },
 | 
						|
		{"group:wood", "",           "group:wood"},
 | 
						|
		{"group:wood", "group:wood", "group:wood"},
 | 
						|
	},
 | 
						|
})
 | 
						|
 | 
						|
minetest.register_craft({
 | 
						|
	type = "fuel",
 | 
						|
	recipe = "boats:boat",
 | 
						|
	burntime = 20,
 | 
						|
})
 |