Apply the new ground_level method.
[crawl.git] / crawl-ref / source / mon-project.cc
blobc168ce78c6910f227ecdb290d78f2fd96e274723
1 /*
2 * File: mon-project.cc
3 * Summary: Slow projectiles, done as monsters.
4 * Written by: Adam Borowski
5 */
7 #include "AppHdr.h"
9 #include "mon-project.h"
11 #include <string.h>
12 #include <stdlib.h>
13 #include <stdio.h>
14 #include <cmath>
16 #include "externs.h"
18 #include "cloud.h"
19 #include "directn.h"
20 #include "coord.h"
21 #include "env.h"
22 #include "itemprop.h"
23 #include "mgen_data.h"
24 #include "mon-place.h"
25 #include "mon-stuff.h"
26 #include "mon-util.h"
27 #include "shout.h"
28 #include "stuff.h"
29 #include "terrain.h"
30 #include "viewchar.h"
32 static void _fuzz_direction(monster& mon, int pow);
34 bool cast_iood(actor *caster, int pow, bolt *beam)
36 int mtarg = mgrd(beam->target);
37 if (beam->target == you.pos())
38 mtarg = MHITYOU;
40 int mind = -1;
41 for (int i=0; i < 10 && mind == -1; i++)
43 mind = mons_place(mgen_data(MONS_ORB_OF_DESTRUCTION,
44 (caster->atype() == ACT_PLAYER) ? BEH_FRIENDLY :
45 ((monster*)caster)->friendly() ? BEH_FRIENDLY : BEH_HOSTILE,
46 caster,
48 SPELL_IOOD,
49 coord_def(-1, -1),
50 mtarg,
52 GOD_NO_GOD));
54 if (mind == -1)
56 mpr("Failed to spawn projectile.", MSGCH_WARN);
57 /*canned_msg(MSG_NOTHING_HAPPENS);*/
58 return (false);
61 monster& mon = menv[mind];
62 const coord_def pos = caster->pos();
63 beam->choose_ray();
64 dprf("beam (%d,%d)+t*(%d,%d) ray (%f,%f)+t*(%f,%f)",
65 pos.x, pos.y, beam->target.x - pos.x, beam->target.y - pos.y,
66 beam->ray.r.start.x - 0.5, beam->ray.r.start.y - 0.5,
67 beam->ray.r.dir.x, beam->ray.r.dir.y);
68 mon.props["iood_x"].get_float() = beam->ray.r.start.x - 0.5;
69 mon.props["iood_y"].get_float() = beam->ray.r.start.y - 0.5;
70 mon.props["iood_vx"].get_float() = beam->ray.r.dir.x;
71 mon.props["iood_vy"].get_float() = beam->ray.r.dir.y;
72 mon.props["iood_kc"].get_byte() = (caster->atype() == ACT_PLAYER) ? KC_YOU :
73 ((monster*)caster)->friendly() ? KC_FRIENDLY : KC_OTHER;
74 mon.props["iood_pow"].get_short() = pow;
75 mon.flags &= ~MF_JUST_SUMMONED;
76 mon.props["iood_caster"].get_string() = caster->as_monster()
77 ? caster->name(DESC_PLAIN, true)
78 : "";
79 mon.props["iood_mid"].get_int() = caster->mid;
81 _fuzz_direction(mon, pow);
83 // Move away from the caster's square.
84 iood_act(mon, true);
85 // We need to take at least one full move (for the above), but let's
86 // randomize it and take more so players won't get guaranteed instant
87 // damage.
88 mon.lose_energy(EUT_MOVE, 2, random2(3)+2);
89 return (true);
92 static void _normalize(float &x, float &y)
94 const float d = sqrt(x*x + y*y);
95 if (d <= 0.000001)
96 return;
97 x/=d;
98 y/=d;
101 // angle measured in chord length
102 static bool _in_front(float vx, float vy, float dx, float dy, float angle)
104 return ((dx-vx)*(dx-vx) + (dy-vy)*(dy-vy) <= (angle*angle));
107 static void _iood_dissipate(monster& mon, bool msg = true)
109 if (msg)
110 simple_monster_message(&mon, " dissipates.");
111 dprf("iood: dissipating");
112 monster_die(&mon, KILL_DISMISSED, NON_MONSTER);
115 static void _fuzz_direction(monster& mon, int pow)
117 const float x = mon.props["iood_x"];
118 const float y = mon.props["iood_y"];
119 float vx = mon.props["iood_vx"];
120 float vy = mon.props["iood_vy"];
122 _normalize(vx, vy);
124 if (pow < 10)
125 pow = 10;
126 const float off = (coinflip() ? -1 : 1) * 0.25;
127 float tan = (random2(31) - 15) * 0.019; // approx from degrees
128 tan *= 75.0 / pow;
129 if (wearing_amulet(AMU_INACCURACY))
130 tan *= 2;
132 // Cast either from left or right hand.
133 mon.props["iood_x"] = x + vy*off;
134 mon.props["iood_y"] = y - vx*off;
135 // And off the direction a bit.
136 mon.props["iood_vx"] = vx + vy*tan;
137 mon.props["iood_vy"] = vy - vx*tan;
140 // Alas, too much differs to reuse beam shield blocks :(
141 static bool _iood_shielded(monster& mon, actor &victim)
143 if (!victim.shield() || victim.incapacitated())
144 return (false);
146 const int to_hit = 15 + mon.props["iood_pow"].get_short()/12;
147 const int con_block = random2(to_hit + victim.shield_block_penalty());
148 const int pro_block = victim.shield_bonus();
149 dprf("iood shield: pro %d, con %d", pro_block, con_block);
150 return (pro_block >= con_block);
153 static bool _iood_hit(monster& mon, const coord_def &pos, bool big_boom = false)
155 bolt beam;
156 beam.name = "orb of destruction";
157 beam.flavour = BEAM_NUKE;
158 beam.attitude = mon.attitude;
160 actor *caster = actor_by_mid(mon.props["iood_mid"].get_int());
161 if (!caster) // caster is dead/gone, blame the orb itself (as its
162 caster = &mon; // friendliness is correct)
163 beam.set_agent(caster);
164 beam.colour = WHITE;
165 beam.glyph = dchar_glyph(DCHAR_FIRED_BURST);
166 beam.range = 1;
167 beam.source = pos;
168 beam.target = pos;
169 beam.hit = AUTOMATIC_HIT;
170 beam.source_name = mon.props["iood_caster"].get_string();
172 int pow = mon.props["iood_pow"].get_short();
173 pow = stepdown_value(pow, 30, 30, 200, -1);
174 const int dist = mon.props["iood_distance"].get_int();
175 ASSERT(dist >= 0);
176 if (dist < 4)
177 pow = pow * (dist*2+3) / 10;
178 beam.damage = dice_def(9, pow / 4);
180 if (dist < 3)
181 beam.name = "wavering " + beam.name;
182 if (dist < 2)
183 beam.hit_verb = "weakly hits";
184 beam.ex_size = 1;
185 beam.loudness = 7;
187 monster_die(&mon, KILL_DISMISSED, NON_MONSTER);
189 if (big_boom)
190 beam.explode(true, false);
191 else
192 beam.fire();
194 return (true);
197 // returns true if the orb is gone
198 bool iood_act(monster& mon, bool no_trail)
200 ASSERT(mons_is_projectile(mon.type));
202 float x = mon.props["iood_x"];
203 float y = mon.props["iood_y"];
204 float vx = mon.props["iood_vx"];
205 float vy = mon.props["iood_vy"];
207 dprf("iood_act: pos=(%d,%d) rpos=(%f,%f) v=(%f,%f) foe=%d",
208 mon.pos().x, mon.pos().y,
209 x, y, vx, vy, mon.foe);
211 if (!vx && !vy) // not initialized
213 _iood_dissipate(mon);
214 return (true);
217 _normalize(vx, vy);
219 const actor *foe = mon.get_foe();
220 // If the target is gone, the orb continues on a ballistic course since
221 // picking a new one would require intelligence.
223 if (foe)
225 const coord_def target = foe->pos();
226 float dx = target.x - x;
227 float dy = target.y - y;
228 _normalize(dx, dy);
230 // Special case:
231 // Moving diagonally when the orb is just about to hit you
232 // 2
233 // ->*1
234 // (from 1 to 2) would be a guaranteed escape. This may be
235 // realistic (strafing!), but since the game has no non-cheesy
236 // means of waiting a small fraction of a turn, we don't want it.
237 const int old_t_pos = mon.props["iood_tpos"].get_short();
238 const coord_def rpos(static_cast<int>(round(x)), static_cast<int>(round(y)));
239 if (old_t_pos && old_t_pos != (256 * target.x + target.y)
240 && (rpos - target).rdist() <= 1
241 // ... but following an orb is ok.
242 && _in_front(vx, vy, dx, dy, 1.5)) // ~97 degrees
244 vx = dx;
245 vy = dy;
247 mon.props["iood_tpos"].get_short() = 256 * target.x + target.y;
249 if (!_in_front(vx, vy, dx, dy, 0.3)) // ~17 degrees
251 float ax, ay;
252 if (dy*vx < dx*vy)
253 ax = vy, ay = -vx, dprf("iood: veering left");
254 else
255 ax = -vy, ay = vx, dprf("iood: veering right");
256 vx += ax * 0.3;
257 vy += ay * 0.3;
259 else
260 dprf("iood: keeping course");
262 _normalize(vx, vy);
263 mon.props["iood_vx"] = vx;
264 mon.props["iood_vy"] = vy;
267 move_again:
269 x += vx;
270 y += vy;
272 mon.props["iood_x"] = x;
273 mon.props["iood_y"] = y;
274 mon.props["iood_distance"].get_int()++;
276 const coord_def pos(static_cast<int>(round(x)), static_cast<int>(round(y)));
277 if (!in_bounds(pos))
279 _iood_dissipate(mon);
280 return (true);
283 if (mon.props["iood_kc"].get_byte() == KC_YOU
284 && (you.pos() - pos).rdist() > LOS_RADIUS)
285 { // not actual vision, because of the smoke trail
286 _iood_dissipate(mon);
287 return (true);
290 if (pos == mon.pos())
291 return (false);
293 if (!no_trail)
294 place_cloud(CLOUD_MAGIC_TRAIL, mon.pos(), 2 + random2(3), &mon);
296 actor *victim = actor_at(pos);
297 if (cell_is_solid(pos) || victim)
299 if (cell_is_solid(pos))
301 if (you.see_cell(pos))
302 mprf("%s hits %s", mon.name(DESC_CAP_THE, true).c_str(),
303 feature_description(pos, false, DESC_NOCAP_A).c_str());
306 monster* mons = (victim && victim->atype() == ACT_MONSTER) ?
307 (monster*) victim : 0;
309 if (mons && mons_is_projectile(victim->type))
311 if (mon.observable())
312 mpr("The orbs collide in a blinding explosion!");
313 else
314 noisy(40, pos, "You hear a loud magical explosion!");
315 monster_die(mons, KILL_DISMISSED, NON_MONSTER);
316 _iood_hit(mon, pos, true);
317 return (true);
320 if (mons && mons->submerged())
322 // Try to swap with the submerged creature.
323 if (mons->is_habitable(mon.pos()))
325 dprf("iood: Swapping with a submerged monster.");
326 mons->set_position(mon.pos());
327 mon.set_position(pos);
328 mgrd(mons->pos()) = mons->mindex();
329 mgrd(pos) = mon.mindex();
331 return (false);
333 else // if swap fails, move ahead
335 dprf("iood: Boosting above a submerged monster (can't swap).");
336 mon.lose_energy(EUT_MOVE);
337 goto move_again;
341 if (victim && _iood_shielded(mon, *victim))
343 item_def *shield = victim->shield();
344 if (!shield_reflects(*shield))
346 if (victim->atype() == ACT_PLAYER)
348 mprf("You block %s.", mon.name(DESC_NOCAP_THE, true).c_str());
350 else
352 simple_monster_message(mons, (" blocks "
353 + mon.name(DESC_NOCAP_THE, true) + ".").c_str());
355 victim->shield_block_succeeded(&mon);
356 _iood_dissipate(mon);
357 return (true);
360 if (victim->atype() == ACT_PLAYER)
362 mprf("Your %s reflects %s!",
363 shield->name(DESC_PLAIN).c_str(),
364 mon.name(DESC_NOCAP_THE, true).c_str());
365 ident_reflector(shield);
367 else if (you.see_cell(pos))
369 if (victim->observable())
371 mprf("%s reflects %s with %s %s!",
372 victim->name(DESC_CAP_THE, true).c_str(),
373 mon.name(DESC_NOCAP_THE, true).c_str(),
374 mon.pronoun(PRONOUN_NOCAP_POSSESSIVE).c_str(),
375 shield->name(DESC_PLAIN).c_str());
376 ident_reflector(shield);
378 else
380 mprf("%s bounces off thin air!",
381 mon.name(DESC_CAP_THE, true).c_str());
384 victim->shield_block_succeeded(&mon);
386 mon.props["iood_vx"] = vx = -vx;
387 mon.props["iood_vy"] = vy = -vy;
389 // Need to get out of the victim's square.
391 // If you're next to the caster and both of you wear shields of
392 // reflection, this can lead to a brief game of ping-pong, but
393 // rapidly increasing shield penalties will make it short.
394 mon.lose_energy(EUT_MOVE);
395 goto move_again;
398 // Yay for inconsistencies in beam-vs-player and beam-vs-monsters.
399 if (victim == &you)
400 mprf("%s hits you!", mon.name(DESC_CAP_THE, true).c_str());
402 if (_iood_hit(mon, pos))
403 return (true);
406 if (!mon.move_to_pos(pos))
408 _iood_dissipate(mon);
409 return (true);
412 // move_to_pos() just trashed the coords, set them again
413 mon.props["iood_x"] = x;
414 mon.props["iood_y"] = y;
416 return (false);
419 // Reduced copy of iood_act to move the orb while the player
420 // is off-level. Just goes straight and dissipates instead of
421 // hitting anything.
422 static bool _iood_catchup_move(monster& mon)
424 float x = mon.props["iood_x"];
425 float y = mon.props["iood_y"];
426 float vx = mon.props["iood_vx"];
427 float vy = mon.props["iood_vy"];
429 if (!vx && !vy) // not initialized
431 _iood_dissipate(mon, false);
432 return (true);
435 _normalize(vx, vy);
437 x += vx;
438 y += vy;
440 mon.props["iood_x"] = x;
441 mon.props["iood_y"] = y;
442 mon.props["iood_distance"].get_int()++;
444 const coord_def pos(static_cast<int>(round(x)), static_cast<int>(round(y)));
445 if (!in_bounds(pos))
447 _iood_dissipate(mon, true);
448 return (true);
451 if (pos == mon.pos())
452 return (false);
454 actor *victim = actor_at(pos);
455 if (cell_is_solid(pos) || victim)
457 // Just dissipate instead of hitting something.
458 _iood_dissipate(mon, true);
459 return (true);
462 if (!mon.move_to_pos(pos))
464 _iood_dissipate(mon);
465 return (true);
468 // move_to_pos() just trashed the coords, set them again
469 mon.props["iood_x"] = x;
470 mon.props["iood_y"] = y;
472 return (false);
475 void iood_catchup(monster* mons, int pturns)
477 monster& mon = *mons;
478 ASSERT(mons_is_projectile(mon.type));
480 const int moves = pturns * mon.speed / BASELINE_DELAY;
482 if (moves > 50)
484 _iood_dissipate(mon, false);
485 return;
488 if (mon.props["iood_kc"].get_byte() == KC_YOU)
490 // Left player's vision.
491 _iood_dissipate(mon, false);
492 return;
496 for (int i = 0; i < moves; ++i)
497 if (_iood_catchup_move(mon))
498 return;