1
2--
3-- Helper functions
4--
5
6local function is_water(pos)
7	local nn = minetest.get_node(pos).name
8	return minetest.get_item_group(nn, "water") ~= 0
9end
10
11local function get_sign(i)
12	if i == 0 then
13		return 0
14	else
15		return i / math.abs(i)
16	end
17end
18
19local function get_velocity(v, yaw, y)
20	local x = -math.sin(yaw) * v
21	local z =  math.cos(yaw) * v
22	return {x = x, y = y, z = z}
23end
24
25local function get_v(v)
26	return math.sqrt(v.x ^ 2 + v.z ^ 2)
27end
28
29--
30-- Boat entity
31--
32
33local boat = {
34	physical = true,
35	collisionbox = {-0.5, -0.4, -0.5, 0.5, 0.3, 0.5},
36	visual = "mesh",
37	mesh = "boat.x",
38	textures = {"default_wood.png"},
39
40	driver = nil,
41	v = 0,
42	last_v = 0,
43	removed = false
44}
45
46function boat.on_rightclick(self, clicker)
47	if not clicker or not clicker:is_player() then
48		return
49	end
50	local name = clicker:get_player_name()
51	if self.driver and clicker == self.driver then
52		self.driver = nil
53		clicker:set_detach()
54		default.player_attached[name] = false
55		default.player_set_animation(clicker, "stand" , 30)
56	elseif not self.driver then
57		self.driver = clicker
58		clicker:set_attach(self.object, "", {x = 0, y = 11, z = -3}, {x = 0, y = 0, z = 0})
59		default.player_attached[name] = true
60		minetest.after(0.2, function()
61			default.player_set_animation(clicker, "sit" , 30)
62		end)
63		self.object:setyaw(clicker:get_look_yaw() - math.pi / 2)
64	end
65end
66
67function boat.on_activate(self, staticdata, dtime_s)
68	self.object:set_armor_groups({immortal = 1})
69	if staticdata then
70		self.v = tonumber(staticdata)
71	end
72	self.last_v = self.v
73end
74
75function boat.get_staticdata(self)
76	return tostring(self.v)
77end
78
79function boat.on_punch(self, puncher, time_from_last_punch, tool_capabilities, direction)
80	if not puncher or not puncher:is_player() or self.removed then
81		return
82	end
83	if self.driver and puncher == self.driver then
84		self.driver = nil
85		puncher:set_detach()
86		default.player_attached[puncher:get_player_name()] = false
87	end
88	if not self.driver then
89		self.removed = true
90		-- delay remove to ensure player is detached
91		minetest.after(0.1, function()
92			self.object:remove()
93		end)
94		if not minetest.setting_getbool("creative_mode") then
95			puncher:get_inventory():add_item("main", "boats:boat")
96		end
97	end
98end
99
100function boat.on_step(self, dtime)
101	self.v = get_v(self.object:getvelocity()) * get_sign(self.v)
102	if self.driver then
103		local ctrl = self.driver:get_player_control()
104		local yaw = self.object:getyaw()
105		if ctrl.up then
106			self.v = self.v + 0.1
107		elseif ctrl.down then
108			self.v = self.v - 0.1
109		end
110		if ctrl.left then
111			if self.v < 0 then
112				self.object:setyaw(yaw - (1 + dtime) * 0.03)
113			else
114				self.object:setyaw(yaw + (1 + dtime) * 0.03)
115			end
116		elseif ctrl.right then
117			if self.v < 0 then
118				self.object:setyaw(yaw + (1 + dtime) * 0.03)
119			else
120				self.object:setyaw(yaw - (1 + dtime) * 0.03)
121			end
122		end
123	end
124	local velo = self.object:getvelocity()
125	if self.v == 0 and velo.x == 0 and velo.y == 0 and velo.z == 0 then
126		self.object:setpos(self.object:getpos())
127		return
128	end
129	local s = get_sign(self.v)
130	self.v = self.v - 0.02 * s
131	if s ~= get_sign(self.v) then
132		self.object:setvelocity({x = 0, y = 0, z = 0})
133		self.v = 0
134		return
135	end
136	if math.abs(self.v) > 4.5 then
137		self.v = 4.5 * get_sign(self.v)
138	end
139
140	local p = self.object:getpos()
141	p.y = p.y - 0.5
142	local new_velo = {x = 0, y = 0, z = 0}
143	local new_acce = {x = 0, y = 0, z = 0}
144	if not is_water(p) then
145		local nodedef = minetest.registered_nodes[minetest.get_node(p).name]
146		if (not nodedef) or nodedef.walkable then
147			self.v = 0
148			new_acce = {x = 0, y = 1, z = 0}
149		else
150			new_acce = {x = 0, y = -9.8, z = 0}
151		end
152		new_velo = get_velocity(self.v, self.object:getyaw(), self.object:getvelocity().y)
153		self.object:setpos(self.object:getpos())
154	else
155		p.y = p.y + 1
156		if is_water(p) then
157			local y = self.object:getvelocity().y
158			if y >= 4.5 then
159				y = 4.5
160			elseif y < 0 then
161				new_acce = {x = 0, y = 20, z = 0}
162			else
163				new_acce = {x = 0, y = 5, z = 0}
164			end
165			new_velo = get_velocity(self.v, self.object:getyaw(), y)
166			self.object:setpos(self.object:getpos())
167		else
168			new_acce = {x = 0, y = 0, z = 0}
169			if math.abs(self.object:getvelocity().y) < 1 then
170				local pos = self.object:getpos()
171				pos.y = math.floor(pos.y) + 0.5
172				self.object:setpos(pos)
173				new_velo = get_velocity(self.v, self.object:getyaw(), 0)
174			else
175				new_velo = get_velocity(self.v, self.object:getyaw(), self.object:getvelocity().y)
176				self.object:setpos(self.object:getpos())
177			end
178		end
179	end
180	self.object:setvelocity(new_velo)
181	self.object:setacceleration(new_acce)
182end
183
184minetest.register_entity("boats:boat", boat)
185
186minetest.register_craftitem("boats:boat", {
187	description = "Boat",
188	inventory_image = "boat_inventory.png",
189	wield_image = "boat_wield.png",
190	wield_scale = {x = 2, y = 2, z = 1},
191	liquids_pointable = true,
192
193	on_place = function(itemstack, placer, pointed_thing)
194		if pointed_thing.type ~= "node" then
195			return
196		end
197		if not is_water(pointed_thing.under) then
198			return
199		end
200		pointed_thing.under.y = pointed_thing.under.y + 0.5
201		minetest.add_entity(pointed_thing.under, "boats:boat")
202		if not minetest.setting_getbool("creative_mode") then
203			itemstack:take_item()
204		end
205		return itemstack
206	end,
207})
208
209minetest.register_craft({
210	output = "boats:boat",
211	recipe = {
212		{"",           "",           ""          },
213		{"group:wood", "",           "group:wood"},
214		{"group:wood", "group:wood", "group:wood"},
215	},
216})
217
218