init.lua 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. if not minetest.global_exists("pm") then pm = {} end
  2. pm.modpath = minetest.get_modpath("pm")
  3. pm.sight_range = 30
  4. pm.nest_range = 15
  5. -- Pathfinder cooldown min/max time.
  6. pm.pf_cooldown_min = 5
  7. pm.pf_cooldown_max = 15
  8. -- Cooldown timer to acquire a new target.
  9. pm.aq_cooldown_min = 1
  10. pm.aq_cooldown_max = 10
  11. -- Range at which entity is considered to have found its target.
  12. pm.range = 2
  13. pm.velocity = 3
  14. pm.run_velocity = 4.5
  15. pm.walk_velocity = 2
  16. -- Localize for performance.
  17. local vector_distance = vector.distance
  18. local vector_round = vector.round
  19. local math_floor = math.floor
  20. local math_max = math.max
  21. local math_random = math.random
  22. dofile(pm.modpath .. "/seek.lua")
  23. dofile(pm.modpath .. "/action.lua")
  24. dofile(pm.modpath .. "/spawner.lua")
  25. dofile(pm.modpath .. "/entity.lua")
  26. function pm.target_is_player_or_mob(target)
  27. if target:is_player() then
  28. return true
  29. end
  30. local ent = target:get_luaentity()
  31. if ent.mob then
  32. return true
  33. end
  34. end
  35. function pm.debug_chat(text)
  36. -- Comment or uncomment as needed for debugging.
  37. --minetest.chat_send_all(text)
  38. end
  39. function pm.debug_path(path)
  40. -- Ditto.
  41. --for k, v in ipairs(path) do pm.spawn_path_particle(v) end
  42. end
  43. function pm.debug_goal(pos)
  44. -- Ditto.
  45. --pm.spawn_path_particle(pos)
  46. --pm.death_particle_effect(pos)
  47. end
  48. function pm.death_particle_effect(pos)
  49. local particles = {
  50. amount = 100,
  51. time = 1.1,
  52. minpos = vector.add(pos, {x=-0.1, y=-0.1, z=-0.1}),
  53. maxpos = vector.add(pos, {x=0.1, y=0.1, z=0.1}),
  54. minvel = vector.new(-3.5, -3.5, -3.5),
  55. maxvel = vector.new(3.5, 3.5, 3.5),
  56. minacc = {x=0, y=0, z=0},
  57. maxacc = {x=0, y=0, z=0},
  58. minexptime = 1.5,
  59. maxexptime = 2.0,
  60. minsize = 0.5,
  61. maxsize = 1.0,
  62. collisiondetection = false,
  63. collision_removal = false,
  64. vertical = false,
  65. texture = "quartz_crystal_piece.png",
  66. glow = 14,
  67. --attached = entity,
  68. }
  69. minetest.add_particlespawner(particles)
  70. end
  71. -- Get objects inside radius, but remove self from the returned list.
  72. function pm.get_nearby_objects(self, pos, radius)
  73. local objects = minetest.get_objects_inside_radius(pos, radius)
  74. if self._identity then
  75. for i=1, #objects, 1 do
  76. local ent = objects[i]:get_luaentity()
  77. if ent and ent._identity then
  78. if ent._identity == self._identity then
  79. table.remove(objects, i)
  80. break
  81. end
  82. end
  83. end
  84. end
  85. return objects
  86. end
  87. function pm.spawn_path_particle(pos)
  88. local particles = {
  89. amount = 1,
  90. time = 0.1,
  91. minpos = pos,
  92. maxpos = pos,
  93. minvel = {x=0, y=0, z=0},
  94. maxvel = {x=0, y=0, z=0},
  95. minacc = {x=0, y=0, z=0},
  96. maxacc = {x=0, y=0, z=0},
  97. minexptime = 5,
  98. maxexptime = 5,
  99. minsize = 2.0,
  100. maxsize = 2.0,
  101. collisiondetection = false,
  102. collision_removal = false,
  103. vertical = false,
  104. texture = "default_mese_crystal.png",
  105. glow = 14,
  106. --attached = entity,
  107. }
  108. minetest.add_particlespawner(particles)
  109. end
  110. function pm.follower_spawn_particles(pos, entity)
  111. local particles = {
  112. amount = 10,
  113. time = 1,
  114. minpos = vector.add(pos, {x=-0.1, y=-0.1, z=-0.1}),
  115. maxpos = vector.add(pos, {x=0.1, y=0.1, z=0.1}),
  116. minvel = vector.new(-0.5, -0.5, -0.5),
  117. maxvel = vector.new(0.5, 0.5, 0.5),
  118. minacc = {x=0, y=0, z=0},
  119. maxacc = {x=0, y=0, z=0},
  120. minexptime = 0.5,
  121. maxexptime = 2.0,
  122. minsize = 0.5,
  123. maxsize = 1.0,
  124. collisiondetection = false,
  125. collision_removal = false,
  126. vertical = false,
  127. texture = "quartz_crystal_piece.png",
  128. glow = 14,
  129. --attached = entity,
  130. }
  131. minetest.add_particlespawner(particles)
  132. end
  133. local overrides = {
  134. nest_guard = {
  135. -- Wisp is persistent and will not alter behavior.
  136. _no_autochose_behavior = true,
  137. _no_lifespan_limit = true,
  138. },
  139. nest_worker = {
  140. -- Wisp is persistent and will not alter behavior.
  141. _no_autochose_behavior = true,
  142. _no_lifespan_limit = true,
  143. },
  144. }
  145. -- Create entity at position, if possible.
  146. function pm.spawn_wisp(pos, behavior)
  147. pos = vector_round(pos)
  148. local node = minetest.get_node(pos)
  149. local pos_ok = false
  150. if node.name == "air" then
  151. pos_ok = true
  152. else
  153. local ndef = minetest.registered_nodes[node.name]
  154. if ndef and not ndef.walkable then
  155. pos_ok = true
  156. end
  157. end
  158. if pos_ok then
  159. local ent = minetest.add_entity(pos, "pm:follower")
  160. if ent then
  161. local luaent = ent:get_luaentity()
  162. if luaent then
  163. luaent._behavior = behavior
  164. luaent._behavior_timer = math_random(1, 20)*60
  165. -- Allows to uniquely identify the wisp to other wisps, with little chance of collision.
  166. -- In particular this allows the wisp to ignore itself in any object queries.
  167. luaent._identity = math_random(1, 32000)
  168. -- This is so the wisp knows where it spawned at.
  169. -- We format it as a string so that this data is saved statically.
  170. luaent._spawn_origin = minetest.pos_to_string(pos)
  171. -- Wisp has a chance to be completely silent.
  172. if math_random(1, 10) == 1 then
  173. luaent._no_sound = true
  174. end
  175. -- Apply overrides if wanted.
  176. if overrides[behavior] then
  177. local o = overrides[behavior]
  178. for k, v in pairs(o) do
  179. luaent[k] = v
  180. end
  181. end
  182. return ent, luaent
  183. else
  184. ent:remove()
  185. end
  186. end
  187. end
  188. end
  189. local behaviors = {
  190. "follower",
  191. "pest",
  192. "thief",
  193. "healer",
  194. "explorer",
  195. "boom", -- Never chosen by chance.
  196. "communal",
  197. "solitary",
  198. "guard",
  199. "nest_guard",
  200. "arsonist",
  201. "porter",
  202. "pusher",
  203. "nest_worker",
  204. "loot_dropper",
  205. }
  206. function pm.choose_random_behavior(self)
  207. self._behavior = behaviors[math_random(1, #behaviors)]
  208. -- Don't chose a self-destructive behavior by chance.
  209. if self._behavior == "boom" then
  210. self._behavior = "follower"
  211. elseif self._behavior == "nest_guard" then
  212. self._behavior = "guard"
  213. elseif self._behavior == "nest_worker" then
  214. self._behavior = "explorer"
  215. end
  216. end
  217. -- Create entity at position, if possible.
  218. function pm.spawn_random_wisp(pos)
  219. local act = behaviors[math_random(1, #behaviors)]
  220. if act == "boom" then
  221. act = "follower"
  222. elseif act == "nest_guard" then
  223. act = "guard"
  224. elseif act == "nest_worker" then
  225. act = "explorer"
  226. end
  227. return pm.spawn_wisp(pos, act)
  228. end
  229. -- Table of functions for obtaining interest points.
  230. local interests = {
  231. follower = function(self, pos)
  232. return pm.seek_player_or_mob_or_item(self, pos)
  233. end,
  234. thief = function(self, pos)
  235. return pm.seek_player_or_item(self, pos)
  236. end,
  237. pest = function(self, pos)
  238. return pm.seek_player(self, pos)
  239. end,
  240. healer = function(self, pos)
  241. return pm.seek_player(self, pos)
  242. end,
  243. explorer = function(self, pos)
  244. return pm.seek_node_with_meta(self, pos)
  245. end,
  246. -- Suicide, never chosen at random.
  247. boom = function(self, pos)
  248. return pm.seek_player_or_mob_not_wisp(self, pos)
  249. end,
  250. communal = function(self, pos)
  251. return pm.seek_wisp(self, pos)
  252. end,
  253. solitary = function(self, pos)
  254. return pm.seek_solitude(self, pos)
  255. end,
  256. guard = function(self, pos)
  257. -- Seek target in sight-range of spawn origin, otherwise return to origin.
  258. if self._spawn_origin then
  259. local origin = minetest.string_to_pos(self._spawn_origin)
  260. if origin then
  261. if vector_distance(origin, self.object:get_pos()) > pm.sight_range then
  262. return origin, nil
  263. else
  264. -- Within sight range of spawn origin, seek target.
  265. return pm.seek_player_or_mob_not_wisp(self, pos)
  266. end
  267. end
  268. end
  269. return nil, nil
  270. end,
  271. -- Never chosen at random, can only be deliberately created.
  272. nest_guard = function(self, pos)
  273. -- Seek target in sight-range of spawn origin, otherwise return to origin.
  274. if self._spawn_origin then
  275. local origin = minetest.string_to_pos(self._spawn_origin)
  276. if origin then
  277. if vector_distance(origin, self.object:get_pos()) > pm.nest_range then
  278. return origin, nil
  279. else
  280. -- Within sight range of spawn origin, seek target.
  281. return pm.seek_player(self, pos)
  282. end
  283. end
  284. end
  285. return nil, nil
  286. end,
  287. -- Never chosen at random, can only be deliberately created.
  288. nest_worker = function(self, pos)
  289. -- Seek target in sight-range of spawn origin, otherwise return to origin.
  290. if self._spawn_origin then
  291. local origin = minetest.string_to_pos(self._spawn_origin)
  292. if origin then
  293. if vector_distance(origin, self.object:get_pos()) > pm.sight_range then
  294. return origin, nil
  295. else
  296. -- Within sight range of spawn origin, seek target.
  297. return pm.seek_flora(self, pos)
  298. end
  299. end
  300. end
  301. return nil, nil
  302. end,
  303. arsonist = function(self, pos)
  304. local target = pm.seek_flammable_node(self, pos)
  305. if not target then
  306. return pm.seek_player_or_mob(self, pos)
  307. end
  308. return target, nil
  309. end,
  310. porter = function(self, pos)
  311. return pm.seek_player(self, pos)
  312. end,
  313. pusher = function(self, pos)
  314. return pm.seek_player(self, pos)
  315. end,
  316. loot_dropper = function(self, pos)
  317. return pm.seek_node_with_meta(self, pos)
  318. end,
  319. }
  320. -- Table of possible action functions to take on arriving at a target.
  321. local actions = {
  322. pest = function(self, pos, target)
  323. pm.hurt_nearby_players(self)
  324. end,
  325. healer = function(self, pos, target)
  326. pm.heal_nearby_players(self)
  327. end,
  328. thief = function(self, pos, target)
  329. pm.steal_nearby_item(self, target)
  330. end,
  331. boom = function(self, pos, target)
  332. pm.explode_nearby_target(self, target)
  333. end,
  334. guard = function(self, pos, target)
  335. -- Attack target, but only if in sight-range of spawn origin.
  336. if self._spawn_origin then
  337. local origin = minetest.string_to_pos(self._spawn_origin)
  338. if origin then
  339. if vector_distance(origin, self.object:get_pos()) < pm.sight_range then
  340. pm.hurt_nearby_player_or_mob_not_wisp(self)
  341. end
  342. end
  343. end
  344. end,
  345. nest_guard = function(self, pos, target)
  346. -- Attack target, but only if in sight-range of spawn origin.
  347. if self._spawn_origin then
  348. local origin = minetest.string_to_pos(self._spawn_origin)
  349. if origin then
  350. if vector_distance(origin, self.object:get_pos()) < pm.nest_range then
  351. pm.hurt_nearby_player_or_mob_not_wisp(self)
  352. end
  353. end
  354. end
  355. end,
  356. arsonist = function(self, pos, target)
  357. pm.commit_arson_at_target(pos)
  358. end,
  359. porter = function(self, pos, target)
  360. pm.teleport_player_to_prior_location(target)
  361. end,
  362. pusher = function(self, pos, target)
  363. pm.shove_player(self, target)
  364. end,
  365. loot_dropper = function(self, pos, target)
  366. pm.drop_loot(pos)
  367. end,
  368. }
  369. function pm.interest_point(self, pos)
  370. if self._behavior then
  371. if interests[self._behavior] then
  372. return interests[self._behavior](self, pos)
  373. end
  374. end
  375. return nil, nil
  376. end
  377. function pm.on_arrival(self, pos, other)
  378. pm.debug_chat('arrived at target')
  379. if self._behavior then
  380. pm.debug_chat('have behavior: ' .. self._behavior)
  381. if actions[self._behavior] then
  382. actions[self._behavior](self, pos, other)
  383. end
  384. end
  385. end
  386. if not pm.registered then
  387. local entity = {
  388. initial_properties = {
  389. visual = "cube",
  390. textures = {
  391. "quartz_crystal_piece.png",
  392. "quartz_crystal_piece.png",
  393. "quartz_crystal_piece.png",
  394. "quartz_crystal_piece.png",
  395. "quartz_crystal_piece.png",
  396. "quartz_crystal_piece.png",
  397. },
  398. visual_size = {x=0.2, y=0.2, z=0.2},
  399. collide_with_objects = false,
  400. pointable = false,
  401. is_visible = true,
  402. makes_footstep_sound = false,
  403. glow = 14,
  404. automatic_rotate = 0.5,
  405. -- This is so that wandering/drifting wisps don't bury themselves.
  406. physical = true,
  407. collisionbox = {-0.2, -0.2, -0.2, 0.2, 0.2, 0.2},
  408. },
  409. -- So other game code can tell what this entity is.
  410. _name = "pm:follower",
  411. description = "Seon",
  412. mob = true,
  413. _cmi_is_mob = true,
  414. on_step = function(...) return pm.follower_on_step(...) end,
  415. on_punch = function(...) return pm.follower_on_punch(...) end,
  416. on_activate = function(...) return pm.follower_on_activate(...) end,
  417. get_staticdata = function(...) return pm.follower_get_staticdata(...) end,
  418. _interest_point = function(...) return pm.interest_point(...) end,
  419. _on_arrival = function(...) return pm.on_arrival(...) end,
  420. }
  421. minetest.register_entity("pm:follower", entity)
  422. minetest.register_node("pm:spawner", {
  423. drawtype = "airlike",
  424. description = "Wisp Spawner (Please Report to Admin)",
  425. paramtype = "light",
  426. sunlight_propagates = true,
  427. walkable = false,
  428. pointable = false,
  429. groups = {immovable = 1},
  430. climbable = false,
  431. buildable_to = true,
  432. floodable = true,
  433. drop = "",
  434. on_construct = function(...)
  435. return pm.on_nodespawner_construct(...)
  436. end,
  437. on_destruct = function(...)
  438. return pm.on_nodespawner_destruct(...)
  439. end,
  440. on_timer = function(...)
  441. return pm.on_nodespawner_timer(...)
  442. end,
  443. on_finish_collapse = function(pos, node)
  444. minetest.remove_node(pos)
  445. end,
  446. on_collapse_to_entity = function(pos, node)
  447. -- Do nothing.
  448. end,
  449. })
  450. minetest.register_node("pm:quartz_ore", {
  451. description = "Quartz Crystals In Sand",
  452. tiles = {"default_desert_sand.png^quartz_ore.png"},
  453. groups = utility.dig_groups("mineral"),
  454. drop = 'quartz:quartz_crystal',
  455. _tnt_drop = "quartz:quartz_crystal 3",
  456. sounds = default.node_sound_stone_defaults(),
  457. silverpick_drop = true,
  458. place_param2 = 10,
  459. movement_speed_multiplier = default.SLOW_SPEED,
  460. after_destruct = function(pos, oldnode)
  461. if math_random(1, 500) == 1 then
  462. pm.spawn_random_wisp(pos)
  463. end
  464. end,
  465. })
  466. local c = "pm:core"
  467. local f = pm.modpath .. "/init.lua"
  468. reload.register_file(c, f, false)
  469. pm.registered = true
  470. end