entity.lua 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  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. -- Distance limit paths, disallow large search regions.
  267. pm.debug_chat("trying to find path")
  268. if vector_distance(a1, a2) < 75 then
  269. self._path = minetest.find_path(a1, a2, r, 1, 1, "A*_noprefetch")
  270. end
  271. if not self._path then
  272. pm.debug_chat('no path found')
  273. -- If we couldn't find a path to this location, we should remove this
  274. -- goal. Also set the pathfinder cooldown timer.
  275. self._goto = nil
  276. self._failed_pathfind_cooldown = math_random(pm.pf_cooldown_min, pm.pf_cooldown_max)
  277. else
  278. if #(self._path) >= 1 then
  279. pm.debug_chat("got path")
  280. self._stuck_timer = nil
  281. pm.debug_chat('welding pre and post paths')
  282. local path = {}
  283. for i=1, #prepath, 1 do
  284. path[#path+1] = prepath[i]
  285. end
  286. for i=1, #(self._path), 1 do
  287. path[#path+1] = self._path[i]
  288. end
  289. for i=1, #postpath, 1 do
  290. path[#path+1] = postpath[i]
  291. end
  292. self._path = path
  293. self._stuck_timer = nil
  294. -- Debug render path.
  295. pm.debug_path(self._path)
  296. -- If start and end points are equal, toss this path out.
  297. -- Also set the pathfinder cooldown timer.
  298. if vector.equals(self._path[1], self._path[#(self._path)]) then
  299. pm.debug_chat('tossing path because start and end are equal')
  300. self._path = nil
  301. self._goto = nil
  302. self._failed_pathfind_cooldown = math_random(pm.pf_cooldown_min, pm.pf_cooldown_max)
  303. end
  304. -- If path's start position is too far away, we can't use the path.
  305. if self._path then
  306. if vector_distance(self._path[1], pos) > pm.range then
  307. pm.debug_chat('tossing path because start is too far away')
  308. self._path = nil
  309. self._goto = nil
  310. self._failed_pathfind_cooldown = math_random(pm.pf_cooldown_min, pm.pf_cooldown_max)
  311. end
  312. end
  313. else
  314. -- Not a real path!
  315. -- Must have at least one position.
  316. pm.debug_chat('tossing path because it is bogus')
  317. self._goto = nil
  318. self._path = nil
  319. self._failed_pathfind_cooldown = math_random(pm.pf_cooldown_min, pm.pf_cooldown_max)
  320. end
  321. end
  322. else
  323. -- One or both positions not accessible (no nearby air).
  324. -- Thus we must give up this target.
  325. self._goto = nil
  326. self._path = nil
  327. self._failed_pathfind_cooldown = math_random(pm.pf_cooldown_min, pm.pf_cooldown_max)
  328. end
  329. end
  330. end
  331. end
  332. -- Follow current path.
  333. if self._path and #(self._path) > 0 then
  334. -- For paths of longer than trivial length, try to optimize with LOS.
  335. -- We can do this because this mob can fly over gaps and such. This also
  336. -- makes the movement look better.
  337. if #(self._path) > 5 then
  338. -- Don't do LOS optimization if the current waypoint is already so marked.
  339. local p = self._path[2]
  340. if not p.los then
  341. while #(self._path) > 1 and minetest.line_of_sight(pos, p) do
  342. table.remove(self._path, 1)
  343. p = self._path[2]
  344. end
  345. local dir = vector.subtract(self._path[1], pos)
  346. local dst = vector.length(dir)
  347. dir = vector.normalize(dir) -- Returns 0,0,0 for zero-length vector.
  348. -- Assemble a straight-line path.
  349. local path = {}
  350. for i=1, dst, 1 do
  351. path[#path+1] = vector.add(pos, vector.multiply(dir, i))
  352. path[#path].los = true -- Mark waypoint as a LOS point.
  353. end
  354. -- Append the remainder of the real path.
  355. for i=1, #(self._path), 1 do
  356. path[#path+1] = self._path[i]
  357. end
  358. -- Set new path.
  359. self._path = path
  360. self._stuck_timer = nil
  361. -- Debug render path.
  362. pm.debug_path(self._path)
  363. end
  364. end
  365. pm.debug_chat('following path')
  366. local waypoint = self._path[1]
  367. local waynode = minetest.get_node(waypoint)
  368. -- Check if path runs through an obstruction.
  369. -- Nodes must be 'air' or non-walkable (like plants).
  370. local obstructed = false
  371. if waynode.name ~= "air" then
  372. local ndef = minetest.registered_nodes[waynode.name]
  373. if not ndef or ndef.walkable then
  374. obstructed = true
  375. end
  376. end
  377. if not obstructed then
  378. if self._path_is_los or waypoint.los then
  379. -- Follow line-of-sight paths directly.
  380. --self.object:move_to(waypoint, true)
  381. --table.remove(self._path, 1)
  382. -- Smooth movement.
  383. local dir = vector.subtract(waypoint, pos)
  384. if vector.length(dir) > 0.4 then
  385. dir = vector.normalize(dir)
  386. dir = vector.multiply(dir, pm.get_wanted_velocity(self))
  387. self.object:set_velocity(dir)
  388. end
  389. else
  390. -- Cause entity to float 1.5 meters above ground when following path,
  391. -- if there's enough head room. But not for the last waypoint in the path.
  392. waypoint.y = waypoint.y + 1
  393. local n = minetest.get_node(waypoint)
  394. if n.name == "air" and #(self._path) > 1 then
  395. waypoint.y = waypoint.y - 0.5
  396. --self.object:move_to(waypoint, true)
  397. --table.remove(self._path, 1)
  398. -- Smooth movement.
  399. local dir = vector.subtract(waypoint, pos)
  400. if vector.length(dir) > 0.4 then
  401. dir = vector.normalize(dir)
  402. dir = vector.multiply(dir, pm.get_wanted_velocity(self))
  403. self.object:set_velocity(dir)
  404. end
  405. else
  406. waypoint.y = waypoint.y - 1
  407. --self.object:move_to(waypoint, true)
  408. --table.remove(self._path, 1)
  409. -- Smooth movement.
  410. local dir = vector.subtract(waypoint, pos)
  411. if vector.length(dir) > 0.4 then
  412. dir = vector.normalize(dir)
  413. dir = vector.multiply(dir, pm.get_wanted_velocity(self))
  414. self.object:set_velocity(dir)
  415. end
  416. end
  417. end
  418. else
  419. -- Path obstructed. Need new path, this one is bad.
  420. pm.debug_chat('path obstructed: ' .. waynode.name)
  421. self._path = nil
  422. self._path_is_los = nil
  423. self._goto = nil
  424. self.object:set_velocity({x=0, y=0, z=0})
  425. end
  426. end
  427. -- Dynamic targets can move while we're trying to path to them.
  428. -- Update path as long as we have LOS to the target.
  429. if self._target and self._path and self._goto then
  430. local target_pos = self._target:get_pos()
  431. if target_pos then
  432. if #(self._path) > 0 then
  433. local end_path = self._path[#(self._path)]
  434. if vector_distance(target_pos, end_path) > 3 then
  435. local los, obstruction = minetest.line_of_sight(vector_round(pos), vector_round(target_pos))
  436. if los then
  437. pm.debug_chat('target moved, repathing via LOS')
  438. self._goto = vector_round(target_pos)
  439. local dir = vector.subtract(self._goto, vector_round(pos))
  440. local dst = vector.length(dir)
  441. dir = vector.normalize(dir) -- Returns 0,0,0 for zero-length vector.
  442. -- Assemble a straight-line path.
  443. local path = {}
  444. for i=1, dst, 1 do
  445. path[#path+1] = vector.add(pos, vector.multiply(dir, i))
  446. end
  447. if #path > 0 then
  448. self._path = path
  449. self._path_is_los = true
  450. self._stuck_timer = nil
  451. end
  452. end
  453. end
  454. end
  455. end
  456. end
  457. -- Remove target waypoint once we're close enough to it.
  458. -- Only if done following path.
  459. if self._goto and not self._path then
  460. pm.debug_chat('distance to goal: ' .. vector_distance(self._goto, pos))
  461. pm.debug_goal(self._goto)
  462. if vector_distance(self._goto, pos) < pm.range then
  463. pm.debug_chat('reached goal')
  464. --self.object:move_to(self._goto, true)
  465. -- Have we arrived at the target (if we did indeed have a target)?
  466. if self._target then
  467. local s = self._target:get_pos()
  468. if s then
  469. s = vector_round(s)
  470. s.y = s.y + 1 -- For entities, we seek above them, not at their feet.
  471. if vector_distance(pos, s) < pm.range then
  472. pm.debug_chat('reached dynamic target')
  473. -- We have reached our moveable target.
  474. -- We can clear this and set a timer to delay acquiring the next target.
  475. self._on_arrival(self, self._goto, self._target)
  476. self._goto = nil
  477. self._target = nil
  478. self._acquire_target_cooldown = math_random(pm.aq_cooldown_min, pm.aq_cooldown_max)
  479. else
  480. -- Our moveable target has moved. We must move toward it again.
  481. -- Do so right away, without delay.
  482. pm.debug_chat('target has moved, reacquiring')
  483. self._goto = s
  484. self._acquire_target_cooldown = nil
  485. self._failed_pathfind_cooldown = nil
  486. end
  487. else
  488. -- Moving target no longer available. We must clear this.
  489. self._target = nil
  490. self._goto = nil
  491. end
  492. else
  493. pm.debug_chat('reached static target')
  494. self._on_arrival(self, self._goto, nil)
  495. -- No moving target, so we can clear this.
  496. self._goto = nil
  497. self._acquire_target_cooldown = math_random(pm.aq_cooldown_min, pm.aq_cooldown_max)
  498. end
  499. end
  500. end
  501. -- Drift behavior, as long as we don't have a target to go to.
  502. if not self._wander_cooldown then
  503. if not self._goto then
  504. local dir = {
  505. x = math_random(-1, 1)/10,
  506. y = math_random(-1, 1)/10,
  507. z = math_random(-1, 1)/10
  508. }
  509. self.object:set_velocity(dir)
  510. self._wander_cooldown = math_random(1, 5)
  511. end
  512. end
  513. end
  514. function pm.follower_on_punch(self, puncher, time_from_last_punch, tool_capabilities, dir)
  515. local pos = self.object:get_pos()
  516. pm.death_particle_effect(pos)
  517. minetest.add_item(pos, "glowstone:glowing_dust " .. math_random(1, 3))
  518. self._mkrm = true
  519. end