nssm_api.lua 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887
  1. -- set content id's
  2. local c_air = minetest.get_content_id("air")
  3. local c_ignore = minetest.get_content_id("ignore")
  4. local c_obsidian = minetest.get_content_id("default:obsidian")
  5. local c_brick = minetest.get_content_id("default:obsidianbrick")
  6. local c_chest = minetest.get_content_id("default:chest_locked")
  7. nssm.lessvirulent = minetest.setting_getbool("nssm.lessvirulent") or false
  8. nssm.safebones = minetest.setting_getbool("nssm.safebones") or false
  9. nssm.cryosave = minetest.setting_getbool("nssm.cryosave") or false
  10. function nssm:virulence(mobe)
  11. if not nssm.lessvirulent then
  12. return 0
  13. end
  14. return math.ceil(100 / mobe.hp_max)
  15. end
  16. function nssm:affectbones(mobe) -- as function for adaptable heuristic
  17. return not nssm.safebones
  18. end
  19. function drops(drop)
  20. if drop then
  21. drop:setvelocity({
  22. x = math.random(-10, 10) / 9,
  23. y = 5,
  24. z = math.random(-10, 10) / 9,
  25. })
  26. end
  27. end
  28. function perpendicular_vector(vec) --returns a vector rotated of 90° in 2D
  29. local ang = math.pi/2
  30. local c = math.cos(ang)
  31. local s = math.sin(ang)
  32. local i = vec.x*c - vec.z*s
  33. local k = vec.x*s + vec.z*c
  34. local j = 0
  35. vec = {x=i, y=j, z=k}
  36. return vec
  37. end
  38. function add_entity_and_particles(entity, pos, particles, multiplier)
  39. minetest.add_particlespawner({
  40. amount = 100*multiplier,
  41. time = 2,
  42. minpos = {x=pos.x-2, y=pos.y-1, z=pos.z-2},
  43. maxpos = {x=pos.x+2, y=pos.y+4, z=pos.z+2},
  44. minvel = {x=0, y=0, z=0},
  45. maxvel = {x=1, y=2, z=1},
  46. minacc = {x=-0.5,y=0.6,z=-0.5},
  47. maxacc = {x=0.5,y=0.7,z=0.5},
  48. minexptime = 2,
  49. maxexptime = 3,
  50. minsize = 3,
  51. maxsize = 5,
  52. collisiondetection = false,
  53. vertical = false,
  54. texture = particles,
  55. })
  56. minetest.add_entity(pos, entity)
  57. end
  58. -- get node but use fallback for nil or unknown
  59. function node_ok(pos, fallback)
  60. fallback = fallback or "default:dirt"
  61. local node = minetest.get_node_or_nil(pos)
  62. if not node then
  63. return minetest.registered_nodes[fallback]
  64. end
  65. if minetest.registered_nodes[node.name] then
  66. return node
  67. end
  68. return minetest.registered_nodes[fallback]
  69. end
  70. function dist_pos(p, s)
  71. local v = {x = math.abs(s.x-p.x), y = math.abs(s.y-p.y), z = math.abs(s.z-p.z)}
  72. local r = math.sqrt(v.x^2+v.y^2+v.z^2)
  73. return r
  74. end
  75. --check_for_death functions customized for monsters who respawns (Masticone)
  76. function check_for_death_hydra(self)
  77. local hp = self.object:get_hp()
  78. if hp > 0 then
  79. self.health = hp
  80. if self.sounds.damage ~= nil then
  81. minetest.sound_play(self.sounds.damage,{
  82. object = self.object,
  83. max_hear_distance = self.sounds.distance
  84. })
  85. end
  86. return false
  87. end
  88. local pos = self.object:get_pos()
  89. local obj = nil
  90. if self.sounds.death ~= nil then
  91. minetest.sound_play(self.sounds.death,{
  92. object = self.object,
  93. max_hear_distance = self.sounds.distance
  94. })
  95. end
  96. self.object:remove()
  97. return true
  98. end
  99. function round(n)
  100. if (n>0) then
  101. return n % 1 >= 0.5 and math.ceil(n) or math.floor(n)
  102. else
  103. n=-n
  104. local t = n % 1 >= 0.5 and math.ceil(n) or math.floor(n)
  105. return -t
  106. end
  107. end
  108. function explosion_particles(pos, exp_radius)
  109. minetest.add_particlespawner(
  110. 100*exp_radius/2, --amount
  111. 0.1, --time
  112. {x=pos.x-exp_radius, y=pos.y-exp_radius, z=pos.z-exp_radius}, --minpos
  113. {x=pos.x+exp_radius, y=pos.y+exp_radius, z=pos.z+exp_radius}, --maxpos
  114. {x=0, y=0, z=0}, --minvel
  115. {x=0.1, y=0.3, z=0.1}, --maxvel
  116. {x=-0.5,y=1,z=-0.5}, --minacc
  117. {x=0.5,y=1,z=0.5}, --maxacc
  118. 0.1, --minexptime
  119. 4, --maxexptime
  120. 6, --minsize
  121. 12, --maxsize
  122. false, --collisiondetection
  123. "tnt_smoke.png" --texture
  124. )
  125. end
  126. --[[
  127. function explosion(pos, exp_radius, fire, kamehameha_bad)
  128. local radius = exp_radius
  129. -- if area protected or near map limits then no blast damage
  130. if minetest.is_protected(pos, "")
  131. or not within_limits(pos, radius) then
  132. return
  133. end
  134. --sound
  135. minetest.sound_play("boom", {
  136. pos = pos,
  137. max_hear_distance = exp_radius*4,
  138. })
  139. --particles:
  140. explosion_particles(pos, exp_radius)
  141. --Damages entities around (not the player)
  142. local objects = minetest.get_objects_inside_radius(pos, exp_radius)
  143. for _,obj in ipairs(objects) do
  144. local obj_p = obj:get_pos()
  145. local vec = {x=obj_p.x-pos.x, y=obj_p.y-pos.y, z=obj_p.z-pos.z}
  146. local dist = (vec.x^2+vec.y^2+vec.z^2)^0.5
  147. local damage = (-exp_radius*dist+exp_radius^2)*2
  148. if not kamehameha_bad then
  149. if obj:is_player() then
  150. obj:set_hp(obj:get_hp()-damage)
  151. elseif obj:get_luaentity().health then
  152. obj:get_luaentity().health = obj:get_luaentity().health - damage
  153. check_for_death(obj:get_luaentity())
  154. end
  155. else
  156. if (obj:get_luaentity()) then
  157. local name = obj:get_luaentity().name
  158. if (name~="nssm:morvalar0") and (name~="nssm:morvalar5") then
  159. if obj:is_player() then
  160. obj:set_hp(obj:get_hp()-damage)
  161. elseif obj:get_luaentity().health then
  162. obj:get_luaentity().health = obj.get_luaentity().health - damage
  163. check_for_death(obj:get_luaentity())
  164. end
  165. end
  166. end
  167. end
  168. end
  169. --damages blocks around and if necessary put some fire
  170. pos = vector.round(pos) -- voxelmanip doesn't work properly unless pos is rounded ?!?!
  171. local vm = VoxelManip()
  172. local minp, maxp = vm:read_from_map(vector.subtract(pos, radius), vector.add(pos, radius))
  173. local a = VoxelArea:new({MinEdge = minp, MaxEdge = maxp})
  174. local data = vm:get_data()
  175. local p = {}
  176. local pr = PseudoRandom(os.time())
  177. --remove everything near the center of the explosion
  178. for dz=-radius,radius do
  179. for dy=-radius,radius do
  180. local vi = a:index(pos.x + (-radius), pos.y + dy, pos.z + dz)
  181. for dx=-radius,radius do
  182. local p = {x=pos.x+dx, y=pos.y+dy, z=pos.z+dz}
  183. if (dx * dx) + (dy * dy) + (dz * dz) <= (radius * radius) + pr:next(-radius, radius)
  184. and data[vi] ~= c_air
  185. and data[vi] ~= c_ignore
  186. and data[vi] ~= c_obsidian
  187. and data[vi] ~= c_brick
  188. and data[vi] ~= c_chest then
  189. local n = node_ok(p).name
  190. local on_blast = minetest.registered_nodes[n].on_blast
  191. if on_blast then
  192. return on_blast(p)
  193. end
  194. if minetest.get_item_group(n, "unbreakable") ~= 1 then
  195. -- if chest then drop items inside
  196. if n == "default:chest"
  197. or n == "3dchest:chest"
  198. or n == "bones:bones" then
  199. local meta = minetest.get_meta(p)
  200. local inv = meta:get_inventory()
  201. for i = 1, inv:get_size("main") do
  202. local m_stack = inv:get_stack("main", i)
  203. local obj = minetest.add_item(p, m_stack)
  204. if obj then
  205. obj:set_velocity({
  206. x = math.random(-2, 2),
  207. y = 7,
  208. z = math.random(-2, 2)
  209. })
  210. end
  211. end
  212. end
  213. -- after effects
  214. if fire > 0
  215. and (minetest.registered_nodes[n].groups.flammable
  216. or math.random(1, 100) <= 3) then
  217. minetest.set_node(p, {name = "fire:basic_flame"})
  218. else
  219. local dist = round(((pos.x-p.x)^2 + (pos.y-p.y)^2 + (pos.z-p.z)^2)^1/2)
  220. local prob = 2/dist
  221. if math.random(1,100)<=prob*100 then
  222. minetest.remove_node(p)
  223. end
  224. end
  225. end
  226. end
  227. vi = vi+1
  228. end
  229. end
  230. end
  231. end
  232. ]]
  233. -- SPECIAL ABILITIES OF SOME MOBS
  234. --[[function digging_ability(
  235. self, --the entity of the mob
  236. group, --group of the blocks the mob can dig: nil=everything
  237. max_vel, --max velocity of the mob
  238. dim --vector representing the dimensions of the mob
  239. )
  240. --if math.random(1,nssm:virulence(self)) ~= 1 then return end
  241. local v = self.object:get_velocity()
  242. local pos = self.object:get_pos()
  243. if minetest.is_protected(pos, "") then
  244. return
  245. end
  246. local h = dim.y
  247. local max = 0
  248. --local posmax = 0 -- 1 = x, -1=-x, 2 = z, -2 = -z
  249. local yaw = (self.object:get_yaw() + self.rotate) or 0
  250. local x = math.sin(yaw)*-1
  251. local z = math.cos(yaw)
  252. local i = 1
  253. local i1 = -1
  254. local k = 1
  255. local k1 = -1
  256. local multiplier = 2
  257. if x>0 then
  258. i = round(x*max_vel)*multiplier
  259. else
  260. i1 = round(x*max_vel)*multiplier
  261. end
  262. if z>0 then
  263. k = round(z*max_vel)*multiplier
  264. else
  265. k1 = round(z*max_vel)*multiplier
  266. end
  267. for dx = i1, i do
  268. for dy = 0, h do
  269. for dz = k1, k do
  270. local p = {x=pos.x+dx, y=pos.y+dy, z=pos.z+dz}
  271. local n = minetest.get_node(p).name
  272. --local up = {x=pos.x+dx, y=pos.y+dy, z=pos.z+dz}
  273. if group == nil then
  274. if minetest.get_item_group(n, "unbreakable") == 1 or minetest.is_protected(p, "") or (n == "bones:bones" and not nssm:affectbones(self) ) then
  275. else
  276. --minetest.set_node(p, {name="air"})
  277. minetest.remove_node(p)
  278. end
  279. else
  280. if (minetest.get_item_group(n, group)==1) and (minetest.get_item_group(n, "unbreakable") ~= 1) and (n == "bones:bones" and not (minetest.is_protected(p, "")) ) then
  281. --minetest.set_node(p, {name="air"})
  282. minetest.remove_node(p)
  283. end
  284. end
  285. end
  286. end
  287. end
  288. end
  289. ]]--
  290. function digging_attack(
  291. self, --the entity of the mob
  292. group, --group of the blocks the mob can dig: nil=everything
  293. max_vel, --max velocity of the mob
  294. dim --vector representing the dimensions of the mob
  295. )
  296. --if math.random(1,nssm:virulence(self)) ~= 1 then return end
  297. if self.attack and self.attack:is_player() then
  298. local s = self.object:get_pos()
  299. local p = self.attack:get_pos()
  300. local dir = vector.subtract(p,s)
  301. dir = vector.normalize(dir)
  302. local per = perpendicular_vector(dir)
  303. local posp = vector.add(s,dir)
  304. --minetest.chat_send_all("La mia posizione:"..minetest.pos_to_string(s))
  305. --minetest.chat_send_all("La posizione davanti:"..minetest.pos_to_string(posp))
  306. posp = vector.subtract(posp,per)
  307. for j = 1,3 do
  308. --minetest.chat_send_all("pos1:"..minetest.pos_to_string(posp).." per.y= "..dim.y)
  309. if minetest.is_protected(posp, "") then
  310. return
  311. end
  312. local pos1 = posp
  313. for i = 0,dim.y do
  314. --minetest.chat_send_all("pos2:"..minetest.pos_to_string(posp).." per.y= "..per.y)
  315. local n = minetest.get_node(pos1).name
  316. --local up = {x=pos.x+dx, y=pos.y+dy, z=pos.z+dz}
  317. if group == nil then
  318. if minetest.get_item_group(n, "unbreakable") == 1 or minetest.is_protected(pos1, "") or (n == "bones:bones" and not nssm:affectbones(self) ) then
  319. else
  320. --minetest.set_node(p, {name="air"})
  321. minetest.remove_node(pos1)
  322. end
  323. else
  324. if ((minetest.get_item_group(n, group)==1) and (minetest.get_item_group(n, "unbreakable") ~= 1) and (n ~= "bones:bones") and not (minetest.is_protected(pos1, "")) ) then
  325. --minetest.set_node(p, {name="air"})
  326. minetest.remove_node(pos1)
  327. end
  328. end
  329. pos1.y = pos1.y+1
  330. end
  331. posp.y=s.y
  332. posp=vector.add(posp,per)
  333. --minetest.chat_send_all("pos3:"..minetest.pos_to_string(posp).." per.y= "..per.y)
  334. end
  335. end
  336. end
  337. function putting_ability( --puts under the mob the block defined as 'p_block'
  338. self, --the entity of the mob
  339. p_block, --definition of the block to use
  340. max_vel --max velocity of the mob
  341. )
  342. --if math.random(1,nssm:virulence(self)) ~= 1 then return end
  343. local v = self.object:get_velocity()
  344. local dx = 0
  345. local dz = 0
  346. if (math.abs(v.x)>math.abs(v.z)) then
  347. if (v.x)>0 then
  348. dx = 1
  349. else
  350. dx = -1
  351. end
  352. else
  353. if (v.z)>0 then
  354. dz = 1
  355. else
  356. dz = -1
  357. end
  358. end
  359. local pos = self.object:get_pos()
  360. local pos1
  361. pos.y=pos.y-1
  362. pos1 = {x = pos.x+dx, y = pos.y, z = pos.z+dz}
  363. local n = minetest.get_node(pos).name
  364. local n1 = minetest.get_node(pos1).name
  365. local oldmetainf = {minetest.get_meta(pos):to_table(),minetest.get_meta(pos1):to_table() }
  366. if n~=p_block and not minetest.is_protected(pos, "") and (n == "bones:bones" and nssm:affectbones(self) ) and n~="air" then
  367. minetest.set_node(pos, {name=p_block})
  368. if nssm.cryosave then
  369. local metai = minetest.get_meta(pos)
  370. metai:from_table(oldmetainf[1]) -- this is enough to save the meta
  371. metai:set_string("nssm",n)
  372. end
  373. end
  374. if n1~=p_block and not minetest.is_protected(pos1, "") and (n == "bones:bones" and nssm:affectbones(self) ) and n~="air" then
  375. minetest.set_node(pos1, {name=p_block})
  376. if nssm.cryosave then
  377. local metai = minetest.get_meta(pos1)
  378. metai:from_table(oldmetainf[2]) -- this is enough to save the meta
  379. metai:set_string("nssm",n1)
  380. end
  381. end
  382. end
  383. function webber_ability( --puts randomly around the block defined as w_block
  384. self, --the entity of the mob
  385. w_block, --definition of the block to use
  386. radius --max distance the block can be put
  387. )
  388. if (nssm:virulence(self)~=0) and (math.random(1,nssm:virulence(self)) ~= 1) then return end
  389. local pos = self.object:get_pos()
  390. if (math.random(1,55)==1) then
  391. local dx=math.random(1,radius)
  392. local dz=math.random(1,radius)
  393. local p = {x=pos.x+dx, y=pos.y-1, z=pos.z+dz}
  394. local t = {x=pos.x+dx, y=pos.y, z=pos.z+dz}
  395. local n = minetest.get_node(p).name
  396. local k = minetest.get_node(t).name
  397. if ((n~="air")and(k=="air")) and not minetest.is_protected(t, "") then
  398. minetest.set_node(t, {name=w_block})
  399. end
  400. end
  401. end
  402. function midas_ability( --ability to transform every blocks it touches in the m_block block
  403. self, --the entity of the mob
  404. m_block,
  405. max_vel, --max velocity of the mob
  406. mult, --multiplier of the dimensions of the area around that need the transformation
  407. height --height of the mob
  408. )
  409. --if math.random(1,nssm:virulence(self)) ~= 1 then return end
  410. local v = self.object:get_velocity()
  411. local pos = self.object:get_pos()
  412. if minetest.is_protected(pos, "") then
  413. return
  414. end
  415. local max = 0
  416. local yaw = (self.object:get_yaw() + self.rotate) or 0
  417. local x = math.sin(yaw)*-1
  418. local z = math.cos(yaw)
  419. local i = 1
  420. local i1 = -1
  421. local k = 1
  422. local k1 = -1
  423. local multiplier = mult
  424. if x>0 then
  425. i = round(x*max_vel)*multiplier
  426. else
  427. i1 = round(x*max_vel)*multiplier
  428. end
  429. if z>0 then
  430. k = round(z*max_vel)*multiplier
  431. else
  432. k1 = round(z*max_vel)*multiplier
  433. end
  434. for dx = i1, i do
  435. for dy = -1, height do
  436. for dz = k1, k do
  437. local p = {x=pos.x+dx, y=pos.y+dy, z=pos.z+dz}
  438. local n = minetest.get_node(p).name
  439. if minetest.get_item_group(n, "unbreakable") == 1
  440. or minetest.is_protected(p, "") or n=="air"
  441. or (n == "bones:bones" and not nssm:affectbones(self))
  442. or n==m_block then
  443. else
  444. minetest.set_node(p, {name=m_block})
  445. end
  446. end
  447. end
  448. end
  449. end
  450. -- NEW EXPLOSION FUNCTION
  451. -- loss probabilities array (one in X will be lost)
  452. local loss_prob = {}
  453. loss_prob["default:cobble"] = 3
  454. loss_prob["default:dirt"] = 4
  455. local tnt_radius = tonumber(minetest.setting_get("tnt_radius") or 3)
  456. local cid_data = {}
  457. minetest.after(0, function()
  458. for name, def in pairs(minetest.registered_nodes) do
  459. cid_data[minetest.get_content_id(name)] = {
  460. name = name,
  461. drops = def.drops,
  462. flammable = def.groups.flammable,
  463. on_blast = def.on_blast,
  464. }
  465. end
  466. end)
  467. local function rand_pos(center, pos, radius)
  468. local def
  469. local reg_nodes = minetest.registered_nodes
  470. local i = 0
  471. repeat
  472. -- Give up and use the center if this takes too long
  473. if i > 4 then
  474. pos.x, pos.z = center.x, center.z
  475. break
  476. end
  477. pos.x = center.x + math.random(-radius, radius)
  478. pos.z = center.z + math.random(-radius, radius)
  479. def = reg_nodes[minetest.get_node(pos).name]
  480. i = i + 1
  481. until def and not def.walkable
  482. end
  483. local function add_effects(pos, radius, drops)
  484. minetest.add_particle({
  485. pos = pos,
  486. velocity = vector.new(),
  487. acceleration = vector.new(),
  488. expirationtime = 0.4,
  489. size = radius * 10,
  490. collisiondetection = false,
  491. vertical = false,
  492. texture = "tnt_boom.png",
  493. })
  494. minetest.add_particlespawner({
  495. amount = 64,
  496. time = 0.5,
  497. minpos = vector.subtract(pos, radius / 2),
  498. maxpos = vector.add(pos, radius / 2),
  499. minvel = {x = -10, y = -10, z = -10},
  500. maxvel = {x = 10, y = 10, z = 10},
  501. minacc = vector.new(),
  502. maxacc = vector.new(),
  503. minexptime = 1,
  504. maxexptime = 2.5,
  505. minsize = radius * 3,
  506. maxsize = radius * 5,
  507. texture = "tnt_smoke.png",
  508. })
  509. -- we just dropped some items. Look at the items entities and pick
  510. -- one of them to use as texture
  511. local texture = "tnt_blast.png" --fallback texture
  512. local most = 0
  513. for name, stack in pairs(drops) do
  514. local count = stack:get_count()
  515. if count > most then
  516. most = count
  517. local def = minetest.registered_nodes[name]
  518. if def and def.tiles and def.tiles[1] then
  519. texture = def.tiles[1]
  520. end
  521. end
  522. end
  523. minetest.add_particlespawner({
  524. amount = 64,
  525. time = 0.1,
  526. minpos = vector.subtract(pos, radius / 2),
  527. maxpos = vector.add(pos, radius / 2),
  528. minvel = {x = -3, y = 0, z = -3},
  529. maxvel = {x = 3, y = 5, z = 3},
  530. minacc = {x = 0, y = -10, z = 0},
  531. maxacc = {x = 0, y = -10, z = 0},
  532. minexptime = 0.8,
  533. maxexptime = 2.0,
  534. minsize = radius * 0.66,
  535. maxsize = radius * 2,
  536. texture = texture,
  537. collisiondetection = true,
  538. })
  539. end
  540. local function eject_drops(drops, pos, radius)
  541. local drop_pos = vector.new(pos)
  542. for _, item in pairs(drops) do
  543. local count = math.min(item:get_count(), item:get_stack_max())
  544. while count > 0 do
  545. local take = math.max(1,math.min(radius * radius,
  546. count,
  547. item:get_stack_max()))
  548. rand_pos(pos, drop_pos, radius)
  549. local dropitem = ItemStack(item)
  550. dropitem:set_count(take)
  551. local obj = minetest.add_item(drop_pos, dropitem)
  552. if obj then
  553. obj:get_luaentity().collect = true
  554. obj:set_acceleration({x = 0, y = -10, z = 0})
  555. obj:set_velocity({x = math.random(-3, 3),
  556. y = math.random(0, 10),
  557. z = math.random(-3, 3)})
  558. end
  559. count = count - take
  560. end
  561. end
  562. end
  563. local function calc_velocity(pos1, pos2, old_vel, power)
  564. -- Avoid errors caused by a vector of zero length
  565. if vector.equals(pos1, pos2) then
  566. return old_vel
  567. end
  568. local vel = vector.direction(pos1, pos2)
  569. vel = vector.normalize(vel)
  570. vel = vector.multiply(vel, power)
  571. -- Divide by distance
  572. local dist = vector.distance(pos1, pos2)
  573. dist = math.max(dist, 1)
  574. vel = vector.divide(vel, dist)
  575. -- Add old velocity
  576. vel = vector.add(vel, old_vel)
  577. -- randomize it a bit
  578. vel = vector.add(vel, {
  579. x = math.random() - 0.5,
  580. y = math.random() - 0.5,
  581. z = math.random() - 0.5,
  582. })
  583. -- Limit to terminal velocity
  584. dist = vector.length(vel)
  585. if dist > 250 then
  586. vel = vector.divide(vel, dist / 250)
  587. end
  588. return vel
  589. end
  590. local function entity_physics(pos, radius, drops)
  591. local objs = minetest.get_objects_inside_radius(pos, radius)
  592. for _, obj in pairs(objs) do
  593. local obj_pos = obj:get_pos()
  594. local dist = math.max(1, vector.distance(pos, obj_pos))
  595. local damage = (4 / dist) * radius
  596. if obj:is_player() then
  597. -- currently the engine has no method to set
  598. -- player velocity. See #2960
  599. -- instead, we knock the player back 1.0 node, and slightly upwards
  600. local dir = vector.normalize(vector.subtract(obj_pos, pos))
  601. local moveoff = vector.multiply(dir, dist + 1.0)
  602. local newpos = vector.add(pos, moveoff)
  603. newpos = vector.add(newpos, {x = 0, y = 0.2, z = 0})
  604. obj:set_pos(newpos)
  605. obj:set_hp(obj:get_hp() - damage)
  606. else
  607. local do_damage = true
  608. local do_knockback = true
  609. local entity_drops = {}
  610. local luaobj = obj:get_luaentity()
  611. local objdef = minetest.registered_entities[luaobj.name]
  612. local name = luaobj.name
  613. if objdef and objdef.on_blast then
  614. if ((name == "nssm:pumpking") or (name == "nssm:morvalar0") or (name== "nssm:morvalar5")) then
  615. do_damage = false
  616. do_knockback = false
  617. else
  618. do_damage, do_knockback, entity_drops = objdef.on_blast(luaobj, damage)
  619. end
  620. end
  621. if do_knockback then
  622. local obj_vel = obj:get_velocity()
  623. obj:set_velocity(calc_velocity(pos, obj_pos,
  624. obj_vel, radius * 10))
  625. end
  626. if do_damage then
  627. if not obj:get_armor_groups().immortal then
  628. obj:punch(obj, 1.0, {
  629. full_punch_interval = 1.0,
  630. damage_groups = {fleshy = damage},
  631. }, nil)
  632. end
  633. end
  634. for _, item in pairs(entity_drops) do
  635. add_drop(drops, item)
  636. end
  637. end
  638. end
  639. end
  640. local function add_drop(drops, item)
  641. item = ItemStack(item)
  642. local name = item:get_name()
  643. if loss_prob[name] ~= nil and math.random(1, loss_prob[name]) == 1 then
  644. return
  645. end
  646. local drop = drops[name]
  647. if drop == nil then
  648. drops[name] = item
  649. else
  650. drop:set_count(drop:get_count() + item:get_count())
  651. end
  652. end
  653. local function destroy(drops, npos, cid, c_air, c_fire, on_blast_queue, ignore_protection, ignore_on_blast)
  654. if not ignore_protection and minetest.is_protected(npos, "") then
  655. return cid
  656. end
  657. local def = cid_data[cid]
  658. if not def then
  659. return c_air
  660. elseif not ignore_on_blast and def.on_blast then
  661. on_blast_queue[#on_blast_queue + 1] = {pos = vector.new(npos), on_blast = def.on_blast}
  662. return cid
  663. elseif def.flammable then
  664. return c_fire
  665. else
  666. local node_drops = minetest.get_node_drops(def.name, "")
  667. for _, item in pairs(node_drops) do
  668. add_drop(drops, item)
  669. end
  670. return c_air
  671. end
  672. end
  673. local function tnt_explode(pos, radius, ignore_protection, ignore_on_blast)
  674. pos = vector.round(pos)
  675. -- scan for adjacent TNT nodes first, and enlarge the explosion
  676. local vm1 = VoxelManip()
  677. local p1 = vector.subtract(pos, 2)
  678. local p2 = vector.add(pos, 2)
  679. local minp, maxp = vm1:read_from_map(p1, p2)
  680. local a = VoxelArea:new({MinEdge = minp, MaxEdge = maxp})
  681. local data = vm1:get_data()
  682. local count = 0
  683. local c_tnt = minetest.get_content_id("tnt:tnt")
  684. local c_tnt_burning = minetest.get_content_id("tnt:tnt_burning")
  685. local c_tnt_boom = minetest.get_content_id("tnt:boom")
  686. local c_air = minetest.get_content_id("air")
  687. for z = pos.z - 2, pos.z + 2 do
  688. for y = pos.y - 2, pos.y + 2 do
  689. local vi = a:index(pos.x - 2, y, z)
  690. for x = pos.x - 2, pos.x + 2 do
  691. local cid = data[vi]
  692. if cid == c_tnt or cid == c_tnt_boom or cid == c_tnt_burning then
  693. count = count + 1
  694. data[vi] = c_air
  695. end
  696. vi = vi + 1
  697. end
  698. end
  699. end
  700. vm1:set_data(data)
  701. vm1:write_to_map()
  702. -- recalculate new radius
  703. radius = math.floor(radius * math.pow(count, 1/3))
  704. -- perform the explosion
  705. local vm = VoxelManip()
  706. local pr = PseudoRandom(os.time())
  707. p1 = vector.subtract(pos, radius)
  708. p2 = vector.add(pos, radius)
  709. minp, maxp = vm:read_from_map(p1, p2)
  710. a = VoxelArea:new({MinEdge = minp, MaxEdge = maxp})
  711. data = vm:get_data()
  712. local drops = {}
  713. local on_blast_queue = {}
  714. local c_fire = minetest.get_content_id("fire:basic_flame")
  715. for z = -radius, radius do
  716. for y = -radius, radius do
  717. local vi = a:index(pos.x + (-radius), pos.y + y, pos.z + z)
  718. for x = -radius, radius do
  719. local r = vector.length(vector.new(x, y, z))
  720. if (radius * radius) / (r * r) >= (pr:next(80, 125) / 100) then
  721. local cid = data[vi]
  722. local p = {x = pos.x + x, y = pos.y + y, z = pos.z + z}
  723. if cid ~= c_air then
  724. data[vi] = destroy(drops, p, cid, c_air, c_fire,
  725. on_blast_queue, ignore_protection,
  726. ignore_on_blast)
  727. end
  728. end
  729. vi = vi + 1
  730. end
  731. end
  732. end
  733. vm:set_data(data)
  734. vm:write_to_map()
  735. vm:update_map()
  736. vm:update_liquids()
  737. -- call nodeupdate for everything within 1.5x blast radius
  738. for y = -radius * 1.5, radius * 1.5 do
  739. for z = -radius * 1.5, radius * 1.5 do
  740. for x = -radius * 1.5, radius * 1.5 do
  741. local rad = {x = x, y = y, z = z}
  742. local s = vector.add(pos, rad)
  743. local r = vector.length(rad)
  744. if r / radius < 1.4 then
  745. --nodeupdate_single(s)
  746. core.check_single_for_falling(s)
  747. end
  748. end
  749. end
  750. end
  751. for _, queued_data in pairs(on_blast_queue) do
  752. local dist = math.max(1, vector.distance(queued_data.pos, pos))
  753. local intensity = (radius * radius) / (dist * dist)
  754. local node_drops = queued_data.on_blast(queued_data.pos, intensity)
  755. if node_drops then
  756. for _, item in pairs(node_drops) do
  757. add_drop(drops, item)
  758. end
  759. end
  760. end
  761. return drops, radius
  762. end
  763. function tnt_boom_nssm(pos, def)
  764. minetest.sound_play("tnt_explode", {pos = pos, gain = 1.5, max_hear_distance = 2*64})
  765. minetest.set_node(pos, {name = "tnt:boom"})
  766. local drops, radius = tnt_explode(pos, def.radius, def.ignore_protection,
  767. def.ignore_on_blast)
  768. -- append entity drops
  769. local damage_radius = (radius / def.radius) * def.damage_radius
  770. entity_physics(pos, damage_radius, drops)
  771. if not def.disable_drops then
  772. eject_drops(drops, pos, radius)
  773. end
  774. add_effects(pos, radius, drops)
  775. end