1 /**
2 * @file
3 * @brief Monster tentacle-related code.
4 **/
5
6 #include "AppHdr.h"
7
8 #include "mon-tentacle.h"
9
10 #include <functional>
11
12 #include "act-iter.h"
13 #include "coordit.h"
14 #include "delay.h"
15 #include "env.h"
16 #include "fprop.h"
17 #include "libutil.h" // map_find
18 #include "losglobal.h"
19 #include "mgen-data.h"
20 #include "mon-death.h"
21 #include "mon-place.h"
22 #include "nearby-danger.h"
23 #include "terrain.h"
24
25 const int MAX_KRAKEN_TENTACLE_DIST = 12;
26 const int MAX_ACTIVE_KRAKEN_TENTACLES = 4;
27 const int MAX_ACTIVE_STARSPAWN_TENTACLES = 2;
28
29 static monster_type _head_child_segment[][3] =
30 {
31 { MONS_KRAKEN, MONS_KRAKEN_TENTACLE,
32 MONS_KRAKEN_TENTACLE_SEGMENT },
33 { MONS_TENTACLED_STARSPAWN, MONS_STARSPAWN_TENTACLE,
34 MONS_STARSPAWN_TENTACLE_SEGMENT },
35 };
36
37 static monster_type _solo_tentacle_to_segment[][2] =
38 {
39 { MONS_ELDRITCH_TENTACLE, MONS_ELDRITCH_TENTACLE_SEGMENT },
40 { MONS_SNAPLASHER_VINE, MONS_SNAPLASHER_VINE_SEGMENT },
41 };
42
_segment_data(const monster & head,coord_def pos,monster_type type)43 static mgen_data _segment_data(const monster& head, coord_def pos,
44 monster_type type)
45 {
46 mgen_data mg(type, SAME_ATTITUDE((&head)), pos, head.foe, MG_FORCE_PLACE);
47 if (mons_is_zombified(head))
48 mg.base_type = head.type;
49 mg.set_summoned(&head, 0, 0, head.god)
50 .set_col(head.colour);
51 return mg;
52 }
53
mons_is_tentacle_head(monster_type mc)54 bool mons_is_tentacle_head(monster_type mc)
55 {
56 for (const monster_type (&m)[3] : _head_child_segment)
57 if (mc == m[0])
58 return true;
59
60 return false;
61 }
62
mons_is_child_tentacle(monster_type mc)63 bool mons_is_child_tentacle(monster_type mc)
64 {
65 for (const monster_type (&m)[3] : _head_child_segment)
66 if (mc == m[1])
67 return true;
68
69 return false;
70 }
71
mons_is_child_tentacle_segment(monster_type mc)72 bool mons_is_child_tentacle_segment(monster_type mc)
73 {
74 for (const monster_type (&m)[3] : _head_child_segment)
75 if (mc == m[2])
76 return true;
77
78 return false;
79 }
80
mons_is_solo_tentacle(monster_type mc)81 bool mons_is_solo_tentacle(monster_type mc)
82 {
83 for (const monster_type (&m)[2] : _solo_tentacle_to_segment)
84 if (mc == m[0])
85 return true;
86
87 return false;
88 }
89
mons_is_tentacle(monster_type mc)90 bool mons_is_tentacle(monster_type mc)
91 {
92 return mons_is_child_tentacle(mc) || mons_is_solo_tentacle(mc);
93 }
94
mons_is_tentacle_segment(monster_type mc)95 bool mons_is_tentacle_segment(monster_type mc)
96 {
97 for (const monster_type (&m)[2] : _solo_tentacle_to_segment)
98 if (mc == m[1])
99 return true;
100
101 return mons_is_child_tentacle_segment(mc);
102 }
103
mons_is_tentacle_or_tentacle_segment(monster_type mc)104 bool mons_is_tentacle_or_tentacle_segment(monster_type mc)
105 {
106 return mons_is_tentacle(mc) || mons_is_tentacle_segment(mc);
107 }
108
mons_tentacle_parent_type(const monster * mons)109 monster_type mons_tentacle_parent_type(const monster* mons)
110 {
111 const monster_type mc = mons_base_type(*mons);
112
113 for (const monster_type (&m)[3] : _head_child_segment)
114 if (mc == m[1])
115 return m[0];
116
117 for (const monster_type (&m)[3] : _head_child_segment)
118 if (mc == m[2])
119 return m[1];
120
121 for (const monster_type (&m)[2] : _solo_tentacle_to_segment)
122 if (mc == m[1])
123 return m[0];
124
125 return MONS_PROGRAM_BUG;
126 }
127
mons_tentacle_child_type(const monster * mons)128 monster_type mons_tentacle_child_type(const monster* mons)
129 {
130 const monster_type mc = mons_base_type(*mons);
131
132 for (const monster_type (&m)[3] : _head_child_segment)
133 if (mc == m[0])
134 return m[1];
135
136 for (const monster_type (&m)[3] : _head_child_segment)
137 if (mc == m[1])
138 return m[2];
139
140 for (const monster_type (&m)[2] : _solo_tentacle_to_segment)
141 if (mc == m[0])
142 return m[1];
143
144 return MONS_PROGRAM_BUG;
145 }
146
is_child_tentacle() const147 bool monster::is_child_tentacle() const
148 {
149 return mons_is_child_tentacle(mons_base_type(*this));
150 }
151
is_child_tentacle_segment() const152 bool monster::is_child_tentacle_segment() const
153 {
154 return mons_is_child_tentacle_segment(mons_base_type(*this));
155 }
156
is_child_monster() const157 bool monster::is_child_monster() const
158 {
159 return is_child_tentacle() || is_child_tentacle_segment();
160 }
161
is_child_tentacle_of(const monster * mons) const162 bool monster::is_child_tentacle_of(const monster* mons) const
163 {
164 return mons_base_type(*mons) == mons_tentacle_parent_type(this)
165 && tentacle_connect == mons->mid;
166 }
167
is_parent_monster_of(const monster * mons) const168 bool monster::is_parent_monster_of(const monster* mons) const
169 {
170 return mons_base_type(*this) == mons_tentacle_parent_type(mons)
171 && mons->tentacle_connect == mid;
172 }
173
174 //Returns whether a given monster is a tentacle segment immediately attached
175 //to the parent monster
mons_tentacle_adjacent(const monster * parent,const monster * child)176 bool mons_tentacle_adjacent(const monster* parent, const monster* child)
177 {
178 return mons_is_tentacle_head(mons_base_type(*parent))
179 && mons_is_tentacle_segment(child->type)
180 && child->props.exists("inwards")
181 && child->props["inwards"].get_int() == (int) parent->mid;
182 }
183
get_tentacle_head(const monster & mon)184 const monster& get_tentacle_head(const monster& mon)
185 {
186 const monster* m = &mon;
187 // For tentacle segments, find the associated tentacle.
188 if (m->is_child_tentacle_segment())
189 {
190 monster* tentacle = monster_by_mid(m->tentacle_connect);
191 if (!tentacle)
192 return *m;
193
194 m = tentacle;
195 }
196
197 // For tentacles, find the associated head.
198 if (m->is_child_tentacle())
199 {
200 monster* head = monster_by_mid(m->tentacle_connect);
201 if (!head)
202 return *m;
203
204 m = head;
205 }
206
207 return *m;
208 }
209
_establish_connection(monster * tentacle,monster * head,set<position_node>::iterator path,monster_type connector_type)210 static void _establish_connection(monster* tentacle,
211 monster* head,
212 set<position_node>::iterator path,
213 monster_type connector_type)
214 {
215 const position_node * last = &(*path);
216 const position_node * current = last->last;
217
218 // Tentacle is adjacent to the end position, not much to do.
219 if (!current)
220 {
221 // This is a little awkward now. Oh well. -cao
222 if (tentacle != head)
223 tentacle->props["inwards"].get_int() = head->mid;
224 else
225 tentacle->props["inwards"].get_int() = MID_NOBODY;
226
227 return;
228 }
229
230 // No base monster case (demonic tentacles)
231 if (!monster_at(last->pos))
232 {
233 mgen_data mg = _segment_data(*head, last->pos, connector_type);
234 mg.props[MGEN_TENTACLE_CONNECT] = int(tentacle->mid);
235 if (monster *connect = create_monster(mg))
236 {
237 connect->props["inwards"].get_int() = MID_NOBODY;
238 connect->props["outwards"].get_int() = MID_NOBODY;
239
240 if (head->holiness() & MH_UNDEAD)
241 connect->flags |= MF_FAKE_UNDEAD;
242
243 connect->max_hit_points = tentacle->max_hit_points;
244 connect->hit_points = tentacle->hit_points;
245 }
246 else
247 {
248 // Big failure mode.
249 return;
250 }
251 }
252
253 while (current)
254 {
255 // Last monster we visited or placed
256 monster* last_mon = monster_at(last->pos);
257 if (!last_mon)
258 {
259 // Should be something there, what to do if there isn't?
260 mpr("Error! failed to place monster in tentacle connect change");
261 break;
262 }
263
264 // Monster at the current square, should be the end of the line if there
265 monster* current_mons = monster_at(current->pos);
266 if (current_mons)
267 {
268 // Todo verify current monster type
269 current_mons->props["inwards"].get_int() = last_mon->mid;
270 last_mon->props["outwards"].get_int() = current_mons->mid;
271 break;
272 }
273
274 // place a connector
275 mgen_data mg = _segment_data(*head, current->pos, connector_type);
276 mg.props[MGEN_TENTACLE_CONNECT] = int(tentacle->mid);
277 if (monster *connect = create_monster(mg))
278 {
279 connect->max_hit_points = tentacle->max_hit_points;
280 connect->hit_points = tentacle->hit_points;
281
282 connect->props["inwards"].get_int() = last_mon->mid;
283 connect->props["outwards"].get_int() = MID_NOBODY;
284
285 if (last_mon->type == connector_type)
286 last_mon->props["outwards"].get_int() = connect->mid;
287
288 if (head->holiness() & MH_UNDEAD)
289 connect->flags |= MF_FAKE_UNDEAD;
290
291 if (monster_can_submerge(connect, env.grid(connect->pos())))
292 connect->add_ench(ENCH_SUBMERGED);
293 }
294 else
295 {
296 // connector placement failed, what to do?
297 mprf("connector placement failed at %d %d", current->pos.x, current->pos.y);
298 }
299
300 last = current;
301 current = current->last;
302 }
303 }
304
305 struct tentacle_attack_constraints
306 {
307 vector<coord_def> * target_positions;
308
309 map<coord_def, set<int> > * connection_constraints;
310 monster *base_monster;
311 int max_string_distance;
312 int connect_idx[8];
313
tentacle_attack_constraintstentacle_attack_constraints314 tentacle_attack_constraints()
315 {
316 for (int i=0; i<8; i++)
317 connect_idx[i] = i;
318 }
319
min_disttentacle_attack_constraints320 int min_dist(const coord_def & pos)
321 {
322 int min = INT_MAX;
323 for (coord_def tpos : *target_positions)
324 min = std::min(min, grid_distance(pos, tpos));
325 return min;
326 }
327
operator ()tentacle_attack_constraints328 void operator()(const position_node & node,
329 vector<position_node> & expansion)
330 {
331 shuffle_array(connect_idx);
332
333 // mprf("expanding %d %d, string dist %d", node.pos.x, node.pos.y, node.string_distance);
334 for (int idx : connect_idx)
335 {
336 position_node temp;
337
338 temp.pos = node.pos + Compass[idx];
339 temp.string_distance = node.string_distance;
340 temp.departure = node.departure;
341 temp.connect_level = node.connect_level;
342 temp.path_distance = node.path_distance;
343 temp.estimate = 0;
344
345 if (!in_bounds(temp.pos) || is_sanctuary(temp.pos))
346 continue;
347
348 if (!base_monster->is_habitable(temp.pos))
349 temp.path_distance = DISCONNECT_DIST;
350 else
351 {
352 actor * act_at = actor_at(temp.pos);
353 monster* mons_at = monster_at(temp.pos);
354
355 if (!act_at)
356 temp.path_distance += 1;
357 // Can still search through a firewood monster, just at a higher
358 // path cost.
359 else if (mons_at && mons_is_firewood(*mons_at)
360 && !mons_aligned(base_monster, mons_at))
361 {
362 temp.path_distance += 10;
363 }
364 // An actor we can't path through is there
365 else
366 temp.path_distance = DISCONNECT_DIST;
367
368 }
369
370 int connect_level = temp.connect_level;
371 int base_connect_level = connect_level;
372
373 if (auto constraint = map_find(*connection_constraints, temp.pos))
374 {
375 int max_val = constraint->empty()
376 ? INT_MAX : *constraint->rbegin();
377
378 if (max_val < connect_level)
379 temp.departure = true;
380
381 // If we can still feasibly retract (haven't left connect range)
382 if (!temp.departure)
383 {
384 if (constraint->count(connect_level))
385 {
386 while (constraint->count(connect_level + 1))
387 connect_level++;
388 }
389
390 int delta = connect_level - base_connect_level;
391 temp.connect_level = connect_level;
392 if (delta)
393 temp.string_distance -= delta;
394 }
395
396 if (connect_level < max_val)
397 temp.path_distance = DISCONNECT_DIST;
398 }
399 else
400 {
401 // We stopped retracting
402 temp.departure = true;
403 }
404
405 if (temp.departure)
406 temp.string_distance++;
407
408 // if (temp.string_distance > MAX_KRAKEN_TENTACLE_DIST)
409 if (temp.string_distance > max_string_distance)
410 temp.path_distance = DISCONNECT_DIST;
411
412 if (temp.path_distance != DISCONNECT_DIST)
413 temp.estimate = min_dist(temp.pos);
414
415 expansion.push_back(temp);
416 }
417 }
418 };
419
420 struct tentacle_connect_constraints
421 {
422 map<coord_def, set<int> > * connection_constraints;
423
424 monster* base_monster;
425
tentacle_connect_constraintstentacle_connect_constraints426 tentacle_connect_constraints()
427 {
428 for (int i=0; i<8; i++)
429 connect_idx[i] = i;
430 }
431
432 int connect_idx[8];
operator ()tentacle_connect_constraints433 void operator()(const position_node & node,
434 vector<position_node> & expansion)
435 {
436 shuffle_array(connect_idx);
437
438 for (int idx : connect_idx)
439 {
440 position_node temp;
441
442 temp.pos = node.pos + Compass[idx];
443
444 if (!in_bounds(temp.pos))
445 continue;
446
447 auto constraint = map_find(*connection_constraints, temp.pos);
448
449 if (!constraint || !constraint->count(node.connect_level))
450 continue;
451
452 if (!base_monster->is_habitable(temp.pos) || actor_at(temp.pos))
453 temp.path_distance = DISCONNECT_DIST;
454 else
455 temp.path_distance = 1 + node.path_distance;
456
457 //temp.estimate = grid_distance(temp.pos, kraken->pos());
458 // Don't bother with an estimate, the search is highly constrained
459 // so it's not really going to help
460 temp.estimate = 0;
461 int test_level = node.connect_level;
462
463 while (constraint->count(test_level + 1))
464 test_level++;
465
466 int max = constraint->empty() ? INT_MAX : *constraint->rbegin();
467
468 if (test_level < max)
469 continue;
470
471 temp.connect_level = test_level;
472
473 expansion.push_back(temp);
474 }
475 }
476 };
477
478 struct target_position
479 {
480 coord_def target;
operator ()target_position481 bool operator() (const coord_def & pos)
482 {
483 return pos == target;
484 }
485 };
486
487 /*struct target_monster
488 {
489 int target_mindex;
490
491 bool operator() (const coord_def & pos)
492 {
493 monster* temp = monster_at(pos);
494 if (!temp || temp->mindex() != target_mindex)
495 return false;
496 return true;
497
498 }
499 };*/
500
501 struct multi_target
502 {
503 vector<coord_def> * targets;
504
operator ()multi_target505 bool operator() (const coord_def & pos)
506 {
507 return find(begin(*targets), end(*targets), pos) != end(*targets);
508 }
509 };
510
511 // returns pathfinding success/failure
_tentacle_pathfind(monster * tentacle,tentacle_attack_constraints & attack_constraints,coord_def & new_position,vector<coord_def> & target_positions,int total_length)512 static bool _tentacle_pathfind(monster* tentacle,
513 tentacle_attack_constraints & attack_constraints,
514 coord_def & new_position,
515 vector<coord_def> & target_positions,
516 int total_length)
517 {
518 multi_target foe_check { &target_positions };
519
520 vector<set<position_node>::iterator > tentacle_path;
521
522 set<position_node> visited;
523 visited.clear();
524
525 position_node temp;
526 temp.pos = tentacle->pos();
527
528 auto constraint = map_find(*attack_constraints.connection_constraints,
529 temp.pos);
530 ASSERT(constraint);
531 temp.connect_level = 0;
532 while (constraint->count(temp.connect_level + 1))
533 temp.connect_level++;
534
535 temp.departure = false;
536 temp.string_distance = total_length;
537
538 search_astar(temp,
539 foe_check, attack_constraints,
540 visited, tentacle_path);
541
542 bool path_found = false;
543 // Did we find a path?
544 if (!tentacle_path.empty())
545 {
546 // The end position is the enemy or target square, we need
547 // to rewind the found path to find the next move
548
549 const position_node * current = &(*tentacle_path[0]);
550 const position_node * last;
551
552 // The last position in the chain is the base position,
553 // so we want to stop at the one before the last.
554 while (current && current->last)
555 {
556 last = current;
557 current = current->last;
558 new_position = last->pos;
559 path_found = true;
560 }
561 }
562
563 return path_found;
564 }
565
_try_tentacle_connect(const coord_def & new_pos,const coord_def & base_position,monster * tentacle,monster * head,tentacle_connect_constraints & connect_costs,monster_type connect_type)566 static bool _try_tentacle_connect(const coord_def & new_pos,
567 const coord_def & base_position,
568 monster* tentacle,
569 monster* head,
570 tentacle_connect_constraints & connect_costs,
571 monster_type connect_type)
572 {
573 // Nothing to do here.
574 // Except fix the tentacle end's pointer, idiot.
575 if (base_position == new_pos)
576 {
577 if (tentacle == head)
578 tentacle->props["inwards"].get_int() = MID_NOBODY;
579 else
580 tentacle->props["inwards"].get_int() = head->mid;
581 return true;
582 }
583
584 int start_level = 0;
585 // This condition should never miss
586 if (auto constraint = map_find(*connect_costs.connection_constraints,
587 new_pos))
588 {
589 while (constraint->count(start_level + 1))
590 start_level++;
591 }
592
593 // Find the tentacle -> head path
594 target_position current_target;
595 current_target.target = base_position;
596 /* target_monster current_target;
597 current_target.target_mindex = headnum;
598 */
599
600 set<position_node> visited;
601 vector<set<position_node>::iterator> candidates;
602
603 position_node temp;
604 temp.pos = new_pos;
605 temp.connect_level = start_level;
606
607 search_astar(temp,
608 current_target, connect_costs,
609 visited, candidates);
610
611 if (candidates.empty())
612 return false;
613
614 _establish_connection(tentacle, head, candidates[0], connect_type);
615
616 return true;
617 }
618
_collect_tentacles(monster * mons,vector<monster_iterator> & tentacles)619 static void _collect_tentacles(monster* mons,
620 vector<monster_iterator> & tentacles)
621 {
622 // TODO: reorder tentacles based on distance to head or something.
623 for (monster_iterator mi; mi; ++mi)
624 if (mons->is_parent_monster_of(*mi))
625 tentacles.push_back(mi);
626 }
627
_purge_connectors(monster * tentacle)628 static void _purge_connectors(monster* tentacle)
629 {
630 for (monster_iterator mi; mi; ++mi)
631 {
632 if (mi->is_child_tentacle_of(tentacle))
633 {
634 int hp = mi->hit_points;
635 if (hp > 0 && hp < tentacle->hit_points)
636 tentacle->hit_points = hp;
637
638 monster_die(**mi, KILL_MISC, NON_MONSTER, true);
639 }
640 }
641 ASSERT(tentacle->alive());
642 }
643
_collect_foe_positions(monster * mons,vector<coord_def> & foe_positions,function<bool (const actor *)> sight_check)644 static void _collect_foe_positions(monster *mons,
645 vector<coord_def> &foe_positions,
646 function<bool(const actor *)> sight_check)
647 {
648 coord_def foe_pos(-1, -1);
649 actor * foe = mons->get_foe();
650 if (foe && sight_check(foe))
651 {
652 foe_positions.push_back(mons->get_foe()->pos());
653 foe_pos = foe_positions.back();
654 }
655
656 for (monster_iterator mi; mi; ++mi)
657 {
658 const monster * const test = *mi;
659 if (!mons_is_firewood(*test)
660 && !mons_aligned(test, mons)
661 && test->pos() != foe_pos
662 && sight_check(test))
663 {
664 foe_positions.push_back(test->pos());
665 }
666 }
667 }
668
669 // Return value is a retract position for the tentacle or -1, -1 if no
670 // retract position exists.
671 //
672 // move_kraken_tentacle should check retract pos, it could potentially
673 // give the kraken head's position as a retract pos.
_collect_connection_data(monster * start_monster,map<coord_def,set<int>> & connection_data,coord_def & retract_pos)674 static int _collect_connection_data(monster* start_monster,
675 map<coord_def, set<int> > & connection_data,
676 coord_def & retract_pos)
677 {
678 int current_count = 0;
679 monster* current_mon = start_monster;
680 retract_pos.x = -1;
681 retract_pos.y = -1;
682 bool retract_found = false;
683
684 while (current_mon)
685 {
686 for (adjacent_iterator adj_it(current_mon->pos(), false);
687 adj_it; ++adj_it)
688 {
689 connection_data[*adj_it].insert(current_count);
690 }
691
692 bool basis = current_mon->props.exists("inwards");
693 monster* next = basis ? monster_by_mid(current_mon->props["inwards"].get_int()) : nullptr;
694
695 if (next && next->is_child_tentacle_of(start_monster))
696 {
697 current_mon = next;
698 if (current_mon->tentacle_connect != start_monster->mid)
699 mpr("link information corruption!!! tentacle in chain doesn't match mindex");
700 if (!retract_found)
701 {
702 retract_pos = current_mon->pos();
703 retract_found = true;
704 }
705 }
706 else
707 {
708 current_mon = nullptr;
709 // mprf("null at count %d", current_count);
710 }
711 current_count++;
712 }
713
714 // mprf("returned count %d", current_count);
715 return current_count;
716 }
717
move_solo_tentacle(monster * tentacle)718 void move_solo_tentacle(monster* tentacle)
719 {
720 if (!tentacle || !mons_is_solo_tentacle(mons_base_type(*tentacle)))
721 return;
722
723 vector<coord_def> foe_positions;
724
725 bool attack_foe = false;
726 bool severed = tentacle->has_ench(ENCH_SEVERED);
727
728 coord_def base_position;
729 if (!tentacle->props.exists("base_position"))
730 tentacle->props["base_position"].get_coord() = tentacle->pos();
731
732 base_position = tentacle->props["base_position"].get_coord();
733
734 if (!severed)
735 {
736 _collect_foe_positions(tentacle, foe_positions,
737 [tentacle, base_position](const actor *test) -> bool
738 {
739 return test->visible_to(tentacle)
740 && cell_see_cell(base_position, test->pos(),
741 LOS_SOLID_SEE);
742 });
743 attack_foe = !foe_positions.empty();
744 }
745
746 coord_def retract_pos;
747 map<coord_def, set<int> > connection_data;
748
749 int visited_count = _collect_connection_data(tentacle,
750 connection_data,
751 retract_pos);
752
753 bool retract_found = retract_pos.x != -1 && retract_pos.y != -1;
754
755 _purge_connectors(tentacle);
756
757 if (severed)
758 {
759 for (fair_adjacent_iterator ai(base_position); ai; ++ai)
760 {
761 if (!actor_at(*ai) && tentacle->is_habitable(*ai))
762 {
763 tentacle->props["base_position"].get_coord() = *ai;
764 base_position = *ai;
765 break;
766 }
767 }
768 }
769
770 coord_def new_pos = tentacle->pos();
771 coord_def old_pos = tentacle->pos();
772
773 int demonic_max_dist = (tentacle->type == MONS_ELDRITCH_TENTACLE ? 5 : 8);
774 tentacle_attack_constraints attack_constraints;
775 attack_constraints.base_monster = tentacle;
776 attack_constraints.max_string_distance = demonic_max_dist;
777 attack_constraints.connection_constraints = &connection_data;
778 attack_constraints.target_positions = &foe_positions;
779
780 bool path_found = false;
781 if (attack_foe)
782 {
783 path_found = _tentacle_pathfind(tentacle, attack_constraints,
784 new_pos, foe_positions, visited_count);
785 }
786
787 //If this tentacle is constricting a creature, attempt to pull it back
788 //towards the head.
789 bool pull_constrictee = false;
790 bool shift_constrictee = false;
791 coord_def shift_pos;
792 actor* constrictee = nullptr;
793 if (tentacle->is_constricting())
794 {
795 constrictee = actor_by_mid(tentacle->constricting->begin()->first);
796
797 // Don't drag things that cannot move
798 if (!constrictee->is_stationary())
799 {
800 if (retract_found)
801 {
802 if (constrictee->is_habitable(old_pos))
803 {
804 pull_constrictee = true;
805 shift_pos = old_pos;
806 }
807 }
808 else if (tentacle->type == MONS_SNAPLASHER_VINE)
809 {
810 // Don't shift our victim if they're already next to a tree
811 // (To avoid shaking players back and forth constantly)
812 bool near_tree = false;
813 for (adjacent_iterator ai(constrictee->pos()); ai; ++ai)
814 {
815 if (feat_is_tree(env.grid(*ai)))
816 {
817 near_tree = true;
818 break;
819 }
820 }
821
822 if (!near_tree)
823 {
824 for (adjacent_iterator ai(tentacle->pos()); ai; ++ai)
825 {
826 if (adjacent(*ai, constrictee->pos())
827 && constrictee->is_habitable(*ai)
828 && !actor_at(*ai))
829 {
830 for (adjacent_iterator ai2(*ai); ai2; ++ai2)
831 {
832 if (feat_is_tree(env.grid(*ai2)))
833 {
834 pull_constrictee = true;
835 shift_constrictee = true;
836 shift_pos = *ai;
837 break;
838 }
839 }
840 }
841 }
842 }
843 }
844 }
845 }
846
847 if (!attack_foe || !path_found)
848 {
849 // todo: set a random position?
850
851 dprf("pathing failed, target %d %d", new_pos.x, new_pos.y);
852 for (fair_adjacent_iterator ai(old_pos); ai; ++ai)
853 {
854 if (!in_bounds(*ai) || is_sanctuary(*ai) || actor_at(*ai))
855 continue;
856
857 int escalated = 0;
858 auto constraint = map_find(connection_data, *ai);
859 ASSERT(constraint);
860
861 while (constraint->count(escalated + 1))
862 escalated++;
863
864 if (!severed
865 && tentacle->is_habitable(*ai)
866 && escalated == *constraint->rbegin()
867 && (visited_count < demonic_max_dist
868 || constraint->size() > 1))
869 {
870 new_pos = *ai;
871 break;
872 }
873 else if (tentacle->is_habitable(*ai)
874 && visited_count > 1
875 && escalated == *constraint->rbegin()
876 && constraint->size() > 1)
877 {
878 new_pos = *ai;
879 break;
880 }
881 }
882 }
883 else if (pull_constrictee && !shift_constrictee)
884 new_pos = retract_pos;
885
886 if (new_pos != old_pos)
887 {
888 // Did we path into a target?
889 if (actor* blocking_actor = actor_at(new_pos))
890 {
891 tentacle->target = new_pos;
892 tentacle->foe = blocking_actor->mindex();
893 new_pos = old_pos;
894 }
895 }
896
897 // Why do I have to do this move? I don't get it.
898 // specifically, if tentacle isn't registered at its new position on env.mgrid
899 // the search fails (sometimes), Don't know why. -cao
900 tentacle->move_to_pos(new_pos);
901
902 if (pull_constrictee)
903 {
904 if (you.can_see(*tentacle))
905 {
906 mprf("The vine drags %s backwards!",
907 constrictee->name(DESC_THE).c_str());
908 }
909
910 if (constrictee->as_player())
911 move_player_to_grid(shift_pos, false);
912 else
913 constrictee->move_to_pos(shift_pos);
914
915 // Interrupt stair travel and passwall.
916 if (constrictee->is_player())
917 stop_delay(true);
918 }
919 tentacle->clear_invalid_constrictions();
920
921 tentacle_connect_constraints connect_costs;
922 connect_costs.connection_constraints = &connection_data;
923 connect_costs.base_monster = tentacle;
924
925 bool connected = _try_tentacle_connect(new_pos, base_position,
926 tentacle, tentacle,
927 connect_costs,
928 mons_tentacle_child_type(tentacle));
929
930 if (!connected)
931 {
932 // This should really never fail for demonic tentacles (they don't
933 // have the whole shifting base problem). -cao
934 mprf("tentacle connect failed! What the heck! severed status %d",
935 tentacle->has_ench(ENCH_SEVERED));
936 mprf("pathed to %d %d from %d %d mid %d count %d", new_pos.x, new_pos.y,
937 old_pos.x, old_pos.y, tentacle->mid, visited_count);
938
939 // Is it ok to purge the tentacle here?
940 monster_die(*tentacle, KILL_MISC, NON_MONSTER, true);
941 return;
942 }
943
944 // mprf("mindex %d vsisted %d", tentacle_idx, visited_count);
945 tentacle->check_redraw(old_pos);
946 tentacle->apply_location_effects(old_pos);
947 }
948
move_child_tentacles(monster * mons)949 void move_child_tentacles(monster* mons)
950 {
951 ASSERT(mons);
952
953 if (!mons_is_tentacle_head(mons_base_type(*mons))
954 || mons->asleep())
955 {
956 return;
957 }
958
959 bool no_foe = false;
960
961 vector<coord_def> foe_positions;
962 _collect_foe_positions(mons, foe_positions,
963 [mons](const actor *test) -> bool
964 {
965 return mons->can_see(*test);
966 });
967
968 //if (!kraken->near_foe())
969 if (foe_positions.empty()
970 || mons->behaviour == BEH_FLEE
971 || mons->behaviour == BEH_WANDER)
972 {
973 no_foe = true;
974 }
975 vector<monster_iterator> tentacles;
976 _collect_tentacles(mons, tentacles);
977
978 // Move each tentacle in turn
979 for (monster_iterator &tent_it : tentacles)
980 {
981 // XXX: Why not just use *tent_it ?
982 monster* tentacle = monster_at(tent_it->pos());
983
984 if (!tentacle)
985 {
986 dprf("Missing tentacle in path.");
987 continue;
988 }
989
990 tentacle_connect_constraints connect_costs;
991 map<coord_def, set<int> > connection_data;
992
993 monster* current_mon = tentacle;
994 int current_count = 0;
995 bool retract_found = false;
996 coord_def retract_pos(-1, -1);
997
998 while (current_mon)
999 {
1000 for (adjacent_iterator adj_it(current_mon->pos(), false);
1001 adj_it; ++adj_it)
1002 {
1003 connection_data[*adj_it].insert(current_count);
1004 }
1005
1006 bool basis = current_mon->props.exists("inwards");
1007 monster* inward = basis ? monster_by_mid(current_mon->props["inwards"].get_int()) : nullptr;
1008
1009 if (inward
1010 && (inward->is_child_tentacle_of(tentacle)
1011 || inward->is_parent_monster_of(tentacle)))
1012 {
1013 current_mon = inward;
1014 if (!retract_found
1015 && current_mon->is_child_tentacle_of(tentacle))
1016 {
1017 retract_pos = current_mon->pos();
1018 retract_found = true;
1019 }
1020 }
1021 else
1022 current_mon = nullptr;
1023 current_count++;
1024 }
1025
1026 _purge_connectors(tentacle);
1027
1028 if (no_foe
1029 && grid_distance(tentacle->pos(), mons->pos()) == 1)
1030 {
1031 // Drop the tentacle if no enemies are in sight and it is
1032 // adjacent to the main body. This is to prevent players from
1033 // just sniping tentacles while outside the kraken's fov.
1034 monster_die(*tentacle, KILL_MISC, NON_MONSTER, true);
1035 continue;
1036 }
1037
1038 coord_def new_pos = tentacle->pos();
1039 coord_def old_pos = tentacle->pos();
1040 bool path_found = false;
1041
1042 tentacle_attack_constraints attack_constraints;
1043 attack_constraints.base_monster = tentacle;
1044 attack_constraints.max_string_distance = MAX_KRAKEN_TENTACLE_DIST;
1045 attack_constraints.connection_constraints = &connection_data;
1046 attack_constraints.target_positions = &foe_positions;
1047
1048 //If this tentacle is constricting a creature, attempt to pull it back
1049 //towards the head.
1050 bool pull_constrictee = false;
1051 actor* constrictee = nullptr;
1052 if (tentacle->is_constricting() && retract_found)
1053 {
1054 constrictee = actor_by_mid(tentacle->constricting->begin()->first);
1055 if (feat_has_solid_floor(env.grid(old_pos))
1056 && constrictee->is_habitable(old_pos))
1057 {
1058 pull_constrictee = true;
1059 }
1060 }
1061
1062 if (!no_foe && !pull_constrictee)
1063 {
1064 path_found = _tentacle_pathfind(
1065 tentacle,
1066 attack_constraints,
1067 new_pos,
1068 foe_positions,
1069 current_count);
1070 }
1071
1072 if (no_foe || !path_found || pull_constrictee)
1073 {
1074 if (retract_found)
1075 new_pos = retract_pos;
1076 else
1077 {
1078 // What happened here? Usually retract found should be true
1079 // or we should get pruned (due to being adjacent to the
1080 // head), in any case just stay here.
1081 }
1082 }
1083
1084 // Did we path into a target?
1085 if (actor* blocking_actor = actor_at(new_pos))
1086 {
1087 tentacle->target = new_pos;
1088 tentacle->foe = blocking_actor->mindex();
1089 new_pos = old_pos;
1090 }
1091
1092 // Why do I have to do this move? I don't get it.
1093 // specifically, if tentacle isn't registered at its new position on
1094 // env.mgrid the search fails (sometimes), Don't know why. -cao
1095 tentacle->move_to_pos(new_pos);
1096
1097 if (pull_constrictee)
1098 {
1099 if (you.can_see(*tentacle))
1100 {
1101 mprf("The tentacle pulls %s backwards!",
1102 constrictee->name(DESC_THE).c_str());
1103 }
1104
1105 if (constrictee->as_player())
1106 move_player_to_grid(old_pos, false);
1107 else
1108 constrictee->move_to_pos(old_pos);
1109
1110 // Interrupt stair travel and passwall.
1111 if (constrictee->is_player())
1112 stop_delay(true);
1113 }
1114 tentacle->clear_invalid_constrictions();
1115
1116 connect_costs.connection_constraints = &connection_data;
1117 connect_costs.base_monster = tentacle;
1118 bool connected = _try_tentacle_connect(new_pos, mons->pos(),
1119 tentacle, mons,
1120 connect_costs,
1121 mons_tentacle_child_type(tentacle));
1122
1123 // Can't connect, usually the head moved and invalidated our position
1124 // in some way. Should look into this more at some point -cao
1125 if (!connected)
1126 {
1127 env.mgrid(tentacle->pos()) = tentacle->mindex();
1128 monster_die(*tentacle, KILL_MISC, NON_MONSTER, true);
1129
1130 continue;
1131 }
1132
1133 tentacle->check_redraw(old_pos);
1134 tentacle->apply_location_effects(old_pos);
1135 }
1136 }
1137
_mons_get_parent_monster(monster * mons)1138 static monster* _mons_get_parent_monster(monster* mons)
1139 {
1140 for (monster_iterator mi; mi; ++mi)
1141 {
1142 if (mi->is_parent_monster_of(mons))
1143 return *mi;
1144 }
1145
1146 return nullptr;
1147 }
1148
1149 // When given either a tentacle end or segment, kills the end and all segments
1150 // of that tentacle.
destroy_tentacle(monster * mons)1151 bool destroy_tentacle(monster* mons)
1152 {
1153 bool any = false;
1154
1155 monster* head = mons_is_tentacle_segment(mons->type)
1156 ? _mons_get_parent_monster(mons) : mons;
1157
1158 //If we tried to find the head, but failed (probably because it is already
1159 //dead), cancel trying to kill this tentacle
1160 if (head == nullptr)
1161 return false;
1162
1163 // Some issue with using monster_die leading to DEAD_MONSTER
1164 // or w/e. Using hurt seems to cause more problems though.
1165 for (monster_iterator mi; mi; ++mi)
1166 {
1167 if (mi->is_child_tentacle_of(head))
1168 {
1169 any = true;
1170 //mi->hurt(*mi, INSTANT_DEATH);
1171 monster_die(**mi, KILL_MISC, NON_MONSTER, true);
1172 }
1173 }
1174
1175 if (mons != head)
1176 {
1177 any = true;
1178 monster_die(*head, KILL_MISC, NON_MONSTER, true);
1179 }
1180
1181 return any;
1182 }
1183
destroy_tentacles(monster * head)1184 bool destroy_tentacles(monster* head)
1185 {
1186 bool any = false;
1187 for (monster_iterator mi; mi; ++mi)
1188 {
1189 if (mi->is_child_tentacle_of(head))
1190 {
1191 any |= destroy_tentacle(*mi);
1192 if (!mi->is_child_tentacle_segment())
1193 {
1194 monster_die(*mi->as_monster(), KILL_MISC, NON_MONSTER, true);
1195 any = true;
1196 }
1197 }
1198 }
1199 return any;
1200 }
1201
_max_tentacles(const monster * mon)1202 static int _max_tentacles(const monster* mon)
1203 {
1204 if (mons_base_type(*mon) == MONS_KRAKEN)
1205 return MAX_ACTIVE_KRAKEN_TENTACLES;
1206 else if (mon->type == MONS_TENTACLED_STARSPAWN)
1207 return MAX_ACTIVE_STARSPAWN_TENTACLES;
1208 else
1209 return 0;
1210 }
1211
mons_available_tentacles(monster * head)1212 int mons_available_tentacles(monster* head)
1213 {
1214 int tentacle_count = 0;
1215
1216 for (monster_iterator mi; mi; ++mi)
1217 {
1218 if (mi->is_child_tentacle_of(head))
1219 tentacle_count++;
1220 }
1221
1222 return _max_tentacles(head) - tentacle_count;
1223 }
1224
mons_create_tentacles(monster * head)1225 void mons_create_tentacles(monster* head)
1226 {
1227 int possible_count = mons_available_tentacles(head);
1228
1229 if (possible_count <= 0)
1230 return;
1231
1232 monster_type tent_type = mons_tentacle_child_type(head);
1233
1234 vector<coord_def> adj_squares;
1235
1236 // Collect open adjacent squares. Candidate squares must be
1237 // unoccupied.
1238 for (adjacent_iterator adj_it(head->pos()); adj_it; ++adj_it)
1239 {
1240 if (monster_habitable_grid(tent_type, env.grid(*adj_it))
1241 && !actor_at(*adj_it))
1242 {
1243 adj_squares.push_back(*adj_it);
1244 }
1245 }
1246
1247 if (unsigned(possible_count) > adj_squares.size())
1248 possible_count = adj_squares.size();
1249 else if (adj_squares.size() > unsigned(possible_count))
1250 shuffle_array(adj_squares);
1251
1252 int visible_count = 0;
1253
1254 for (int i = 0 ; i < possible_count; ++i)
1255 {
1256 mgen_data mg = _segment_data(*head, adj_squares[i], tent_type);
1257 mg.props[MGEN_TENTACLE_CONNECT] = int(head->mid);
1258 if (monster *tentacle = create_monster(mg))
1259 {
1260 if (you.can_see(*tentacle))
1261 visible_count++;
1262
1263 tentacle->props["inwards"].get_int() = head->mid;
1264
1265 if (head->holiness() & MH_UNDEAD)
1266 tentacle->flags |= MF_FAKE_UNDEAD;
1267 }
1268 }
1269
1270 if (mons_base_type(*head) == MONS_KRAKEN)
1271 {
1272 if (visible_count == 1)
1273 mpr("A tentacle rises from the water!");
1274 else if (visible_count > 1)
1275 mpr("Tentacles burst out of the water!");
1276 }
1277 else if (head->type == MONS_TENTACLED_STARSPAWN)
1278 {
1279 if (visible_count == 1)
1280 mpr("A tentacle flies out from the starspawn's body!");
1281 else if (visible_count > 1)
1282 mpr("Tentacles burst from the starspawn's body!");
1283 }
1284 return;
1285 }
1286