1 /**
2 * @file
3 * @brief Functions related to teleportation and blinking.
4 **/
5
6 #include "AppHdr.h"
7
8 #include "teleport.h"
9
10 #include "cloud.h"
11 #include "coord.h"
12 #include "coordit.h"
13 #include "delay.h"
14 #include "env.h"
15 #include "fprop.h"
16 #include "libutil.h"
17 #include "losglobal.h"
18 #include "message.h"
19 #include "mon-behv.h"
20 #include "mon-death.h"
21 #include "mon-place.h"
22 #include "mon-tentacle.h"
23 #include "random.h"
24 #include "terrain.h"
25 #include "view.h"
26
blink_to(const coord_def & dest,bool quiet)27 bool player::blink_to(const coord_def& dest, bool quiet)
28 {
29 // We rely on the non-generalized move_player_to_cell.
30 ASSERT(is_player());
31
32 if (dest == pos())
33 return false;
34
35 if (no_tele(true, true, true))
36 {
37 if (!quiet)
38 canned_msg(MSG_STRANGE_STASIS);
39 return false;
40 }
41
42 if (!quiet)
43 canned_msg(MSG_YOU_BLINK);
44
45 stop_delay(true);
46
47 const coord_def origin = pos();
48 move_player_to_grid(dest, false);
49
50 if (!cell_is_solid(origin))
51 place_cloud(CLOUD_TLOC_ENERGY, origin, 1 + random2(3), this);
52
53 return true;
54 }
55
blink_to(const coord_def & dest,bool quiet)56 bool monster::blink_to(const coord_def& dest, bool quiet)
57 {
58 // For this call, let the monster choose whether it's blinking
59 // or jumping. When blinked by another source, use the other
60 // overload.
61 return blink_to(dest, quiet, is_jumpy());
62 }
63
blink_to(const coord_def & dest,bool quiet,bool jump)64 bool monster::blink_to(const coord_def& dest, bool quiet, bool jump)
65 {
66 if (dest == pos())
67 return false;
68
69 bool was_constricted = false;
70 const string verb = (jump ? "leap" : "blink");
71
72 if (is_constricted())
73 {
74 was_constricted = true;
75
76 if (!attempt_escape(2))
77 {
78 if (!quiet)
79 {
80 string message = " struggles to " + verb
81 + " free from constriction.";
82 simple_monster_message(*this, message.c_str());
83 }
84 return false;
85 }
86 }
87
88 if (!quiet)
89 {
90 string message = " " + conj_verb(verb)
91 + (was_constricted ? " free!" : "!");
92 simple_monster_message(*this, message.c_str());
93 }
94
95 if (!(flags & MF_WAS_IN_VIEW))
96 seen_context = jump ? SC_LEAP_IN : SC_TELEPORT_IN;
97
98 const coord_def oldplace = pos();
99 if (!move_to_pos(dest, true))
100 return false;
101
102 // Leave a cloud.
103 if (!props.exists(FAKE_BLINK_KEY) && !cell_is_solid(oldplace))
104 {
105 place_cloud(jump ? CLOUD_DUST : CLOUD_TLOC_ENERGY,
106 oldplace, 1 + random2(3), this);
107 }
108
109 check_redraw(oldplace);
110 apply_location_effects(oldplace);
111
112 mons_relocated(this);
113
114 return true;
115 }
116
117 // If the returned value is mon.pos(), then nothing was found.
_random_monster_nearby_habitable_space(const monster & mon)118 static coord_def _random_monster_nearby_habitable_space(const monster& mon)
119 {
120 const bool respect_sanctuary = mon.wont_attack();
121
122 coord_def target;
123 int tries;
124
125 for (tries = 0; tries < 150; ++tries)
126 {
127 coord_def delta;
128 delta.x = random2(13) - 6;
129 delta.y = random2(13) - 6;
130
131 // Check that we don't get something too close to the
132 // starting point.
133 if (delta.origin())
134 continue;
135
136 // Blinks by 1 cell are not allowed.
137 if (delta.rdist() == 1)
138 continue;
139
140 // Update target.
141 target = delta + mon.pos();
142
143 // Check that the target is valid and survivable.
144 if (!in_bounds(target))
145 continue;
146
147 if (!monster_habitable_grid(&mon, env.grid(target)))
148 continue;
149
150 if (respect_sanctuary && is_sanctuary(target))
151 continue;
152
153 if (target == you.pos())
154 continue;
155
156 if (!cell_see_cell(mon.pos(), target, LOS_NO_TRANS))
157 continue;
158
159 // Survived everything, break out (with a good value of target.)
160 break;
161 }
162
163 if (tries == 150)
164 target = mon.pos();
165
166 return target;
167 }
168
monster_blink(monster * mons,bool quiet)169 bool monster_blink(monster* mons, bool quiet)
170 {
171 coord_def near = _random_monster_nearby_habitable_space(*mons);
172 return mons->blink_to(near, quiet);
173 }
174
monster_space_valid(const monster * mons,coord_def target,bool forbid_sanctuary)175 bool monster_space_valid(const monster* mons, coord_def target,
176 bool forbid_sanctuary)
177 {
178 if (!in_bounds(target))
179 return false;
180
181 // Don't land on top of another monster.
182 if (actor_at(target))
183 return false;
184
185 if (is_sanctuary(target) && forbid_sanctuary)
186 return false;
187
188 if (testbits(env.pgrid(target), FPROP_NO_TELE_INTO))
189 return false;
190
191 return monster_habitable_grid(mons, env.grid(target));
192 }
193
_monster_random_space(const monster * mons,coord_def & target,bool forbid_sanctuary)194 static bool _monster_random_space(const monster* mons, coord_def& target,
195 bool forbid_sanctuary)
196 {
197 int tries = 0;
198 while (tries++ < 1000)
199 {
200 target = random_in_bounds();
201 if (monster_space_valid(mons, target, forbid_sanctuary))
202 return true;
203 }
204
205 return false;
206 }
207
mons_relocated(monster * mons)208 void mons_relocated(monster* mons)
209 {
210 if (mons_is_tentacle_head(mons_base_type(*mons)))
211 destroy_tentacles(mons); // If the main body teleports get rid of the tentacles
212 else if (mons->is_child_monster())
213 destroy_tentacle(mons); // If a tentacle/segment is relocated just kill the tentacle
214 else if (mons->type == MONS_ELDRITCH_TENTACLE
215 || mons->type == MONS_ELDRITCH_TENTACLE_SEGMENT)
216 {
217 // Kill an eldritch tentacle and all its segments.
218 monster* tentacle = mons->type == MONS_ELDRITCH_TENTACLE
219 ? mons : monster_by_mid(mons->tentacle_connect);
220
221 // this should take care of any tentacles
222 monster_die(*tentacle, KILL_RESET, -1, true, false);
223 }
224 }
225
monster_teleport(monster * mons,bool instan,bool silent)226 void monster_teleport(monster* mons, bool instan, bool silent)
227 {
228 ASSERT(mons); // XXX: change to monster &mons
229 bool was_seen = !silent && you.can_see(*mons);
230
231 if (!instan)
232 {
233 if (mons->del_ench(ENCH_TP))
234 {
235 if (!silent)
236 simple_monster_message(*mons, " seems more stable.");
237 }
238 else
239 {
240 if (!silent)
241 simple_monster_message(*mons, " looks slightly unstable.");
242
243 mons->add_ench(mon_enchant(ENCH_TP, 0, 0,
244 random_range(20, 30)));
245 }
246
247 return;
248 }
249
250 coord_def newpos;
251
252 if (!_monster_random_space(mons, newpos, !mons->wont_attack()))
253 {
254 simple_monster_message(*mons, " flickers for a moment.");
255 return;
256 }
257
258 if (!silent)
259 simple_monster_message(*mons, " disappears!");
260
261 const coord_def oldplace = mons->pos();
262
263 // Move it to its new home.
264 mons->move_to_pos(newpos);
265
266 const bool now_visible = you.see_cell(newpos);
267 if (!silent && now_visible)
268 {
269 if (was_seen)
270 simple_monster_message(*mons, " reappears nearby!");
271 else
272 {
273 // Even if it doesn't interrupt an activity (the player isn't
274 // delayed, the monster isn't hostile) we still want to give
275 // a message.
276 activity_interrupt_data ai(mons, SC_TELEPORT_IN);
277 if (!interrupt_activity(activity_interrupt::see_monster, ai))
278 simple_monster_message(*mons, " appears out of thin air!");
279 }
280 }
281
282 if (mons->visible_to(&you) && now_visible)
283 handle_seen_interrupt(mons);
284
285 // Leave a purple cloud.
286 // XXX: If silent is true, this is not an actual teleport, but
287 // the game moving a monster out of the way.
288 if (!silent && !cell_is_solid(oldplace))
289 place_cloud(CLOUD_TLOC_ENERGY, oldplace, 1 + random2(3), mons);
290
291 mons->check_redraw(oldplace);
292 mons->apply_location_effects(oldplace);
293
294 mons_relocated(mons);
295
296 shake_off_monsters(mons);
297 }
298
299 // Try to find a "safe" place for moved close or far from the target.
300 // keep_los indicates that the destination should be in LOS of the target.
301 //
302 // XXX: Check the result against in_bounds(), not coord_def::origin(),
303 // because of a memory problem described below. (isn't this fixed now? -rob)
random_space_weighted(actor * moved,actor * target,bool close,bool keep_los=true,bool allow_sanct=true)304 static coord_def random_space_weighted(actor* moved, actor* target,
305 bool close, bool keep_los = true,
306 bool allow_sanct = true)
307 {
308 vector<coord_weight> dests;
309 const coord_def tpos = target->pos();
310
311 for (radius_iterator ri(moved->pos(), LOS_NO_TRANS); ri; ++ri)
312 {
313 if (!valid_blink_destination(moved, *ri, !allow_sanct)
314 || (keep_los && !target->see_cell_no_trans(*ri)))
315 {
316 continue;
317 }
318
319 int weight;
320 int dist = (tpos - *ri).rdist();
321 if (close)
322 weight = (LOS_RADIUS - dist) * (LOS_RADIUS - dist);
323 else
324 weight = dist;
325 if (weight < 0)
326 weight = 0;
327 dests.emplace_back(*ri, weight);
328 }
329
330 coord_def* choice = random_choose_weighted(dests);
331 return choice ? *choice : coord_def(0, 0);
332 }
333
334 // Blink the victim closer to the monster at target.
blink_other_close(actor * victim,const coord_def & target)335 void blink_other_close(actor* victim, const coord_def &target)
336 {
337 actor* caster = actor_at(target);
338 if (!caster)
339 return;
340 if (is_sanctuary(you.pos()))
341 return;
342 coord_def dest = random_space_weighted(victim, caster, true);
343 if (!in_bounds(dest))
344 return;
345 // If it's a monster, force them to "blink" rather than "jump"
346 if (victim->is_monster())
347 victim->as_monster()->blink_to(dest, false, false);
348 else
349 victim->blink_to(dest);
350 }
351
352 // Blink a monster away from the caster.
blink_away(monster * mon,actor * caster,bool from_seen,bool self_cast)353 bool blink_away(monster* mon, actor* caster, bool from_seen, bool self_cast)
354 {
355 ASSERT(mon); // XXX: change to monster &mon
356 ASSERT(caster); // XXX: change to actor &caster
357
358 if (from_seen && !mon->can_see(*caster))
359 return false;
360 bool jumpy = self_cast && mon->is_jumpy();
361 coord_def dest = random_space_weighted(mon, caster, false, false, true);
362 if (dest.origin())
363 return false;
364 bool success = mon->blink_to(dest, false, jumpy);
365 ASSERT(success || mon->is_constricted());
366 return success;
367 }
368
369 // Blink the monster away from its foe.
blink_away(monster * mon,bool self_cast)370 bool blink_away(monster* mon, bool self_cast)
371 {
372 actor* foe = mon->get_foe();
373 if (!foe)
374 return false;
375 return blink_away(mon, foe, true, self_cast);
376 }
377
378 // Blink the monster within range but at distance to its foe.
blink_range(monster * mon)379 void blink_range(monster* mon)
380 {
381 ASSERT(mon); // XXX: change to monster &mon
382
383 actor* foe = mon->get_foe();
384 if (!foe || !mon->can_see(*foe))
385 return;
386 coord_def dest = random_space_weighted(mon, foe, false, true);
387 if (dest.origin())
388 return;
389 bool success = mon->blink_to(dest);
390 ASSERT(success || mon->is_constricted());
391 #ifndef ASSERTS
392 UNUSED(success);
393 #endif
394 }
395
396 // Blink the monster close to its foe.
blink_close(monster * mon)397 void blink_close(monster* mon)
398 {
399 ASSERT(mon); // XXX: change to monster &mon
400
401 actor* foe = mon->get_foe();
402 if (!foe || !mon->can_see(*foe))
403 return;
404 coord_def dest = random_space_weighted(mon, foe, true, true, true);
405 if (dest.origin())
406 return;
407 bool success = mon->blink_to(dest, false);
408 ASSERT(success || mon->is_constricted());
409 #ifndef ASSERTS
410 UNUSED(success);
411 #endif
412 }
413
414 // This only checks the contents of the tile - nothing in between.
415 // Could compact most of this into a big boolean if you wanted to trade
416 // readability for dubious speed improvements.
valid_blink_destination(const actor * moved,const coord_def & target,bool forbid_sanctuary,bool forbid_unhabitable)417 bool valid_blink_destination(const actor* moved, const coord_def& target,
418 bool forbid_sanctuary,
419 bool forbid_unhabitable)
420 {
421 ASSERT(moved);
422
423 if (!in_bounds(target))
424 return false;
425 if (actor_at(target))
426 return false;
427 if (forbid_unhabitable)
428 {
429 if (!moved->is_habitable(target))
430 return false;
431 if (moved->is_player() && is_feat_dangerous(env.grid(target), true))
432 return false;
433 }
434 if (forbid_sanctuary && is_sanctuary(target))
435 return false;
436 if (!moved->see_cell_no_trans(target))
437 return false;
438
439 return true;
440 }
441
random_near_space(const actor * victim,const coord_def & origin,coord_def & target,bool allow_adjacent,bool forbid_sanctuary,bool forbid_unhabitable)442 bool random_near_space(const actor* victim,
443 const coord_def& origin, coord_def& target,
444 bool allow_adjacent, bool forbid_sanctuary,
445 bool forbid_unhabitable)
446 {
447 // This might involve ray tracing (LOS calcs in valid_blink_destination),
448 // so cache results to avoid duplicating ray traces.
449 #define RNS_OFFSET 6
450 #define RNS_WIDTH (2*RNS_OFFSET + 1)
451 FixedArray<bool, RNS_WIDTH, RNS_WIDTH> tried;
452 const coord_def tried_o = coord_def(RNS_OFFSET, RNS_OFFSET);
453 tried.init(false);
454
455 for (int tries = 0; tries < 150; tries++)
456 {
457 coord_def p;
458 p.x = random2(RNS_WIDTH);
459 p.y = random2(RNS_WIDTH);
460 if (tried(p))
461 continue;
462 else
463 tried(p) = true;
464
465 target = origin + (p - tried_o);
466
467 if (valid_blink_destination(victim, target,
468 forbid_sanctuary, forbid_unhabitable)
469 && (allow_adjacent || grid_distance(origin, target) > 1))
470 {
471 return true;
472 }
473 }
474
475 return false;
476 }
477