airboat.lua 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. local random = math.random
  2. -- Functions
  3. local function get_sign(i)
  4. if i == 0 then
  5. return 0
  6. else
  7. return i / math.abs(i)
  8. end
  9. end
  10. local function get_velocity(v, yaw, y)
  11. local x = -math.sin(yaw) * v
  12. local z = math.cos(yaw) * v
  13. return {x = x, y = y, z = z}
  14. end
  15. local function get_v(v)
  16. return math.sqrt(v.x ^ 2 + v.z ^ 2)
  17. end
  18. -- Airboat entity
  19. local airboat = {
  20. initial_properties = {
  21. physical = true,
  22. collide_with_objects = false, -- Workaround fix for a MT engine bug
  23. collisionbox = {-0.9, 0.3, -0.9, 0.9, 1.7, 0.9},
  24. visual = "wielditem",
  25. visual_size = {x = 2.0, y = 2.0}, -- Scale up of nodebox is these * 1.5
  26. textures = {"artifacts:airboat_nodebox"},
  27. },
  28. -- Custom fields
  29. driver = nil,
  30. removed = false,
  31. v = 0,
  32. vy = 0,
  33. rot = 0,
  34. auto = false,
  35. }
  36. function airboat.on_rightclick(self, clicker)
  37. if not clicker or not clicker:is_player() then
  38. return
  39. end
  40. local name = clicker:get_player_name()
  41. if self.driver and name == self.driver then
  42. -- Detach
  43. self.driver = nil
  44. self.auto = false
  45. clicker:set_detach()
  46. player_api.player_attached[name] = false
  47. minetest.after(0.2, function()
  48. player_api.set_animation(clicker, "stand" , 30)
  49. clicker:set_eye_offset({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0})
  50. end)
  51. minetest.sound_play("artifacts_airboat_gear", {pos = pos, gain = 1, max_hear_distance = 6})
  52. local pos = clicker:getpos()
  53. minetest.after(0.1, function()
  54. clicker:setpos(pos)
  55. end)
  56. elseif not self.driver then
  57. -- Attach
  58. local attach = clicker:get_attach()
  59. if attach and attach:get_luaentity() then
  60. local luaentity = attach:get_luaentity()
  61. if luaentity.driver then
  62. luaentity.driver = nil
  63. end
  64. clicker:set_detach()
  65. end
  66. self.driver = name
  67. clicker:set_attach(self.object, "",
  68. {x = 0, y = -8, z = 0}, {x = 0, y = 0, z = 0})
  69. player_api.player_attached[name] = true
  70. minetest.after(0.2, function()
  71. player_api.set_animation(clicker, "sit" , 30)
  72. clicker:set_eye_offset({x = 0, y = -12, z = 0}, {x = 0, y = 0, z = 0})
  73. end)
  74. minetest.sound_play("artifacts_airboat_gear", {pos = pos, gain = 1, max_hear_distance = 6})
  75. clicker:set_look_horizontal(self.object:getyaw())
  76. end
  77. end
  78. function airboat.on_activate(self, staticdata, dtime_s)
  79. self.object:set_armor_groups({immortal = 1})
  80. end
  81. function airboat.on_punch(self, puncher)
  82. if not puncher or not puncher:is_player() or self.removed then
  83. return
  84. end
  85. local name = puncher:get_player_name()
  86. if self.driver and name == self.driver then
  87. -- Detach
  88. --only use on_rightclick
  89. --[[
  90. self.driver = nil
  91. puncher:set_detach()
  92. puncher:set_eye_offset({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0})
  93. player_api.player_attached[name] = false
  94. ]]
  95. end
  96. if not self.driver then
  97. -- Move to inventory
  98. self.removed = true
  99. local inv = puncher:get_inventory()
  100. if not (creative and creative.is_enabled_for
  101. and creative.is_enabled_for(name))
  102. or not inv:contains_item("main", "artifacts:airboat") then
  103. local leftover = inv:add_item("main", "artifacts:airboat")
  104. if not leftover:is_empty() then
  105. minetest.add_item(self.object:getpos(), leftover)
  106. end
  107. end
  108. minetest.after(0.1, function()
  109. self.object:remove()
  110. minetest.sound_play("artifacts_airboat_gear", {pos = pos, gain = 1, max_hear_distance = 6})
  111. end)
  112. end
  113. end
  114. function airboat.on_step(self, dtime)
  115. self.v = get_v(self.object:getvelocity()) * get_sign(self.v)
  116. self.vy = self.object:getvelocity().y
  117. -- Controls
  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.sound_play("artifacts_airboat_gear", {pos = pos, gain = 1, max_hear_distance = 6})
  126. minetest.chat_send_player(self.driver,
  127. "[airboat] Cruise on")
  128. end
  129. elseif ctrl.down then
  130. self.v = self.v - 0.1
  131. if self.auto then
  132. self.auto = false
  133. minetest.sound_play("artifacts_airboat_gear", {pos = pos, gain = 1, max_hear_distance = 6})
  134. minetest.chat_send_player(self.driver,
  135. "[airboat] Cruise off")
  136. end
  137. elseif ctrl.up or self.auto then
  138. self.v = self.v + 0.1
  139. end
  140. if ctrl.left then
  141. self.rot = self.rot + 0.001
  142. if random()>0.98 then
  143. minetest.sound_play("artifacts_airboat_gear", {pos = pos, gain = 0.25, max_hear_distance = 6})
  144. end
  145. elseif ctrl.right then
  146. self.rot = self.rot - 0.001
  147. if random()>0.98 then
  148. minetest.sound_play("artifacts_airboat_gear", {pos = pos, gain = 0.25, max_hear_distance = 6})
  149. end
  150. end
  151. if ctrl.jump then
  152. self.vy = self.vy + 0.06
  153. if random()>0.98 then
  154. minetest.sound_play("artifacts_airboat_gear", {pos = pos, gain = 0.25, max_hear_distance = 6})
  155. end
  156. elseif ctrl.sneak then
  157. self.vy = self.vy - 0.06
  158. if random()>0.98 then
  159. minetest.sound_play("artifacts_airboat_gear", {pos = pos, gain = 0.25, max_hear_distance = 6})
  160. end
  161. end
  162. else
  163. -- Player left server while driving
  164. -- In MT 5.0.0 use 'airboat:on_detach_child()' to do this
  165. self.driver = nil
  166. self.auto = false
  167. minetest.log("warning", "[airboat] Driver left server while" ..
  168. " driving. This may cause some 'Pushing ObjectRef to" ..
  169. " removed/deactivated object' warnings.")
  170. end
  171. end
  172. -- Early return for stationary vehicle
  173. if self.v == 0 and self.rot == 0 and self.vy == 0 then
  174. self.object:setpos(self.object:getpos())
  175. return
  176. end
  177. -- Reduction and limiting of linear speed
  178. local s = get_sign(self.v)
  179. self.v = self.v - 0.02 * s
  180. if s ~= get_sign(self.v) then
  181. self.v = 0
  182. end
  183. if math.abs(self.v) > 2 then
  184. self.v = 2 * get_sign(self.v)
  185. end
  186. -- Reduction and limiting of rotation
  187. local sr = get_sign(self.rot)
  188. self.rot = self.rot - 0.0003 * sr
  189. if sr ~= get_sign(self.rot) then
  190. self.rot = 0
  191. end
  192. if math.abs(self.rot) > 0.015 then
  193. self.rot = 0.015 * get_sign(self.rot)
  194. end
  195. -- Reduction and limiting of vertical speed
  196. local sy = get_sign(self.vy)
  197. self.vy = self.vy - 0.03 * sy
  198. if sy ~= get_sign(self.vy) then
  199. self.vy = 0
  200. end
  201. if math.abs(self.vy) > 1 then
  202. self.vy = 1 * get_sign(self.vy)
  203. end
  204. local new_acce = {x = 0, y = 0, z = 0}
  205. -- Bouyancy in liquids
  206. local p = self.object:getpos()
  207. p.y = p.y - 1.5
  208. local def = minetest.registered_nodes[minetest.get_node(p).name]
  209. if def and (def.liquidtype == "source" or def.liquidtype == "flowing") then
  210. new_acce = {x = 0, y = 10, z = 0}
  211. end
  212. self.object:setpos(self.object:getpos())
  213. self.object:setvelocity(get_velocity(self.v, self.object:getyaw(), self.vy))
  214. self.object:setacceleration(new_acce)
  215. self.object:setyaw(self.object:getyaw() + (1 + dtime) * self.rot)
  216. end
  217. minetest.register_entity("artifacts:airboat", airboat)
  218. -- Craftitem
  219. minetest.register_craftitem("artifacts:airboat", {
  220. description = "Airboat",
  221. inventory_image = "artifacts_airboat_inv.png",
  222. stack_max = 1,
  223. wield_scale = {x = 4, y = 4, z = 4},
  224. liquids_pointable = true,
  225. on_place = function(itemstack, placer, pointed_thing)
  226. local under = pointed_thing.under
  227. local node = minetest.get_node(under)
  228. local udef = minetest.registered_nodes[node.name]
  229. -- Run any on_rightclick function of pointed node instead
  230. if udef and udef.on_rightclick and
  231. not (placer and placer:is_player() and
  232. placer:get_player_control().sneak) then
  233. return udef.on_rightclick(under, node, placer, itemstack,
  234. pointed_thing) or itemstack
  235. end
  236. if pointed_thing.type ~= "node" then
  237. return itemstack
  238. end
  239. pointed_thing.under.y = pointed_thing.under.y + 2
  240. local airboat = minetest.add_entity(pointed_thing.under,
  241. "artifacts:airboat")
  242. minetest.sound_play("artifacts_airboat_gear", {pos = pos, gain = 1, max_hear_distance = 6})
  243. if airboat then
  244. if placer then
  245. airboat:setyaw(placer:get_look_horizontal())
  246. end
  247. local player_name = placer and placer:get_player_name() or ""
  248. if not (creative and creative.is_enabled_for and
  249. creative.is_enabled_for(player_name)) then
  250. itemstack:take_item()
  251. end
  252. end
  253. return itemstack
  254. end,
  255. })
  256. -- Nodebox for entity wielditem visual
  257. minetest.register_node("artifacts:airboat_nodebox", {
  258. description = "Airboat Nodebox",
  259. tiles = { -- Top, base, right, left, front, back
  260. "artifacts_airboat_top.png",
  261. "artifacts_airboat_base.png",
  262. "artifacts_airboat_right.png",
  263. "artifacts_airboat_left.png",
  264. "artifacts_airboat_front.png",
  265. "artifacts_airboat_back.png",
  266. },
  267. paramtype = "light",
  268. drawtype = "nodebox",
  269. node_box = {
  270. type = "fixed",
  271. fixed = {
  272. {-0.1875, -0.375, -0.1875, 0.1875, -0.3125, 0.25}, -- seat_floor
  273. {-0.25, -0.1875, -0.1875, -0.1875, -0.0625, 0.1875}, -- seat_side_1
  274. {0.1875, -0.1875, -0.1875, 0.25, -0.0625, 0.1875}, -- seat_side_2
  275. {-0.25, -0.375, -0.25, 0.25, 0.125, -0.1875}, -- seat_side_back
  276. {-0.25, -0.3125, 0.1875, 0.25, -0.125, 0.25}, -- seat_side_front
  277. {-0.25, 0.125, -0.25, 0.25, 0.1875, 0.125}, -- seat_roof
  278. {-0.5, 0.1875, -0.5, -0.125, 0.5, 0.5}, -- balloon
  279. {0.125, 0.1875, -0.5, 0.5, 0.5, 0.5}, -- balloon2
  280. }
  281. },
  282. groups = {not_in_creative_inventory = 1},
  283. })