init.lua 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. -- boats/init.lua
  2. -- Load support for MT game translation.
  3. local S = minetest.get_translator("boats")
  4. --
  5. -- Helper functions
  6. --
  7. local function is_water(pos)
  8. local nn = minetest.get_node(pos).name
  9. return minetest.get_item_group(nn, "water") ~= 0
  10. end
  11. local function get_velocity(v, yaw, y)
  12. local x = -math.sin(yaw) * v
  13. local z = math.cos(yaw) * v
  14. return {x = x, y = y, z = z}
  15. end
  16. local function get_v(v)
  17. return math.sqrt(v.x ^ 2 + v.z ^ 2)
  18. end
  19. --
  20. -- Boat entity
  21. --
  22. local boat = {
  23. initial_properties = {
  24. physical = true,
  25. -- Warning: Do not change the position of the collisionbox top surface,
  26. -- lowering it causes the boat to fall through the world if underwater
  27. collisionbox = {-0.5, -0.35, -0.5, 0.5, 0.3, 0.5},
  28. visual = "mesh",
  29. mesh = "boats_boat.obj",
  30. textures = {"default_wood.png"},
  31. },
  32. driver = nil,
  33. v = 0,
  34. last_v = 0,
  35. removed = false,
  36. auto = false
  37. }
  38. function boat.on_rightclick(self, clicker)
  39. if not clicker or not clicker:is_player() then
  40. return
  41. end
  42. local name = clicker:get_player_name()
  43. if self.driver and name == self.driver then
  44. self.driver = nil
  45. self.auto = false
  46. clicker:set_detach()
  47. player_api.player_attached[name] = false
  48. player_api.set_animation(clicker, "stand" , 30)
  49. local pos = clicker:get_pos()
  50. pos = {x = pos.x, y = pos.y + 0.2, z = pos.z}
  51. minetest.after(0.1, function()
  52. clicker:set_pos(pos)
  53. end)
  54. elseif not self.driver then
  55. local attach = clicker:get_attach()
  56. if attach and attach:get_luaentity() then
  57. local luaentity = attach:get_luaentity()
  58. if luaentity.driver then
  59. luaentity.driver = nil
  60. end
  61. clicker:set_detach()
  62. end
  63. self.driver = name
  64. clicker:set_attach(self.object, "",
  65. {x = 0.5, y = 1, z = -3}, {x = 0, y = 0, z = 0})
  66. player_api.player_attached[name] = true
  67. minetest.after(0.2, function()
  68. player_api.set_animation(clicker, "sit" , 30)
  69. end)
  70. clicker:set_look_horizontal(self.object:get_yaw())
  71. end
  72. end
  73. -- If driver leaves server while driving boat
  74. function boat.on_detach_child(self, child)
  75. self.driver = nil
  76. self.auto = false
  77. end
  78. function boat.on_activate(self, staticdata, dtime_s)
  79. self.object:set_armor_groups({immortal = 1})
  80. if staticdata then
  81. self.v = tonumber(staticdata)
  82. end
  83. self.last_v = self.v
  84. end
  85. function boat.get_staticdata(self)
  86. return tostring(self.v)
  87. end
  88. function boat.on_punch(self, puncher)
  89. if not puncher or not puncher:is_player() or self.removed then
  90. return
  91. end
  92. local name = puncher:get_player_name()
  93. if self.driver and name == self.driver then
  94. self.driver = nil
  95. puncher:set_detach()
  96. player_api.player_attached[name] = false
  97. end
  98. if not self.driver then
  99. self.removed = true
  100. local inv = puncher:get_inventory()
  101. if not (creative and creative.is_enabled_for
  102. and creative.is_enabled_for(name))
  103. or not inv:contains_item("main", "boats:boat") then
  104. local leftover = inv:add_item("main", "boats:boat")
  105. -- if no room in inventory add a replacement boat to the world
  106. if not leftover:is_empty() then
  107. minetest.add_item(self.object:get_pos(), leftover)
  108. end
  109. end
  110. -- delay remove to ensure player is detached
  111. minetest.after(0.1, function()
  112. self.object:remove()
  113. end)
  114. end
  115. end
  116. function boat.on_step(self, dtime)
  117. self.v = get_v(self.object:get_velocity()) * math.sign(self.v)
  118. if self.driver then
  119. local driver_objref = minetest.get_player_by_name(self.driver)
  120. if driver_objref then
  121. local ctrl = driver_objref:get_player_control()
  122. if ctrl.up and ctrl.down then
  123. if not self.auto then
  124. self.auto = true
  125. minetest.chat_send_player(self.driver, S("Boat cruise mode on"))
  126. end
  127. elseif ctrl.down then
  128. self.v = self.v - dtime * 2.0
  129. if self.auto then
  130. self.auto = false
  131. minetest.chat_send_player(self.driver, S("Boat cruise mode off"))
  132. end
  133. elseif ctrl.up or self.auto then
  134. self.v = self.v + dtime * 2.0
  135. end
  136. if ctrl.left then
  137. if self.v < -0.001 then
  138. self.object:set_yaw(self.object:get_yaw() - dtime * 0.9)
  139. else
  140. self.object:set_yaw(self.object:get_yaw() + dtime * 0.9)
  141. end
  142. elseif ctrl.right then
  143. if self.v < -0.001 then
  144. self.object:set_yaw(self.object:get_yaw() + dtime * 0.9)
  145. else
  146. self.object:set_yaw(self.object:get_yaw() - dtime * 0.9)
  147. end
  148. end
  149. end
  150. end
  151. local velo = self.object:get_velocity()
  152. if self.v == 0 and velo.x == 0 and velo.y == 0 and velo.z == 0 then
  153. self.object:set_pos(self.object:get_pos())
  154. return
  155. end
  156. -- We need to preserve velocity sign to properly apply drag force
  157. -- while moving backward
  158. local drag = dtime * math.sign(self.v) * (0.01 + 0.0796 * self.v * self.v)
  159. -- If drag is larger than velocity, then stop horizontal movement
  160. if math.abs(self.v) <= math.abs(drag) then
  161. self.v = 0
  162. else
  163. self.v = self.v - drag
  164. end
  165. local p = self.object:get_pos()
  166. p.y = p.y - 0.5
  167. local new_velo
  168. local new_acce = {x = 0, y = 0, z = 0}
  169. if not is_water(p) then
  170. local nodedef = minetest.registered_nodes[minetest.get_node(p).name]
  171. if (not nodedef) or nodedef.walkable then
  172. self.v = 0
  173. new_acce = {x = 0, y = 1, z = 0}
  174. else
  175. new_acce = {x = 0, y = -9.8, z = 0}
  176. end
  177. new_velo = get_velocity(self.v, self.object:get_yaw(),
  178. self.object:get_velocity().y)
  179. self.object:set_pos(self.object:get_pos())
  180. else
  181. p.y = p.y + 1
  182. if is_water(p) then
  183. local y = self.object:get_velocity().y
  184. if y >= 5 then
  185. y = 5
  186. elseif y < 0 then
  187. new_acce = {x = 0, y = 20, z = 0}
  188. else
  189. new_acce = {x = 0, y = 5, z = 0}
  190. end
  191. new_velo = get_velocity(self.v, self.object:get_yaw(), y)
  192. self.object:set_pos(self.object:get_pos())
  193. else
  194. new_acce = {x = 0, y = 0, z = 0}
  195. if math.abs(self.object:get_velocity().y) < 1 then
  196. local pos = self.object:get_pos()
  197. pos.y = math.floor(pos.y) + 0.5
  198. self.object:set_pos(pos)
  199. new_velo = get_velocity(self.v, self.object:get_yaw(), 0)
  200. else
  201. new_velo = get_velocity(self.v, self.object:get_yaw(),
  202. self.object:get_velocity().y)
  203. self.object:set_pos(self.object:get_pos())
  204. end
  205. end
  206. end
  207. self.object:set_velocity(new_velo)
  208. self.object:set_acceleration(new_acce)
  209. end
  210. minetest.register_entity("boats:boat", boat)
  211. minetest.register_craftitem("boats:boat", {
  212. description = S("Boat"),
  213. inventory_image = "boats_inventory.png",
  214. wield_image = "boats_wield.png",
  215. wield_scale = {x = 2, y = 2, z = 1},
  216. liquids_pointable = true,
  217. groups = {flammable = 2},
  218. on_place = function(itemstack, placer, pointed_thing)
  219. local under = pointed_thing.under
  220. local node = minetest.get_node(under)
  221. local udef = minetest.registered_nodes[node.name]
  222. if udef and udef.on_rightclick and
  223. not (placer and placer:is_player() and
  224. placer:get_player_control().sneak) then
  225. return udef.on_rightclick(under, node, placer, itemstack,
  226. pointed_thing) or itemstack
  227. end
  228. if pointed_thing.type ~= "node" then
  229. return itemstack
  230. end
  231. if not is_water(pointed_thing.under) then
  232. return itemstack
  233. end
  234. pointed_thing.under.y = pointed_thing.under.y + 0.5
  235. boat = minetest.add_entity(pointed_thing.under, "boats:boat")
  236. if boat then
  237. if placer then
  238. boat:set_yaw(placer:get_look_horizontal())
  239. end
  240. local player_name = placer and placer:get_player_name() or ""
  241. if not (creative and creative.is_enabled_for and
  242. creative.is_enabled_for(player_name)) then
  243. itemstack:take_item()
  244. end
  245. end
  246. return itemstack
  247. end,
  248. })
  249. minetest.register_craft({
  250. output = "boats:boat",
  251. recipe = {
  252. {"", "", "" },
  253. {"group:wood", "", "group:wood"},
  254. {"group:wood", "group:wood", "group:wood"},
  255. },
  256. })
  257. minetest.register_craft({
  258. type = "fuel",
  259. recipe = "boats:boat",
  260. burntime = 20,
  261. })