/*
* 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();
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);
}