movement.lua 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. --localize global functions
  2. local vector_new = vector.new
  3. local math_hypot = math.hypot
  4. local atan = math.atan
  5. local cos = math.cos
  6. local sin = math.sin
  7. local abs = math.abs
  8. local pi = math.pi
  9. local min = math.min
  10. --max speed settings
  11. local max_ballooning_vertical_speed = 1
  12. local max_ballooning_horizontal_speed = 3
  13. local function is_water_is_air(pos)
  14. local node_name = minetest.get_node(pos).name
  15. return minetest.get_item_group(node_name, "water") > 0,
  16. node_name == "air"
  17. end
  18. local function get_vertical_acceleration(self)
  19. local heat = self.heat
  20. local vel_y = self.object:getvelocity().y
  21. local acc_candidate = heat / 1000 - 0.5
  22. --enforce max speed
  23. if vel_y > max_ballooning_vertical_speed
  24. and acc_candidate * vel_y > 0
  25. then
  26. return 0
  27. else
  28. return acc_candidate
  29. end
  30. end
  31. --if balloon is submerged
  32. local function float_up(self, vel)
  33. self.submerged = true
  34. vel.y = 1
  35. return vel
  36. end
  37. local function swim(self, vel)
  38. --allow controls, allow up
  39. local pos = self.object:get_pos()
  40. --keep y constant or rising
  41. local acc_y = get_vertical_acceleration(self)
  42. if self.submerged or acc_y < 0
  43. then
  44. self.submerged = false
  45. vel.y = 0
  46. return 0, vel
  47. else
  48. return acc_y, vel
  49. end
  50. end
  51. local tau = pi * 2
  52. --returns the radian equivalent of a in the range [0, tau)
  53. local function to_radian(a)
  54. if a < 0
  55. then
  56. return to_radian(a + tau)
  57. elseif a >= tau
  58. then
  59. return to_radian(a - tau)
  60. else
  61. return a
  62. end
  63. end
  64. --decides which is the shortest way to rotate towards where the player is looking
  65. local function get_rotate_direction(a, b)
  66. return to_radian(a - b) < to_radian(b - a)
  67. end
  68. --rotates the balloon towards where the player is looking
  69. local pi_192ths = pi / 192 --radians to turn each step
  70. local function rotate(self, player)
  71. -- + pi so it finishes rotating when looking towards where the player is looking
  72. local player_yaw = player:get_look_horizontal() + pi
  73. local self_yaw = self.object:getyaw()
  74. if get_rotate_direction(player_yaw, self_yaw)
  75. then
  76. self.object:setyaw(to_radian(self_yaw - pi_192ths))
  77. else
  78. self.object:setyaw(to_radian(self_yaw + pi_192ths))
  79. end
  80. end
  81. --takes wasd and turns it into a 2d vector
  82. local pi_halves = pi / 2
  83. function get_direction(right, left, up, down)
  84. local inline, cross = 0, 0
  85. local move = right or left or up or down
  86. if left then cross = 1 end
  87. if right then cross = cross - 1 end
  88. if up then inline = 1 end
  89. if down then inline = inline - 1 end
  90. local arg
  91. if inline < 0
  92. then
  93. return atan(cross / inline) + pi, move
  94. elseif inline > 0
  95. then
  96. return atan(cross / inline), move
  97. else
  98. return pi_halves * cross, move
  99. end
  100. end
  101. --[[
  102. space to rotate where the player is looking
  103. wasd to apply acceleration
  104. shift to let out hot air, cooling the balloon
  105. ]]
  106. local function handle_control(self, vel)
  107. if not self.pilot
  108. then
  109. return 0, 0
  110. end
  111. local player = minetest.get_player_by_name(self.pilot)
  112. if not player --player left, balloon should get deleted
  113. then
  114. return 0, 0
  115. end
  116. local control = player:get_player_control()
  117. if control.sneak --lowering heat quickly
  118. then
  119. local heat = self.heat - 30
  120. if heat < 0
  121. then
  122. self.heat = 0
  123. else
  124. self.heat = heat
  125. end
  126. end
  127. if control.jump --rotate towards player yaw
  128. then
  129. rotate(self, player)
  130. end
  131. --taking direction from get_direction
  132. --and turning it into radians.
  133. --if max speed is reached, only acceleration in the opposite direction is applied.
  134. local dir_radians, move = get_direction(control.right, control.left, control.up, control.down)
  135. if move and math_hypot(vel.x, vel.z)
  136. then
  137. dir_radians = dir_radians + player:get_look_horizontal()
  138. local x, z = -sin(dir_radians), cos(dir_radians)
  139. if math_hypot(vel.x, vel.z) > max_ballooning_horizontal_speed
  140. then
  141. if x * vel.x > 0
  142. then
  143. x = 0
  144. end
  145. if z * vel.z > 0
  146. then
  147. z = 0
  148. end
  149. end
  150. return x, z
  151. end
  152. return 0, 0
  153. end
  154. --[[handle movement in different cases
  155. movement restrictions:
  156. -on ground: only vertical movement
  157. -on water: free movement, though vertical only if up
  158. -submerged: free movement, vertical goes up automatically
  159. -in air: completely free movement
  160. ]]
  161. return function(self)
  162. local pos_in = self.object:get_pos()
  163. local pos_under = vector_new(pos_in.x, pos_in.y - 0.1, pos_in.z)
  164. local on_water, in_air = is_water_is_air(pos_under)
  165. local acc = vector_new(0, 0, 0)
  166. local vel = self.object:getvelocity()
  167. if is_water_is_air(pos_in) --if submerged
  168. then
  169. vel = float_up(self, vel)
  170. acc.x, acc.z = handle_control(self, vel)
  171. self.object:setvelocity(vel)
  172. elseif on_water --if on water
  173. then
  174. acc.y, vel = swim(self, vel)
  175. self.object:setvelocity(vel)
  176. acc.x, acc.z = handle_control(self, vel)
  177. elseif in_air
  178. then
  179. --allow controls and height change
  180. acc.y = get_vertical_acceleration(self)
  181. acc.x, acc.z = handle_control(self, vel)
  182. else --if on ground
  183. --only allow height change
  184. acc.y = get_vertical_acceleration(self)
  185. vel.x = 0
  186. vel.z = 0
  187. self.object:setvelocity(vel)
  188. end
  189. self.object:setacceleration(acc)
  190. end