/* * File: mon-project.cc * Summary: Slow projectiles, done as monsters. * Written by: Adam Borowski */ #include "AppHdr.h" #include "mon-project.h" #include #include #include #include #include "externs.h" #include "cloud.h" #include "directn.h" #include "coord.h" #include "env.h" #include "itemprop.h" #include "mgen_data.h" #include "mon-place.h" #include "mon-stuff.h" #include "shout.h" #include "stuff.h" #include "terrain.h" #include "viewchar.h" bool cast_iood(actor *caster, int pow, bolt *beam) { int mtarg = mgrd(beam->target); if (beam->target == you.pos()) mtarg = MHITYOU; int mind = mons_place(mgen_data(MONS_ORB_OF_DESTRUCTION, (caster->atype() == ACT_PLAYER) ? BEH_FRIENDLY : ((monsters*)caster)->friendly() ? BEH_FRIENDLY : BEH_HOSTILE, caster, 0, SPELL_IOOD, coord_def(-1, -1), mtarg, 0, GOD_NO_GOD)); if (mind == -1) { canned_msg(MSG_NOTHING_HAPPENS); return (false); } monsters &mon = menv[mind]; const coord_def pos = caster->pos(); beam->choose_ray(); dprf("beam (%d,%d)+t*(%d,%d) ray (%f,%f)+t*(%f,%f)", pos.x, pos.y, beam->target.x - pos.x, beam->target.y - pos.y, beam->ray.r.start.x - 0.5, beam->ray.r.start.y - 0.5, beam->ray.r.dir.x, beam->ray.r.dir.y); mon.props["iood_x"].get_float() = beam->ray.r.start.x - 0.5; mon.props["iood_y"].get_float() = beam->ray.r.start.y - 0.5; mon.props["iood_vx"].get_float() = beam->ray.r.dir.x; mon.props["iood_vy"].get_float() = beam->ray.r.dir.y; mon.props["iood_kc"].get_byte() = (caster->atype() == ACT_PLAYER) ? KC_YOU : ((monsters*)caster)->friendly() ? KC_FRIENDLY : KC_OTHER; mon.props["iood_pow"].get_short() = pow; mon.flags &= ~MF_JUST_SUMMONED; // Move away from the caster's square. iood_act(mon, true); mon.lose_energy(EUT_MOVE); return (true); } static void _normalize(float &x, float &y) { const float d = sqrt(x*x + y*y); if (d <= 0.000001) return; x/=d; y/=d; } // angle measured in chord length static bool _in_front(float vx, float vy, float dx, float dy, float angle) { return ((dx-vx)*(dx-vx) + (dy-vy)*(dy-vy) <= (angle*angle)); } void _iood_dissipate(monsters &mon) { simple_monster_message(&mon, " dissipates."); dprf("iood: dissipating"); monster_die(&mon, KILL_DISMISSED, NON_MONSTER); } // Alas, too much differs to reuse beam shield blocks :( bool _iood_shielded(monsters &mon, actor &victim) { if (!victim.shield() || victim.incapacitated()) return (false); const int to_hit = 15 + mon.props["iood_pow"].get_short()/12; const int con_block = random2(to_hit + victim.shield_block_penalty()); const int pro_block = victim.shield_bonus(); dprf("iood shield: pro %d, con %d", pro_block, con_block); return (pro_block >= con_block); } bool _iood_hit(monsters &mon, const coord_def &pos, bool big_boom = false) { bolt beam; beam.name = "orb of destruction"; beam.flavour = BEAM_NUKE; beam.attitude = mon.attitude; beam.thrower = (mon.props["iood_kc"].get_byte() == KC_YOU) ? KILL_YOU_MISSILE : KILL_MON_MISSILE; beam.colour = WHITE; beam.type = dchar_glyph(DCHAR_FIRED_BURST); beam.range = 1; beam.source = pos; beam.target = pos; beam.hit = AUTOMATIC_HIT; const int pow = mon.props["iood_pow"].get_short(); beam.damage = dice_def(8, stepdown_value(pow, 30, 30, 200, -1) / 4); beam.ex_size = 1; monster_die(&mon, KILL_DISMISSED, NON_MONSTER); if (big_boom) beam.explode(true, false); else beam.fire(); return (true); } // returns true if the orb is gone bool iood_act(monsters &mon, bool no_trail) { ASSERT(mons_is_projectile(mon.type)); float x = mon.props["iood_x"]; float y = mon.props["iood_y"]; float vx = mon.props["iood_vx"]; float vy = mon.props["iood_vy"]; dprf("iood_act: pos=(%d,%d) rpos=(%f,%f) v=(%f,%f) foe=%d", mon.pos().x, mon.pos().y, x, y, vx, vy, mon.foe); if (!vx && !vy) // not initialized { _iood_dissipate(mon); return (true); } coord_def target(-1, -1); if (mon.foe == MHITYOU) target = you.pos(); else if (invalid_monster_index(mon.foe)) ; else if (invalid_monster_type(menv[mon.foe].type)) { // Our target is gone. Since picking a new one would require // intelligence, the orb continues on a ballistic course. mon.foe = MHITNOT; } else target = menv[mon.foe].pos(); _normalize(vx, vy); if (target != coord_def(-1, -1)) { float dx = target.x - x; float dy = target.y - y; _normalize(dx, dy); // Special case: // Moving diagonally when the orb is just about to hit you // 2 // ->*1 // (from 1 to 2) would be a guaranteed escape. This may be // realistic (strafing!), but since the game has no non-cheesy // means of waiting a small fraction of a turn, we don't want it. const int old_t_pos = mon.props["iood_tpos"].get_short(); if (old_t_pos && old_t_pos != (256 * target.x + target.y) && (coord_def(round(x), round(y)) - target).rdist() <= 1 // ... but following an orb is ok. && _in_front(vx, vy, dx, dy, 1.5)) // ~97 degrees { vx = dx; vy = dy; } mon.props["iood_tpos"].get_short() = 256 * target.x + target.y; if (!_in_front(vx, vy, dx, dy, 0.5)) // ~29 degrees { float ax, ay; if (dy*vx < dx*vy) ax = vy, ay = -vx, dprf("iood: veering left"); else ax = -vy, ay = vx, dprf("iood: veering right"); vx += ax * 0.3; vy += ay * 0.3; } else dprf("iood: keeping course"); _normalize(vx, vy); mon.props["iood_vx"] = vx; mon.props["iood_vy"] = vy; } reflected: x += vx; y += vy; mon.props["iood_x"] = x; mon.props["iood_y"] = y; coord_def pos(round(x), round(y)); if (!in_bounds(pos)) { _iood_dissipate(mon); return (true); } if (mon.props["iood_kc"].get_byte() == KC_YOU && (you.pos() - pos).rdist() >= LOS_RADIUS) { // not actual vision, because of the smoke trail _iood_dissipate(mon); return (true); } if (pos == mon.pos()) return (false); actor *victim = actor_at(pos); if (cell_is_solid(pos) || victim) { if (cell_is_solid(pos)) { if (you.see_cell(pos)) mprf("%s hits %s", mon.name(DESC_CAP_THE, true).c_str(), feature_description(pos, false, DESC_NOCAP_A).c_str()); } if (victim && mons_is_projectile(victim->id())) { if (mon.observable()) mpr("The orbs collide in a blinding explosion!"); else noisy(40, pos, "You hear a loud magical explosion!"); monster_die((monsters*)victim, KILL_DISMISSED, NON_MONSTER); _iood_hit(mon, pos, true); return (true); } if (victim && _iood_shielded(mon, *victim)) { item_def *shield = victim->shield(); if (!shield_reflects(*shield)) { if (victim->atype() == ACT_PLAYER) { mprf("You block %s.", mon.name(DESC_NOCAP_THE, true).c_str()); } else { simple_monster_message((monsters*)victim, (" blocks " + mon.name(DESC_NOCAP_THE, true) + ".").c_str()); } victim->shield_block_succeeded(&mon); _iood_dissipate(mon); return (true); } if (victim->atype() == ACT_PLAYER) { mprf("Your %s reflects %s!", shield->name(DESC_PLAIN).c_str(), mon.name(DESC_NOCAP_THE, true).c_str()); ident_reflector(shield); } else if (you.see_cell(pos)) { if (victim->observable()) { mprf("%s reflects %s with %s %s!", victim->name(DESC_CAP_THE, true).c_str(), mon.name(DESC_NOCAP_THE, true).c_str(), mon.pronoun(PRONOUN_NOCAP_POSSESSIVE).c_str(), shield->name(DESC_PLAIN).c_str()); ident_reflector(shield); } else { mprf("%s bounces off thin air!", mon.name(DESC_CAP_THE, true).c_str()); } } victim->shield_block_succeeded(&mon); mon.props["iood_vx"] = vx = -vx; mon.props["iood_vy"] = vy = -vy; // Need to get out of the victim's square. // If you're next to the caster and both of you wear shields of // reflection, this can lead to a brief game of ping-pong, but // rapidly increasing shield penalties will make it short. goto reflected; } if (_iood_hit(mon, pos)) return (true); } if (!no_trail) { place_cloud(CLOUD_MAGIC_TRAIL, mon.pos(), 2 + random2(3), mon.kill_alignment(), KILL_MON_MISSILE); } if (!mon.move_to_pos(pos)) { _iood_dissipate(mon); return (true); } return (false); }