cart_entity.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. function boost_cart:on_rail_step(entity, pos, distance)
  2. -- Play rail sound
  3. if entity.sound_counter <= 0 then
  4. minetest.sound_play("cart_rail", {
  5. pos = pos,
  6. max_hear_distance = 40,
  7. gain = 0.5
  8. })
  9. entity.sound_counter = math.random(4, 15)
  10. end
  11. entity.sound_counter = entity.sound_counter - distance
  12. if boost_cart.MESECONS then
  13. boost_cart:signal_detector_rail(pos)
  14. end
  15. end
  16. local cart_entity = {
  17. initial_properties = {
  18. physical = false,
  19. collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
  20. visual = "mesh",
  21. mesh = "cart.x",
  22. visual_size = {x=1, y=1},
  23. textures = {"cart.png"},
  24. },
  25. driver = nil,
  26. punched = false, -- used to re-send velocity and position
  27. velocity = {x=0, y=0, z=0}, -- only used on punch
  28. old_dir = {x=1, y=0, z=0}, -- random value to start the cart on punch
  29. old_pos = nil,
  30. old_switch = 0,
  31. sound_counter = 0,
  32. railtype = nil,
  33. attached_items = {}
  34. }
  35. -- Model and textures
  36. if boost_cart.MTG_CARTS then
  37. cart_entity.initial_properties.mesh = "carts_cart.b3d"
  38. cart_entity.initial_properties.textures = {"carts_cart.png"}
  39. end
  40. function cart_entity:on_rightclick(clicker)
  41. if not clicker or not clicker:is_player() then
  42. return
  43. end
  44. local player_name = clicker:get_player_name()
  45. if self.driver and player_name == self.driver then
  46. self.driver = nil
  47. boost_cart:manage_attachment(clicker, nil)
  48. elseif not self.driver then
  49. self.driver = player_name
  50. boost_cart:manage_attachment(clicker, self.object)
  51. end
  52. end
  53. function cart_entity:on_activate(staticdata, dtime_s)
  54. self.object:set_armor_groups({immortal=1})
  55. self.sound_counter = math.random(4, 15)
  56. if string.sub(staticdata, 1, string.len("return")) ~= "return" then
  57. return
  58. end
  59. local data = minetest.deserialize(staticdata)
  60. if type(data) ~= "table" then
  61. return
  62. end
  63. self.railtype = data.railtype
  64. self.old_dir = data.old_dir or self.old_dir
  65. self.old_pos = data.old_pos or self.old_pos
  66. -- Correct the position when the cart drives further after the last 'step()'
  67. if self.old_pos and boost_cart:is_rail(self.old_pos, self.railtype) then
  68. self.object:set_pos(self.old_pos)
  69. end
  70. end
  71. function cart_entity:get_staticdata()
  72. return minetest.serialize({
  73. railtype = self.railtype,
  74. old_dir = self.old_dir,
  75. old_pos = self.old_pos
  76. })
  77. end
  78. -- 0.5.x and later: When the driver leaves
  79. function cart_entity:on_detach_child(child)
  80. if child and child:get_player_name() == self.driver then
  81. self.driver = nil
  82. end
  83. end
  84. function cart_entity:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)
  85. local pos = self.object:get_pos()
  86. local vel = self.object:get_velocity()
  87. if not self.railtype or vector.equals(vel, {x=0, y=0, z=0}) then
  88. local node = minetest.get_node(pos).name
  89. self.railtype = minetest.get_item_group(node, "connect_to_raillike")
  90. end
  91. if not puncher or not puncher:is_player() then
  92. local cart_dir = boost_cart:get_rail_direction(pos, self.old_dir, nil, nil, self.railtype)
  93. if vector.equals(cart_dir, {x=0, y=0, z=0}) then
  94. return
  95. end
  96. self.velocity = vector.multiply(cart_dir, 3)
  97. self.punched = true
  98. return
  99. end
  100. if puncher:get_player_control().sneak then
  101. -- Pick up cart: Drop all attachments
  102. if self.driver then
  103. if self.old_pos then
  104. self.object:set_pos(self.old_pos)
  105. end
  106. local player = minetest.get_player_by_name(self.driver)
  107. boost_cart:manage_attachment(player, nil)
  108. end
  109. for _, obj_ in pairs(self.attached_items) do
  110. if obj_ then
  111. obj_:set_detach()
  112. end
  113. end
  114. local leftover = puncher:get_inventory():add_item("main", "carts:cart")
  115. if not leftover:is_empty() then
  116. minetest.add_item(pos, leftover)
  117. end
  118. self.object:remove()
  119. return
  120. end
  121. -- Driver punches to accelerate the cart
  122. if puncher:get_player_name() == self.driver then
  123. if math.abs(vel.x + vel.z) > boost_cart.punch_speed_max then
  124. return
  125. end
  126. end
  127. local punch_dir = boost_cart:velocity_to_dir(puncher:get_look_dir())
  128. punch_dir.y = 0
  129. local cart_dir = boost_cart:get_rail_direction(pos, punch_dir, nil, nil, self.railtype)
  130. if vector.equals(cart_dir, {x=0, y=0, z=0}) then
  131. return
  132. end
  133. local punch_interval = 1
  134. if tool_capabilities and tool_capabilities.full_punch_interval then
  135. punch_interval = tool_capabilities.full_punch_interval
  136. end
  137. time_from_last_punch = math.min(time_from_last_punch or punch_interval, punch_interval)
  138. local f = 3 * (time_from_last_punch / punch_interval)
  139. self.velocity = vector.multiply(cart_dir, f)
  140. self.old_dir = cart_dir
  141. self.punched = true
  142. end
  143. local v3_len = vector.length
  144. function cart_entity:on_step(dtime)
  145. local vel = self.object:get_velocity()
  146. if self.punched then
  147. vel = vector.add(vel, self.velocity)
  148. self.object:set_velocity(vel)
  149. self.old_dir.y = 0
  150. elseif vector.equals(vel, {x=0, y=0, z=0}) then
  151. return
  152. end
  153. local pos = self.object:get_pos()
  154. local cart_dir = boost_cart:velocity_to_dir(vel)
  155. local same_dir = vector.equals(cart_dir, self.old_dir)
  156. local update = {}
  157. if self.old_pos and not self.punched and same_dir then
  158. local flo_pos = vector.round(pos)
  159. local flo_old = vector.round(self.old_pos)
  160. if vector.equals(flo_pos, flo_old) then
  161. -- Do not check one node multiple times
  162. return
  163. end
  164. end
  165. local ctrl, player
  166. local distance = 1
  167. -- Get player controls
  168. if self.driver then
  169. player = minetest.get_player_by_name(self.driver)
  170. if player then
  171. ctrl = player:get_player_control()
  172. end
  173. end
  174. local stop_wiggle = false
  175. if self.old_pos and same_dir then
  176. -- Detection for "skipping" nodes (perhaps use average dtime?)
  177. -- It's sophisticated enough to take the acceleration in account
  178. local acc = self.object:get_acceleration()
  179. distance = dtime * (v3_len(vel) + 0.5 * dtime * v3_len(acc))
  180. local new_pos, new_dir = boost_cart:pathfinder(
  181. pos, self.old_pos, self.old_dir, distance, ctrl,
  182. self.old_switch, self.railtype
  183. )
  184. if new_pos then
  185. -- No rail found: set to the expected position
  186. pos = new_pos
  187. update.pos = true
  188. cart_dir = new_dir
  189. end
  190. elseif self.old_pos and self.old_dir.y ~= 1 and not self.punched then
  191. -- Stop wiggle
  192. stop_wiggle = true
  193. end
  194. -- dir: New moving direction of the cart
  195. -- switch_keys: Currently pressed L(1) or R(2) key,
  196. -- used to ignore the key on the next rail node
  197. local dir, switch_keys = boost_cart:get_rail_direction(
  198. pos, cart_dir, ctrl, self.old_switch, self.railtype
  199. )
  200. local dir_changed = not vector.equals(dir, self.old_dir)
  201. local acc = 0
  202. if stop_wiggle or vector.equals(dir, {x=0, y=0, z=0}) then
  203. vel = {x=0, y=0, z=0}
  204. local pos_r = vector.round(pos)
  205. if not boost_cart:is_rail(pos_r, self.railtype)
  206. and self.old_pos then
  207. pos = self.old_pos
  208. elseif not stop_wiggle then
  209. pos = pos_r
  210. else
  211. pos.y = math.floor(pos.y + 0.5)
  212. end
  213. update.pos = true
  214. update.vel = true
  215. else
  216. -- Direction change detected
  217. if dir_changed then
  218. vel = vector.multiply(dir, math.abs(vel.x + vel.z))
  219. update.vel = true
  220. if dir.y ~= self.old_dir.y then
  221. pos = vector.round(pos)
  222. update.pos = true
  223. end
  224. end
  225. -- Center on the rail
  226. if dir.z ~= 0 and math.floor(pos.x + 0.5) ~= pos.x then
  227. pos.x = math.floor(pos.x + 0.5)
  228. update.pos = true
  229. end
  230. if dir.x ~= 0 and math.floor(pos.z + 0.5) ~= pos.z then
  231. pos.z = math.floor(pos.z + 0.5)
  232. update.pos = true
  233. end
  234. -- Calculate current cart acceleration
  235. acc = nil
  236. local acc_meta = minetest.get_meta(pos):get_string("cart_acceleration")
  237. if acc_meta == "halt" and not self.punched then
  238. -- Stop rail
  239. vel = {x=0, y=0, z=0}
  240. acc = false
  241. pos = vector.round(pos)
  242. update.pos = true
  243. update.vel = true
  244. end
  245. if acc == nil then
  246. -- Meta speed modifier
  247. local speed_mod = tonumber(acc_meta)
  248. if speed_mod and speed_mod ~= 0 then
  249. -- Try to make it similar to the original carts mod
  250. acc = speed_mod * 10
  251. end
  252. end
  253. if acc == nil and boost_cart.MTG_CARTS then
  254. -- MTG Cart API adaption
  255. local rail_node = minetest.get_node(vector.round(pos))
  256. local railparam = carts.railparams[rail_node.name]
  257. if railparam and railparam.acceleration then
  258. acc = railparam.acceleration
  259. end
  260. end
  261. if acc ~= false then
  262. -- Handbrake
  263. if ctrl and ctrl.down then
  264. acc = (acc or 0) - 2
  265. elseif acc == nil then
  266. acc = -0.4
  267. end
  268. end
  269. if acc then
  270. -- Slow down or speed up, depending on Y direction
  271. acc = acc + dir.y * -2.1
  272. else
  273. acc = 0
  274. end
  275. end
  276. -- Limit cart speed
  277. local vel_len = vector.length(vel)
  278. if vel_len > boost_cart.speed_max then
  279. vel = vector.multiply(vel, boost_cart.speed_max / vel_len)
  280. update.vel = true
  281. end
  282. if vel_len >= boost_cart.speed_max and acc > 0 then
  283. acc = 0
  284. end
  285. self.object:set_acceleration(vector.multiply(dir, acc))
  286. self.old_pos = vector.round(pos)
  287. local old_y_dir = self.old_dir.y
  288. if not vector.equals(dir, {x=0, y=0, z=0}) and not stop_wiggle then
  289. self.old_dir = dir
  290. else
  291. -- Cart stopped, set the animation to 0
  292. self.old_dir.y = 0
  293. end
  294. self.old_switch = switch_keys
  295. boost_cart:on_rail_step(self, self.old_pos, distance)
  296. if self.punched then
  297. -- Collect dropped items
  298. for _, obj_ in pairs(minetest.get_objects_inside_radius(pos, 1)) do
  299. if not obj_:is_player() and
  300. obj_:get_luaentity() and
  301. not obj_:get_luaentity().physical_state and
  302. obj_:get_luaentity().name == "__builtin:item" then
  303. obj_:set_attach(self.object, "", {x=0, y=0, z=0}, {x=0, y=0, z=0})
  304. self.attached_items[#self.attached_items + 1] = obj_
  305. end
  306. end
  307. self.punched = false
  308. update.vel = true
  309. end
  310. if not (update.vel or update.pos) then
  311. return
  312. end
  313. -- Re-use "dir", localize self.old_dir
  314. dir = self.old_dir
  315. local yaw = 0
  316. if dir.x < 0 then
  317. yaw = 0.5
  318. elseif dir.x > 0 then
  319. yaw = 1.5
  320. elseif dir.z < 0 then
  321. yaw = 1
  322. end
  323. self.object:set_yaw(yaw * math.pi)
  324. local anim = {x=0, y=0}
  325. if dir.y == -1 then
  326. anim = {x=1, y=1}
  327. elseif dir.y == 1 then
  328. anim = {x=2, y=2}
  329. end
  330. self.object:set_animation(anim, 1, 0)
  331. -- Change player model rotation, depending on the Y direction
  332. if player and dir.y ~= old_y_dir then
  333. local feet = {x=0, y=-4, z=0}
  334. local eye = {x=0, y=-4, z=0}
  335. if dir.y ~= 0 then
  336. -- TODO: Find a better way to calculate this
  337. feet.y = feet.y + 4
  338. feet.z = -dir.y * 2
  339. eye.z = -dir.y * 8
  340. end
  341. player:set_attach(self.object, "", feet,
  342. {x=dir.y * -30, y=0, z=0})
  343. player:set_eye_offset(eye, eye)
  344. end
  345. if update.vel then
  346. self.object:set_velocity(vel)
  347. end
  348. if update.pos then
  349. if dir_changed then
  350. self.object:set_pos(pos)
  351. else
  352. self.object:move_to(pos)
  353. end
  354. end
  355. end
  356. minetest.register_entity(":carts:cart", cart_entity)
  357. -- Register item to place the entity
  358. if not boost_cart.MTG_CARTS then
  359. minetest.register_craftitem(":carts:cart", {
  360. description = "Cart (Sneak+Click to pick up)",
  361. inventory_image = minetest.inventorycube(
  362. "cart_top.png",
  363. "cart_side.png",
  364. "cart_side.png"
  365. ),
  366. wield_image = "cart_side.png",
  367. on_place = function(itemstack, placer, pointed_thing)
  368. if not pointed_thing.type == "node" then
  369. return
  370. end
  371. if boost_cart:is_rail(pointed_thing.under) then
  372. minetest.add_entity(pointed_thing.under, "carts:cart")
  373. elseif boost_cart:is_rail(pointed_thing.above) then
  374. minetest.add_entity(pointed_thing.above, "carts:cart")
  375. else
  376. return
  377. end
  378. if not minetest.settings:get_bool("creative_mode") then
  379. itemstack:take_item()
  380. end
  381. return itemstack
  382. end,
  383. })
  384. minetest.register_craft({
  385. output = "carts:cart",
  386. recipe = {
  387. {"default:steel_ingot", "", "default:steel_ingot"},
  388. {"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
  389. },
  390. })
  391. end