botete.lua 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. local S = minetest.get_translator("mob_botete")
  2. local function place_goo(pos, life)
  3. if life == nil then
  4. life = 1
  5. elseif life <= 0 then
  6. return false
  7. end
  8. local node = minetest.get_node_or_nil(pos)
  9. if not node or node.name ~= "air" and not minetest.registered_nodes[node.name].buildable_to then
  10. return false
  11. end
  12. if node.name == "mob_botete:goo" then
  13. minetest.dig_node(pos)
  14. minetest.place_node(pos, {name="mob_botete:goo_block"})
  15. minetest.get_meta(pos):set_int("life", life)
  16. return true
  17. else
  18. local dirs = {
  19. {x = 0, y =-1, z = 0},
  20. {x =-1, y = 0, z = 0},
  21. {x = 0, y = 0, z = 1},
  22. {x = 0, y = 0, z =-1},
  23. {x = 1, y = 0, z = 0},
  24. {x = 0, y = 1, z = 0},
  25. }
  26. local dir = nil
  27. for _,d in ipairs(dirs) do
  28. local wall_pos = vector.add(pos, d)
  29. local wall = minetest.get_node_or_nil(wall_pos)
  30. if wall and wall.name ~= "air" and minetest.get_item_group(wall.name, "caustic_goo") == 0 then
  31. dir = d
  32. break
  33. end
  34. end
  35. if dir then
  36. local wallmounted = minetest.dir_to_wallmounted(dir)
  37. if minetest.registered_nodes[node.name].buildable_to then
  38. minetest.place_node(pos, {name="mob_botete:goo", param2=wallmounted})
  39. minetest.get_meta(pos):set_int("life", life)
  40. return true
  41. end
  42. end
  43. end
  44. return false
  45. end
  46. local function goo_consume(pos, life)
  47. local node = minetest.get_node_or_nil(pos)
  48. if not node or node.name == "air" then
  49. return false
  50. end
  51. local neighbors = {
  52. {x = 1, y = 0, z = 0},
  53. {x =-1, y = 0, z = 0},
  54. {x = 0, y = 1, z = 0},
  55. {x = 0, y =-1, z = 0},
  56. {x = 0, y = 0, z = 1},
  57. {x = 0, y = 0, z =-1},
  58. }
  59. local count = 0
  60. for _,n in ipairs(neighbors) do
  61. local neighbor = minetest.get_node_or_nil(vector.add(pos, n))
  62. if neighbor and minetest.get_item_group(neighbor.name, "caustic_goo") > 0 then
  63. count = count + 1
  64. end
  65. end
  66. if count >= math.random(2,5) then
  67. minetest.dig_node(pos)
  68. minetest.place_node(pos, {name="mob_botete:goo_block"})
  69. minetest.get_meta(pos):set_int("life", life)
  70. end
  71. end
  72. local function spread_goo(pos)
  73. local node = minetest.get_node_or_nil(pos)
  74. if node then
  75. local life = minetest.get_meta(pos):get_int("life") or 0
  76. if life > 1 then
  77. minetest.get_meta(pos):set_int("life", life - 1)
  78. local dirs = {
  79. {x = 1, y = 0, z = 0},
  80. {x =-1, y = 0, z = 0},
  81. {x = 0, y = 1, z = 0},
  82. {x = 0, y =-1, z = 0},
  83. {x = 0, y = 0, z = 1},
  84. {x = 0, y = 0, z =-1},
  85. }
  86. local dir = nil
  87. if node.name == "mob_botete:goo" then
  88. dir = minetest.wallmounted_to_dir(node.param2)
  89. else
  90. dir = dirs[4]
  91. end
  92. goo_consume(vector.add(pos, dir), life - 1)
  93. for _,d in ipairs(dirs) do
  94. local dot_product = d.x * dir.x + d.y * dir.y + d.z * dir.z
  95. if dot_product == 0 then
  96. if math.random() < 0.25 then
  97. local npos = vector.add(pos, d)
  98. if not place_goo(vector.add(npos, dir), life - 1) then
  99. if not place_goo(npos, life - 1) then
  100. place_goo(vector.subtract(npos, dir), life - 1)
  101. end
  102. end
  103. end
  104. end
  105. end
  106. end
  107. if life <= 1 and math.random() < 0.2 then
  108. minetest.dig_node(pos)
  109. end
  110. end
  111. end
  112. defense_mob_api.register_mob("mob_botete:botete", {
  113. hp_max = 2,
  114. collisionbox = {-0.6,-0.7,-0.6, 0.6,0.4,0.6},
  115. mesh = "mob_botete_botete.b3d",
  116. textures = {"mob_botete_botete.png"},
  117. makes_footstep_sound = false,
  118. animation = {
  119. idle = {a=0, b=39, rate=20},
  120. attack = {a=80, b=99, rate=25},
  121. move = {a=40, b=79, rate=25},
  122. move_attack = {a=80, b=99, rate=25},
  123. },
  124. smart_path = false,
  125. mass = 1,
  126. movement = "air",
  127. move_speed = 4,
  128. attack_damage = 0,
  129. attack_range = 8,
  130. attack_interval = 16,
  131. on_step = function(self, dtime)
  132. defense_mob_api.default_prototype.on_step(self, dtime)
  133. if self.last_attack_time + self.attack_interval * 0.5 < self.timer or self.last_attack_time + 0.25 > self.timer then
  134. self:hunt()
  135. elseif not self.destination then
  136. self.destination = vector.add(self.object:get_pos(), {x=math.random(-10,10), y=math.random(-5,6), z=math.random(-10,10)})
  137. self.automatic_face_movement_dir = true
  138. self.object:set_properties({automatic_face_movement_dir = self.automatic_face_movement_dir})
  139. end
  140. end,
  141. attack = function(self, obj, dir)
  142. local pos = self.object:get_pos()
  143. local hdir = vector.normalize({x=dir.x, y=0, z=dir.z})
  144. pos = vector.add(pos, vector.multiply(hdir, 0.4))
  145. local s = 9 -- Launch speed
  146. local g = -defense_mob_api.gravity
  147. -- Calculate launch angle
  148. local angle
  149. local delta = vector.subtract(obj:get_pos(), pos)
  150. local x2 = delta.x*delta.x + delta.z*delta.z
  151. if x2 == 0 then return end
  152. local s2 = s*s
  153. local r = s2*s2 - g * (g*x2 + 2*delta.y*s2)
  154. if r >= 0 then
  155. angle = math.atan((s2 + math.sqrt(r) * (math.random(0,1)*2-1))/(g*math.sqrt(x2)))
  156. else
  157. angle = math.pi/4
  158. end
  159. -- Calculate initial velocity
  160. local xs = math.cos(angle) * s * (0.9 + math.random() * 0.2)
  161. local ys = math.sin(angle) * s * (0.9 + math.random() * 0.2)
  162. local horiz_angle = math.atan2(delta.z, delta.x) + (math.random() * 0.1 - 0.05)
  163. local v = {
  164. x = math.cos(horiz_angle) * xs,
  165. y = ys,
  166. z = math.sin(horiz_angle) * xs
  167. }
  168. -- Launch projectile
  169. local projectile = minetest.add_entity(pos, "mob_botete:gooball")
  170. projectile:setvelocity(v)
  171. self.object:setvelocity(vector.multiply(v, -0.4))
  172. self.automatic_face_movement_dir = false
  173. self.object:set_properties({automatic_face_movement_dir = self.automatic_face_movement_dir})
  174. if math.random() < 0.1 then
  175. self.attack_range = 4 + math.random() * 4
  176. end
  177. end,
  178. on_death = function(self)
  179. local pos = self.object:get_pos()
  180. for i=1,math.random(5,7) do
  181. local projectile = minetest.add_entity(pos, "mob_botete:gooball")
  182. projectile:setvelocity({
  183. x = -2 + math.random() * 4,
  184. y = -1 + math.random() * 4,
  185. z = -2 + math.random() * 4
  186. })
  187. end
  188. end,
  189. })
  190. -- Goo projectile
  191. minetest.register_entity("mob_botete:gooball", {
  192. physical = false,
  193. visual = "sprite",
  194. visual_size = {x=1, y=1},
  195. textures = {"mob_botete_gooball.png"},
  196. on_activate = function(self, staticdata)
  197. self.object:setacceleration({x=0, y=defense_mob_api.gravity, z=0})
  198. end,
  199. on_step = function(self, dtime)
  200. local pos = self.object:get_pos()
  201. local node = minetest.get_node(pos)
  202. if minetest.get_item_group(node.name, "caustic_goo") > 0 then
  203. self.object:remove()
  204. elseif node.name ~= "air" then
  205. local space = pos
  206. local nvel = vector.normalize(self.object:getvelocity())
  207. local back = vector.multiply(nvel, -1)
  208. local bnode
  209. repeat
  210. space = vector.add(space, back)
  211. bnode = minetest.get_node_or_nil(space)
  212. until not bnode or bnode.name == "air"
  213. place_goo(space, 8)
  214. self.object:remove()
  215. end
  216. end,
  217. })
  218. -- Goo node
  219. minetest.register_node("mob_botete:goo", {
  220. description = S("Caustic Goo"),
  221. tiles = {{
  222. name="mob_botete_goo.png",
  223. animation={type="vertical_frames", aspect_w=16, aspect_h=16, length=0.7}
  224. }},
  225. inventory_image = "mob_botete_gooball.png",
  226. drawtype = "nodebox",
  227. node_box = {
  228. type = "fixed",
  229. fixed = {-0.5, -0.5, -0.5, 0.5, -0.5+2/16, 0.5},
  230. },
  231. selection_box = {
  232. type = "wallmounted",
  233. },
  234. paramtype = "light",
  235. paramtype2 = "wallmounted",
  236. liquid_viscosity = 4,
  237. liquidtype = "source",
  238. liquid_alternative_flowing = "mob_botete:goo",
  239. liquid_alternative_source = "mob_botete:goo",
  240. liquid_renewable = false,
  241. liquid_range = 0,
  242. groups = {caustic_goo=1, crumbly=3, dig_immediate=3, liquid=3, attached_node=1, disable_jump=1},
  243. drop = "",
  244. walkable = false,
  245. buildable_to = false,
  246. damage_per_second = 1,
  247. on_construct = function(pos)
  248. minetest.get_node_timer(pos):start(1 + math.random() * 4)
  249. end,
  250. on_timer = function(pos, elapsed)
  251. spread_goo(pos)
  252. return true
  253. end,
  254. })
  255. -- Goo block
  256. minetest.register_node("mob_botete:goo_block", {
  257. description = S("Caustic Goo Block"),
  258. tiles = {{
  259. name="mob_botete_goo.png",
  260. animation={type="vertical_frames", aspect_w=16, aspect_h=16, length=0.7}
  261. }},
  262. drawtype = "allfaces",
  263. paramtype = "light",
  264. liquid_viscosity = 8,
  265. liquidtype = "source",
  266. liquid_alternative_flowing = "mob_botete:goo_block",
  267. liquid_alternative_source = "mob_botete:goo_block",
  268. liquid_renewable = false,
  269. liquid_range = 0,
  270. post_effect_color = {r=100, g=240, b=0, a=240},
  271. groups = {caustic_goo=1, crumbly=3, dig_immediate=3, liquid=3, falling_node=1, disable_jump=1},
  272. drop = "",
  273. walkable = false,
  274. buildable_to = false,
  275. damage_per_second = 1,
  276. on_construct = function(pos)
  277. minetest.get_node_timer(pos):start(0.5 + math.random() * 2)
  278. end,
  279. on_timer = function(pos, elapsed)
  280. spread_goo(pos)
  281. return true
  282. end,
  283. })
  284. minetest.register_alias("botete", "mob_boteete:botete")