summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/mon-project.cc
diff options
context:
space:
mode:
Diffstat (limited to 'crawl-ref/source/mon-project.cc')
-rw-r--r--crawl-ref/source/mon-project.cc330
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);
+}