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