monster_ai.h 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. #ifndef __MONSTER_AI_H
  2. #define __MONSTER_AI_H
  3. bool process_ailment(tag_t ail, Player& p, GameState& state, const monsters::pt& mxy, monsters::Monster& mon) {
  4. auto a = constants().ailments.find(ail);
  5. if (a == constants().ailments.end())
  6. throw std::runtime_error("Unknown ailment.");
  7. const auto& ailment = a->second;
  8. int tmp;
  9. int n = attack_from_env(p, ailment.attacks, ailment.level, state, mxy, mon, tmp, false, true);
  10. for (const auto& z : ailment.inc_stat) {
  11. if (mon.stats.sinc(z.first, z.second))
  12. mon.dead = true;
  13. }
  14. return (ailment.oneshot && n > 0);
  15. }
  16. inline void cast_cloud(GameState& state, unsigned int x, unsigned int y, unsigned int r,
  17. tag_t terraintag) {
  18. const Terrain& tt = terrain().get(terraintag);
  19. auto fg = tt.skin[0].fore;
  20. auto bg = tt.skin[0].back;
  21. state.render.draw_circle(x, y, r, fg, bg,
  22. [&](unsigned int _x, unsigned int _y) {
  23. features::Feature tmp;
  24. if (state.features.get(_x, _y, tmp) && tmp.tag != terraintag) {
  25. const Terrain& t = terrain().get(tmp.tag);
  26. if (!t.air)
  27. return;
  28. }
  29. if (!state.grid.is_walk(_x, _y)) return;
  30. state.features.set(_x, _y, terraintag, state.render);
  31. });
  32. }
  33. template <typename T>
  34. inline void monster_blast_process_point(Player& p, GameState& state, const Species& s, T& actor,
  35. unsigned int _x, unsigned int _y, const damage::attacks_t& attacks,
  36. bool player_is_attacking) {
  37. if (_x == p.px && _y == p.py) {
  38. damage::defenses_t defenses;
  39. p.get_defense(defenses);
  40. defend(p, defenses, p.get_computed_level(), s, actor, attacks, state, player_is_attacking);
  41. } else {
  42. monsters::Monster& mon = state.monsters.get(_x, _y);
  43. if (!mon.null()) {
  44. attack_from_env(p, attacks, s.get_computed_level(), state, monsters::pt(_x, _y), mon,
  45. actor, player_is_attacking);
  46. }
  47. }
  48. }
  49. template <typename T>
  50. inline void do_monster_blast(Player& p, GameState& state, const Species& s, T& actor,
  51. unsigned int tx, unsigned int ty, unsigned int rad,
  52. const damage::attacks_t& attacks, bool track_kills = false) {
  53. if (rad == 0) {
  54. monster_blast_process_point(p, state, s, actor, tx, ty, attacks, track_kills);
  55. } else {
  56. state.render.draw_circle(tx, ty, rad, s.skin.a.fore, maudit::color::bright_black,
  57. [&](unsigned int _x, unsigned int _y) {
  58. monster_blast_process_point(p, state, s, actor, _x, _y, attacks, track_kills);
  59. });
  60. }
  61. }
  62. inline bool do_monster_magic(Player& p, GameState& state, std::vector<summons_t>& summons,
  63. const monsters::pt& target, unsigned int dist2, bool is_player, tag_t ally,
  64. const monsters::pt& mxy, monsters::Monster& m, const Species& s) {
  65. if (!s.magic_cost.stat.null()) {
  66. if (m.stats.is_min(s.magic_cost.stat))
  67. return false;
  68. }
  69. bool ret = false;
  70. if ((is_player && !m.ally.null()) || (!is_player && ally == m.ally))
  71. return ret;
  72. if (s.summon.size() > 0) {
  73. for (const auto& c : s.summon) {
  74. if ((state.ticks & c.turns) != 0) continue;
  75. double v = state.rng.gauss(0.0, 1.0);
  76. if (v <= c.chance) continue;
  77. summons.emplace_back(mxy.first, mxy.second, summons_t::type_t::SPECIFIC,
  78. c.speciestag, 0, 1, m.tag, m.ally, c.msg);
  79. }
  80. }
  81. if (s.spawns.size() > 0) {
  82. for (const auto& c : s.spawns) {
  83. if ((state.ticks & c.turns) != 0) continue;
  84. double v = state.rng.gauss(0.0, 1.0);
  85. if (v <= c.chance) continue;
  86. summons.emplace_back(mxy.first, mxy.second, summons_t::type_t::LEVEL,
  87. tag_t(), c.level, 1, m.tag, m.ally, c.msg);
  88. ret = true;
  89. }
  90. }
  91. if (!s.morph.species.null()) {
  92. double v = state.rng.gauss(0.0, 1.0);
  93. if (v > s.morph.chance) {
  94. tag_t news = s.morph.species;
  95. m.tag = news;
  96. state.render.invalidate(mxy.first, mxy.second);
  97. ret = true;
  98. }
  99. }
  100. for (const auto& b : s.blast) {
  101. if (dist2 >= b.range * b.range)
  102. continue;
  103. if ((state.ticks % b.turns) != 0)
  104. continue;
  105. double v = state.rng.gauss(0.0, 1.0);
  106. if (v <= b.chance) continue;
  107. do_monster_blast(p, state, s, m, target.first, target.second, b.radius, b.attacks);
  108. ret = true;
  109. }
  110. for (const auto& c : s.cast_cloud) {
  111. if ((state.ticks % c.turns) != 0) continue;
  112. double v = state.rng.gauss(0.0, 1.0);
  113. if (v <= c.chance) continue;
  114. cast_cloud(state, target.first, target.second, c.radius, c.terraintag);
  115. if (state.render.is_in_fov(mxy.first, mxy.second))
  116. state.render.do_message(nlp::message("%s casts %s!"_m, s, c.name));
  117. ret = true;
  118. }
  119. if (!s.magic_cost.stat.null()) {
  120. if (m.stats.sinc(s.magic_cost.stat, -s.magic_cost.cost)) {
  121. m.dead = true;
  122. }
  123. }
  124. return ret;
  125. }
  126. namespace {
  127. inline unsigned int dist2(int x1, int x2, int y1, int y2) {
  128. int a = x1 - x2;
  129. int b = y1 - y2;
  130. return a*a + b*b;
  131. }
  132. inline bool is_closer(const monsters::pt& a, const monsters::pt& b, const monsters::pt& target) {
  133. unsigned int da = dist2(a.first, a.second, target.first, target.second);
  134. unsigned int db = dist2(b.first, b.second, target.first, target.second);
  135. if (da < db)
  136. return true;
  137. return false;
  138. }
  139. inline void find_furthest(const std::vector<monsters::pt>& possible_xy,
  140. const monsters::pt& beeline_xy, const monsters::pt& target,
  141. monsters::pt& nxy) {
  142. bool found = false;
  143. for (const auto& v : possible_xy) {
  144. if (!found) {
  145. nxy = v;
  146. found = true;
  147. } else if (!is_closer(v, nxy, target)) {
  148. nxy = v;
  149. }
  150. }
  151. }
  152. inline void find_closest(const std::vector<monsters::pt>& possible_xy,
  153. const monsters::pt& beeline_xy, const monsters::pt& target,
  154. monsters::pt& nxy) {
  155. bool found = false;
  156. for (const auto& v : possible_xy) {
  157. if (beeline_xy == v) {
  158. nxy = v;
  159. break;
  160. } else if (!found) {
  161. nxy = v;
  162. found = true;
  163. } else if (is_closer(v, nxy, target)) {
  164. nxy = v;
  165. }
  166. }
  167. }
  168. }
  169. inline bool move_monster_main(Player& p, GameState& state,
  170. std::vector<summons_t>& summons,
  171. const monsters::pt& mxy, monsters::Monster& m, const Species& s,
  172. monsters::pt& nxy, bool& do_die) {
  173. bool do_stop = false;
  174. unsigned int range = s.range;
  175. if (s.ai == Species::ai_t::none)
  176. do_stop = true;
  177. features::Feature feat;
  178. if (state.features.get(mxy.first, mxy.second, feat) &&
  179. !s.flags.terrain_immune) {
  180. const Terrain& t = terrain().get(feat.tag);
  181. if (!t.attacks.empty()) {
  182. if (t.player_attack) {
  183. attack_from_env(p, t.attacks, t.attack_level, state, mxy, m, p, true);
  184. } else {
  185. int tmp;
  186. attack_from_env(p, t.attacks, t.attack_level, state, mxy, m, tmp, false);
  187. }
  188. if (t.uncharge.attack) {
  189. state.features.uncharge(mxy.first, mxy.second, state.render);
  190. }
  191. }
  192. if (t.sticky && !do_stop) {
  193. if (t.uncharge.move) {
  194. state.features.uncharge(mxy.first, mxy.second, state.render);
  195. }
  196. do_stop = true;
  197. }
  198. if (t.view_radius >= 0) {
  199. // HACK. Monsters are dumber than the player, give the player a slight advantage.
  200. range = std::max(0, t.view_radius - 1);
  201. }
  202. }
  203. {
  204. const Levelskin& ls = levelskins().get(p.worldz);
  205. if (!ls.ailment.null()) {
  206. process_ailment(ls.ailment, p, state, mxy, m);
  207. }
  208. }
  209. if (m.dead) {
  210. if (!s.death_summon.null()) {
  211. summons.emplace_back(mxy.first, mxy.second, summons_t::type_t::SPECIFIC,
  212. s.death_summon, 0, 1, m.tag, m.ally, "");
  213. }
  214. do_die = true;
  215. return true;
  216. }
  217. // Keep track of seen monsters, hack.
  218. if (p.seen_monsters.tally.count(m.tag) == 0 && state.render.is_in_fov(mxy.first, mxy.second)) {
  219. p.seen_monsters.tally.insert(m.tag);
  220. p.seen_monsters.timeline.push_back(m.tag);
  221. }
  222. if (do_stop) {
  223. return false;
  224. }
  225. //
  226. m.stats.tick();
  227. double blind = 0;
  228. bool stun = false;
  229. bool fear = false;
  230. m.sleep = false;
  231. for (const auto& i : m.stats.counts) {
  232. const Count& ct = counts().get(i.first);
  233. if (ct.blind) blind = (double)i.second.val / (double)ct.cmax;
  234. if (ct.stun) stun = true;
  235. if (ct.fear) fear = true;
  236. if (ct.sleep) m.sleep = true;
  237. }
  238. if (m.sleep)
  239. return false;
  240. if (blind > 0) {
  241. range = std::max(0, (int)(range * (1.0 - blind)));
  242. }
  243. std::vector<monsters::pt> possible_xy;
  244. bool did_possible = false;
  245. bool do_random = false;
  246. unsigned int maxd2 = (range + 1) * (range + 1);
  247. bool full_empty;
  248. auto nearest = state.monsters.nearest.get(mxy.first, mxy.second, maxd2, full_empty);
  249. if (nearest.empty()) {
  250. // Short-path AI for out-of-range monsters.
  251. if (s.idle_ai == Species::idle_ai_t::random && !full_empty) {
  252. do_random = true;
  253. } else {
  254. return false;
  255. }
  256. } else {
  257. if (s.ai == Species::ai_t::random) {
  258. do_random = true;
  259. } else {
  260. int pri = -1;
  261. monsters::pt target;
  262. bool enemy_is_player = false;
  263. tag_t enemy_ally;
  264. monsters::pt beeline_xy;
  265. if (s.ai != Species::ai_t::magic_none && s.ai != Species::ai_t::magic_none_awake) {
  266. for (const auto& v_ : state.neigh(mxy)) {
  267. auto v = state.neigh.mk(v_, mxy);
  268. if (!monster_walkable(state, s, v.first, v.second))
  269. continue;
  270. possible_xy.push_back(v);
  271. }
  272. did_possible = true;
  273. }
  274. for (const auto& i : nearest) {
  275. const monsters::Monster& other = state.monsters.get(i.x, i.y);
  276. bool is_player = (i.x == p.px && i.y == p.py);
  277. if (other.null() && !is_player)
  278. continue;
  279. if (!is_player && m.ally == other.ally && i.dist2 <= 2)
  280. continue;
  281. int thispri = 0;
  282. if (m.ally.null() && is_player) {
  283. thispri = 3;
  284. } else if (!m.ally.null() && is_player) {
  285. thispri = 1;
  286. } else if (m.ally != other.ally) {
  287. thispri = 2;
  288. }
  289. unsigned int d2 = i.dist2;
  290. if (thispri < pri)
  291. continue;
  292. bool enemy_sleeping = (is_player ? p.sleep > 0 : other.sleep > 0);
  293. if (enemy_sleeping && (s.ai == Species::ai_t::magic_none_awake || s.ai == Species::ai_t::seek_awake))
  294. continue;
  295. unsigned int tmpnn = 0;
  296. monsters::pt bxy;
  297. bool ok = reachable(state, mxy.first, mxy.second, i.x, i.y,
  298. [&tmpnn,&bxy,&s](GameState& state, unsigned int x, unsigned int y) {
  299. if (!player_walkable_aux(state, x, y, (s.digging > 0)))
  300. return false;
  301. ++tmpnn;
  302. if (tmpnn == 2)
  303. bxy = monsters::pt(x, y);
  304. return true;
  305. });
  306. if (!ok)
  307. continue;
  308. if (thispri == pri && d2 >= maxd2)
  309. continue;
  310. pri = thispri;
  311. maxd2 = d2;
  312. beeline_xy = bxy;
  313. target = monsters::pt(i.x, i.y);
  314. enemy_is_player = is_player;
  315. enemy_ally = (is_player ? tag_t() : other.ally);
  316. }
  317. // HACK
  318. if (!m.ally.null() && enemy_is_player && maxd2 <= 2) {
  319. pri = -1;
  320. }
  321. if (pri < 0) {
  322. if (s.ai == Species::ai_t::magic_none || s.ai == Species::ai_t::magic_none_awake) {
  323. return false;
  324. } else {
  325. do_random = true;
  326. }
  327. } else {
  328. // We found a target.
  329. if (!fear &&
  330. do_monster_magic(p, state, summons, target, maxd2, enemy_is_player, enemy_ally, mxy, m, s)) {
  331. return false;
  332. }
  333. if (possible_xy.empty())
  334. return false;
  335. if (s.ai == Species::ai_t::magic_random ||
  336. !monster_walkable(state, s, target.first, target.second)) {
  337. do_random = true;
  338. } else {
  339. if (fear) {
  340. find_furthest(possible_xy, beeline_xy, target, nxy);
  341. } else {
  342. find_closest(possible_xy, beeline_xy, target, nxy);
  343. }
  344. // OK! If we got here, then 'nxy' holds a valid move and the monster
  345. // is actually doing something intelligent.
  346. }
  347. }
  348. }
  349. }
  350. if (do_random) {
  351. if (!did_possible) {
  352. for (const auto& v_ : state.neigh(mxy)) {
  353. auto v = state.neigh.mk(v_, mxy);
  354. if (monster_walkable(state, s, v.first, v.second)) {
  355. possible_xy.push_back(v);
  356. }
  357. }
  358. did_possible = true;
  359. }
  360. if (possible_xy.empty())
  361. return false;
  362. nxy = possible_xy[state.rng.n(possible_xy.size())];
  363. }
  364. if (stun) {
  365. if (mxy.first != nxy.first && state.rng.range(0, 2) == 0) {
  366. nxy.first = mxy.first + mxy.first - nxy.first;
  367. }
  368. if (mxy.second != nxy.second && state.rng.range(0, 2) == 0) {
  369. nxy.second = mxy.second + mxy.second - nxy.second;
  370. }
  371. if (!monster_walkable(state, s, nxy.first, nxy.second))
  372. return false;
  373. }
  374. if (!state.grid.is_walk(nxy.first, nxy.second)) {
  375. if (digging_step(state, nxy.first, nxy.second, s.digging)) {
  376. state.render.do_message("You hear a loud crashing noise."_m);
  377. } else {
  378. return false;
  379. }
  380. }
  381. if (nxy.first == p.px && nxy.second == p.py) {
  382. if (s.ai == Species::ai_t::suicide) {
  383. monster_kill(p, state, mxy, m, s, false, std::set<tag_t>());
  384. do_die = true;
  385. return true;
  386. }
  387. if (!s.steal.null()) {
  388. items::Item tmp;
  389. if (p.inv.take(s.steal, tmp)) {
  390. state.render.do_message(nlp::message("%S disappears in a puff of smoke."_m, s), true);
  391. do_die = true;
  392. return true;
  393. }
  394. }
  395. if (!m.ally.null())
  396. return false;
  397. damage::defenses_t defenses;
  398. p.get_defense(defenses);
  399. defend(p, defenses, p.get_computed_level(), s, m, state);
  400. return false;
  401. }
  402. if (!s.trail.terrain.null() &&
  403. state.features.x_set(nxy.first, nxy.second, s.trail.terrain, state.render)) {
  404. double c = state.rng.gauss(s.trail.cost.mean, s.trail.cost.deviation);
  405. if (c != 0 && m.stats.sinc(s.trail.stat, c)) {
  406. do_die = true;
  407. return true;
  408. }
  409. }
  410. return true;
  411. }
  412. inline bool move_monster(Player& p, GameState& state,
  413. std::vector<summons_t>& summons,
  414. const monsters::pt& mxy, monsters::Monster& m, const Species& s,
  415. monsters::pt& nxy, bool& do_die) {
  416. bool moved = move_monster_main(p, state, summons, mxy, m, s, nxy, do_die);
  417. if (s.flags.stealthy && !moved && !do_die) {
  418. m.hidden = true;
  419. state.render.invalidate(mxy.first, mxy.second);
  420. }
  421. return moved;
  422. }
  423. inline int conflict_monster(Player& p, GameState& state,
  424. const monsters::pt& mxya, monsters::Monster& ma,
  425. const monsters::pt& mxyb, monsters::Monster& mb) {
  426. // 1 means first monster wins and second dies.
  427. // 2 means second monster wins and first dies.
  428. // 0 means nobody dies and conflicts are resolved by not moving.
  429. // No double attacks for monsters!
  430. if (ma.ally == mb.ally)
  431. return 0;
  432. const Species& sa = species().get(ma.tag);
  433. const Species& sb = species().get(mb.tag);
  434. if (!ma.did_attack) {
  435. ma.did_attack = true;
  436. attack_from_env(p, sa.attacks, sa.get_computed_level(), state, mxyb, mb, ma, false);
  437. }
  438. if (!mb.did_attack) {
  439. mb.did_attack = true;
  440. attack_from_env(p, sb.attacks, sb.get_computed_level(), state, mxya, ma, mb, false);
  441. }
  442. int ret = 0;
  443. auto do_message = [](GameState& state, const monsters::pt& mxy, const monsters::Monster& m,
  444. const Species& s) {
  445. if (state.render.is_in_fov(mxy.first, mxy.second)) {
  446. std::string verb = (s.flags.robot || s.flags.plant ? "destroyed"_m : "killed"_m);
  447. if (m.ally.null()) {
  448. state.render.do_message(nlp::message("%S is %s."_m, s, verb));
  449. } else {
  450. state.render.do_message(nlp::message("%S ally is %s!"_m, s, verb));
  451. }
  452. }
  453. };
  454. if (ma.dead) {
  455. do_message(state, mxya, ma, sa);
  456. ret |= 2;
  457. }
  458. if (mb.dead) {
  459. do_message(state, mxyb, mb, sb);
  460. ret |= 1;
  461. }
  462. return ret;
  463. }
  464. #endif