entity.lua 18 KB

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