entity.lua 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. -- Localize for performance.
  2. local vector_distance = vector.distance
  3. local vector_round = vector.round
  4. local math_floor = math.floor
  5. local math_max = math.max
  6. local math_random = math.random
  7. function pm.follower_on_activate(self, staticdata, dtime_s)
  8. if staticdata and staticdata ~= "" then
  9. local data = minetest.deserialize(staticdata)
  10. if type(data) == "table" then
  11. for k, v in pairs(data) do
  12. pm.debug_chat("on_activate(): self["..k.."]="..tostring(v))
  13. self[k] = v
  14. end
  15. return
  16. end
  17. end
  18. -- Otherwise, set up default data.
  19. self._timer = self._timer or 0
  20. self._lifetime = self._lifetime or 60*60*24
  21. self._sound_time = self._sound_time or 0
  22. end
  23. function pm.follower_get_staticdata(self)
  24. local data = {}
  25. for k, v in pairs(self) do
  26. local t = type(v)
  27. if t == "number" or t == "string" or t == "boolean" then
  28. if k:find("_") == 1 then
  29. pm.debug_chat("get_staticdata(): data["..k.."]="..tostring(v))
  30. data[k] = v
  31. end
  32. end
  33. end
  34. return minetest.serialize(data) or ""
  35. end
  36. function pm.get_wanted_velocity(self)
  37. if self and self._path then
  38. if #(self._path) > (pm.sight_range * 0.75) then
  39. return pm.run_velocity
  40. elseif #(self._path) < 5 then
  41. return pm.walk_velocity
  42. else
  43. return pm.velocity
  44. end
  45. end
  46. return pm.velocity
  47. end
  48. function pm.follower_on_step(self, dtime, moveresult)
  49. -- Remove object once we're old enough.
  50. if not self._lifetime then
  51. self.object:remove()
  52. return
  53. end
  54. if not self._no_lifespan_limit then
  55. self._lifetime = self._lifetime - dtime
  56. if self._lifetime < 0 then
  57. self.object:remove()
  58. return
  59. end
  60. end
  61. if self._mkrm then
  62. self.object:remove()
  63. return
  64. end
  65. -- Cooldown timer for the pathfinder, since using it is intensive.
  66. if self._failed_pathfind_cooldown then
  67. self._failed_pathfind_cooldown = self._failed_pathfind_cooldown - dtime
  68. if self._failed_pathfind_cooldown < 0 then
  69. self._failed_pathfind_cooldown = nil
  70. -- Also reset path/target info so we have a chance to acquire fresh data.
  71. self._goto = nil
  72. self._path = nil
  73. end
  74. end
  75. if self._acquire_target_cooldown then
  76. self._acquire_target_cooldown = self._acquire_target_cooldown - dtime
  77. if self._acquire_target_cooldown < 0 then
  78. self._acquire_target_cooldown = nil
  79. end
  80. end
  81. if self._wander_cooldown then
  82. self._wander_cooldown = self._wander_cooldown - dtime
  83. if self._wander_cooldown < 0 then
  84. self._wander_cooldown = nil
  85. end
  86. end
  87. -- Entity changes its behavior every so often.
  88. if not self._no_autochose_behavior then
  89. if not self._behavior_timer or self._behavior_timer < 0 then
  90. self._behavior_timer = math_random(1, 20)*60
  91. pm.choose_random_behavior(self)
  92. end
  93. self._behavior_timer = self._behavior_timer - dtime
  94. end
  95. -- Entities sometimes get stuck against objects.
  96. -- Unstick them by rounding their positions to the nearest air node.
  97. if not self._unstick_timer or self._unstick_timer < 0 then
  98. self._unstick_timer = math_random(1, 30)
  99. local air = minetest.find_node_near(self.object:get_pos(), 1, "air", true)
  100. if air then
  101. self.object:set_pos(air)
  102. end
  103. end
  104. self._unstick_timer = self._unstick_timer - dtime
  105. -- Sound timer.
  106. if not self._sound_time then self._sound_time = 0 end
  107. self._sound_time = self._sound_time - dtime
  108. if self._sound_time < 0 then
  109. self._sound_time = math_random(100, 300)/100
  110. if not self._no_sound then
  111. ambiance.sound_play("wisp", self.object:get_pos(), 0.2, 32)
  112. end
  113. end
  114. -- If currently following a path, remove waypoints as we reach them.
  115. if self._path and #(self._path) > 0 then
  116. local p = self._path[1]
  117. -- Remove waypoint from path if we've reached it.
  118. if vector_distance(p, self.object:get_pos()) < 0.75 then
  119. pm.debug_chat('hit waypoint')
  120. self._stuck_timer = 2
  121. table.remove(self._path, 1)
  122. end
  123. -- Remove path when all waypoints exhausted.
  124. if #(self._path) < 1 then
  125. pm.debug_chat('finished following path: ' .. minetest.pos_to_string(self._goto))
  126. pm.debug_chat('node at terminus: ' .. minetest.get_node(self._goto).name)
  127. self._path = nil
  128. self._path_is_los = nil
  129. self._stuck_timer = nil
  130. self._failed_pathfind_cooldown = nil
  131. self.object:set_velocity({x=0, y=0, z=0})
  132. end
  133. if self._stuck_timer then
  134. self._stuck_timer = self._stuck_timer - dtime
  135. if self._stuck_timer < 0 then
  136. pm.debug_chat('got stuck following path')
  137. -- Got stuck trying to follow path.
  138. -- This is caused because the entity is physical and may collide with
  139. -- the environment. Blocks may have been added in the entity's path, or
  140. -- (more usually) the entity did not properly navigate a corner.
  141. -- We should seek a new target.
  142. self._goto = nil
  143. self._path = nil
  144. self._target = nil
  145. self._failed_pathfind_cooldown = math_random(pm.pf_cooldown_min, pm.pf_cooldown_max)
  146. self._stuck_timer = nil
  147. self._wander_cooldown = nil
  148. self.object:set_velocity({x=0, y=0, z=0})
  149. end
  150. end
  151. end
  152. -- Main logic timer.
  153. -- This controls how often the main "AI" logic runs.
  154. self._timer = self._timer + dtime
  155. if self._timer < 0.3 then return end
  156. self._timer = 0
  157. -- Spawn particles to indicate our location.
  158. local pos = self.object:get_pos()
  159. pm.follower_spawn_particles(pos, self.object)
  160. -- Find new target/goal-waypoint if we don't have one.
  161. if not self._acquire_target_cooldown and not self._failed_pathfind_cooldown then
  162. if not self._goto and not self._path then
  163. local tp, target = self._interest_point(self, pos)
  164. if target then
  165. -- Target is another entity.
  166. pm.debug_chat('acquired moving target')
  167. --if target:is_player() then pm.debug_chat('targeting player') end
  168. local s = tp
  169. if not s then s = target:get_pos() end
  170. if s then
  171. s = vector_round(s)
  172. if pm.target_is_player_or_mob(target) then
  173. s.y = s.y + 1 -- For players or mobs, seek above them, not at their feet.
  174. end
  175. s = minetest.find_node_near(s, 1, "air", true)
  176. -- Target must be standing in air.
  177. -- Otherwise it would never be reachable.
  178. if s then
  179. -- Don't reacquire target if we're already sitting on it.
  180. if vector_distance(pos, s) > pm.range then
  181. pm.debug_chat('set moving target goal')
  182. self._goto = vector_round(s)
  183. self._target = target -- Userdata object.
  184. end
  185. end
  186. end
  187. self._acquire_target_cooldown = math_random(pm.aq_cooldown_min, pm.aq_cooldown_max)
  188. elseif tp then
  189. -- Target is a static location.
  190. pm.debug_chat('acquired static target')
  191. -- Don't reacquire target if we're already sitting on it.
  192. if vector_distance(pos, tp) > pm.range then
  193. pm.debug_chat('set static target goal')
  194. self._goto = vector_round(tp)
  195. self._target = nil
  196. end
  197. self._acquire_target_cooldown = math_random(pm.aq_cooldown_min, pm.aq_cooldown_max)
  198. else
  199. -- No target acquired. Wait awhile before calling function again.
  200. pm.debug_chat('no target acquired')
  201. self._acquire_target_cooldown = math_random(pm.aq_cooldown_min, pm.aq_cooldown_max)
  202. end
  203. end
  204. end
  205. -- Get a path to our target if we don't have a path yet, and target is not nearby.
  206. if not self._failed_pathfind_cooldown then
  207. if self._goto and not self._path and vector_distance(self._goto, pos) > pm.range then
  208. pm.debug_chat('want path to target')
  209. local los, obstruction = minetest.line_of_sight(vector_round(pos), vector_round(self._goto))
  210. if los then
  211. -- We have LOS (line of sight) direct to target.
  212. pm.debug_chat('LOS confirmed')
  213. local dir = vector.subtract(vector_round(self._goto), vector_round(pos))
  214. local dst = vector.length(dir)
  215. dir = vector.normalize(dir) -- Returns 0,0,0 for zero-length vector.
  216. -- Assemble a straight-line path.
  217. local path = {}
  218. for i=1, dst, 1 do
  219. path[#path+1] = vector.add(pos, vector.multiply(dir, i))
  220. end
  221. if #path > 0 then
  222. self._path = path
  223. self._path_is_los = true
  224. self._stuck_timer = nil
  225. end
  226. else
  227. -- No line of sight to target. Use pathfinder!
  228. pm.debug_chat('will try pathfinder')
  229. local rp1 = vector_round(pos)
  230. local rp2 = vector_round(self._goto)
  231. local a1 = rp1
  232. local a2 = rp2
  233. local d1 = minetest.registered_nodes[minetest.get_node(rp1).name]
  234. local d2 = minetest.registered_nodes[minetest.get_node(rp2).name]
  235. -- If either start or end are non-walkable, we don't need to look for air.
  236. if d1.walkable then
  237. a1 = minetest.find_node_near(rp1, 2, "air", true)
  238. end
  239. if d2.walkable then
  240. a2 = minetest.find_node_near(rp2, 2, "air", true)
  241. end
  242. if a1 and a2 then
  243. pm.debug_chat('start and end position are both in air')
  244. local prepath = {table.copy(a1)}
  245. local postpath = {table.copy(a2)}
  246. -- Find air directly above ground for the wisp's start position.
  247. -- This is necessary because the wisp usually flies a little bit above
  248. -- the ground. Pathfinding will fail if we don't start at ground level.
  249. while minetest.get_node(vector.add(a1, {x=0, y=-1, z=0})).name == "air" do
  250. a1.y = a1.y - 1
  251. table.insert(prepath, table.copy(a1))
  252. end
  253. -- Find air directly above ground for the target position.
  254. local target_y = a2.y
  255. while minetest.get_node(vector.add(a2, {x=0, y=-1, z=0})).name == "air" do
  256. a2.y = a2.y - 1
  257. table.insert(postpath, 1, table.copy(a2))
  258. end
  259. -- If this triggers then the target is flying, or hanging over a high ledge.
  260. if (target_y - a2.y) > 2 then
  261. end
  262. -- The shorter the apparent distance between these 2 points, the farther
  263. -- we can afford to look around.
  264. local d = vector_distance(a1, a2)
  265. local r = math_max(10, math_floor(pm.sight_range - d))
  266. pm.debug_chat("trying to find path")
  267. self._path = minetest.find_path(a1, a2, r, 1, 1, "A*_noprefetch")
  268. if not self._path then
  269. pm.debug_chat('no path found')
  270. -- If we couldn't find a path to this location, we should remove this
  271. -- goal. Also set the pathfinder cooldown timer.
  272. self._goto = nil
  273. self._failed_pathfind_cooldown = math_random(pm.pf_cooldown_min, pm.pf_cooldown_max)
  274. else
  275. if #(self._path) >= 1 then
  276. pm.debug_chat("got path")
  277. self._stuck_timer = nil
  278. pm.debug_chat('welding pre and post paths')
  279. local path = {}
  280. for i=1, #prepath, 1 do
  281. path[#path+1] = prepath[i]
  282. end
  283. for i=1, #(self._path), 1 do
  284. path[#path+1] = self._path[i]
  285. end
  286. for i=1, #postpath, 1 do
  287. path[#path+1] = postpath[i]
  288. end
  289. self._path = path
  290. self._stuck_timer = nil
  291. -- Debug render path.
  292. pm.debug_path(self._path)
  293. -- If start and end points are equal, toss this path out.
  294. -- Also set the pathfinder cooldown timer.
  295. if vector.equals(self._path[1], self._path[#(self._path)]) then
  296. pm.debug_chat('tossing path because start and end are equal')
  297. self._path = nil
  298. self._goto = nil
  299. self._failed_pathfind_cooldown = math_random(pm.pf_cooldown_min, pm.pf_cooldown_max)
  300. end
  301. -- If path's start position is too far away, we can't use the path.
  302. if self._path then
  303. if vector_distance(self._path[1], pos) > pm.range then
  304. pm.debug_chat('tossing path because start is too far away')
  305. self._path = nil
  306. self._goto = nil
  307. self._failed_pathfind_cooldown = math_random(pm.pf_cooldown_min, pm.pf_cooldown_max)
  308. end
  309. end
  310. else
  311. -- Not a real path!
  312. -- Must have at least one position.
  313. pm.debug_chat('tossing path because it is bogus')
  314. self._goto = nil
  315. self._path = nil
  316. self._failed_pathfind_cooldown = math_random(pm.pf_cooldown_min, pm.pf_cooldown_max)
  317. end
  318. end
  319. else
  320. -- One or both positions not accessible (no nearby air).
  321. -- Thus we must give up this target.
  322. self._goto = nil
  323. self._path = nil
  324. self._failed_pathfind_cooldown = math_random(pm.pf_cooldown_min, pm.pf_cooldown_max)
  325. end
  326. end
  327. end
  328. end
  329. -- Follow current path.
  330. if self._path and #(self._path) > 0 then
  331. -- For paths of longer than trivial length, try to optimize with LOS.
  332. -- We can do this because this mob can fly over gaps and such. This also
  333. -- makes the movement look better.
  334. if #(self._path) > 5 then
  335. -- Don't do LOS optimization if the current waypoint is already so marked.
  336. local p = self._path[2]
  337. if not p.los then
  338. while #(self._path) > 1 and minetest.line_of_sight(pos, p) do
  339. table.remove(self._path, 1)
  340. p = self._path[2]
  341. end
  342. local dir = vector.subtract(self._path[1], pos)
  343. local dst = vector.length(dir)
  344. dir = vector.normalize(dir) -- Returns 0,0,0 for zero-length vector.
  345. -- Assemble a straight-line path.
  346. local path = {}
  347. for i=1, dst, 1 do
  348. path[#path+1] = vector.add(pos, vector.multiply(dir, i))
  349. path[#path].los = true -- Mark waypoint as a LOS point.
  350. end
  351. -- Append the remainder of the real path.
  352. for i=1, #(self._path), 1 do
  353. path[#path+1] = self._path[i]
  354. end
  355. -- Set new path.
  356. self._path = path
  357. self._stuck_timer = nil
  358. -- Debug render path.
  359. pm.debug_path(self._path)
  360. end
  361. end
  362. pm.debug_chat('following path')
  363. local waypoint = self._path[1]
  364. local waynode = minetest.get_node(waypoint)
  365. -- Check if path runs through an obstruction.
  366. -- Nodes must be 'air' or non-walkable (like plants).
  367. local obstructed = false
  368. if waynode.name ~= "air" then
  369. local ndef = minetest.registered_nodes[waynode.name]
  370. if not ndef or ndef.walkable then
  371. obstructed = true
  372. end
  373. end
  374. if not obstructed then
  375. if self._path_is_los or waypoint.los then
  376. -- Follow line-of-sight paths directly.
  377. --self.object:move_to(waypoint, true)
  378. --table.remove(self._path, 1)
  379. -- Smooth movement.
  380. local dir = vector.subtract(waypoint, pos)
  381. if vector.length(dir) > 0.4 then
  382. dir = vector.normalize(dir)
  383. dir = vector.multiply(dir, pm.get_wanted_velocity(self))
  384. self.object:set_velocity(dir)
  385. end
  386. else
  387. -- Cause entity to float 1.5 meters above ground when following path,
  388. -- if there's enough head room. But not for the last waypoint in the path.
  389. waypoint.y = waypoint.y + 1
  390. local n = minetest.get_node(waypoint)
  391. if n.name == "air" and #(self._path) > 1 then
  392. waypoint.y = waypoint.y - 0.5
  393. --self.object:move_to(waypoint, true)
  394. --table.remove(self._path, 1)
  395. -- Smooth movement.
  396. local dir = vector.subtract(waypoint, pos)
  397. if vector.length(dir) > 0.4 then
  398. dir = vector.normalize(dir)
  399. dir = vector.multiply(dir, pm.get_wanted_velocity(self))
  400. self.object:set_velocity(dir)
  401. end
  402. else
  403. waypoint.y = waypoint.y - 1
  404. --self.object:move_to(waypoint, true)
  405. --table.remove(self._path, 1)
  406. -- Smooth movement.
  407. local dir = vector.subtract(waypoint, pos)
  408. if vector.length(dir) > 0.4 then
  409. dir = vector.normalize(dir)
  410. dir = vector.multiply(dir, pm.get_wanted_velocity(self))
  411. self.object:set_velocity(dir)
  412. end
  413. end
  414. end
  415. else
  416. -- Path obstructed. Need new path, this one is bad.
  417. pm.debug_chat('path obstructed: ' .. waynode.name)
  418. self._path = nil
  419. self._path_is_los = nil
  420. self._goto = nil
  421. self.object:set_velocity({x=0, y=0, z=0})
  422. end
  423. end
  424. -- Dynamic targets can move while we're trying to path to them.
  425. -- Update path as long as we have LOS to the target.
  426. if self._target and self._path and self._goto then
  427. local target_pos = self._target:get_pos()
  428. if target_pos then
  429. if #(self._path) > 0 then
  430. local end_path = self._path[#(self._path)]
  431. if vector_distance(target_pos, end_path) > 3 then
  432. local los, obstruction = minetest.line_of_sight(vector_round(pos), vector_round(target_pos))
  433. if los then
  434. pm.debug_chat('target moved, repathing via LOS')
  435. self._goto = vector_round(target_pos)
  436. local dir = vector.subtract(self._goto, vector_round(pos))
  437. local dst = vector.length(dir)
  438. dir = vector.normalize(dir) -- Returns 0,0,0 for zero-length vector.
  439. -- Assemble a straight-line path.
  440. local path = {}
  441. for i=1, dst, 1 do
  442. path[#path+1] = vector.add(pos, vector.multiply(dir, i))
  443. end
  444. if #path > 0 then
  445. self._path = path
  446. self._path_is_los = true
  447. self._stuck_timer = nil
  448. end
  449. end
  450. end
  451. end
  452. end
  453. end
  454. -- Remove target waypoint once we're close enough to it.
  455. -- Only if done following path.
  456. if self._goto and not self._path then
  457. pm.debug_chat('distance to goal: ' .. vector_distance(self._goto, pos))
  458. pm.debug_goal(self._goto)
  459. if vector_distance(self._goto, pos) < pm.range then
  460. pm.debug_chat('reached goal')
  461. --self.object:move_to(self._goto, true)
  462. -- Have we arrived at the target (if we did indeed have a target)?
  463. if self._target then
  464. local s = self._target:get_pos()
  465. if s then
  466. s = vector_round(s)
  467. s.y = s.y + 1 -- For entities, we seek above them, not at their feet.
  468. if vector_distance(pos, s) < pm.range then
  469. pm.debug_chat('reached dynamic target')
  470. -- We have reached our moveable target.
  471. -- We can clear this and set a timer to delay acquiring the next target.
  472. self._on_arrival(self, self._goto, self._target)
  473. self._goto = nil
  474. self._target = nil
  475. self._acquire_target_cooldown = math_random(pm.aq_cooldown_min, pm.aq_cooldown_max)
  476. else
  477. -- Our moveable target has moved. We must move toward it again.
  478. -- Do so right away, without delay.
  479. pm.debug_chat('target has moved, reacquiring')
  480. self._goto = s
  481. self._acquire_target_cooldown = nil
  482. self._failed_pathfind_cooldown = nil
  483. end
  484. else
  485. -- Moving target no longer available. We must clear this.
  486. self._target = nil
  487. self._goto = nil
  488. end
  489. else
  490. pm.debug_chat('reached static target')
  491. self._on_arrival(self, self._goto, nil)
  492. -- No moving target, so we can clear this.
  493. self._goto = nil
  494. self._acquire_target_cooldown = math_random(pm.aq_cooldown_min, pm.aq_cooldown_max)
  495. end
  496. end
  497. end
  498. -- Drift behavior, as long as we don't have a target to go to.
  499. if not self._wander_cooldown then
  500. if not self._goto then
  501. local dir = {
  502. x = math_random(-1, 1)/10,
  503. y = math_random(-1, 1)/10,
  504. z = math_random(-1, 1)/10
  505. }
  506. self.object:set_velocity(dir)
  507. self._wander_cooldown = math_random(1, 5)
  508. end
  509. end
  510. end
  511. function pm.follower_on_punch(self, puncher, time_from_last_punch, tool_capabilities, dir)
  512. local pos = self.object:get_pos()
  513. pm.death_particle_effect(pos)
  514. minetest.add_item(pos, "glowstone:glowing_dust " .. math_random(1, 3))
  515. self._mkrm = true
  516. end