Add two new Ru sacrifices: resistance and eye
[crawl.git] / crawl-ref / source / actor.cc
1 #include "AppHdr.h"
2
3 #include "actor.h"
4
5 #include <sstream>
6
7 #include "act-iter.h"
8 #include "areas.h"
9 #include "art-enum.h"
10 #include "attack.h"
11 #include "directn.h"
12 #include "env.h"
13 #include "fprop.h"
14 #include "itemprop.h"
15 #include "los.h"
16 #include "misc.h"
17 #include "mon-behv.h"
18 #include "mon-death.h"
19 #include "religion.h"
20 #include "stepdown.h"
21 #include "stringutil.h"
22 #include "terrain.h"
23 #include "transform.h"
24 #include "traps.h"
25
26 actor::~actor()
27 {
28     if (constricting)
29         delete constricting;
30 }
31
32 bool actor::will_trigger_shaft() const
33 {
34     return is_valid_shaft_level()
35            // let's pretend that they always make their saving roll
36            && !(is_monster()
37                 && mons_is_elven_twin(static_cast<const monster* >(this)));
38 }
39
40 level_id actor::shaft_dest(bool known = false) const
41 {
42     return generic_shaft_dest(pos(), known);
43 }
44
45 /**
46  * Check if the actor is on the ground (or in water).
47  */
48 bool actor::ground_level() const
49 {
50     return !airborne() && !is_wall_clinging()
51 #if TAG_MAJOR_VERSION == 34
52         && mons_species() != MONS_DJINNI
53 #endif
54         ;
55 }
56
57 bool actor::stand_on_solid_ground() const
58 {
59     return ground_level() && feat_has_solid_floor(grd(pos()))
60            && !feat_is_water(grd(pos()));
61 }
62
63 // Give hands required to wield weapon.
64 hands_reqd_type actor::hands_reqd(const item_def &item) const
65 {
66     return basic_hands_reqd(item, body_size());
67 }
68
69 /**
70  * Wrapper around the virtual actor::can_wield(const item_def&,bool,bool,bool,bool) const overload.
71  * @param item May be nullptr, in which case a dummy item will be passed in.
72  */
73 bool actor::can_wield(const item_def* item, bool ignore_curse,
74                       bool ignore_brand, bool ignore_shield,
75                       bool ignore_transform) const
76 {
77     if (item == nullptr)
78     {
79         // Unarmed combat.
80         item_def fake;
81         fake.base_type = OBJ_UNASSIGNED;
82         return can_wield(fake, ignore_curse, ignore_brand, ignore_shield, ignore_transform);
83     }
84     else
85         return can_wield(*item, ignore_curse, ignore_brand, ignore_shield, ignore_transform);
86 }
87
88 bool actor::can_pass_through(int x, int y) const
89 {
90     return can_pass_through_feat(grd[x][y]);
91 }
92
93 bool actor::can_pass_through(const coord_def &c) const
94 {
95     return can_pass_through_feat(grd(c));
96 }
97
98 bool actor::is_habitable(const coord_def &_pos) const
99 {
100     if (can_cling_to(_pos))
101         return true;
102
103     return is_habitable_feat(grd(_pos));
104 }
105
106 bool actor::handle_trap()
107 {
108     trap_def* trap = find_trap(pos());
109     if (trap)
110         trap->trigger(*this);
111     return trap != nullptr;
112 }
113
114 int actor::skill_rdiv(skill_type sk, int mult, int div) const
115 {
116     return div_rand_round(skill(sk, mult * 256), div * 256);
117 }
118
119 int actor::check_res_magic(int power)
120 {
121     const int mrs = res_magic();
122
123     if (mrs == MAG_IMMUNE)
124         return 100;
125
126     // Evil, evil hack to make weak one hd monsters easier for first level
127     // characters who have resistable 1st level spells. Six is a very special
128     // value because mrs = hd * 2 * 3 for most monsters, and the weak, low
129     // level monsters have been adjusted so that the "3" is typically a 1.
130     // There are some notable one hd monsters that shouldn't fall under this,
131     // so we do < 6, instead of <= 6...  or checking mons->hit_dice. The
132     // goal here is to make the first level easier for these classes and give
133     // them a better shot at getting to level two or three and spells that can
134     // help them out (or building a level or two of their base skill so they
135     // aren't resisted as often). - bwr
136     if (is_monster() && mrs < 6 && coinflip())
137         return -1;
138
139     power = ench_power_stepdown(power);
140
141     const int mrchance = (100 + mrs) - power;
142     const int mrch2 = random2(100) + random2(101);
143
144     dprf("Power: %d, MR: %d, target: %d, roll: %d",
145          power, mrs, mrchance, mrch2);
146
147     return mrchance - mrch2;
148 }
149
150 void actor::set_position(const coord_def &c)
151 {
152     const coord_def oldpos = position;
153     position = c;
154     los_actor_moved(this, oldpos);
155     areas_actor_moved(this, oldpos);
156 }
157
158 bool actor::can_hibernate(bool holi_only, bool intrinsic_only) const
159 {
160     // Undead, nonliving, and plants don't sleep. If the monster is
161     // berserk or already asleep, it doesn't sleep either.
162     if (!can_sleep(holi_only))
163         return false;
164
165     if (!holi_only)
166     {
167         // The monster is cold-resistant and can't be hibernated.
168         if (intrinsic_only && is_monster()
169                 ? get_mons_resist(as_monster(), MR_RES_COLD) > 0
170                 : res_cold() > 0)
171         {
172             return false;
173         }
174
175         // The monster has slept recently.
176         if (is_monster() && !intrinsic_only
177             && static_cast<const monster* >(this)->has_ench(ENCH_SLEEP_WARY))
178         {
179             return false;
180         }
181     }
182
183     return true;
184 }
185
186 bool actor::can_sleep(bool holi_only) const
187 {
188     const mon_holy_type holi = holiness();
189     if (holi == MH_UNDEAD || holi == MH_NONLIVING || holi == MH_PLANT)
190         return false;
191
192     if (!holi_only)
193         return !(berserk() || asleep());
194
195     return true;
196 }
197
198 void actor::shield_block_succeeded(actor *foe)
199 {
200     item_def *sh = shield();
201     const unrandart_entry *unrand_entry;
202
203     if (sh
204         && sh->base_type == OBJ_ARMOUR
205         && get_armour_slot(*sh) == EQ_SHIELD
206         && is_artefact(*sh)
207         && is_unrandom_artefact(*sh)
208         && (unrand_entry = get_unrand_entry(sh->special))
209         && unrand_entry->melee_effects)
210     {
211         unrand_entry->melee_effects(sh, this, foe, false, 0);
212     }
213 }
214
215 int actor::inaccuracy() const
216 {
217     return wearing(EQ_AMULET, AMU_INACCURACY);
218 }
219
220 bool actor::gourmand(bool calc_unid, bool items) const
221 {
222     return items && wearing(EQ_AMULET, AMU_THE_GOURMAND, calc_unid);
223 }
224
225 bool actor::res_corr(bool calc_unid, bool items) const
226 {
227     return items && (wearing(EQ_AMULET, AMU_RESIST_CORROSION, calc_unid)
228                      || scan_artefacts(ARTP_RCORR, calc_unid));
229 }
230
231 // This is a bit confusing. This is not the function that determines whether or
232 // not an actor is capable of teleporting, only whether they are specifically
233 // under the influence of the "notele" effect. See actor::no_tele() for a
234 // superset of this function.
235 bool actor::has_notele_item(bool calc_unid, vector<item_def> *matches) const
236 {
237     return scan_artefacts(ARTP_PREVENT_TELEPORTATION, calc_unid, matches);
238 }
239
240 bool actor::stasis(bool calc_unid, bool items) const
241 {
242     return items && wearing(EQ_AMULET, AMU_STASIS, calc_unid);
243 }
244
245 // permaswift effects like boots of running and lightning scales
246 bool actor::run(bool calc_unid, bool items) const
247 {
248     return items && wearing_ego(EQ_BOOTS, SPARM_RUNNING, calc_unid);
249 }
250
251 bool actor::angry(bool calc_unid, bool items) const
252 {
253     return items && scan_artefacts(ARTP_ANGRY, calc_unid);
254 }
255
256 bool actor::clarity(bool calc_unid, bool items) const
257 {
258     return items && (wearing(EQ_AMULET, AMU_CLARITY, calc_unid)
259                      || scan_artefacts(ARTP_CLARITY, calc_unid));
260 }
261
262 bool actor::faith(bool calc_unid, bool items) const
263 {
264     return items && wearing(EQ_AMULET, AMU_FAITH, calc_unid);
265 }
266
267 bool actor::warding(bool calc_unid, bool items) const
268 {
269     // Note: when adding a new source of warding, please add it to
270     // melee_attack::attack_warded_off() as well.
271     return items && (wearing(EQ_AMULET, AMU_WARDING, calc_unid)
272                      || wearing(EQ_STAFF, STAFF_SUMMONING, calc_unid));
273 }
274
275 int actor::archmagi(bool calc_unid, bool items) const
276 {
277     if (!items)
278         return 0;
279
280     return wearing_ego(EQ_ALL_ARMOUR, SPARM_ARCHMAGI, calc_unid);
281 }
282
283 bool actor::no_cast(bool calc_unid, bool items) const
284 {
285     return items && scan_artefacts(ARTP_PREVENT_SPELLCASTING, calc_unid);
286 }
287
288 bool actor::rmut_from_item(bool calc_unid) const
289 {
290     return wearing(EQ_AMULET, AMU_RESIST_MUTATION, calc_unid)
291            || scan_artefacts(ARTP_RMUT, calc_unid);
292 }
293
294 bool actor::evokable_berserk(bool calc_unid) const
295 {
296     return wearing(EQ_AMULET, AMU_RAGE, calc_unid)
297            || scan_artefacts(ARTP_BERSERK, calc_unid);
298 }
299
300 bool actor::evokable_invis(bool calc_unid) const
301 {
302     return wearing(EQ_RINGS, RING_INVISIBILITY, calc_unid)
303            || wearing_ego(EQ_CLOAK, SPARM_INVISIBILITY, calc_unid)
304            || scan_artefacts(ARTP_INVISIBLE, calc_unid);
305 }
306
307 // Return an int so we know whether an item is the sole source.
308 int actor::evokable_flight(bool calc_unid) const
309 {
310     if (is_player() && get_form()->forbids_flight())
311         return 0;
312
313     return wearing(EQ_RINGS, RING_FLIGHT, calc_unid)
314            + wearing_ego(EQ_ALL_ARMOUR, SPARM_FLYING, calc_unid)
315            + scan_artefacts(ARTP_FLY, calc_unid);
316 }
317
318 int actor::spirit_shield(bool calc_unid, bool items) const
319 {
320     int ss = 0;
321
322     if (items)
323     {
324         ss += wearing_ego(EQ_ALL_ARMOUR, SPARM_SPIRIT_SHIELD, calc_unid);
325         ss += wearing(EQ_AMULET, AMU_GUARDIAN_SPIRIT, calc_unid);
326     }
327
328     if (is_player())
329         ss += player_mutation_level(MUT_MANA_SHIELD);
330
331     return ss;
332 }
333
334 int actor::apply_ac(int damage, int max_damage, ac_type ac_rule,
335                     int stab_bypass) const
336 {
337     int ac = max(armour_class() - stab_bypass, 0);
338     int gdr = gdr_perc();
339     int saved = 0;
340     switch (ac_rule)
341     {
342     case AC_NONE:
343         return damage; // no GDR, too
344     case AC_PROPORTIONAL:
345         ASSERT(stab_bypass == 0);
346         saved = damage - apply_chunked_AC(damage, ac);
347         saved = max(saved, div_rand_round(max_damage * gdr, 100));
348         return max(damage - saved, 0);
349
350     case AC_NORMAL:
351         saved = random2(1 + ac);
352         break;
353     case AC_HALF:
354         saved = random2(1 + ac) / 2;
355         ac /= 2;
356         gdr /= 2;
357         break;
358     case AC_TRIPLE:
359         saved = random2(1 + ac) + random2(1 + ac) + random2(1 + ac);
360         ac *= 3;
361         // apply GDR only twice rather than thrice, that's probably still waaay
362         // too good.  50% gives 75% rather than 100%, too.
363         gdr = 100 - gdr * gdr / 100;
364         break;
365     default:
366         die("invalid AC rule");
367     }
368
369     saved = max(saved, min(gdr * max_damage / 100, ac / 2));
370     return max(damage - saved, 0);
371 }
372
373 bool actor_slime_wall_immune(const actor *act)
374 {
375     return
376        act->is_player() && you_worship(GOD_JIYVA) && !you.penance[GOD_JIYVA]
377        || act->res_acid() == 3;
378 }
379
380 /**
381  * Accessor method to the clinging member.
382  *
383  * @return  The value of clinging.
384  */
385 bool actor::is_wall_clinging() const
386 {
387     return props.exists(CLING_KEY) && props[CLING_KEY].get_bool();
388 }
389
390 /**
391  * Check a cell to see if actor can keep clinging if it moves to it.
392  *
393  * @param p Coordinates of the cell checked.
394  * @return  Whether the actor can cling.
395  */
396 bool actor::can_cling_to(const coord_def& p) const
397 {
398     if (!is_wall_clinging() || !can_pass_through_feat(grd(p)))
399         return false;
400
401     return cell_can_cling_to(pos(), p);
402 }
403
404 /**
405  * Update the clinging status of an actor.
406  *
407  * It checks adjacent orthogonal walls to see if the actor can cling to them.
408  * If actor has fallen from the wall (wall dug or actor changed form), print a
409  * message and apply location effects.
410  *
411  * @param stepped Whether the actor has taken a step.
412  * @return the new clinging status.
413  */
414 bool actor::check_clinging(bool stepped, bool door)
415 {
416     bool was_clinging = is_wall_clinging();
417     bool clinging = can_cling_to_walls() && cell_is_clingable(pos())
418                     && !airborne();
419
420     if (can_cling_to_walls())
421         props[CLING_KEY] = clinging;
422     else if (props.exists(CLING_KEY))
423         props.erase(CLING_KEY);
424
425     if (!stepped && was_clinging && !clinging)
426     {
427         if (you.can_see(this))
428         {
429             mprf("%s %s off the %s.", name(DESC_THE).c_str(),
430                  conj_verb("fall").c_str(),
431                  door ? "door" : "wall");
432         }
433         apply_location_effects(pos());
434     }
435     return clinging;
436 }
437
438 void actor::clear_clinging()
439 {
440     if (props.exists(CLING_KEY))
441         props[CLING_KEY] = false;
442 }
443
444 void actor::clear_constricted()
445 {
446     constricted_by = 0;
447     held = HELD_NONE;
448     escape_attempts = 0;
449 }
450
451 // End my constriction of i->first, but don't yet update my constricting map,
452 // so as not to invalidate i.
453 void actor::end_constriction(mid_t whom, bool intentional, bool quiet)
454 {
455     actor *const constrictee = actor_by_mid(whom);
456
457     if (!constrictee)
458         return;
459
460     constrictee->clear_constricted();
461
462     if (!quiet && alive() && constrictee->alive()
463         && (you.see_cell(pos()) || you.see_cell(constrictee->pos())))
464     {
465         mprf("%s %s %s grip on %s.",
466                 name(DESC_THE).c_str(),
467                 conj_verb(intentional ? "release" : "lose").c_str(),
468                 pronoun(PRONOUN_POSSESSIVE).c_str(),
469                 constrictee->name(DESC_THE).c_str());
470     }
471
472     if (constrictee->is_player())
473         you.redraw_evasion = true;
474 }
475
476 void actor::stop_constricting(mid_t whom, bool intentional, bool quiet)
477 {
478     if (!constricting)
479         return;
480
481     auto i = constricting->find(whom);
482
483     if (i != constricting->end())
484     {
485         end_constriction(whom, intentional, quiet);
486         constricting->erase(i);
487
488         if (constricting->empty())
489         {
490             delete constricting;
491             constricting = 0;
492         }
493     }
494 }
495
496 void actor::stop_constricting_all(bool intentional, bool quiet)
497 {
498     if (!constricting)
499         return;
500
501     for (const auto &entry : *constricting)
502         end_constriction(entry.first, intentional, quiet);
503
504     delete constricting;
505     constricting = 0;
506 }
507
508 void actor::stop_being_constricted(bool quiet)
509 {
510     // Make sure we are actually being constricted.
511     actor* const constrictor = actor_by_mid(constricted_by);
512
513     if (constrictor)
514         constrictor->stop_constricting(mid, false, quiet);
515
516     // In case the actor no longer exists.
517     clear_constricted();
518 }
519
520 void actor::clear_far_constrictions()
521 {
522     clear_far_engulf();
523     actor* const constrictor = actor_by_mid(constricted_by);
524
525     if (!constrictor || !adjacent(pos(), constrictor->pos()))
526         stop_being_constricted();
527
528     if (!constricting)
529         return;
530
531     vector<mid_t> need_cleared;
532     for (const auto &entry : *constricting)
533     {
534         actor* const constrictee = actor_by_mid(entry.first);
535         if (!constrictee || !adjacent(pos(), constrictee->pos()))
536             need_cleared.push_back(entry.first);
537     }
538
539     for (mid_t whom : need_cleared)
540         stop_constricting(whom, false, false);
541 }
542
543 void actor::start_constricting(actor &whom, int dur)
544 {
545     if (!constricting)
546         constricting = new constricting_t();
547
548     ASSERT(constricting->find(whom.mid) == constricting->end());
549
550     (*constricting)[whom.mid] = dur;
551     whom.constricted_by = mid;
552     whom.held = constriction_damage() ? HELD_CONSTRICTED : HELD_MONSTER;
553
554     if (whom.is_player())
555         you.redraw_evasion = true;
556 }
557
558 int actor::num_constricting() const
559 {
560     return constricting ? constricting->size() : 0;
561 }
562
563 bool actor::is_constricting() const
564 {
565     return constricting && !constricting->empty();
566 }
567
568 bool actor::is_constricted() const
569 {
570     return constricted_by;
571 }
572
573 void actor::accum_has_constricted()
574 {
575     if (!constricting)
576         return;
577
578     for (auto &entry : *constricting)
579         entry.second += you.time_taken;
580 }
581
582 bool actor::can_constrict(actor* defender)
583 {
584     return (!is_constricting() || has_usable_tentacle())
585            && !defender->is_constricted()
586            && can_see(defender)
587            && !confused()
588            && body_size(PSIZE_BODY) >= defender->body_size(PSIZE_BODY)
589            && defender->res_constrict() < 3
590            && adjacent(pos(), defender->pos());
591 }
592
593 #ifdef DEBUG_DIAGNOSTICS
594 # define DIAG_ONLY(x) x
595 #else
596 # define DIAG_ONLY(x) (void)0
597 #endif
598
599 // Deal damage over time
600 void actor::handle_constriction()
601 {
602     if (is_sanctuary(pos()))
603         stop_constricting_all(true);
604
605     // Constriction should have stopped the moment the actors became
606     // non-adjacent; but disabling constriction by hand in every single place
607     // is too error-prone.
608     clear_far_constrictions();
609
610     if (!constricting || !constriction_damage())
611         return;
612
613     auto i = constricting->begin();
614     // monster_die() can cause constricting() to go away.
615     while (constricting && i != constricting->end())
616     {
617         actor* const defender = actor_by_mid(i->first);
618         int duration = i->second;
619         ASSERT(defender);
620
621         // Must increment before potentially killing the constrictee and
622         // thus invalidating the old i.
623         ++i;
624
625         int damage = constriction_damage();
626
627         DIAG_ONLY(const int basedam = damage);
628         damage += div_rand_round(damage * stepdown((float)duration, 50.0),
629                                  BASELINE_DELAY * 5);
630         if (is_player())
631             damage = div_rand_round(damage * (27 + 2 * you.experience_level), 81);
632         DIAG_ONLY(const int durdam = damage);
633         damage -= random2(1 + (defender->armour_class() / 2));
634         DIAG_ONLY(const int acdam = damage);
635         damage = timescale_damage(this, damage);
636         DIAG_ONLY(const int timescale_dam = damage);
637
638         damage = defender->hurt(this, damage, BEAM_MISSILE,
639                                 KILLED_BY_MONSTER, "", "", false);
640         DIAG_ONLY(const int infdam = damage);
641
642         string exclamations;
643         if (damage <= 0 && is_player()
644             && you.can_see(defender))
645         {
646             exclamations = ", but do no damage.";
647         }
648         else if (damage < HIT_WEAK)
649             exclamations = ".";
650         else if (damage < HIT_MED)
651             exclamations = "!";
652         else if (damage < HIT_STRONG)
653             exclamations = "!!";
654         else
655         {
656             int tmpdamage = damage;
657             exclamations = "!!!";
658             while (tmpdamage >= 2*HIT_STRONG)
659             {
660                 exclamations += "!";
661                 tmpdamage >>= 1;
662             }
663         }
664
665         if (is_player() || you.can_see(this))
666         {
667             mprf("%s %s %s%s%s",
668                  (is_player() ? "You"
669                               : name(DESC_THE).c_str()),
670                  conj_verb("constrict").c_str(),
671                  defender->name(DESC_THE).c_str(),
672 #ifdef DEBUG_DIAGNOSTICS
673                  make_stringf(" for %d", damage).c_str(),
674 #else
675                  "",
676 #endif
677                  exclamations.c_str());
678         }
679         else if (you.can_see(defender) || defender->is_player())
680         {
681             mprf("%s %s constricted%s%s",
682                  defender->name(DESC_THE).c_str(),
683                  defender->conj_verb("are").c_str(),
684 #ifdef DEBUG_DIAGNOSTICS
685                  make_stringf(" for %d", damage).c_str(),
686 #else
687                  "",
688 #endif
689                  exclamations.c_str());
690         }
691
692         dprf("constrict at: %s df: %s base %d dur %d ac %d tsc %d inf %d",
693              name(DESC_PLAIN, true).c_str(),
694              defender->name(DESC_PLAIN, true).c_str(),
695              basedam, durdam, acdam, timescale_dam, infdam);
696
697         if (defender->is_monster()
698             && defender->as_monster()->hit_points < 1)
699         {
700             monster_die(defender->as_monster(), this);
701         }
702     }
703 }
704
705 string actor::describe_props() const
706 {
707     ostringstream oss;
708
709     if (props.size() == 0)
710         return "";
711
712     for (auto i = props.begin(); i != props.end(); ++i)
713     {
714         if (i != props.begin())
715             oss <<  ", ";
716         oss << string(i->first) << ": ";
717
718         CrawlStoreValue val = i->second;
719
720         switch (val.get_type())
721         {
722             case SV_BOOL:
723                 oss << val.get_bool();
724                 break;
725             case SV_BYTE:
726                 oss << val.get_byte();
727                 break;
728             case SV_SHORT:
729                 oss << val.get_short();
730                 break;
731             case SV_INT:
732                 oss << val.get_int();
733                 break;
734             case SV_FLOAT:
735                 oss << val.get_float();
736                 break;
737             case SV_STR:
738                 oss << val.get_string();
739                 break;
740             case SV_COORD:
741             {
742                 coord_def coord = val.get_coord();
743                 oss << "(" << coord.x << ", " << coord.y << ")";
744                 break;
745             }
746             case SV_MONST:
747             {
748                 monster mon = val.get_monster();
749                 oss << mon.name(DESC_PLAIN) << "(" << mon.mid << ")";
750                 break;
751             }
752             case SV_INT64:
753                 oss << val.get_int64();
754                 break;
755
756             default:
757                 oss << "???";
758                 break;
759         }
760     }
761     return oss.str();
762 }
763
764 /**
765  * Is the actor currently being slowed by a torpor snail?
766  */
767 bool actor::torpor_slowed() const
768 {
769     if (!props.exists(TORPOR_SLOWED_KEY) || is_sanctuary(pos())
770         || is_stationary()
771         || (is_monster() && as_monster()->check_stasis(true))
772         || (!is_monster() && stasis()))
773     {
774         return false;
775     }
776
777     for (monster_near_iterator ri(pos(), LOS_SOLID_SEE); ri; ++ri)
778     {
779         const monster *mons = *ri;
780         if (mons && mons->type == MONS_TORPOR_SNAIL
781             && !is_sanctuary(mons->pos())
782             && !mons_aligned(mons, this)
783             && !mons->has_ench(ENCH_CHARM) && mons->attitude == ATT_HOSTILE)
784             // friendly torpor snails are way too abusable otherwise :(
785         {
786             return true;
787         }
788     }
789
790     return false;
791 }
792
793 string actor::resist_margin_phrase(int margin) const
794 {
795     if (res_magic() == MAG_IMMUNE)
796         return " " + conj_verb("are") + " unaffected.";
797
798     static const string resist_messages[][2] =
799     {
800       { " barely %s.",                  "resist" },
801       { " %s to resist.",               "struggle" },
802       { " %s with significant effort.", "resist" },
803       { " %s with some effort.",        "resist" },
804       { " easily %s.",                  "resist" },
805       { " %s with almost no effort.",   "resist" },
806     };
807
808     const int index = max(0, min((int)ARRAYSZ(resist_messages) - 1,
809                                  ((margin + 45) / 15)));
810
811     return make_stringf(resist_messages[index][0].c_str(),
812                         conj_verb(resist_messages[index][1]).c_str());
813 }
814
815 void actor::collide(coord_def newpos, const actor *agent, int pow)
816 {
817     actor *other = actor_at(newpos);
818     ASSERT(this != other);
819     ASSERT(alive());
820
821     if (is_insubstantial())
822         return;
823
824     if (is_monster())
825         behaviour_event(as_monster(), ME_WHACK, agent);
826
827     dice_def damage(2, 1 + pow / 10);
828
829     if (other && other->alive())
830     {
831         if (other->is_monster())
832             behaviour_event(other->as_monster(), ME_WHACK, agent);
833         if (you.can_see(this) || you.can_see(other))
834         {
835             mprf("%s %s with %s!",
836                  name(DESC_THE).c_str(),
837                  conj_verb("collide").c_str(),
838                  other->name(DESC_THE).c_str());
839         }
840         const string thisname = name(DESC_A, true);
841         const string othername = other->name(DESC_A, true);
842         other->hurt(agent, other->apply_ac(damage.roll()),
843                     BEAM_MISSILE, KILLED_BY_COLLISION,
844                     othername, thisname);
845         if (alive())
846         {
847             hurt(agent, apply_ac(damage.roll()), BEAM_MISSILE,
848                  KILLED_BY_COLLISION, thisname, othername);
849         }
850         return;
851     }
852
853     if (you.can_see(this))
854     {
855         if (!can_pass_through_feat(grd(newpos)))
856         {
857             mprf("%s %s into %s!",
858                  name(DESC_THE).c_str(), conj_verb("slam").c_str(),
859                  env.map_knowledge(newpos).known()
860                  ? feature_description_at(newpos, false, DESC_THE, false)
861                        .c_str()
862                  : "something");
863         }
864         else
865         {
866             mprf("%s violently %s moving!",
867                  name(DESC_THE).c_str(), conj_verb("stop").c_str());
868         }
869     }
870     hurt(agent, apply_ac(damage.roll()), BEAM_MISSILE,
871          KILLED_BY_COLLISION, "",
872          feature_description_at(newpos, false, DESC_A, false));
873 }