summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--crawl-ref/source/beam.cc34
-rw-r--r--crawl-ref/source/cloud.cc7
-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.h7
-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/itemprop.cc6
-rw-r--r--crawl-ref/source/itemprop.h1
-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.cc330
-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
-rw-r--r--crawl-ref/source/tutorial.cc1
33 files changed, 493 insertions, 21 deletions
diff --git a/crawl-ref/source/beam.cc b/crawl-ref/source/beam.cc
index 451cadd81d..e991e44b51 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)
@@ -3704,12 +3720,6 @@ bool bolt::is_reflectable(const item_def *it) const
return (it && is_shield(*it) && shield_reflects(*it));
}
-static void _ident_reflector(item_def *item)
-{
- if (!is_artefact(*item))
- set_ident_flags(*item, ISFLAG_KNOW_TYPE);
-}
-
// Reflect a beam back the direction it came. This is used
// by shields of reflection.
void bolt::reflect()
@@ -3836,7 +3846,7 @@ bool bolt::misses_player()
mprf( "Your %s reflects the %s!",
you.shield()->name(DESC_PLAIN).c_str(),
name.c_str() );
- _ident_reflector(you.shield());
+ ident_reflector(you.shield());
reflect();
}
else
@@ -4802,7 +4812,7 @@ bool bolt::attempt_block(monsters* mon)
name.c_str(),
mon->pronoun(PRONOUN_NOCAP_POSSESSIVE).c_str(),
shield->name(DESC_PLAIN).c_str());
- _ident_reflector(shield);
+ ident_reflector(shield);
}
else if (you.see_cell(pos()))
mprf("The %s bounces off of thin air!", name.c_str());
@@ -6103,6 +6113,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/cloud.cc b/crawl-ref/source/cloud.cc
index 58eb185c97..823164846d 100644
--- a/crawl-ref/source/cloud.cc
+++ b/crawl-ref/source/cloud.cc
@@ -886,6 +886,7 @@ bool is_harmless_cloud(cloud_type type)
case CLOUD_TLOC_ENERGY:
case CLOUD_MIST:
case CLOUD_RAIN:
+ case CLOUD_MAGIC_TRAIL:
case CLOUD_DEBUGGING:
return (true);
default:
@@ -960,6 +961,8 @@ std::string cloud_name(cloud_type type)
return "rain";
case CLOUD_MUTAGENIC:
return "mutagenic fog";
+ case CLOUD_MAGIC_TRAIL:
+ return "magical condensation";
default:
return "buggy goodness";
}
@@ -1096,6 +1099,10 @@ int get_cloud_colour(int cloudno)
which_colour = ETC_MUTAGENIC;
break;
+ case CLOUD_MAGIC_TRAIL:
+ which_colour = ETC_MAGIC;
+ break;
+
default:
which_colour = LIGHTGREY;
break;
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 7dfc0255a7..58487ff6a9 100644
--- a/crawl-ref/source/enum.h
+++ b/crawl-ref/source/enum.h
@@ -431,6 +431,7 @@ enum cloud_type
CLOUD_CHAOS,
CLOUD_RAIN,
CLOUD_MUTAGENIC,
+ CLOUD_MAGIC_TRAIL,
CLOUD_RANDOM = 98,
CLOUD_DEBUGGING = 99 // 99: used once as 'nonexistent cloud' {dlb}
};
@@ -1663,8 +1664,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
@@ -2921,6 +2922,7 @@ enum spell_type
SPELL_SUMMON_PLAYER_GHOST,
SPELL_PRIMAL_WAVE,
SPELL_CALL_TIDE,
+ SPELL_IOOD,
NUM_SPELLS
};
@@ -3169,6 +3171,7 @@ enum zap_type
ZAP_PORKALATOR,
ZAP_SLEEP,
ZAP_PRIMAL_WAVE,
+ 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 1a5885f909..55ae7ffc26 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/itemprop.cc b/crawl-ref/source/itemprop.cc
index 58c5b12c25..9856f7260d 100644
--- a/crawl-ref/source/itemprop.cc
+++ b/crawl-ref/source/itemprop.cc
@@ -2456,6 +2456,12 @@ bool shield_reflects(const item_def &shield)
return (get_armour_ego_type(shield) == SPARM_REFLECTION);
}
+void ident_reflector(item_def *item)
+{
+ if (!is_artefact(*item))
+ set_ident_flags(*item, ISFLAG_KNOW_TYPE);
+}
+
std::string item_base_name(const item_def &item)
{
switch (item.base_type)
diff --git a/crawl-ref/source/itemprop.h b/crawl-ref/source/itemprop.h
index 5604d352f5..d1231bc9f5 100644
--- a/crawl-ref/source/itemprop.h
+++ b/crawl-ref/source/itemprop.h
@@ -84,6 +84,7 @@ bool is_shield(const item_def &item);
bool is_shield_incompatible(const item_def &weapon,
const item_def *shield = NULL);
bool shield_reflects(const item_def &shield);
+void ident_reflector(item_def *item);
// Only works for armour/weapons/missiles
// weapon functions:
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 2984379968..092fb0aaa7 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"
@@ -1725,6 +1726,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 ad2e109a8d..b3c35c66f4 100644
--- a/crawl-ref/source/mon-data.h
+++ b/crawl-ref/source/mon-data.h
@@ -4320,6 +4320,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..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);
+}
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 b7e6c40e97..008ce07a2c 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));
@@ -2024,7 +2029,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 814e36ceed..573a7d0f27 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 95b7609e5a..cf18035dfb 100644
--- a/crawl-ref/source/tilepick.cc
+++ b/crawl-ref/source/tilepick.cc
@@ -914,6 +914,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:
@@ -3061,6 +3063,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;
diff --git a/crawl-ref/source/tutorial.cc b/crawl-ref/source/tutorial.cc
index e0e3f68dc2..9eb857d78a 100644
--- a/crawl-ref/source/tutorial.cc
+++ b/crawl-ref/source/tutorial.cc
@@ -4461,6 +4461,7 @@ static void _tutorial_describe_cloud(int x, int y)
case CLOUD_TLOC_ENERGY:
case CLOUD_PURPLE_SMOKE:
case CLOUD_MIST:
+ case CLOUD_MAGIC_TRAIL:
ostr << "harmless. ";
break;