api.lua 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. mobs = {}
  2. function mobs:register_mob(name, def)
  3. minetest.register_entity(name, {
  4. hp_max = def.hp_max,
  5. physical = true,
  6. collisionbox = def.collisionbox,
  7. visual = def.visual,
  8. visual_size = def.visual_size,
  9. mesh = def.mesh,
  10. textures = def.textures,
  11. makes_footstep_sound = def.makes_footstep_sound,
  12. view_range = def.view_range,
  13. walk_velocity = def.walk_velocity,
  14. run_velocity = def.run_velocity,
  15. damage = def.damage,
  16. light_damage = def.light_damage,
  17. water_damage = def.water_damage,
  18. lava_damage = def.lava_damage,
  19. disable_fall_damage = def.disable_fall_damage,
  20. drops = def.drops,
  21. armor = def.armor,
  22. drawtype = def.drawtype,
  23. on_rightclick = def.on_rightclick,
  24. type = def.type,
  25. attack_type = def.attack_type,
  26. arrow = def.arrow,
  27. shoot_interval = def.shoot_interval,
  28. sounds = def.sounds,
  29. animation = def.animation,
  30. follow = def.follow,
  31. jump = def.jump or true,
  32. timer = 0,
  33. env_damage_timer = 0, -- only if state = "attack"
  34. attack = {player=nil, dist=nil},
  35. state = "stand",
  36. v_start = false,
  37. old_y = nil,
  38. lifetimer = 600,
  39. tamed = false,
  40. set_velocity = function(self, v)
  41. local yaw = self.object:getyaw()
  42. if self.drawtype == "side" then
  43. yaw = yaw+(math.pi/2)
  44. end
  45. local x = math.sin(yaw) * -v
  46. local z = math.cos(yaw) * v
  47. self.object:setvelocity({x=x, y=self.object:getvelocity().y, z=z})
  48. end,
  49. get_velocity = function(self)
  50. local v = self.object:getvelocity()
  51. return (v.x^2 + v.z^2)^(0.5)
  52. end,
  53. set_animation = function(self, type)
  54. if not self.animation then
  55. return
  56. end
  57. if not self.animation.current then
  58. self.animation.current = ""
  59. end
  60. if type == "stand" and self.animation.current ~= "stand" then
  61. if
  62. self.animation.stand_start
  63. and self.animation.stand_end
  64. and self.animation.speed_normal
  65. then
  66. self.object:set_animation(
  67. {x=self.animation.stand_start,y=self.animation.stand_end},
  68. self.animation.speed_normal, 0
  69. )
  70. self.animation.current = "stand"
  71. end
  72. elseif type == "walk" and self.animation.current ~= "walk" then
  73. if
  74. self.animation.walk_start
  75. and self.animation.walk_end
  76. and self.animation.speed_normal
  77. then
  78. self.object:set_animation(
  79. {x=self.animation.walk_start,y=self.animation.walk_end},
  80. self.animation.speed_normal, 0
  81. )
  82. self.animation.current = "walk"
  83. end
  84. elseif type == "run" and self.animation.current ~= "run" then
  85. if
  86. self.animation.run_start
  87. and self.animation.run_end
  88. and self.animation.speed_run
  89. then
  90. self.object:set_animation(
  91. {x=self.animation.run_start,y=self.animation.run_end},
  92. self.animation.speed_run, 0
  93. )
  94. self.animation.current = "run"
  95. end
  96. elseif type == "punch" and self.animation.current ~= "punch" then
  97. if
  98. self.animation.punch_start
  99. and self.animation.punch_end
  100. and self.animation.speed_normal
  101. then
  102. self.object:set_animation(
  103. {x=self.animation.punch_start,y=self.animation.punch_end},
  104. self.animation.speed_normal, 0
  105. )
  106. self.animation.current = "punch"
  107. end
  108. end
  109. end,
  110. on_step = function(self, dtime)
  111. if self.type == "monster" and minetest.setting_getbool("only_peaceful_mobs") then
  112. self.object:remove()
  113. end
  114. self.lifetimer = self.lifetimer - dtime
  115. if self.lifetimer <= 0 and not self.tamed then
  116. local player_count = 0
  117. for _,obj in ipairs(minetest.env:get_objects_inside_radius(self.object:getpos(), 20)) do
  118. if obj:is_player() then
  119. player_count = player_count+1
  120. end
  121. end
  122. if player_count == 0 and self.state ~= "attack" then
  123. self.object:remove()
  124. return
  125. end
  126. end
  127. if self.object:getvelocity().y > 0.1 then
  128. local yaw = self.object:getyaw()
  129. if self.drawtype == "side" then
  130. yaw = yaw+(math.pi/2)
  131. end
  132. local x = math.sin(yaw) * -2
  133. local z = math.cos(yaw) * 2
  134. self.object:setacceleration({x=x, y=-10, z=z})
  135. else
  136. self.object:setacceleration({x=0, y=-10, z=0})
  137. end
  138. if self.disable_fall_damage and self.object:getvelocity().y == 0 then
  139. if not self.old_y then
  140. self.old_y = self.object:getpos().y
  141. else
  142. local d = self.old_y - self.object:getpos().y
  143. if d > 5 then
  144. local damage = d-5
  145. self.object:set_hp(self.object:get_hp()-damage)
  146. if self.object:get_hp() == 0 then
  147. self.object:remove()
  148. end
  149. end
  150. self.old_y = self.object:getpos().y
  151. end
  152. end
  153. self.timer = self.timer+dtime
  154. if self.state ~= "attack" then
  155. if self.timer < 1 then
  156. return
  157. end
  158. self.timer = 0
  159. end
  160. if self.sounds and self.sounds.random and math.random(1, 100) <= 1 then
  161. minetest.sound_play(self.sounds.random, {object = self.object})
  162. end
  163. local do_env_damage = function(self)
  164. local pos = self.object:getpos()
  165. local n = minetest.env:get_node(pos)
  166. if self.light_damage and self.light_damage ~= 0
  167. and pos.y>0
  168. and minetest.env:get_node_light(pos)
  169. and minetest.env:get_node_light(pos) > 4
  170. and minetest.env:get_timeofday() > 0.2
  171. and minetest.env:get_timeofday() < 0.8
  172. then
  173. self.object:set_hp(self.object:get_hp()-self.light_damage)
  174. if self.object:get_hp() == 0 then
  175. self.object:remove()
  176. end
  177. end
  178. if self.water_damage and self.water_damage ~= 0 and
  179. minetest.get_item_group(n.name, "water") ~= 0
  180. then
  181. self.object:set_hp(self.object:get_hp()-self.water_damage)
  182. if self.object:get_hp() == 0 then
  183. self.object:remove()
  184. end
  185. end
  186. if self.lava_damage and self.lava_damage ~= 0 and
  187. minetest.get_item_group(n.name, "lava") ~= 0
  188. then
  189. self.object:set_hp(self.object:get_hp()-self.lava_damage)
  190. if self.object:get_hp() == 0 then
  191. self.object:remove()
  192. end
  193. end
  194. end
  195. self.env_damage_timer = self.env_damage_timer + dtime
  196. if self.state == "attack" and self.env_damage_timer > 1 then
  197. self.env_damage_timer = 0
  198. do_env_damage(self)
  199. elseif self.state ~= "attack" then
  200. do_env_damage(self)
  201. end
  202. if self.type == "monster" and minetest.setting_getbool("enable_damage") then
  203. for _,player in pairs(minetest.get_connected_players()) do
  204. local s = self.object:getpos()
  205. local p = player:getpos()
  206. local dist = ((p.x-s.x)^2 + (p.y-s.y)^2 + (p.z-s.z)^2)^0.5
  207. if dist < self.view_range then
  208. if self.attack.dist then
  209. if self.attack.dist < dist then
  210. self.state = "attack"
  211. self.attack.player = player
  212. self.attack.dist = dist
  213. end
  214. else
  215. self.state = "attack"
  216. self.attack.player = player
  217. self.attack.dist = dist
  218. end
  219. end
  220. end
  221. end
  222. if self.follow ~= "" and not self.following then
  223. for _,player in pairs(minetest.get_connected_players()) do
  224. local s = self.object:getpos()
  225. local p = player:getpos()
  226. local dist = ((p.x-s.x)^2 + (p.y-s.y)^2 + (p.z-s.z)^2)^0.5
  227. if self.view_range and dist < self.view_range then
  228. self.following = player
  229. end
  230. end
  231. end
  232. if self.following and self.following:is_player() then
  233. if self.following:get_wielded_item():get_name() ~= self.follow then
  234. self.following = nil
  235. self.v_start = false
  236. else
  237. local s = self.object:getpos()
  238. local p = self.following:getpos()
  239. local dist = ((p.x-s.x)^2 + (p.y-s.y)^2 + (p.z-s.z)^2)^0.5
  240. if dist > self.view_range then
  241. self.following = nil
  242. self.v_start = false
  243. else
  244. local vec = {x=p.x-s.x, y=p.y-s.y, z=p.z-s.z}
  245. local yaw = math.atan(vec.z/vec.x)+math.pi/2
  246. if self.drawtype == "side" then
  247. yaw = yaw+(math.pi/2)
  248. end
  249. if p.x > s.x then
  250. yaw = yaw+math.pi
  251. end
  252. self.object:setyaw(yaw)
  253. if dist > 2 then
  254. if not self.v_start then
  255. self.v_start = true
  256. self.set_velocity(self, self.walk_velocity)
  257. else
  258. if self.jump and self.get_velocity(self) <= 0.5 and self.object:getvelocity().y == 0 then
  259. local v = self.object:getvelocity()
  260. v.y = 5
  261. self.object:setvelocity(v)
  262. end
  263. self.set_velocity(self, self.walk_velocity)
  264. end
  265. self:set_animation("walk")
  266. else
  267. self.v_start = false
  268. self.set_velocity(self, 0)
  269. self:set_animation("stand")
  270. end
  271. return
  272. end
  273. end
  274. end
  275. if self.state == "stand" then
  276. if math.random(1, 4) == 1 then
  277. self.object:setyaw(self.object:getyaw()+((math.random(0,360)-180)/180*math.pi))
  278. end
  279. self.set_velocity(self, 0)
  280. self.set_animation(self, "stand")
  281. if math.random(1, 100) <= 50 then
  282. self.set_velocity(self, self.walk_velocity)
  283. self.state = "walk"
  284. self.set_animation(self, "walk")
  285. end
  286. elseif self.state == "walk" then
  287. if math.random(1, 100) <= 30 then
  288. self.object:setyaw(self.object:getyaw()+((math.random(0,360)-180)/180*math.pi))
  289. end
  290. if self.jump and self.get_velocity(self) <= 0.5 and self.object:getvelocity().y == 0 then
  291. local v = self.object:getvelocity()
  292. v.y = 5
  293. self.object:setvelocity(v)
  294. end
  295. self:set_animation("walk")
  296. self.set_velocity(self, self.walk_velocity)
  297. if math.random(1, 100) <= 10 then
  298. self.set_velocity(self, 0)
  299. self.state = "stand"
  300. self:set_animation("stand")
  301. end
  302. elseif self.state == "attack" and self.attack_type == "dogfight" then
  303. if not self.attack.player or not self.attack.player:is_player() then
  304. self.state = "stand"
  305. self:set_animation("stand")
  306. return
  307. end
  308. local s = self.object:getpos()
  309. local p = self.attack.player:getpos()
  310. local dist = ((p.x-s.x)^2 + (p.y-s.y)^2 + (p.z-s.z)^2)^0.5
  311. if dist > self.view_range or self.attack.player:get_hp() <= 0 then
  312. self.state = "stand"
  313. self.v_start = false
  314. self.set_velocity(self, 0)
  315. self.attack = {player=nil, dist=nil}
  316. self:set_animation("stand")
  317. return
  318. else
  319. self.attack.dist = dist
  320. end
  321. local vec = {x=p.x-s.x, y=p.y-s.y, z=p.z-s.z}
  322. local yaw = math.atan(vec.z/vec.x)+math.pi/2
  323. if self.drawtype == "side" then
  324. yaw = yaw+(math.pi/2)
  325. end
  326. if p.x > s.x then
  327. yaw = yaw+math.pi
  328. end
  329. self.object:setyaw(yaw)
  330. if self.attack.dist > 2 then
  331. if not self.v_start then
  332. self.v_start = true
  333. self.set_velocity(self, self.run_velocity)
  334. else
  335. if self.jump and self.get_velocity(self) <= 0.5 and self.object:getvelocity().y == 0 then
  336. local v = self.object:getvelocity()
  337. v.y = 5
  338. self.object:setvelocity(v)
  339. end
  340. self.set_velocity(self, self.run_velocity)
  341. end
  342. self:set_animation("run")
  343. else
  344. self.set_velocity(self, 0)
  345. self:set_animation("punch")
  346. self.v_start = false
  347. if self.timer > 1 then
  348. self.timer = 0
  349. if self.sounds and self.sounds.attack then
  350. minetest.sound_play(self.sounds.attack, {object = self.object})
  351. end
  352. self.attack.player:punch(self.object, 1.0, {
  353. full_punch_interval=1.0,
  354. damage_groups = {fleshy=self.damage}
  355. }, vec)
  356. end
  357. end
  358. elseif self.state == "attack" and self.attack_type == "shoot" then
  359. if not self.attack.player or not self.attack.player:is_player() then
  360. self.state = "stand"
  361. self:set_animation("stand")
  362. return
  363. end
  364. local s = self.object:getpos()
  365. local p = self.attack.player:getpos()
  366. local dist = ((p.x-s.x)^2 + (p.y-s.y)^2 + (p.z-s.z)^2)^0.5
  367. if dist > self.view_range or self.attack.player:get_hp() <= 0 then
  368. self.state = "stand"
  369. self.v_start = false
  370. self.set_velocity(self, 0)
  371. self.attack = {player=nil, dist=nil}
  372. self:set_animation("stand")
  373. return
  374. else
  375. self.attack.dist = dist
  376. end
  377. local vec = {x=p.x-s.x, y=p.y-s.y, z=p.z-s.z}
  378. local yaw = math.atan(vec.z/vec.x)+math.pi/2
  379. if self.drawtype == "side" then
  380. yaw = yaw+(math.pi/2)
  381. end
  382. if p.x > s.x then
  383. yaw = yaw+math.pi
  384. end
  385. self.object:setyaw(yaw)
  386. self.set_velocity(self, 0)
  387. if self.timer > self.shoot_interval and math.random(1, 100) <= 60 then
  388. self.timer = 0
  389. self:set_animation("punch")
  390. if self.sounds and self.sounds.attack then
  391. minetest.sound_play(self.sounds.attack, {object = self.object})
  392. end
  393. local p = self.object:getpos()
  394. p.y = p.y + (self.collisionbox[2]+self.collisionbox[5])/2
  395. local obj = minetest.env:add_entity(p, self.arrow)
  396. local amount = (vec.x^2+vec.y^2+vec.z^2)^0.5
  397. local v = obj:get_luaentity().velocity
  398. vec.y = vec.y+1
  399. vec.x = vec.x*v/amount
  400. vec.y = vec.y*v/amount
  401. vec.z = vec.z*v/amount
  402. obj:setvelocity(vec)
  403. end
  404. end
  405. end,
  406. on_activate = function(self, staticdata, dtime_s)
  407. self.object:set_armor_groups({fleshy=self.armor})
  408. self.object:setacceleration({x=0, y=-10, z=0})
  409. self.state = "stand"
  410. self.object:setvelocity({x=0, y=self.object:getvelocity().y, z=0})
  411. self.object:setyaw(math.random(1, 360)/180*math.pi)
  412. if self.type == "monster" and minetest.setting_getbool("only_peaceful_mobs") then
  413. self.object:remove()
  414. end
  415. self.lifetimer = 600 - dtime_s
  416. if staticdata then
  417. local tmp = minetest.deserialize(staticdata)
  418. if tmp and tmp.lifetimer then
  419. self.lifetimer = tmp.lifetimer - dtime_s
  420. end
  421. if tmp and tmp.tamed then
  422. self.tamed = tmp.tamed
  423. end
  424. end
  425. if self.lifetimer <= 0 and not self.tamed then
  426. self.object:remove()
  427. end
  428. end,
  429. get_staticdata = function(self)
  430. local tmp = {
  431. lifetimer = self.lifetimer,
  432. tamed = self.tamed,
  433. }
  434. return minetest.serialize(tmp)
  435. end,
  436. on_punch = function(self, hitter)
  437. if self.object:get_hp() <= 0 then
  438. if hitter and hitter:is_player() and hitter:get_inventory() then
  439. for _,drop in ipairs(self.drops) do
  440. if math.random(1, drop.chance) == 1 then
  441. hitter:get_inventory():add_item("main", ItemStack(drop.name.." "..math.random(drop.min, drop.max)))
  442. end
  443. end
  444. end
  445. end
  446. end,
  447. })
  448. end
  449. mobs.spawning_mobs = {}
  450. function mobs:register_spawn(name, nodes, max_light, min_light, chance, active_object_count, max_height, spawn_func)
  451. mobs.spawning_mobs[name] = true
  452. minetest.register_abm({
  453. nodenames = nodes,
  454. neighbors = {"air"},
  455. interval = 30,
  456. chance = chance,
  457. action = function(pos, node, _, active_object_count_wider)
  458. if active_object_count_wider > active_object_count then
  459. return
  460. end
  461. if not mobs.spawning_mobs[name] then
  462. return
  463. end
  464. pos.y = pos.y+1
  465. if not minetest.env:get_node_light(pos) then
  466. return
  467. end
  468. if minetest.env:get_node_light(pos) > max_light then
  469. return
  470. end
  471. if minetest.env:get_node_light(pos) < min_light then
  472. return
  473. end
  474. if pos.y > max_height then
  475. return
  476. end
  477. if minetest.env:get_node(pos).name ~= "air" then
  478. return
  479. end
  480. pos.y = pos.y+1
  481. if minetest.env:get_node(pos).name ~= "air" then
  482. return
  483. end
  484. if spawn_func and not spawn_func(pos, node) then
  485. return
  486. end
  487. if minetest.setting_getbool("display_mob_spawn") then
  488. minetest.chat_send_all("[mobs] Add "..name.." at "..minetest.pos_to_string(pos))
  489. end
  490. minetest.env:add_entity(pos, name)
  491. end
  492. })
  493. end
  494. function mobs:register_arrow(name, def)
  495. minetest.register_entity(name, {
  496. physical = false,
  497. visual = def.visual,
  498. visual_size = def.visual_size,
  499. textures = def.textures,
  500. velocity = def.velocity,
  501. hit_player = def.hit_player,
  502. hit_node = def.hit_node,
  503. on_step = function(self, dtime)
  504. local pos = self.object:getpos()
  505. if minetest.env:get_node(self.object:getpos()).name ~= "air" then
  506. self.hit_node(self, pos, node)
  507. self.object:remove()
  508. return
  509. end
  510. pos.y = pos.y-1
  511. for _,player in pairs(minetest.env:get_objects_inside_radius(pos, 1)) do
  512. if player:is_player() then
  513. self.hit_player(self, player)
  514. self.object:remove()
  515. return
  516. end
  517. end
  518. end
  519. })
  520. end