dragonflies.lua 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. local modname = minetest.get_current_modname()
  2. local function get_random_colormod()
  3. local r = math.random(256) - 1
  4. local g = math.random(256) - 1
  5. local b = math.random(256) - 1
  6. local hex = string.format("%.2X%.2X%.2X", r, g, b)
  7. return "^[multiply:#" .. hex
  8. end
  9. local function get_dragonfly_textures(colormod)
  10. return {
  11. "eg_bugs_dragonfly_thorax.png" .. colormod,
  12. "eg_bugs_dragonfly_wings.png",
  13. "eg_bugs_dragonfly_head.png" .. colormod,
  14. "eg_bugs_dragonfly_eye.png",
  15. }
  16. end
  17. local function get_firebreath_toolcaps(size)
  18. return {
  19. full_punch_interval = 10,
  20. damage_groups = {
  21. fire = math.ceil(size)
  22. }
  23. }
  24. end
  25. minetest.register_entity(modname .. ":firebreath", {
  26. initial_properties = {
  27. visual = "sprite",
  28. textures = {"eg_bugs_firebreath.png"},
  29. glow = 10, --BROKEN?
  30. collide_with_objects = true,
  31. physical = true,
  32. collisionbox = {-0.25, -0.35, -0.25, 0.25, 0.25, 0.25},
  33. },
  34. on_activate = function(self, staticdata)
  35. local data = minetest.deserialize(staticdata)
  36. -- Don't brick the world with crashes if no staticdata is passed for some reason.
  37. if not data then
  38. self.object:remove()
  39. return
  40. end
  41. self.object:set_velocity(data.vel)
  42. self.size = data.size
  43. end,
  44. get_staticdata = function(self)
  45. local data = {
  46. vel = self.object:get_velocity(),
  47. size = self.size,
  48. }
  49. if not vel then return end
  50. return minetest.serialize(data)
  51. end,
  52. on_step = function(self, dtime, moveresult)
  53. self.size = self.size - dtime
  54. local pos = self.object:get_pos()
  55. if self.size < 0.1 or
  56. minetest.get_item_group(minetest.get_node(pos).name, "water") > 0 then
  57. self.object:remove()
  58. return
  59. end
  60. if moveresult.collides then
  61. -- Spawn a smoke puff
  62. minetest.add_particlespawner({
  63. amount = 10,
  64. time = 0.1,
  65. minpos = pos,
  66. maxpos = pos,
  67. glow = 15,
  68. minvel = vector.new(-0.3, 1, -0.3),
  69. maxvel = vector.new(0.3, 3, 0.3),
  70. minsize = 1,
  71. maxsize = 5,
  72. texture = "eg_bugs_smokepuff.png",
  73. minexptime = 0.3,
  74. maxexptime = 0.5,
  75. })
  76. -- Damage collided objects
  77. for i, v in ipairs(moveresult.collisions) do
  78. if v.type == "object" then
  79. v.object:punch(self.object, 10, get_firebreath_toolcaps(self.size))
  80. end
  81. end
  82. --disappear
  83. self.object:remove()
  84. return
  85. end
  86. local props = self.object:get_properties()
  87. local collbox_offset = self.size * 0.125
  88. props.collisionbox = {
  89. -collbox_offset,
  90. -collbox_offset,
  91. -collbox_offset,
  92. collbox_offset,
  93. collbox_offset,
  94. collbox_offset,
  95. }
  96. props.visual_size = vector.new(self.size, self.size, self.size)
  97. self.object:set_properties(props)
  98. end,
  99. })
  100. minetest.register_craftitem(modname .. ":firebreath_debug", {
  101. inventory_image = "eg_bugs_firebreath.png",
  102. on_use = function(itemstack, user)
  103. local dir = user:get_look_dir()
  104. local pos = user:get_pos()
  105. pos.y = pos.y + 1.4
  106. pos = vector.add(pos, dir)
  107. local data = {vel = vector.multiply(dir, 5), size = math.random(3)}
  108. minetest.add_entity(pos, modname .. ":firebreath", minetest.serialize(data))
  109. end
  110. })
  111. local function lq_hover_to_pos(self, pos, turn_speed, tolerance, speed_factor)
  112. speed_factor = speed_factor or 1
  113. tolerance = tolerance or 1
  114. turn_speed = turn_speed or 1
  115. local oldpos = pos
  116. local func = function()
  117. local selfpos = self.object:get_pos()
  118. -- Stop when there or stuck
  119. if vector.distance(selfpos, pos) < tolerance or
  120. vector.distance(selfpos, oldpos) < self.max_speed * speed_factor * self.dtime then
  121. return true
  122. end
  123. oldpos = selfpos
  124. local dir = vector.direction(selfpos, pos)
  125. mobkit.turn2yaw(self, minetest.dir_to_yaw(dir), turn_speed)
  126. self.object:set_velocity(vector.multiply(dir, self.max_speed * speed_factor))
  127. end
  128. mobkit.queue_low(self, func)
  129. end
  130. local function lq_turn2yaw_hovering(self, desired_yaw, rate)
  131. local func = function()
  132. self.object:set_velocity(vector.new(0, 0, 0)) -- TODO: maybe small random movement
  133. return mobkit.turn2yaw(self, desired_yaw, rate)
  134. end
  135. mobkit.queue_low(self, func)
  136. end
  137. local function lq_turn2pos_hovering(self, destpos, rate)
  138. local yaw = minetest.dir_to_yaw(vector.direction(self.object:get_pos(), destpos))
  139. lq_turn2yaw_hovering(self, yaw, rate)
  140. end
  141. local function flyable(selfpos, tgtpos)
  142. local raycast = Raycast(selfpos, tgtpos, true, true)
  143. raycast:next() --Get self out of list
  144. return not raycast:next()
  145. end
  146. local function attack(self, tgt)
  147. local data = {}
  148. local selfpos = self.object:get_pos()
  149. local tgtpos = tgt:get_pos()
  150. tgtpos.y = tgtpos.y + 1
  151. local dir = vector.direction(selfpos, tgtpos)
  152. local pos = vector.add(selfpos, dir)
  153. data.vel = vector.multiply(dir, 10)
  154. data.size = math.random(3)
  155. minetest.add_entity(pos, modname .. ":firebreath", minetest.serialize(data))
  156. mobkit.make_sound(self, "breathefire")
  157. end
  158. local function lq_strafe(self, tgtpos)
  159. local selfpos = self.object:get_pos()
  160. local dir = vector.direction(selfpos, tgtpos)
  161. local dest
  162. for i = 1, 4 do
  163. dest = vector.add(selfpos, vector.multiply(vector.rotate_around_axis(
  164. vector.new(0, 0, 1),
  165. dir,
  166. math.random() * 2 * math.pi),
  167. math.random() * 2 + 1))
  168. if flyable(selfpos, dest) then
  169. break
  170. end
  171. end
  172. lq_hover_to_pos(self, dest, 6)
  173. end
  174. minetest.register_on_joinplayer(function(player)
  175. player:set_armor_groups({fire = 100})
  176. end)
  177. local function hq_dragonfly_roam(self, prty)
  178. local func = function()
  179. if mobkit.timer(self, 0.5) then
  180. if not mobkit.is_queue_empty_low(self) then
  181. return
  182. end
  183. local rand = math.random(10)
  184. if rand == 1 then
  185. lq_turn2yaw_hovering(self, math.random() * 2 * math.pi, 4)
  186. elseif rand > 1 then
  187. --find position
  188. local rand_vec = vector.normalize(vector.new(math.random() - 0.5, math.random() - (not self.isonground and 0.55 or 0), math.random() - 0.5))
  189. if vector.length(rand_vec) == 0 then
  190. return
  191. end
  192. rand_vec = vector.multiply(rand_vec, math.random(self.view_range))
  193. local pos = self.object:get_pos()
  194. local dest = vector.add(pos, rand_vec)
  195. --fly to position if possible
  196. if flyable(pos, dest) then
  197. lq_hover_to_pos(self, dest, 6)
  198. end
  199. end
  200. end
  201. end
  202. mobkit.queue_high(self, func, prty)
  203. end
  204. local function hq_dragonfly_hunt(self, tgtobj, prty)
  205. local tgt = tgt or mobkit.get_nearby_player(self)
  206. local func = function()
  207. if not mobkit.timer(self, 0.5) then
  208. return
  209. end
  210. if not mobkit.exists(tgt) then
  211. --TODO: test if gives up dead player
  212. return true
  213. end
  214. mobkit.clear_queue_low(self)
  215. local selfpos = self.object:get_pos()
  216. local tgtpos = tgt:get_pos()
  217. tgtpos.y = tgtpos.y + 1 --TODO: un-hardcode
  218. local dist = vector.distance(selfpos, tgtpos)
  219. if dist > 16 then
  220. local cpos = vector.divide(vector.add(tgtpos, selfpos), 2)
  221. if not flyable(selfpos, cpos) then
  222. cpos = vector.add(selfpos, vector.new(math.random() * 2 - 1,
  223. math.random() * 2 - 1,
  224. math.random() * 2 - 1))
  225. end
  226. lq_hover_to_pos(self, cpos, 6)
  227. elseif dist < 5 then
  228. local dir = vector.direction(tgtpos, selfpos)
  229. local cpos = vector.add(tgtpos, vector.multiply(dir, 6))
  230. for i = 1, 5 do
  231. if flyable(selfpos, cpos) then
  232. break
  233. end
  234. cpos = vector.add(selfpos, vector.new(math.random() * 8 - 4,
  235. math.random() * 8 - 3,
  236. math.random() * 8 - 4))
  237. end
  238. lq_hover_to_pos(self, cpos, 6)
  239. elseif math.random(2) == 1 then
  240. lq_turn2pos_hovering(self, tgtpos, 6)
  241. attack(self, tgt)
  242. else
  243. lq_strafe(self, tgtpos)
  244. end
  245. end
  246. mobkit.queue_high(self, func, prty)
  247. end
  248. minetest.register_entity(modname .. ":dragonfly",
  249. {
  250. initial_properties =
  251. {
  252. visual = "mesh",
  253. mesh = "eg_bugs_dragonfly.b3d",
  254. physical = true,
  255. collisionbox = {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25},
  256. glow = 5,
  257. backface_culling = false,
  258. },
  259. timeout = 50,
  260. buoyancy = 0.2,
  261. lung_capacity = 10,
  262. max_hp = 10,
  263. sounds = {
  264. breathefire =
  265. "eg_bugs_dragonfly_firebreath",
  266. },
  267. on_step = mobkit.stepfunc,
  268. on_activate = function(self, staticdata, dtime_s)
  269. mobkit.actfunc(self, staticdata, dtime_s)
  270. local props = self.object:get_properties()
  271. -- set texture
  272. local colormod = mobkit.recall(self, "colormod")
  273. if not colormod then
  274. colormod = get_random_colormod()
  275. mobkit.remember(self, "colormod", colormod)
  276. end
  277. props.textures = get_dragonfly_textures(colormod)
  278. -- set size
  279. local size = mobkit.recall(self, "size")
  280. if not size then
  281. size = math.random()
  282. -- hypercube size so small bugs are more common
  283. size = (size * size * size * size * 3) + 1
  284. mobkit.remember(self, "size", size)
  285. end
  286. props.visual_size = vector.new(size, size, size)
  287. for i, v in ipairs(props.collisionbox) do
  288. props.collisionbox[i] = v * size
  289. end
  290. self.object:set_properties(props)
  291. hq_dragonfly_roam(self, 1)
  292. end,
  293. get_staticdata = mobkit.statfunc,
  294. logic = function(self)
  295. if mobkit.is_queue_empty_low(self) then
  296. self.object:set_acceleration({x = 0, y = -0.2, z = 0})
  297. end
  298. if mobkit.get_queue_priority(self) < 5 then
  299. local p = mobkit.get_nearby_player(self)
  300. if p then
  301. hq_dragonfly_hunt(self, p, 10)
  302. end
  303. end
  304. end,
  305. max_speed = 5,
  306. jump_height = 1,
  307. view_range = 16,
  308. attack = {range = 1, damage_groups = {fleshy = 2}},
  309. armor_groups = {fleshy = 3}
  310. })