1 #include "AppHdr.h"
2
3 #include "spl-damage.h"
4
5 #include <cfloat>
6 #include <cmath>
7
8 #include "areas.h"
9 #include "cloud.h"
10 #include "coord.h"
11 #include "coordit.h"
12 #include "delay.h"
13 #include "directn.h"
14 #include "env.h"
15 #include "fight.h"
16 #include "fineff.h"
17 #include "fprop.h"
18 #include "god-conduct.h"
19 #include "libutil.h"
20 #include "message.h"
21 #include "mon-behv.h"
22 #include "mon-tentacle.h"
23 #include "ouch.h"
24 #include "prompt.h"
25 #include "religion.h"
26 #include "shout.h"
27 #include "target.h"
28 #include "terrain.h"
29 #include "transform.h"
30
_airtight(coord_def c)31 static bool _airtight(coord_def c)
32 {
33 // Broken by 6f473416 -- we should re-allow the wind through grates.
34
35 // return (feat_is_wall(env.grid(c)) || feat_is_opaque(env.grid(c))) && env.grid(c);
36 return !feat_is_reachable_past(env.grid(c));
37 }
38
39 /* Explanation of the algorithm:
40 http://en.wikipedia.org/wiki/Biconnected_component
41 We include everything up to and including the first articulation vertex,
42 the center is never considered to be one.
43 */
44 class WindSystem
45 {
46 coord_def org;
47 SquareArray<int, POLAR_VORTEX_RADIUS+1> depth;
48 SquareArray<bool, POLAR_VORTEX_RADIUS+1> cut, wind;
49 int visit(coord_def c, int d, coord_def parent);
50 void pass_wind(coord_def c);
51 public:
52 WindSystem(coord_def _org);
53 bool has_wind(coord_def c);
54 };
55
WindSystem(coord_def _org)56 WindSystem::WindSystem(coord_def _org)
57 : org(_org)
58 {
59 depth.init(-1);
60 cut.init(false);
61 visit(org, 0, coord_def(0,0));
62 cut(coord_def(0,0)) = false;
63 wind.init(false);
64 pass_wind(org);
65 }
66
visit(coord_def c,int d,coord_def parent)67 int WindSystem::visit(coord_def c, int d, coord_def parent)
68 {
69 depth(c - org) = d;
70 int low = d;
71 int sonmax = -1;
72
73 for (adjacent_iterator ai(c); ai; ++ai)
74 {
75 if ((*ai - org).rdist() > POLAR_VORTEX_RADIUS || _airtight(*ai))
76 continue;
77 if (depth(*ai - org) == -1)
78 {
79 int sonlow = visit(*ai, d+1, c);
80 low = min(low, sonlow);
81 sonmax = max(sonmax, sonlow);
82 }
83 else if (*ai != parent)
84 low = min(low, depth(*ai - org));
85 }
86
87 cut(c - org) = (sonmax >= d);
88 return low;
89 }
90
pass_wind(coord_def c)91 void WindSystem::pass_wind(coord_def c)
92 {
93 wind(c - org) = true;
94 depth(c - org) = -1;
95 if (cut(c - org))
96 return;
97
98 for (adjacent_iterator ai(c); ai; ++ai)
99 if (depth(*ai - org) != -1)
100 pass_wind(*ai);
101 }
102
has_wind(coord_def c)103 bool WindSystem::has_wind(coord_def c)
104 {
105 ASSERT(grid_distance(c, org) <= POLAR_VORTEX_RADIUS); // might say no instead
106 return wind(c - org);
107 }
108
_set_vortex_durations()109 static void _set_vortex_durations()
110 {
111 int dur = 60;
112 you.duration[DUR_VORTEX] = dur;
113 if (!get_form()->forbids_flight())
114 you.duration[DUR_FLIGHT] = max(dur, you.duration[DUR_FLIGHT]);
115 }
116
cast_polar_vortex(int,bool fail)117 spret cast_polar_vortex(int /*powc*/, bool fail)
118 {
119 targeter_radius hitfunc(&you, LOS_NO_TRANS, POLAR_VORTEX_RADIUS);
120 if (stop_attack_prompt(hitfunc, "make a polar vortex",
121 [](const actor *act) -> bool {
122 return !act->res_polar_vortex()
123 && (!act->is_monster()
124 || !god_protects(&you, act->as_monster(), true));
125 }))
126 {
127 return spret::abort;
128 }
129
130 fail_check();
131
132 mprf("A great freezing vortex %s.",
133 (you.airborne() || get_form()->forbids_flight()) ?
134 "appears around you" : "appears and lifts you up");
135
136 if (you.fishtail)
137 merfolk_stop_swimming();
138
139 you.props["polar_vortex_since"].get_int() = you.elapsed_time;
140 _set_vortex_durations();
141 if (you.has_mutation(MUT_TENGU_FLIGHT))
142 you.redraw_evasion = true;
143
144 return spret::success;
145 }
146
_mons_is_unmovable(const monster * mons)147 static bool _mons_is_unmovable(const monster *mons)
148 {
149 // hard to explain uprooted oklobs surviving
150 if (mons->is_stationary())
151 return true;
152 // we'd have to rotate everything
153 if (mons_is_tentacle_or_tentacle_segment(mons->type)
154 || mons_is_tentacle_head(mons_base_type(*mons)))
155 {
156 return true;
157 }
158 return false;
159 }
160
_get_ang(int x,int y)161 static double _get_ang(int x, int y)
162 {
163 if (abs(x) > abs(y))
164 {
165 if (x > 0)
166 return double(y)/double(x);
167 else
168 return 4 + double(y)/double(x);
169 }
170 else
171 {
172 if (y > 0)
173 return 2 - double(x)/double(y);
174 else
175 return -2 - double(x)/double(y);
176 }
177 }
178
_rotate(coord_def org,coord_def from,vector<coord_def> & avail,int rdur)179 static coord_def _rotate(coord_def org, coord_def from,
180 vector<coord_def> &avail, int rdur)
181 {
182 if (avail.empty())
183 return from;
184
185 coord_def best = from;
186 double hiscore = DBL_MAX;
187
188 double dist0 = (from - org).rdist();
189 double ang0 = _get_ang(from.x - org.x, from.y - org.y) - rdur * 0.01 * 4 / 3;
190 for (coord_def pos : avail)
191 {
192 double dist = (pos - org).rdist();
193 double distdiff = fabs(dist - dist0);
194 double ang = _get_ang(pos.x - org.x, pos.y - org.y);
195 double angdiff = min(fabs(ang - ang0), fabs(ang - ang0 - 8));
196
197 double score = distdiff + angdiff * 3 / 2;
198 if (score < hiscore)
199 best = pos, hiscore = score;
200 }
201
202 // must find _something_, the original space might be already taken
203 ASSERT(hiscore != DBL_MAX);
204
205 return best;
206 }
207
_rdam(int rage)208 static int _rdam(int rage)
209 {
210 // integral of damage done until given age-radius
211 if (rage <= 0)
212 return 0;
213 else if (rage < 10)
214 return sqr(rage) / 2;
215 else
216 return rage * 10 - 50;
217 }
218
_vortex_age(const actor * caster)219 static int _vortex_age(const actor *caster)
220 {
221 const string name = "polar_vortex_since";
222 if (caster->props.exists(name.c_str()))
223 return you.elapsed_time - caster->props[name.c_str()].get_int();
224 return 100; // for permanent vortices
225 }
226
227 // time needed to reach the given radius
_age_needed(int r)228 static int _age_needed(int r)
229 {
230 if (r <= 0)
231 return 0;
232 if (r > POLAR_VORTEX_RADIUS)
233 return INT_MAX;
234 return sqr(r) * 7 / 5;
235 }
236
polar_vortex_damage(actor * caster,int dur)237 void polar_vortex_damage(actor *caster, int dur)
238 {
239 if (!dur)
240 return;
241
242 int pow;
243 const int max_radius = POLAR_VORTEX_RADIUS;
244
245 // Not stored so unwielding that staff will reduce damage.
246 if (caster->is_player())
247 pow = calc_spell_power(SPELL_POLAR_VORTEX, true);
248 else
249 // XXX TODO: use the normal spellpower calc functions
250 pow = caster->as_monster()->get_hit_dice() * 4;
251 const coord_def org = caster->pos();
252 int noise = 0;
253 WindSystem winds(org);
254
255 const coord_def old_player_pos = you.pos();
256 coord_def new_player_pos = old_player_pos;
257
258 int age = _vortex_age(caster);
259 ASSERT(age >= 0);
260
261 vector<coord_def> move_avail; // legal destinations
262 map<mid_t, coord_def> move_dest; // chosen destination
263 int rdurs[POLAR_VORTEX_RADIUS + 1]; // durations at radii
264 int cnt_open = 0;
265 int cnt_all = 0;
266
267 distance_iterator count_i(org, false);
268 distance_iterator dam_i(org, true);
269 for (int r = 1; r <= max_radius; r++)
270 {
271 while (count_i && count_i.radius() == r)
272 {
273 if (winds.has_wind(*count_i))
274 cnt_open++;
275 ++cnt_all;
276 ++count_i;
277 }
278 // effective age at radius r
279 int rage = age - _age_needed(r);
280 /* Not just "portion of time affected":
281 **
282 **
283 ----++----
284 **......
285 **........
286 here, damage done is 3/4, not 1/2.
287 */
288 // effective duration at the radius
289 int rdur = _rdam(rage + abs(dur)) - _rdam(rage);
290 rdurs[r] = rdur;
291 // power at the radius
292 int rpow = div_rand_round(pow * cnt_open * rdur, cnt_all * 100);
293 if (!rpow)
294 break;
295
296 noise = max(div_rand_round(r * rdur * 3, 100), noise);
297
298 vector<coord_def> clouds;
299 for (; dam_i && dam_i.radius() == r; ++dam_i)
300 {
301 bool veto =
302 env.markers.property_at(*dam_i, MAT_ANY, "veto_destroy") == "veto";
303
304 if ((feat_is_tree(env.grid(*dam_i)) && !is_temp_terrain(*dam_i))
305 && !veto && dur > 0
306 && bernoulli(rdur * 0.01, 0.05)) // 5% chance per 10 aut
307 {
308 env.grid(*dam_i) = DNGN_FLOOR;
309 set_terrain_changed(*dam_i);
310 if (you.see_cell(*dam_i))
311 mpr("A tree falls to the furious winds!");
312 if (caster->is_player())
313 did_god_conduct(DID_KILL_PLANT, 1);
314 }
315
316 if (!winds.has_wind(*dam_i))
317 continue;
318
319 bool leda = false; // squares with ledaed enemies are no-go
320 if (actor* victim = actor_at(*dam_i))
321 {
322 if (victim->submerged())
323 continue;
324 if (victim->is_player() && monster_at(*dam_i))
325 {
326 // A far-fetched case: you're using Fedhas' passthrough
327 // or standing on a submerged air elemental, there are
328 // no free spots, and a monster vortex rotates you.
329 // Plants don't get uprooted, so the logic would be
330 // really complex. Let's not go there.
331 continue;
332 }
333 if (victim->is_player() && get_form()->forbids_flight())
334 continue;
335
336 leda = victim->liquefied_ground()
337 || victim->is_monster()
338 && _mons_is_unmovable(victim->as_monster());
339 if (!victim->res_polar_vortex())
340 {
341 if (victim->is_monster())
342 {
343 monster *mon = victim->as_monster();
344 if (!leda)
345 {
346 // fly the monster so you get only one attempt
347 // at tossing them into water/lava
348 mon_enchant ench(ENCH_FLIGHT, 0, caster, 20);
349 if (mon->has_ench(ENCH_FLIGHT))
350 mon->update_ench(ench);
351 else
352 mon->add_ench(ench);
353 }
354 behaviour_event(mon, ME_ANNOY, caster);
355 }
356 else if (!leda)
357 {
358 bool standing = !you.airborne();
359 if (standing)
360 mpr("The freezing vortex lifts you up.");
361 you.duration[DUR_FLIGHT]
362 = max(you.duration[DUR_FLIGHT], 20);
363 if (standing)
364 float_player();
365 }
366
367 // alive check here in case the annoy event above dismissed
368 // the victim.
369 if (dur > 0 && victim->alive()
370 && (!caster->is_player()
371 || !victim->is_monster()
372 || !god_protects(caster, victim->as_monster(), true)))
373 {
374 const int base_dmg = div_rand_round(roll_dice(12, rpow), 15);
375 const int post_res_dmg
376 = resist_adjust_damage(victim, BEAM_ICE, base_dmg);
377 const int post_ac_dmg
378 = victim->apply_ac(post_res_dmg, 0, ac_type::proportional);
379 dprf("damage done: %d", post_ac_dmg);
380 victim->hurt(caster, post_ac_dmg, BEAM_ICE, KILLED_BY_BEAM,
381 "", "vortex");
382 }
383 }
384
385 if (victim->alive() && !leda && dur > 0)
386 move_dest[victim->mid] = victim->pos();
387 }
388
389 if (cell_is_solid(*dam_i))
390 continue;
391
392 if ((!cloud_at(*dam_i) || cloud_at(*dam_i)->type == CLOUD_VORTEX)
393 && x_chance_in_y(rpow, 20))
394 {
395 place_cloud(CLOUD_VORTEX, *dam_i, 2 + random2(2), caster);
396 }
397 clouds.push_back(*dam_i);
398 swap_clouds(clouds[random2(clouds.size())], *dam_i);
399
400 if (!leda)
401 move_avail.push_back(*dam_i);
402 }
403 }
404
405 noisy(noise, org, caster->mid);
406
407 if (dur <= 0)
408 return;
409
410 // Gather actors who are to be moved.
411 for (auto &entry : move_dest)
412 if (actor* act = actor_by_mid(entry.first)) // should still be alive...
413 {
414 ASSERT(entry.second == act->pos());
415
416 // Temporarily move to (0,0) to allow permutations.
417 if (env.mgrid(act->pos()) == act->mindex())
418 env.mgrid(act->pos()) = NON_MONSTER;
419 act->moveto(coord_def());
420 if (act->is_player())
421 stop_delay(true);
422 }
423
424 // Need to check available positions again, as the damage call could
425 // have spawned something new (like Royal Jelly spawns).
426 erase_if(move_avail, actor_at);
427
428 // Calculate destinations.
429 for (auto &entry : move_dest)
430 {
431 const int r = entry.second.distance_from(org);
432 coord_def dest = _rotate(org, entry.second, move_avail, rdurs[r]);
433 // Only one monster per destination.
434 erase_if(move_avail, [&dest](const coord_def& p) { return p == dest; });
435 entry.second = dest;
436 }
437
438 // Actually move actors into place.
439 for (auto &entry : move_dest)
440 if (actor* act = actor_by_mid(entry.first)) // should still be alive...
441 {
442 const coord_def newpos = entry.second;
443 ASSERT(!actor_at(newpos));
444 act->move_to_pos(newpos);
445 ASSERT(act->pos() == newpos);
446
447 if (act->is_player())
448 new_player_pos = newpos;
449 }
450
451 if (caster->is_player())
452 fire_final_effects();
453 else
454 {
455 if (new_player_pos != old_player_pos
456 && !need_expiration_warning(old_player_pos)
457 && need_expiration_warning(new_player_pos))
458 {
459 mprf(MSGCH_DANGER, "Careful! You are now flying above %s.",
460 feature_description_at(new_player_pos, false, DESC_PLAIN)
461 .c_str());
462 }
463 }
464 }
465
cancel_polar_vortex(bool tloc)466 void cancel_polar_vortex(bool tloc)
467 {
468 if (!you.duration[DUR_VORTEX])
469 return;
470
471 dprf("Aborting vortex.");
472 if (you.duration[DUR_VORTEX] == you.duration[DUR_FLIGHT])
473 {
474 if (tloc)
475 {
476 // it'd be better to abort flight instantly, but let's first
477 // make damn sure all ways of translocating are prevented from
478 // landing you in water. Insta-kill due to an arrow of dispersal
479 // is not nice.
480 you.duration[DUR_FLIGHT] = min(20, you.duration[DUR_FLIGHT]);
481 }
482 else
483 {
484 // Vortex ended by using something stairslike, so the destination
485 // is safe
486 you.duration[DUR_FLIGHT] = 0;
487 if (you.has_mutation(MUT_TENGU_FLIGHT))
488 you.redraw_evasion = true;
489 }
490 }
491 you.duration[DUR_VORTEX] = 0;
492 you.duration[DUR_VORTEX_COOLDOWN] = random_range(35, 45);
493 }
494