summaryrefslogtreecommitdiffstats
path: root/crawl-ref
diff options
context:
space:
mode:
Diffstat (limited to 'crawl-ref')
-rw-r--r--crawl-ref/source/beam.cc24
-rw-r--r--crawl-ref/source/dat/database/monspell.txt8
-rw-r--r--crawl-ref/source/dat/descript/spells.txt6
-rw-r--r--crawl-ref/source/enum.h6
-rw-r--r--crawl-ref/source/fight.cc3
-rw-r--r--crawl-ref/source/ghost.cc6
-rw-r--r--crawl-ref/source/godabil.cc3
-rw-r--r--crawl-ref/source/main.cc2
-rw-r--r--crawl-ref/source/makefile.obj1
-rw-r--r--crawl-ref/source/mgen_data.h2
-rw-r--r--crawl-ref/source/mon-abil.cc1
-rw-r--r--crawl-ref/source/mon-act.cc9
-rw-r--r--crawl-ref/source/mon-behv.cc3
-rw-r--r--crawl-ref/source/mon-cast.cc9
-rw-r--r--crawl-ref/source/mon-data.h13
-rw-r--r--crawl-ref/source/mon-project.cc257
-rw-r--r--crawl-ref/source/mon-project.h15
-rw-r--r--crawl-ref/source/mon-stuff.cc7
-rw-r--r--crawl-ref/source/mon-util.cc8
-rw-r--r--crawl-ref/source/mon-util.h2
-rw-r--r--crawl-ref/source/religion.cc2
-rw-r--r--crawl-ref/source/rltiles/dc-mon.txt1
-rw-r--r--crawl-ref/source/rltiles/dc-mon/orb_of_destruction.pngbin0 -> 1012 bytes
-rw-r--r--crawl-ref/source/rltiles/dc-spells.txt1
-rw-r--r--crawl-ref/source/rltiles/spells/conjuration/orb_of_destruction.pngbin0 -> 2481 bytes
-rw-r--r--crawl-ref/source/spl-book.cc2
-rw-r--r--crawl-ref/source/spl-cast.cc8
-rw-r--r--crawl-ref/source/spl-data.h13
-rw-r--r--crawl-ref/source/tilepick.cc3
29 files changed, 402 insertions, 13 deletions
diff --git a/crawl-ref/source/beam.cc b/crawl-ref/source/beam.cc
index 451cadd81d..41eddbe46b 100644
--- a/crawl-ref/source/beam.cc
+++ b/crawl-ref/source/beam.cc
@@ -1370,7 +1370,23 @@ const zap_info zap_data[] = {
false,
false,
0
- }
+ },
+
+ {
+ ZAP_IOOD,
+ "0",
+ 200,
+ NULL,
+ new tohit_calculator<AUTOMATIC_HIT>,
+ WHITE,
+ false,
+ BEAM_NUKE,
+ DCHAR_FIRED_ZAP,
+ true,
+ true,
+ false,
+ 0
+ },
};
static void _zappy(zap_type z_type, int power, bolt &pbolt)
@@ -6103,6 +6119,12 @@ bool bolt::nasty_to(const monsters *mon) const
if (flavour == BEAM_HOLY)
return (mon->res_holy_energy(agent()) <= 0);
+ // The orbs are made of pure disintegration energy. This also has the side
+ // effect of not stopping us from firing further orbs when the previous one
+ // is still flying.
+ if (flavour == BEAM_DISINTEGRATION || flavour == BEAM_NUKE)
+ return (mon->type != MONS_ORB_OF_DESTRUCTION);
+
// Take care of other non-enchantments.
if (!is_enchantment())
return (true);
diff --git a/crawl-ref/source/dat/database/monspell.txt b/crawl-ref/source/dat/database/monspell.txt
index c1556fcc50..0e3660d2db 100644
--- a/crawl-ref/source/dat/database/monspell.txt
+++ b/crawl-ref/source/dat/database/monspell.txt
@@ -116,6 +116,14 @@ Symbol of Torment cast
unseen Symbol of Torment cast
@The_something@ calls on the powers of Hell!
+%%%%
+Orb of Destruction cast
+
+@The_monster@ conjures a glowing orb.
+
+@The_monster@ conjures an orb of pure magic.
+
+@The_monster@ launches a ball of destructive magic.
#####################################################
# Individual innate spells (breathing and spitting).
#####################################################
diff --git a/crawl-ref/source/dat/descript/spells.txt b/crawl-ref/source/dat/descript/spells.txt
index 8e58527c87..1543714508 100644
--- a/crawl-ref/source/dat/descript/spells.txt
+++ b/crawl-ref/source/dat/descript/spells.txt
@@ -378,6 +378,12 @@ Olgreb's Toxic Radiance
This spell bathes the caster's surroundings in poisonous green light.
%%%%
+Orb of Destruction
+
+This spell conjures an orb made of pure destructive magic. Compared to most other projectiles, these orbs travel at a relatively slow pace. The orbs home onto their targets, yet because of their huge inertia, especially agile opponents may be able to outmaneuver them.
+
+It is said that most copies of this spell suffer from a scribal error that makes the orbs unstable beyond a short range. Certain individuals may have access to spellbooks without this flaw, so be ware!
+%%%%
Ozocubu's Armour
This spell envelops the caster's body in a protective layer of ice, the power of which depends on his or her skill with ice magic. The caster and the caster's equipment are protected from the cold, but the spell will not function for casters already wearing heavy armour. The effects of this spell are boosted if the caster is in ice form.
diff --git a/crawl-ref/source/enum.h b/crawl-ref/source/enum.h
index 27560aa979..5ea569e163 100644
--- a/crawl-ref/source/enum.h
+++ b/crawl-ref/source/enum.h
@@ -1674,8 +1674,8 @@ enum monster_type // (int) menv[].type
MONS_KOBOLD_DEMONOLOGIST,
MONS_ORC_WIZARD,
MONS_ORC_KNIGHT, // 55
- //MONS_WORM_TAIL = 56, // deprecated and now officially removed {dlb}
- MONS_WYVERN = 57, // 57
+ MONS_ORB_OF_DESTRUCTION, // a projectile, not a real mon
+ MONS_WYVERN,
MONS_BIG_KOBOLD,
MONS_GIANT_EYEBALL,
MONS_WIGHT, // 60
@@ -2932,6 +2932,7 @@ enum spell_type
SPELL_SUMMON_PLAYER_GHOST,
SPELL_PRIMAL_WAVE,
SPELL_CALL_TIDE,
+ SPELL_IOOD,
NUM_SPELLS
};
@@ -3180,6 +3181,7 @@ enum zap_type
ZAP_SLIME,
ZAP_PORKALATOR,
ZAP_SLEEP,
+ ZAP_IOOD,
NUM_ZAPS
};
diff --git a/crawl-ref/source/fight.cc b/crawl-ref/source/fight.cc
index 0df1de443c..d6ae6ad37e 100644
--- a/crawl-ref/source/fight.cc
+++ b/crawl-ref/source/fight.cc
@@ -4301,7 +4301,8 @@ bool melee_attack::mons_attack_mons()
bool melee_attack::mons_self_destructs()
{
if (attacker->id() == MONS_GIANT_SPORE
- || attacker->id() == MONS_BALL_LIGHTNING)
+ || attacker->id() == MONS_BALL_LIGHTNING
+ || attacker->id() == MONS_ORB_OF_DESTRUCTION)
{
attacker_as_monster()->hit_points = -1;
// Do the explosion right now.
diff --git a/crawl-ref/source/ghost.cc b/crawl-ref/source/ghost.cc
index e20e7e2317..69014f76d6 100644
--- a/crawl-ref/source/ghost.cc
+++ b/crawl-ref/source/ghost.cc
@@ -39,7 +39,6 @@ std::vector<ghost_demon> ghosts;
// Order for looking for conjurations for the 1st & 2nd spell slots,
// when finding spells to be remembered by a player's ghost.
static spell_type search_order_conj[] = {
-// 0
SPELL_LEHUDIBS_CRYSTAL_SPEAR,
SPELL_FIRE_STORM,
SPELL_ICE_STORM,
@@ -50,8 +49,8 @@ static spell_type search_order_conj[] = {
SPELL_LIGHTNING_BOLT,
SPELL_AIRSTRIKE,
SPELL_STICKY_FLAME,
-// 10
SPELL_ISKENDERUNS_MYSTIC_BLAST,
+ SPELL_IOOD,
SPELL_BOLT_OF_MAGMA,
SPELL_THROW_ICICLE,
SPELL_BOLT_OF_FIRE,
@@ -61,7 +60,6 @@ static spell_type search_order_conj[] = {
SPELL_VENOM_BOLT,
SPELL_IRON_SHOT,
SPELL_STONE_ARROW,
-// 20
SPELL_THROW_FLAME,
SPELL_THROW_FROST,
SPELL_PAIN,
@@ -285,6 +283,8 @@ void ghost_demon::init_random_demon()
spells[1] = SPELL_ISKENDERUNS_MYSTIC_BLAST;
if (one_chance_in(25))
spells[1] = SPELL_HELLFIRE;
+ if (one_chance_in(25))
+ spells[1] = SPELL_IOOD;
if (one_chance_in(25))
spells[2] = SPELL_SMITING;
diff --git a/crawl-ref/source/godabil.cc b/crawl-ref/source/godabil.cc
index 8888f868ab..68acb0790e 100644
--- a/crawl-ref/source/godabil.cc
+++ b/crawl-ref/source/godabil.cc
@@ -1256,7 +1256,8 @@ bool ponderousify_armour()
static int _slouch_monsters(coord_def where, int pow, int, actor* agent)
{
monsters* mon = monster_at(where);
- if (mon == NULL || mons_is_stationary(mon) || mon->cannot_move())
+ if (mon == NULL || mons_is_stationary(mon) || mon->cannot_move()
+ || mons_is_projectile(mon->type))
return (0);
int dmg = (mon->speed - 1000/player_movement_speed()/player_speed());
diff --git a/crawl-ref/source/main.cc b/crawl-ref/source/main.cc
index 1ad6e1af6c..8b675c2770 100644
--- a/crawl-ref/source/main.cc
+++ b/crawl-ref/source/main.cc
@@ -4631,7 +4631,7 @@ static void _compile_time_asserts()
COMPILE_CHECK(SP_VAMPIRE == 30 , c3);
COMPILE_CHECK(SPELL_DEBUGGING_RAY == 103 , c4);
COMPILE_CHECK(SPELL_RETURNING_AMMUNITION == 162 , c5);
- COMPILE_CHECK(NUM_SPELLS == 217 , c6);
+ COMPILE_CHECK(NUM_SPELLS == 218 , c6);
//jmf: NEW ASSERTS: we ought to do a *lot* of these
COMPILE_CHECK(NUM_SPECIES < SP_UNKNOWN , c7);
diff --git a/crawl-ref/source/makefile.obj b/crawl-ref/source/makefile.obj
index 1b1d038f31..a6eb79d4c3 100644
--- a/crawl-ref/source/makefile.obj
+++ b/crawl-ref/source/makefile.obj
@@ -115,6 +115,7 @@ mon-movetarget.o \
mon-pathfind.o \
mon-pick.o \
mon-place.o \
+mon-project.o \
mon-util.o \
mon-speak.o \
mon-stuff.o \
diff --git a/crawl-ref/source/mgen_data.h b/crawl-ref/source/mgen_data.h
index 6934100ede..d2794796ec 100644
--- a/crawl-ref/source/mgen_data.h
+++ b/crawl-ref/source/mgen_data.h
@@ -132,7 +132,7 @@ struct mgen_data
non_actor_summoner(nas), props()
{
ASSERT(summon_type == 0 || (abj >= 1 && abj <= 6)
- || mt == MONS_BALL_LIGHTNING);
+ || mt == MONS_BALL_LIGHTNING || mt == MONS_ORB_OF_DESTRUCTION);
}
bool permit_bands() const { return (flags & MG_PERMIT_BANDS); }
diff --git a/crawl-ref/source/mon-abil.cc b/crawl-ref/source/mon-abil.cc
index 1c98be0c34..0927248da1 100644
--- a/crawl-ref/source/mon-abil.cc
+++ b/crawl-ref/source/mon-abil.cc
@@ -484,6 +484,7 @@ static bool _siren_movement_effect(const monsters *monster)
coord_def swapdest;
if (mon->wont_attack()
&& !mons_is_stationary(mon)
+ && !mons_is_projectile(mon->type)
&& !mon->cannot_act()
&& !mon->asleep()
&& swap_check(mon, swapdest, true))
diff --git a/crawl-ref/source/mon-act.cc b/crawl-ref/source/mon-act.cc
index 067d59d3a6..8112618b0f 100644
--- a/crawl-ref/source/mon-act.cc
+++ b/crawl-ref/source/mon-act.cc
@@ -33,6 +33,7 @@
#include "mon-cast.h"
#include "mon-iter.h"
#include "mon-place.h"
+#include "mon-project.h"
#include "mgen_data.h"
#include "coord.h"
#include "mon-stuff.h"
@@ -1730,6 +1731,14 @@ static void _handle_monster_move(monsters *monster)
}
old_energy = monster->speed_increment;
+ if (mons_is_projectile(monster->type))
+ {
+ if (iood_act(*monster))
+ return;
+ monster->lose_energy(EUT_MOVE);
+ continue;
+ }
+
monster->shield_blocks = 0;
cloud_type cl_type;
diff --git a/crawl-ref/source/mon-behv.cc b/crawl-ref/source/mon-behv.cc
index aea54df3e6..5120688038 100644
--- a/crawl-ref/source/mon-behv.cc
+++ b/crawl-ref/source/mon-behv.cc
@@ -628,6 +628,7 @@ static bool _mons_check_foe(monsters *mon, const coord_def& p,
{
if (foe != mon
&& mon->can_see(foe)
+ && !mons_is_projectile(foe->type)
&& (friendly || !is_sanctuary(p))
&& (foe->friendly() != friendly
|| (neutral && !foe->neutral())))
@@ -680,6 +681,8 @@ void behaviour_event(monsters *mon, mon_event_type event, int src,
ASSERT(src >= 0 && src <= MHITYOU);
ASSERT(!crawl_state.arena || src != MHITYOU);
ASSERT(in_bounds(src_pos) || src_pos.origin());
+ if (mons_is_projectile(mon->type))
+ return; // projectiles have no AI
const beh_type old_behaviour = mon->behaviour;
diff --git a/crawl-ref/source/mon-cast.cc b/crawl-ref/source/mon-cast.cc
index e1bca14d78..f844fe7218 100644
--- a/crawl-ref/source/mon-cast.cc
+++ b/crawl-ref/source/mon-cast.cc
@@ -22,6 +22,7 @@
#include "mon-behv.h"
#include "mon-iter.h"
#include "mon-place.h"
+#include "mon-project.h"
#include "terrain.h"
#include "tutorial.h"
#include "mislead.h"
@@ -688,6 +689,11 @@ bolt mons_spells( monsters *mons, spell_type spell_cast, int power,
beam.is_beam = true;
break;
+ case SPELL_IOOD: // tracer only
+ beam.flavour = BEAM_NUKE;
+ beam.is_beam = true;
+ break;
+
default:
if (check_validity)
{
@@ -2331,6 +2337,9 @@ void mons_cast(monsters *monster, bolt &pbolt, spell_type spell_cast,
monster->foe, 0, god));
}
return;
+ case SPELL_IOOD:
+ cast_iood(monster, 6 * monster->hit_dice, &pbolt);
+ return;
}
// If a monster just came into view and immediately cast a spell,
diff --git a/crawl-ref/source/mon-data.h b/crawl-ref/source/mon-data.h
index ac0bb050f1..7523989417 100644
--- a/crawl-ref/source/mon-data.h
+++ b/crawl-ref/source/mon-data.h
@@ -4318,6 +4318,19 @@ static monsterentry mondata[] = {
MONUSE_NOTHING, MONEAT_NOTHING, SIZE_LITTLE
},
+{
+ MONS_ORB_OF_DESTRUCTION, '*', WHITE, "orb of destruction",
+ M_INSUBSTANTIAL | M_GLOWS | M_NO_EXP_GAIN,
+ mrd(MR_RES_FIRE | MR_RES_HELLFIRE | MR_RES_POISON | MR_RES_COLD, 3)
+ | MR_RES_ELEC | MR_RES_STICKY_FLAME | MR_RES_ACID,
+ 0, 0, MONS_ORB_OF_DESTRUCTION, MONS_ORB_OF_DESTRUCTION, MH_NONLIVING, MAG_IMMUNE,
+ { AT_NO_ATK, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK },
+ { 5, 0, 0, 50 },
+ 0, 10, MST_NO_SPELLS, CE_NOCORPSE, Z_NOZOMBIE, S_SILENT,
+ I_PLANT, HT_LAND, FL_LEVITATE, 30, DEFAULT_ENERGY,
+ MONUSE_NOTHING, MONEAT_NOTHING, SIZE_LITTLE
+},
+
// other symbols
{
MONS_VAPOUR, '#', LIGHTGREY, "vapour",
diff --git a/crawl-ref/source/mon-project.cc b/crawl-ref/source/mon-project.cc
new file mode 100644
index 0000000000..d1d3ba4f3b
--- /dev/null
+++ b/crawl-ref/source/mon-project.cc
@@ -0,0 +1,257 @@
+/*
+ * 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 "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)->wont_attack() ? 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)->wont_attack() ? 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);
+}
+
+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;
+ beam.damage = dice_def(6, mon.props["iood_pow"].get_short()/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;
+ }
+
+ 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 (_iood_hit(mon, pos))
+ return (true);
+ }
+
+ if (!no_trail)
+ {
+ place_cloud(CLOUD_GREY_SMOKE, mon.pos(),
+ 2 + random2(3), mon.kill_alignment(),
+ KILL_MON_MISSILE);
+ }
+
+ if (!mon.move_to_pos(pos))
+ {
+ _iood_dissipate(mon);
+ return (true);
+ }
+
+ return (false);
+}
diff --git a/crawl-ref/source/mon-project.h b/crawl-ref/source/mon-project.h
new file mode 100644
index 0000000000..f10b543097
--- /dev/null
+++ b/crawl-ref/source/mon-project.h
@@ -0,0 +1,15 @@
+/*
+ * File: mon-project.cc
+ * Summary: Slow projectiles, done as monsters.
+ * Written by: Adam Borowski
+ */
+
+#ifndef MON_PROJECT_H
+#define MON_PROJECT_H
+
+#include "beam.h"
+
+bool cast_iood(actor *caster, int pow, bolt *beam);
+bool iood_act(monsters &mon, bool no_trail = false);
+
+#endif
diff --git a/crawl-ref/source/mon-stuff.cc b/crawl-ref/source/mon-stuff.cc
index 95a418b0a7..12f8c137ad 100644
--- a/crawl-ref/source/mon-stuff.cc
+++ b/crawl-ref/source/mon-stuff.cc
@@ -2801,6 +2801,13 @@ bool swap_check(monsters *monster, coord_def &loc, bool quiet)
return (false);
}
+ if (mons_is_projectile(monster->type))
+ {
+ if (!quiet)
+ mpr("It's unwise to walk into this.");
+ return (false);
+ }
+
if (monster->caught())
{
if (!quiet)
diff --git a/crawl-ref/source/mon-util.cc b/crawl-ref/source/mon-util.cc
index fa2ce2be15..82dce8d5a0 100644
--- a/crawl-ref/source/mon-util.cc
+++ b/crawl-ref/source/mon-util.cc
@@ -485,6 +485,11 @@ bool mons_is_fast(const monsters *mon)
return (mon->speed > pspeed);
}
+bool mons_is_projectile(int mc)
+{
+ return (mc == MONS_ORB_OF_DESTRUCTION);
+}
+
bool mons_is_insubstantial(int mc)
{
return (mons_class_flag(mc, M_INSUBSTANTIAL));
@@ -2016,7 +2021,8 @@ bool mons_wields_two_weapons(const monsters *mon)
bool mons_self_destructs(const monsters *m)
{
- return (m->type == MONS_GIANT_SPORE || m->type == MONS_BALL_LIGHTNING);
+ return (m->type == MONS_GIANT_SPORE || m->type == MONS_BALL_LIGHTNING
+ || m->type == MONS_ORB_OF_DESTRUCTION);
}
int mons_base_damage_brand(const monsters *m)
diff --git a/crawl-ref/source/mon-util.h b/crawl-ref/source/mon-util.h
index 95a0ca5d55..931c817748 100644
--- a/crawl-ref/source/mon-util.h
+++ b/crawl-ref/source/mon-util.h
@@ -658,7 +658,7 @@ bool mons_class_is_stationary(int mc);
bool mons_is_stationary(const monsters *mon);
bool mons_is_fast( const monsters *mon );
-
+bool mons_is_projectile(int mc);
bool mons_is_insubstantial(int mc);
bool mons_has_blood(int mc);
diff --git a/crawl-ref/source/religion.cc b/crawl-ref/source/religion.cc
index 8a20c52062..99c2375cc0 100644
--- a/crawl-ref/source/religion.cc
+++ b/crawl-ref/source/religion.cc
@@ -4479,6 +4479,8 @@ bool god_hates_attacking_friend(god_type god, const actor *fr)
bool god_hates_attacking_friend(god_type god, int species)
{
+ if (mons_is_projectile(species))
+ return (false);
switch (god)
{
case GOD_ZIN:
diff --git a/crawl-ref/source/rltiles/dc-mon.txt b/crawl-ref/source/rltiles/dc-mon.txt
index e539d95c86..06220dd2f5 100644
--- a/crawl-ref/source/rltiles/dc-mon.txt
+++ b/crawl-ref/source/rltiles/dc-mon.txt
@@ -160,6 +160,7 @@ death_cob MONS_DEATH_COB /*'%'*/
ball_lightning MONS_BALL_LIGHTNING /*'*'*/
%rim 0
orb_of_fire MONS_ORB_OF_FIRE /*'*'*/
+orb_of_destruction MONS_ORB_OF_DESTRUCTION /*'*'*/
%rim 1
### Demonic monsters
diff --git a/crawl-ref/source/rltiles/dc-mon/orb_of_destruction.png b/crawl-ref/source/rltiles/dc-mon/orb_of_destruction.png
new file mode 100644
index 0000000000..b328f0a7ca
--- /dev/null
+++ b/crawl-ref/source/rltiles/dc-mon/orb_of_destruction.png
Binary files differ
diff --git a/crawl-ref/source/rltiles/dc-spells.txt b/crawl-ref/source/rltiles/dc-spells.txt
index 8dbf264f4e..6c88296a71 100644
--- a/crawl-ref/source/rltiles/dc-spells.txt
+++ b/crawl-ref/source/rltiles/dc-spells.txt
@@ -30,6 +30,7 @@ swiftness SWIFTNESS
%sdir spells/conjuration
iskenderuns_mystic_blast ISKENDERUNS_MYSTIC_BLAST
magic_dart MAGIC_DART
+orb_of_destruction IOOD
%sdir spells/divination
detect_creatures DETECT_CREATURES
diff --git a/crawl-ref/source/rltiles/spells/conjuration/orb_of_destruction.png b/crawl-ref/source/rltiles/spells/conjuration/orb_of_destruction.png
new file mode 100644
index 0000000000..574d0194ad
--- /dev/null
+++ b/crawl-ref/source/rltiles/spells/conjuration/orb_of_destruction.png
Binary files differ
diff --git a/crawl-ref/source/spl-book.cc b/crawl-ref/source/spl-book.cc
index 6630e0c0db..8c6f77f0ff 100644
--- a/crawl-ref/source/spl-book.cc
+++ b/crawl-ref/source/spl-book.cc
@@ -518,12 +518,12 @@ static spell_type spellbook_template_array[][SPELLBOOK_SIZE] =
// Book of Annihilations - Vehumet special
{SPELL_ISKENDERUNS_MYSTIC_BLAST,
SPELL_POISON_ARROW,
+ SPELL_IOOD,
SPELL_CHAIN_LIGHTNING,
SPELL_LEHUDIBS_CRYSTAL_SPEAR,
SPELL_ICE_STORM,
SPELL_FIRE_STORM,
SPELL_NO_SPELL,
- SPELL_NO_SPELL,
},
// Book of Demonology - Vehumet special
diff --git a/crawl-ref/source/spl-cast.cc b/crawl-ref/source/spl-cast.cc
index acaaa7ebca..10c4e7cea9 100644
--- a/crawl-ref/source/spl-cast.cc
+++ b/crawl-ref/source/spl-cast.cc
@@ -37,6 +37,7 @@
#include "message.h"
#include "mon-cast.h"
#include "mon-place.h"
+#include "mon-project.h"
#include "mon-stuff.h"
#include "mutation.h"
#include "ouch.h"
@@ -1518,6 +1519,13 @@ spret_type your_spells(spell_type spell, int powc, bool allow_fail)
return (SPRET_ABORT);
break;
+ case SPELL_IOOD:
+ if (!player_tracer(ZAP_IOOD, powc, beam))
+ return (SPRET_ABORT);
+ if (!cast_iood(&you, powc, &beam))
+ return (SPRET_ABORT);
+ break;
+
// Clouds and explosions.
case SPELL_MEPHITIC_CLOUD:
if (!stinking_cloud(powc, beam))
diff --git a/crawl-ref/source/spl-data.h b/crawl-ref/source/spl-data.h
index 75f92737b3..9de226af2e 100644
--- a/crawl-ref/source/spl-data.h
+++ b/crawl-ref/source/spl-data.h
@@ -2668,6 +2668,19 @@
},
{
+ SPELL_IOOD, "Orb of Destruction",
+ SPTYP_CONJURATION,
+ SPFLAG_DIR_OR_TARGET | SPFLAG_NOT_SELF,
+ 7,
+ 200,
+ 9, 9,
+ 0,
+ NULL,
+ true,
+ false
+},
+
+{
SPELL_NO_SPELL, "nonexistent spell",
0,
SPFLAG_TESTING,
diff --git a/crawl-ref/source/tilepick.cc b/crawl-ref/source/tilepick.cc
index 8881bc867b..4d40a20c0c 100644
--- a/crawl-ref/source/tilepick.cc
+++ b/crawl-ref/source/tilepick.cc
@@ -912,6 +912,8 @@ int tileidx_monster_base(const monsters *mon, bool detected)
return TILEP_MONS_BALL_LIGHTNING;
case MONS_ORB_OF_FIRE:
return TILEP_MONS_ORB_OF_FIRE;
+ case MONS_ORB_OF_DESTRUCTION:
+ return TILEP_MONS_ORB_OF_DESTRUCTION;
// other symbols
case MONS_VAPOUR:
@@ -3048,6 +3050,7 @@ int tileidx_spell(spell_type spell)
// pure Conjuration
case SPELL_MAGIC_DART: return TILEG_MAGIC_DART;
case SPELL_ISKENDERUNS_MYSTIC_BLAST: return TILEG_ISKENDERUNS_MYSTIC_BLAST;
+ case SPELL_IOOD: return TILEG_IOOD;
// Divination (soon to be obsolete, or moved to abilities)
case SPELL_DETECT_SECRET_DOORS: return TILEG_DETECT_SECRET_DOORS;