diff options
Diffstat (limited to 'crawl-ref/source/mon-project.cc')
-rw-r--r-- | crawl-ref/source/mon-project.cc | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/crawl-ref/source/mon-project.cc b/crawl-ref/source/mon-project.cc new file mode 100644 index 0000000000..b5806b7089 --- /dev/null +++ b/crawl-ref/source/mon-project.cc @@ -0,0 +1,330 @@ +/* + * File: mon-project.cc + * Summary: Slow projectiles, done as monsters. + * Written by: Adam Borowski + */ + +#include "AppHdr.h" + +#include "mon-project.h" + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <cmath> + +#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(); + mon.props["iood_x"] = (float)pos.x; + mon.props["iood_y"] = (float)pos.y; + mon.props["iood_vx"] = (float)(beam->target.x - pos.x); + mon.props["iood_vy"] = (float)(beam->target.y - pos.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 the " + + 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); +} |