3 * @brief Slow projectiles, done as monsters.
8 #include "mon-project.h"
22 #include "mgen_data.h"
23 #include "mon-death.h"
24 #include "mon-place.h"
31 static void _fuzz_direction(const actor *caster, monster& mon, int pow);
33 spret_type cast_iood(actor *caster, int pow, bolt *beam, float vx, float vy,
36 const bool is_player = caster->is_player();
37 if (beam && is_player && !player_tracer(ZAP_IOOD, pow, *beam))
42 int mtarg = !beam ? MHITNOT :
43 beam->target == you.pos() ? MHITYOU : mgrd(beam->target);
45 monster *mon = place_monster(mgen_data(MONS_ORB_OF_DESTRUCTION,
46 (is_player) ? BEH_FRIENDLY :
47 ((monster*)caster)->friendly() ? BEH_FRIENDLY : BEH_HOSTILE,
54 GOD_NO_GOD), true, true);
57 mprf(MSGCH_ERROR, "Failed to spawn projectile.");
64 #ifdef DEBUG_DIAGNOSTICS
65 const coord_def pos = caster->pos();
66 dprf("beam (%d,%d)+t*(%d,%d) ray (%f,%f)+t*(%f,%f)",
67 pos.x, pos.y, beam->target.x - pos.x, beam->target.y - pos.y,
68 beam->ray.r.start.x - 0.5, beam->ray.r.start.y - 0.5,
69 beam->ray.r.dir.x, beam->ray.r.dir.y);
71 mon->props[IOOD_X].get_float() = beam->ray.r.start.x - 0.5;
72 mon->props[IOOD_Y].get_float() = beam->ray.r.start.y - 0.5;
73 mon->props[IOOD_VX].get_float() = beam->ray.r.dir.x;
74 mon->props[IOOD_VY].get_float() = beam->ray.r.dir.y;
75 _fuzz_direction(caster, *mon, pow);
79 // Multi-orb: spread the orbs a bit, otherwise diagonal ones might
80 // fail to leave the cardinal direction: orb A moves -0.4,+0.9 and
81 // orb B +0.4,+0.9, both rounded to 0,1.
82 mon->props[IOOD_X].get_float() = caster->pos().x + 0.4 * vx;
83 mon->props[IOOD_Y].get_float() = caster->pos().y + 0.4 * vy;
84 mon->props[IOOD_VX].get_float() = vx;
85 mon->props[IOOD_VY].get_float() = vy;
88 mon->props[IOOD_KC].get_byte() = (is_player) ? KC_YOU :
89 ((monster*)caster)->friendly() ? KC_FRIENDLY : KC_OTHER;
90 mon->props[IOOD_POW].get_short() = pow;
91 mon->flags &= ~MF_JUST_SUMMONED;
92 mon->props[IOOD_CASTER].get_string() = caster->as_monster()
93 ? caster->name(DESC_A, true)
94 : (caster->is_player()) ? "you" : "";
95 mon->summoner = caster->mid;
97 if (caster->is_player() || caster->type == MONS_PLAYER_GHOST
98 || caster->type == MONS_PLAYER_ILLUSION)
100 mon->props[IOOD_FLAWED].get_byte() = true;
103 // Move away from the caster's square.
104 iood_act(*mon, true);
106 // If the foe was adjacent to the caster, that might have destroyed it.
109 // We need to take at least one full move (for the above), but let's
110 // randomize it and take more so players won't get guaranteed instant
112 mon->lose_energy(EUT_MOVE, 2, random2(3)+2);
114 // Multi-orbs don't home during the first move, they'd likely
115 // immediately explode otherwise.
120 return SPRET_SUCCESS;
124 * Find a target for a bursty (non-player-targeted) IOOD.
126 * Try to find an enemy that's at a reasonable angle from the angle the IOOD
127 * is fired at, preferring the given foe (if a non-MHITNOT foe is given) if
128 * they're valid, and otherwise preferring the closest valid foe.
130 * @param angle The angle that the IOOD will be fired at, relative
131 * to the player's position.
132 * @param preferred_foe The mindex of a target to choose if possible; may
133 * be MHITNOT (no preferred target)
134 * @return The mindex of a valid target for the IOOD.
136 static int _burst_iood_target(double iood_angle, int preferred_foe)
138 int closest_foe = MHITNOT;
139 int closest_dist = INT_MAX;
141 for (monster_near_iterator mi(you.pos(), LOS_SOLID); mi; ++mi)
143 const monster* m = *mi;
146 if (!you.can_see(m) || mons_is_projectile(m))
149 // is this position at a valid angle?
150 const coord_def delta = mi->pos() - you.pos();
151 const double angle = atan2(delta.x, delta.y);
152 const double abs_angle_diff = abs(angle - fmod(iood_angle, PI * 2));
153 const double angle_diff = (abs_angle_diff > PI) ?
154 2 * PI - abs_angle_diff :
156 if (angle_diff >= PI / 3)
158 dprf("can't target %s; angle diff %f",
159 m->name(DESC_PLAIN).c_str(), angle_diff);
163 // if preferred foe is valid, choose it.
164 if (m->mindex() == preferred_foe)
166 dprf("preferred target %s is valid burst target (delta %f)",
167 m->name(DESC_PLAIN).c_str(), angle_diff);
168 return preferred_foe;
171 if (mons_aligned(m, &you) || mons_is_firewood(m))
173 dprf("skipping invalid burst target %s (%s)",
174 m->name(DESC_PLAIN).c_str(),
175 mons_aligned(m, &you) ? "aligned" : "firewood");
179 const int dist = grid_distance(m->pos(), you.pos());
180 // on distance ties, bias by iterator order (mindex)
181 if (dist >= closest_dist)
183 dprf("%s not closer to target than closest (%d vs %d)",
184 m->name(DESC_PLAIN).c_str(), dist, closest_dist);
188 dprf("%s is valid burst target (delta %f, dist %d)",
189 m->name(DESC_PLAIN).c_str(), angle_diff, dist);
191 closest_foe = m->mindex();
194 const int foe = closest_foe != MHITNOT ? closest_foe : preferred_foe;
195 dprf("targeting %d", foe);
199 void cast_iood_burst(int pow, coord_def target)
201 const monster* mons = monster_at(target);
202 const int preferred_foe = mons && you.can_see(mons) ?
206 const int n_orbs = random_range(3, 7);
207 dprf("Bursting %d orbs.", n_orbs);
208 const double angle0 = random2(2097152) * PI * 2 / 2097152;
210 for (int i = 0; i < n_orbs; i++)
212 const double angle = angle0 + i * PI * 2 / n_orbs;
213 const int foe = _burst_iood_target(angle, preferred_foe);
214 cast_iood(&you, pow, 0, sin(angle), cos(angle), foe);
218 static void _normalize(float &x, float &y)
220 const float d = sqrt(x*x + y*y);
227 // angle measured in chord length
228 static bool _in_front(float vx, float vy, float dx, float dy, float angle)
230 return (dx-vx)*(dx-vx) + (dy-vy)*(dy-vy) <= (angle*angle);
233 static void _iood_stop(monster& mon, bool msg = true)
238 if (mons_is_boulder(&mon))
240 // Deduct the energy first - the move they made that just stopped
241 // them was a speed 14 move.
242 mon.lose_energy(EUT_MOVE);
243 mon.del_ench(ENCH_ROLLING,!msg);
248 simple_monster_message(&mon, " dissipates.");
249 dprf("iood: dissipating");
250 monster_die(&mon, KILL_DISMISSED, NON_MONSTER);
253 static void _fuzz_direction(const actor *caster, monster& mon, int pow)
255 const float x = mon.props[IOOD_X];
256 const float y = mon.props[IOOD_Y];
257 float vx = mon.props[IOOD_VX];
258 float vy = mon.props[IOOD_VY];
264 const float off = (coinflip() ? -1 : 1) * 0.25;
265 float tan = (random2(31) - 15) * 0.019; // approx from degrees
267 if (caster && caster->inaccuracy())
270 // Cast either from left or right hand.
271 mon.props[IOOD_X] = x + vy*off;
272 mon.props[IOOD_Y] = y - vx*off;
273 // And off the direction a bit.
274 mon.props[IOOD_VX] = vx + vy*tan;
275 mon.props[IOOD_VY] = vy - vx*tan;
278 // Alas, too much differs to reuse beam shield blocks :(
279 static bool _iood_shielded(monster& mon, actor &victim)
281 if (!victim.shielded() || victim.incapacitated())
284 const int to_hit = 15 + (mons_is_projectile(mon.type) ?
285 mon.props[IOOD_POW].get_short()/12 : mon.get_hit_dice()/2);
286 const int con_block = random2(to_hit + victim.shield_block_penalty());
287 const int pro_block = victim.shield_bonus();
288 dprf("iood shield: pro %d, con %d", pro_block, con_block);
289 return pro_block >= con_block;
292 static bool _boulder_hit(monster& mon, const coord_def &pos)
294 actor *victim = actor_at(pos);
297 simple_monster_message(&mon, (string(" smashes into ")
298 + victim->name(DESC_THE) + "!").c_str());
300 int dam = victim->apply_ac(roll_dice(3, 20));
301 victim->hurt(&mon, dam, BEAM_MISSILE, KILLED_BY_ROLLING);
305 return victim && victim->alive() || !mon.alive();
308 static bool _iood_hit(monster& mon, const coord_def &pos, bool big_boom = false)
310 if (mons_is_boulder(&mon))
311 return _boulder_hit(mon, pos);
314 beam.name = "orb of destruction";
315 beam.flavour = BEAM_DEVASTATION;
316 beam.attitude = mon.attitude;
318 actor *caster = actor_by_mid(mon.summoner);
319 if (!caster) // caster is dead/gone, blame the orb itself (as its
320 caster = &mon; // friendliness is correct)
321 beam.set_agent(caster);
322 if (mon.props.exists(IOOD_REFLECTOR))
324 beam.reflections = 1;
326 const mid_t refl_mid = mon.props[IOOD_REFLECTOR].get_int64();
328 if (refl_mid == MID_PLAYER)
329 beam.reflector = MID_PLAYER;
332 // If the reflecting monster has died, credit the original caster.
333 const monster * const rmon = monster_by_mid(refl_mid);
334 beam.reflector = rmon ? refl_mid : caster->mid;
338 beam.glyph = dchar_glyph(DCHAR_FIRED_BURST);
342 beam.hit = AUTOMATIC_HIT;
343 beam.source_name = mon.props[IOOD_CASTER].get_string();
345 int pow = mon.props[IOOD_POW].get_short();
346 pow = stepdown_value(pow, 30, 30, 200, -1);
347 const int dist = mon.props[IOOD_DIST].get_int();
350 pow = pow * (dist*2+3) / 10;
351 beam.damage = dice_def(9, pow / 4);
354 beam.name = "wavering " + beam.name;
356 beam.hit_verb = "weakly hits";
360 monster_die(&mon, KILL_DISMISSED, NON_MONSTER);
363 beam.explode(true, false);
370 // returns true if the orb is gone
371 bool iood_act(monster& mon, bool no_trail)
373 bool iood = mons_is_projectile(mon.type);
374 ASSERT(iood || mons_is_boulder(&mon));
376 float x = mon.props[IOOD_X];
377 float y = mon.props[IOOD_Y];
378 float vx = mon.props[IOOD_VX];
379 float vy = mon.props[IOOD_VY];
381 dprf("iood_act: pos=(%d,%d) rpos=(%f,%f) v=(%f,%f) foe=%d",
382 mon.pos().x, mon.pos().y,
383 x, y, vx, vy, mon.foe);
385 if (!vx && !vy) // not initialized
393 const actor *foe = mon.get_foe();
394 // If the target is gone, the orb continues on a ballistic course since
395 // picking a new one would require intelligence.
397 // Boulders don't home onto their targets. IOODs can't home in on a
398 // submerged creature.
399 if (iood && foe && !foe->submerged())
401 const coord_def target = foe->pos();
402 float dx = target.x - x;
403 float dy = target.y - y;
407 // Moving diagonally when the orb is just about to hit you
410 // (from 1 to 2) would be a guaranteed escape. This may be
411 // realistic (strafing!), but since the game has no non-cheesy
412 // means of waiting a small fraction of a turn, we don't want it.
413 const int old_t_pos = mon.props[IOOD_TPOS].get_short();
414 const coord_def rpos(static_cast<int>(round(x)), static_cast<int>(round(y)));
415 if (old_t_pos && old_t_pos != (256 * target.x + target.y)
416 && (rpos - target).rdist() <= 1
417 // ... but following an orb is ok.
418 && _in_front(vx, vy, dx, dy, 1.5)) // ~97 degrees
423 mon.props[IOOD_TPOS].get_short() = 256 * target.x + target.y;
425 if (!_in_front(vx, vy, dx, dy, 0.3)) // ~17 degrees
429 ax = vy, ay = -vx, dprf("iood: veering left");
431 ax = -vy, ay = vx, dprf("iood: veering right");
436 dprf("iood: keeping course");
439 mon.props[IOOD_VX] = vx;
440 mon.props[IOOD_VY] = vy;
448 mon.props[IOOD_X] = x;
449 mon.props[IOOD_Y] = y;
450 mon.props[IOOD_DIST].get_int()++;
452 const coord_def pos(static_cast<int>(round(x)), static_cast<int>(round(y)));
459 if (iood && mon.props.exists(IOOD_FLAWED))
461 const actor *caster = actor_by_mid(mon.summoner);
462 if (!caster || caster->pos().origin() ||
463 (caster->pos() - pos).rdist() > LOS_RADIUS)
464 { // not actual vision, because of the smoke trail
470 if (pos == mon.pos())
475 place_cloud(iood ? CLOUD_MAGIC_TRAIL : CLOUD_DUST_TRAIL, mon.pos(),
476 2 + random2(3), &mon);
479 actor *victim = actor_at(pos);
480 if (cell_is_solid(pos) || victim)
482 if (cell_is_solid(pos))
484 const int boulder_noisiness = 5; // don't want this to be big
485 if (you.see_cell(pos) && you.see_cell(mon.pos()))
487 mprf("%s hits %s", mon.name(DESC_THE, true).c_str(),
488 feature_description_at(pos, false, DESC_A).c_str());
490 noisy(boulder_noisiness, pos);
492 else if (!iood && !silenced(you.pos()))
493 noisy(boulder_noisiness, pos, "You hear a crash.");
495 if (!iood) // boulders need to stop now
502 monster* mons = (victim && victim->is_monster()) ?
503 (monster*) victim : 0;
505 if (mons && iood && mons_is_projectile(victim->type))
507 // Weak orbs just fizzle instead of exploding.
508 if (mons->props[IOOD_DIST].get_int() < 2
509 || mon.props[IOOD_DIST].get_int() < 2)
511 if (mons->props[IOOD_DIST].get_int() < 2)
513 if (you.see_cell(pos))
514 mpr("The orb fizzles.");
515 monster_die(mons, KILL_DISMISSED, NON_MONSTER);
518 // Return, if the acting orb fizzled.
519 if (mon.props[IOOD_DIST].get_int() < 2)
521 if (you.see_cell(pos))
522 mpr("The orb fizzles.");
523 monster_die(&mon, KILL_DISMISSED, NON_MONSTER);
529 if (mon.observable())
530 mpr("The orbs collide in a blinding explosion!");
532 mpr("You hear a loud magical explosion!");
534 monster_die(mons, KILL_DISMISSED, NON_MONSTER);
535 _iood_hit(mon, pos, true);
540 if (mons && mons_is_boulder(&mon) && mons_is_boulder(mons))
542 if (mon.observable())
544 mpr("The boulders collide with a stupendous crash!");
548 noisy(20, pos, "You hear a loud crashing sound!");
550 // Remove ROLLING and add DAZED
553 if (!mon.check_clarity(false))
554 mon.add_ench(ENCH_CONFUSION);
555 if (!mons->check_clarity(false))
556 mons->add_ench(ENCH_CONFUSION);
560 if (mons && (mons->submerged() || mons->type == MONS_BATTLESPHERE))
562 // Try to swap with the submerged creature.
563 if (mon.swap_with(mons))
565 dprf("iood: Swapping with a submerged monster.");
568 else // if swap fails, move ahead
570 dprf("iood: Boosting above a submerged monster (can't swap).");
571 mon.lose_energy(EUT_MOVE);
576 if (victim && _iood_shielded(mon, *victim))
578 item_def *shield = victim->shield();
579 if (!shield || !shield_reflects(*shield))
581 if (victim->is_player())
582 mprf("You block %s.", mon.name(DESC_THE, true).c_str());
585 simple_monster_message(mons, (" blocks "
586 + mon.name(DESC_THE, true) + ".").c_str());
588 victim->shield_block_succeeded(&mon);
593 if (victim->is_player())
595 mprf("Your %s reflects %s!",
596 shield->name(DESC_PLAIN).c_str(),
597 mon.name(DESC_THE, true).c_str());
598 ident_reflector(shield);
600 else if (you.see_cell(pos))
602 if (victim->observable())
604 mprf("%s reflects %s with %s %s!",
605 victim->name(DESC_THE, true).c_str(),
606 mon.name(DESC_THE, true).c_str(),
607 mon.pronoun(PRONOUN_POSSESSIVE).c_str(),
608 shield->name(DESC_PLAIN).c_str());
609 ident_reflector(shield);
613 mprf("%s bounces off thin air!",
614 mon.name(DESC_THE, true).c_str());
617 victim->shield_block_succeeded(&mon);
619 // mid_t is unsigned so won't fit in a plain int
620 mon.props[IOOD_REFLECTOR] = (int64_t) victim->mid;
621 mon.props[IOOD_VX] = vx = -vx;
622 mon.props[IOOD_VY] = vy = -vy;
624 // Need to get out of the victim's square.
626 // If you're next to the caster and both of you wear shields of
627 // reflection, this can lead to a brief game of ping-pong, but
628 // rapidly increasing shield penalties will make it short.
629 mon.lose_energy(EUT_MOVE);
633 if (_iood_hit(mon, pos))
641 // Boulders stop at lava/water to prevent unusual behaviour;
642 // skimming across the water like a pebble could be justifiable, but
643 // it raises too many questions.
644 if (!iood && (!feat_has_solid_floor(grd(pos)) || feat_is_water(grd(pos))))
646 mprf("%s screeches to a halt.", mon.name(DESC_THE, true).c_str());
647 _iood_stop(mon,false);
651 if (!mon.move_to_pos(pos))
657 // move_to_pos() just trashed the coords, set them again
658 mon.props[IOOD_X] = x;
659 mon.props[IOOD_Y] = y;
664 // Reduced copy of iood_act to move the orb while the player
665 // is off-level. Just goes straight and dissipates instead of
667 static bool _iood_catchup_move(monster& mon)
669 float x = mon.props[IOOD_X];
670 float y = mon.props[IOOD_Y];
671 float vx = mon.props[IOOD_VX];
672 float vy = mon.props[IOOD_VY];
674 if (!vx && !vy) // not initialized
676 _iood_stop(mon, false);
685 mon.props[IOOD_X] = x;
686 mon.props[IOOD_Y] = y;
687 mon.props[IOOD_DIST].get_int()++;
689 const coord_def pos(static_cast<int>(round(x)), static_cast<int>(round(y)));
692 _iood_stop(mon, true);
696 if (pos == mon.pos())
699 actor *victim = actor_at(pos);
700 if (cell_is_solid(pos) || victim)
702 // Just dissipate instead of hitting something.
703 _iood_stop(mon, true);
706 // Boulder doesn't travel over water/lava.
707 if (mons_is_boulder(&mon)
708 && (!feat_has_solid_floor(grd(pos)) || feat_is_water(grd(pos))))
710 _iood_stop(mon, false);
714 if (!mon.move_to_pos(pos))
720 // move_to_pos() just trashed the coords, set them again
721 mon.props[IOOD_X] = x;
722 mon.props[IOOD_Y] = y;
727 void iood_catchup(monster* mons, int pturns)
729 monster& mon = *mons;
730 ASSERT(mon.is_projectile());
732 const int moves = pturns * mon.speed / BASELINE_DELAY;
734 // Handle some cases for IOOD only
735 if (mons_is_projectile(mons))
739 _iood_stop(mon, false);
743 if (mon.props[IOOD_KC].get_byte() == KC_YOU)
745 // Left player's vision.
746 _iood_stop(mon, false);
751 for (int i = 0; i < moves; ++i)
752 if (_iood_catchup_move(mon))
756 void boulder_start(monster *mon, bolt *beam)
758 mon->add_ench(ENCH_ROLLING);
759 // Work out x/y/vx/vy from beam
761 mon->props[IOOD_X].get_float() = beam->ray.r.start.x - 0.5;
762 mon->props[IOOD_Y].get_float() = beam->ray.r.start.y - 0.5;
763 mon->props[IOOD_VX].get_float() = mons_is_fleeing(mon) ?
764 -beam->ray.r.dir.x : beam->ray.r.dir.x;
765 mon->props[IOOD_VY].get_float() = mons_is_fleeing(mon) ?
766 -beam->ray.r.dir.y : beam->ray.r.dir.y;