particles.lua 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. if not minetest.global_exists("particles") then particles = {} end
  2. particles.modpath = minetest.get_modpath("ambiance")
  3. -- Localize for performance.
  4. local vector_distance = vector.distance
  5. local math_random = math.random
  6. -- Food crumbs when player eats something.
  7. function ambiance.particles_eat_item(user, name)
  8. local def = minetest.registered_items[name]
  9. if not def then
  10. return
  11. end
  12. local texture = ""
  13. if def.inventory_image then
  14. texture = def.inventory_image
  15. elseif def.wield_image then
  16. texture = def.wield_image
  17. end
  18. if texture == "" then
  19. return
  20. end
  21. local rnd = function(u, l)
  22. return math_random(u*10, l*10)/10
  23. end
  24. local pos = user:get_pos()
  25. local yaw = user:get_look_horizontal()
  26. local forward = vector.multiply(minetest.yaw_to_dir(yaw), 0.3)
  27. local push = vector.multiply(minetest.yaw_to_dir(yaw), 1.5)
  28. pos = vector.add(pos, forward)
  29. local particles = {
  30. amount = math_random(2, 5),
  31. time = 0.2,
  32. minpos = vector.add(pos, {x=-0.1, y=1.1, z=-0.1}),
  33. maxpos = vector.add(pos, {x=0.1, y=1.6, z=0.1}),
  34. minvel = vector.add(vector.new(-0.3, 0.3, -0.3), push),
  35. maxvel = vector.add(vector.new(0.3, 0.6, 0.3), push),
  36. minacc = vector.new(0.0, -8.0, 0.0),
  37. maxacc = vector.new(0.0, -8.0, 0.0),
  38. minexptime = 0.5,
  39. maxexptime = 2,
  40. minsize = 0.8,
  41. maxsize = 1.3,
  42. collisiondetection = true,
  43. collision_removal = true,
  44. vertical = false,
  45. texture = texture,
  46. }
  47. minetest.add_particlespawner(particles)
  48. end
  49. -- Particles when player is entirely submerged.
  50. function ambiance.particles_underwater(pos)
  51. local rnd = function(u, l)
  52. return math_random(u*10, l*10)/10
  53. end
  54. local particles = {
  55. amount = math_random(5, 10),
  56. time = 1.0,
  57. minpos = vector.add(pos, -0.5),
  58. maxpos = vector.add(pos, 0.5),
  59. minvel = vector.new(0.5, 0.5, 0.5),
  60. maxvel = vector.new(-0.5, -0.5, -0.5),
  61. minacc = vector.new(0.1, 0.1, 0.1),
  62. maxacc = vector.new(-0.1, -0.1, -0.1),
  63. minexptime = 0.5,
  64. maxexptime = 2,
  65. minsize = 0.1,
  66. maxsize = 1.0,
  67. collisiondetection = false,
  68. collision_removal = false,
  69. vertical = false,
  70. texture = "default_water.png",
  71. }
  72. minetest.add_particlespawner(particles)
  73. end
  74. -- Swimming particles when player is swimming on water surface.
  75. function ambiance.particles_swimming(pos)
  76. local rnd = function(u, l)
  77. return math_random(u*10, l*10)/10
  78. end
  79. local bubbles = {
  80. amount = math_random(5, 10),
  81. time = 1.0,
  82. minpos = vector.add(pos, -0.5),
  83. maxpos = vector.add(pos, 0.5),
  84. minvel = vector.new(-0.5, 0.0, -0.5),
  85. maxvel = vector.new(0.5, 0.5, 0.5),
  86. minacc = vector.new(0.0, -6, 0.0),
  87. maxacc = vector.new(0.0, -10, 0.0),
  88. minexptime = 0.5,
  89. maxexptime = 2,
  90. minsize = 0.5,
  91. maxsize = 1.5,
  92. collisiondetection = false,
  93. collision_removal = false,
  94. vertical = false,
  95. texture = "bubble.png",
  96. }
  97. minetest.add_particlespawner(bubbles)
  98. local splash = {
  99. amount = math_random(5, 10),
  100. time = 1.0,
  101. minpos = vector.add(pos, -0.5),
  102. maxpos = vector.add(pos, 0.5),
  103. minvel = vector.new(-0.5, 0.0, -0.5),
  104. maxvel = vector.new(0.5, 0.5, 0.5),
  105. minacc = vector.new(0.0, -6, 0.0),
  106. maxacc = vector.new(0.0, -10, 0.0),
  107. minexptime = 0.5,
  108. maxexptime = 2,
  109. minsize = 0.1,
  110. maxsize = 1.0,
  111. collisiondetection = false,
  112. collision_removal = false,
  113. vertical = false,
  114. texture = "default_water.png",
  115. }
  116. minetest.add_particlespawner(splash)
  117. end
  118. local function get_texture(node)
  119. local texture = ""
  120. local def = minetest.registered_items[node.name]
  121. if def then
  122. if def.tiles and def.tiles[1] then
  123. if type(def.tiles[1]) == "string" then
  124. texture = def.tiles[1]
  125. end
  126. end
  127. end
  128. -- If no texture could be found, calling function should abort.
  129. return texture
  130. end
  131. function ambiance.particles_on_dig(pos, node)
  132. local texture = get_texture(node)
  133. if texture == "" then
  134. return
  135. end
  136. local particles = {
  137. amount = math_random(5, 10),
  138. time = 0.1,
  139. minpos = vector.add(pos, -0.49),
  140. maxpos = vector.add(pos, 0.49),
  141. minvel = {x=0, y=1, z=0},
  142. maxvel = {x=0, y=2, z=0},
  143. minacc = {x=0, y=-10, z=0},
  144. maxacc = {x=0, y=-10, z=0},
  145. minexptime = 0.5,
  146. maxexptime = 1,
  147. minsize = 1,
  148. maxsize = 1,
  149. collisiondetection = true,
  150. collision_removal = false,
  151. vertical = false,
  152. texture = texture,
  153. }
  154. minetest.add_particlespawner(particles)
  155. end
  156. function ambiance.particles_on_punch(pos, node)
  157. local texture = get_texture(node)
  158. if texture == "" then
  159. return
  160. end
  161. local particles = {
  162. amount = math_random(1, 5),
  163. time = 0.1,
  164. minpos = vector.add(pos, -0.49),
  165. maxpos = vector.add(pos, 0.49),
  166. minvel = {x=-0.3, y=0, z=-0.3},
  167. maxvel = {x=0.3, y=0, z=0.3},
  168. minacc = {x=0, y=-10, z=0},
  169. maxacc = {x=0, y=-10, z=0},
  170. minexptime = 0.5,
  171. maxexptime = 1,
  172. minsize = 1,
  173. maxsize = 1,
  174. collisiondetection = true,
  175. collision_removal = false,
  176. vertical = false,
  177. texture = texture,
  178. }
  179. minetest.add_particlespawner(particles)
  180. end
  181. function ambiance.particles_on_place(pos, node)
  182. local texture = get_texture(node)
  183. if texture == "" then
  184. return
  185. end
  186. local particles = {
  187. amount = math_random(5, 10),
  188. time = 0.1,
  189. minpos = vector.add(pos, -0.49),
  190. maxpos = vector.add(pos, 0.49),
  191. minvel = {x=-0.3, y=0, z=-0.3},
  192. maxvel = {x=0.3, y=0, z=0.3},
  193. minacc = {x=0, y=-10, z=0},
  194. maxacc = {x=0, y=-10, z=0},
  195. minexptime = 0.5,
  196. maxexptime = 1,
  197. minsize = 1,
  198. maxsize = 1,
  199. collisiondetection = true,
  200. collision_removal = false,
  201. vertical = false,
  202. texture = texture,
  203. }
  204. minetest.add_particlespawner(particles)
  205. end
  206. local function player_nearby(pos)
  207. local players = minetest.get_connected_players()
  208. for k, v in ipairs(players) do
  209. if vector_distance(pos, v:get_pos()) < 16 then
  210. return true
  211. end
  212. end
  213. end
  214. function ambiance.flamespawner(self, dtime)
  215. self.st = (self.st or 0) - dtime
  216. self.ct = (self.ct or 0) - dtime
  217. self.nt = (self.nt or 0) - dtime
  218. -- Remove spawner if no flame here.
  219. if self.nt < 0 then
  220. self.nt = math_random(10, 60)
  221. local pos = self.object:get_pos()
  222. local nn = minetest.get_node(pos).name
  223. if not string.find(nn, "^fire:") and not string.find(nn, "^maptools:") then
  224. self.object:remove()
  225. return
  226. end
  227. end
  228. -- Check every so often if a player is nearby.
  229. if self.ct < 0 then
  230. local pos = self.object:get_pos()
  231. self.good = player_nearby(pos)
  232. self.ct = math_random(2, 8)
  233. end
  234. if self.st < 0 then
  235. local rnd = function(u, l)
  236. return math_random(u*10, l*10)/10
  237. end
  238. self.st = rnd(0.5, 1.0)
  239. -- Spawn particle only if player nearby.
  240. if self.good then
  241. local pos = self.object:get_pos()
  242. local particle = {
  243. pos = {x=pos.x+rnd(-0.5, 0.5), y=pos.y+rnd(0.0, 0.5), z=pos.z+rnd(-0.5, 0.5)},
  244. velocity = {x=rnd(-0.1, 0.1), y=rnd(0.1, 0.7), z=rnd(-0.1, 0.1)},
  245. acceleration = {x=rnd(-0.1, 0.1), y=rnd(0.1, 0.7), z=rnd(-0.1, 0.1)},
  246. expirationtime = rnd(0.1, 2.0),
  247. size = rnd(0.3, 0.7),
  248. collisiondetection = false,
  249. collision_removal = false,
  250. vertical = false,
  251. texture = "particles.png",
  252. animation = {
  253. type = "vertical_frames",
  254. aspect_w = 1,
  255. aspect_h = 1,
  256. length = 1.0,
  257. },
  258. glow = rnd(10, 15),
  259. }
  260. if math_random(1, 6) == 1 then
  261. particle.texture = "smoke_particles.png"
  262. particle.animation = {
  263. type = "vertical_frames",
  264. aspect_w = 4,
  265. aspect_h = 4,
  266. length = 1.0,
  267. }
  268. particle.size = rnd(0.8, 1.1)
  269. particle.velocity.y = particle.velocity.y + rnd(0.2, 0.4)
  270. particle.pos.y = pos.y + rnd(0.4, 0.6)
  271. end
  272. minetest.add_particle(particle)
  273. -- Occasionally play flame sound.
  274. if math_random(1, 10) == 1 then
  275. ambiance.sound_play("fire_small", pos, 0.3, 16)
  276. end
  277. end
  278. end
  279. end
  280. function particles.add_flame_spawner(pos)
  281. minetest.add_entity(pos, "particles:flamespawner")
  282. end
  283. function particles.del_flame_spawner(pos)
  284. local ents = minetest.get_objects_inside_radius(pos, 0.6)
  285. if ents then
  286. for k, obj in ipairs(ents) do
  287. if obj and obj:get_luaentity() and obj:get_luaentity().name == "particles:flamespawner" then
  288. obj:remove()
  289. end
  290. end
  291. end
  292. end
  293. function particles.restore_flame_spawner(pos)
  294. particles.del_flame_spawner(pos)
  295. particles.add_flame_spawner(pos)
  296. end
  297. if not particles.run_once then
  298. -- File is reloadable.
  299. local c = "particles:core"
  300. local f = particles.modpath .. "/particles.lua"
  301. reload.register_file(c, f, false)
  302. -- Torches no longer spawn particles, this creates too many entities and network packets.
  303. -- The entity definition now only exists to delete existing torchspawners from the world.
  304. local torchspawner = {
  305. visual = "wielditem",
  306. visual_size = {x=0, y=0},
  307. collisionbox = {0, 0, 0, 0, 0, 0},
  308. physical = false,
  309. textures = {"air"},
  310. is_visible = false,
  311. on_activate = function(self, staticdata, dtime_s)
  312. self.object:remove()
  313. end,
  314. on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir)
  315. end,
  316. on_death = function(self, killer)
  317. end,
  318. on_rightclick = function(self, clicker)
  319. end,
  320. get_staticdata = function(self)
  321. return ""
  322. end,
  323. on_step = function(self, dtime)
  324. self.object:remove()
  325. end,
  326. }
  327. minetest.register_entity(":particles:torchspawner", torchspawner)
  328. local flamespawner = {
  329. visual = "wielditem",
  330. visual_size = {x=0, y=0},
  331. collisionbox = {0, 0, 0, 0, 0, 0},
  332. physical = false,
  333. textures = {"air"},
  334. is_visible = false,
  335. on_activate = function(self, staticdata, dtime_s)
  336. end,
  337. on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir)
  338. end,
  339. on_death = function(self, killer)
  340. end,
  341. on_rightclick = function(self, clicker)
  342. end,
  343. get_staticdata = function(self)
  344. return ""
  345. end,
  346. on_step = function(self, dtime)
  347. return ambiance.flamespawner(self, dtime)
  348. end,
  349. }
  350. minetest.register_entity(":particles:flamespawner", flamespawner)
  351. particles.run_once = true
  352. end