Attached Files:
|
op_crusher_gca92d1a38.patch [^] (20,726 bytes) 2014-01-25 10:18 [Show Content] [Hide Content]From 59857d5f0757ee96a36045a7a32431530543663d Mon Sep 17 00:00:00 2001
From: gammafunk <gammafunk@gmail.com>
Date: Thu, 23 Jan 2014 15:45:00 -0600
Subject: [PATCH] Octopode Crusher: An octopode warrior that likes throwing
weight around.
This monster is meant to be roughly on par with the shoals:5 merfolk, so it has
the same HD/HP profile as an impaler. In addition to the spell Corona, it has
one new spell and one new ability.
New spell: (tentatively named) Water Harpoon. Throws a conjured harpoon of
water projectile at the victim dealing the same damage as stone arrow and then
yanking the victim to a random square adjacent to the caster. The landing site
is always habitable for the victim, who must not currently be adjacent to the
caster nor be constricted.
New ability: Throw. A 2 in 5 chance to hurl a victim that's currently being
constricted by the crusher for undodgable, AC-checking damage, preferably "into"
(i.e. adjacent to) a solid feature for 50% increased damage. The base throw
damage is currently HD * 3. The landing site is always habitable for the
victim and the feature site must be visible to the monster if the damage
increase is to apply. If no landing site adjacent to a solid feature can be
found, any habitable landing site in LOS is used, but then only the base damage
applies.
Water harpoon and throw could also be given to more powerful monsters, as the
crusher itself is probably too weak for Depths. The water harpoon spell could
be made into a nice player spell; the throwing ability is not quite so simple to
give to players due to targeting issues, but if we chose a random landing site
and made e.g. size restrictions, it might be usable as either an ability or
spell.
---
crawl-ref/source/dat/descript/monsters.txt | 7 +-
crawl-ref/source/dat/descript/spells.txt | 6 ++
crawl-ref/source/delay.cc | 4 +
crawl-ref/source/enum.h | 6 +-
crawl-ref/source/hiscores.cc | 13 +++-
crawl-ref/source/mon-abil.cc | 113 ++++++++++++++++++++++++++++-
crawl-ref/source/mon-cast.cc | 35 +++++++++
crawl-ref/source/mon-data.h | 13 ++++
crawl-ref/source/mon-spll.h | 11 +++
crawl-ref/source/ouch.h | 2 +-
crawl-ref/source/spl-damage.cc | 59 +++++++++++++++
crawl-ref/source/spl-damage.h | 2 +
crawl-ref/source/spl-data.h | 13 ++++
crawl-ref/source/spl-zap.cc | 2 +
crawl-ref/source/zap-data.h | 16 ++++
15 files changed, 296 insertions(+), 6 deletions(-)
diff --git a/crawl-ref/source/dat/descript/monsters.txt b/crawl-ref/source/dat/descript/monsters.txt
index 8f4078e..f231768 100644
--- a/crawl-ref/source/dat/descript/monsters.txt
+++ b/crawl-ref/source/dat/descript/monsters.txt
@@ -1913,7 +1913,12 @@ A more intelligent cousin of the mighty kraken, capable of using tools and
spells. They are fully amphibious, but most of their species choose to stay
beneath the waves and serve their King.
%%%%
-ogre
+octopode crusher
+
+This octopode warrior is much stronger than its brethren and uses combat magic to
+augment its fighting abilities. Its tentacles move with uncanny grace and look
+more than capable of sending you hurtling into the air.
+%%%% ogre
A large, ugly, and fat creature, distantly related to orcs and goblins. It is
tremendously strong.
diff --git a/crawl-ref/source/dat/descript/spells.txt b/crawl-ref/source/dat/descript/spells.txt
index b386c8f..f934512 100644
--- a/crawl-ref/source/dat/descript/spells.txt
+++ b/crawl-ref/source/dat/descript/spells.txt
@@ -1358,6 +1358,12 @@ This spell causes water around or under the target to violently swirl, dealing
damage. It cannot be cast at those not already in water, although floating
above the surface doesn't make you safe.
%%%%
+Water Harpoon spell
+
+This spell conjures a harpoon made of water and ice and hurtles it at the
+victim. Anyone struck by this harpoon will be immediately pulled over to the
+caster.
+%%%%
Wind Blast spell
This spell causes a strong gust of wind to push away creatures and clouds in a
diff --git a/crawl-ref/source/delay.cc b/crawl-ref/source/delay.cc
index c8dfb73..4291abb 100644
--- a/crawl-ref/source/delay.cc
+++ b/crawl-ref/source/delay.cc
@@ -1579,6 +1579,10 @@ static inline bool _monster_warning(activity_interrupt_type ai,
}
else if (at.context == SC_NONSWIMMER_SURFACES_FROM_DEEP)
text += " emerges from the water.";
+ else if (at.context == SC_PULLED_IN)
+ text += " is pulled into view!";
+ else if (at.context == SC_THROWN_IN)
+ text += " is thrown into view!";
else
text += " comes into view.";
diff --git a/crawl-ref/source/enum.h b/crawl-ref/source/enum.h
index 953997e..7ff502e 100644
--- a/crawl-ref/source/enum.h
+++ b/crawl-ref/source/enum.h
@@ -2838,7 +2838,7 @@ enum monster_type // menv[].type
#endif
MONS_FORMICID_VENOM_MAGE,
MONS_RAIJU,
-
+ MONS_OCTOPODE_CRUSHER,
#if TAG_MAJOR_VERSION == 34
MONS_DRAGON, // genus
MONS_SNAKE, // genus
@@ -3723,6 +3723,7 @@ enum spell_type
SPELL_SHAFT_SELF,
#endif
SPELL_BLINKBOLT,
+ SPELL_WATER_HARPOON,
NUM_SPELLS
};
@@ -3884,6 +3885,7 @@ enum zap_type
ZAP_SEARING_RAY_I,
ZAP_SEARING_RAY_II,
ZAP_SEARING_RAY_III,
+ ZAP_WATER_HARPOON,
NUM_ZAPS
};
@@ -3982,6 +3984,8 @@ enum seen_context_type
SC_DOOR, // they opened a door
SC_GATE, // ... or a big door
SC_LEAP_IN, // leaps into view
+ SC_PULLED_IN, // pulled into view from e.g. water harpoon
+ SC_THROWN_IN, // thrown into view from the monster throw ability
};
enum los_type
diff --git a/crawl-ref/source/hiscores.cc b/crawl-ref/source/hiscores.cc
index 80d9436..cb37b49 100644
--- a/crawl-ref/source/hiscores.cc
+++ b/crawl-ref/source/hiscores.cc
@@ -621,7 +621,7 @@ static const char *kill_method_names[] =
"falling_down_stairs", "acid", "curare",
"beogh_smiting", "divine_wrath", "bounce", "reflect", "self_aimed",
"falling_through_gate", "disintegration", "headbutt", "rolling",
- "mirror_damage", "spines", "frailty",
+ "mirror_damage", "spines", "frailty", "being_thrown",
};
static const char *_kill_method_name(kill_method_type kmt)
@@ -1177,7 +1177,8 @@ void scorefile_entry::init_death_cause(int dam, int dsrc,
|| death_type == KILLED_BY_REFLECTION
|| death_type == KILLED_BY_ROLLING
|| death_type == KILLED_BY_SPINES
- || death_type == KILLED_BY_WATER)
+ || death_type == KILLED_BY_WATER
+ || death_type == KILLED_BY_BEING_THROWN)
&& !invalid_monster_index(death_source)
&& menv[death_source].type != MONS_NO_MONSTER)
{
@@ -2435,6 +2436,14 @@ string scorefile_entry::death_description(death_desc_verbosity verbosity) const
desc += terse ? "frailty" : "Became unviable by " + auxkilldata;
break;
+ case KILLED_BY_BEING_THROWN:
+ if (terse)
+ desc += apostrophise(death_source_desc()) + " throw";
+ else
+ desc += "Thrown by " + death_source_desc();
+ needs_damage = true;
+ break;
+
default:
desc += terse? "program bug" : "Nibbled to death by software bugs";
break;
diff --git a/crawl-ref/source/mon-abil.cc b/crawl-ref/source/mon-abil.cc
index 21748c1..9968f5a 100644
--- a/crawl-ref/source/mon-abil.cc
+++ b/crawl-ref/source/mon-abil.cc
@@ -68,6 +68,7 @@
const int MAX_KRAKEN_TENTACLE_DIST = 12;
static bool _slime_split_merge(monster* thing);
+static bool _do_throw(actor *thrower, actor *victim, int radius, int pow);
template<typename valid_T, typename connect_T>
static void _search_dungeon(const coord_def & start,
valid_T & valid_target,
@@ -4330,7 +4331,17 @@ bool mon_special_ability(monster* mons, bolt & beem)
}
}
break;
-
+ case MONS_OCTOPODE_CRUSHER:
+ if (mons->is_constricting() && x_chance_in_y(2, 5))
+ {
+ actor* const foe = mons->get_foe();
+ // Can't throw something if we're being constricted by it.
+ if ((mons->is_constricted()
+ && actor_by_mid(mons->constricted_by) == foe))
+ break;
+ used = _do_throw(mons, foe, LOS_RADIUS, mons->hit_dice * 3);
+ }
+ break;
default:
break;
}
@@ -4943,3 +4954,103 @@ void waterport_touch(monster* nymph, actor* target)
}
}
}
+
+static bool _do_throw(actor *thrower, actor *victim, int radius, int pow)
+{
+
+ vector<coord_def> floor_sites;
+ vector<coord_def> feat_floor_sites;
+ vector<coord_def> feat_sites;
+
+ ASSERT(radius <= LOS_RADIUS);
+ for (distance_iterator di(thrower->pos(), false, true, radius); di; ++di)
+ {
+ if (adjacent(thrower->pos(), *di))
+ continue;
+ if (victim->is_habitable(*di)
+ && cell_see_cell(thrower->pos(), *di, LOS_NO_TRANS)
+ && cell_see_cell(victim->pos(), *di, LOS_NO_TRANS))
+ floor_sites.push_back(*di);
+ else
+ continue;
+ // See if it's site next to a visible solid feature, which we prefer.
+ for (adjacent_iterator ai(*di); ai; ai++)
+ {
+ if (cell_is_solid(*ai)
+ && grd(*ai) != DNGN_OPEN_SEA
+ && grd(*ai) != DNGN_LAVA_SEA
+ && cell_see_cell(thrower->pos(), *ai, LOS_NO_TRANS))
+ {
+ feat_floor_sites.push_back(*di);
+ feat_sites.push_back(*ai);
+ break;
+ }
+ }
+ }
+ int floor_ind = 0;
+ bool have_feat = feat_floor_sites.size();
+ string feat_desc = "";
+ coord_def floor_pos, feat_pos;
+ if (have_feat)
+ {
+ floor_ind = random2(feat_floor_sites.size());
+ floor_pos = feat_floor_sites[floor_ind];
+ feat_pos = feat_sites[floor_ind];
+ if (victim->is_player()
+ || cell_see_cell(you.pos(), feat_pos, LOS_DEFAULT))
+ {
+ feat_desc = feature_description_at(feat_pos, false, DESC_THE, false);
+ feat_desc = make_stringf(" onto %s", feat_desc.c_str());
+ }
+ else
+ feat_desc = " onto something solid";
+ }
+ // Found an empty space, so we can still throw
+ else if (floor_sites.size())
+ {
+ floor_ind = random2(floor_sites.size());
+ floor_pos = floor_sites[floor_ind];
+ }
+ // Couldn't find a place to throw the victim
+ else
+ return false;
+
+ bool thrower_seen = you.can_see(thrower);
+ bool victim_was_seen = you.can_see(victim);
+ const string thrower_name = thrower->name(DESC_THE);
+
+ // Increase damage by 50% if we hit something hard.
+ int dam = random2(pow) * (have_feat ? 3 : 2);
+ dam = victim->apply_ac(dam / 2);
+ if (victim->is_player())
+ {
+ monster *mon = thrower->as_monster();
+ mprf("%s throws you%s!",
+ (thrower_seen ? thrower_name.c_str() : "Something"),
+ feat_desc.c_str());
+ move_player_to_grid(floor_pos, false, true);
+ ouch(dam, mon->mindex(), KILLED_BY_BEING_THROWN);
+ }
+ else
+ {
+ monster *mon = victim->as_monster();
+ const string victim_name = victim->name(DESC_THE);
+ coord_def old_pos = mon->pos();
+
+ if (!(mon->flags & MF_WAS_IN_VIEW))
+ mon->seen_context = SC_THROWN_IN;
+ mon->move_to_pos(floor_pos);
+ mon->apply_location_effects(old_pos);
+ mon->check_redraw(old_pos);
+ if (thrower_seen || victim_was_seen)
+ {
+ mprf("%s throws %s%s!",
+ (thrower_seen ? thrower_name.c_str() : "Something"),
+ (victim_was_seen ? victim_name.c_str() : "something"),
+ (you.can_see(mon) ? feat_desc.c_str() : "out of view"));
+ }
+ victim->hurt(thrower, dam, BEAM_NONE, true);
+ }
+ return true;
+
+}
diff --git a/crawl-ref/source/mon-cast.cc b/crawl-ref/source/mon-cast.cc
index d721de3..355b5ea 100644
--- a/crawl-ref/source/mon-cast.cc
+++ b/crawl-ref/source/mon-cast.cc
@@ -966,6 +966,14 @@ bolt mons_spell_beam(monster* mons, spell_type spell_cast, int power,
beam.is_beam = true;
break;
+ case SPELL_WATER_HARPOON:
+ beam.name = "harpoon of water";
+ beam.damage = dice_def(3, 5 + (power / 10));
+ beam.colour = LIGHTBLUE;
+ beam.flavour = BEAM_WATER;
+ beam.hit = 17 + power / 25;
+ break;
+
default:
if (check_validity)
{
@@ -2339,6 +2347,23 @@ static bool _should_tornado(monster* agent)
return mons_should_fire(tracer);
}
+static bool _should_water_harpoon(monster* agent)
+{
+ actor * const foe = agent->get_foe();
+
+ // Don't harpoon something already adjacent or something that's constricted.
+ // There may be other instances to consider (spider webs?)
+ if (adjacent(agent->pos(), foe->pos()) || foe->is_constricted())
+ return false;
+
+ for (adjacent_iterator ai(agent->pos()); ai; ++ai)
+ {
+ if (foe->is_habitable(*ai))
+ return true;
+ }
+ return false;
+}
+
//---------------------------------------------------------------
//
// handle_mon_spell
@@ -2874,6 +2899,11 @@ bool handle_mon_spell(monster* mons, bolt &beem)
if (!_should_tornado(mons))
return false;
}
+ else if (spell_cast == SPELL_WATER_HARPOON)
+ {
+ if (!_should_water_harpoon(mons))
+ return false;
+ }
if (mons->type == MONS_BALL_LIGHTNING)
mons->suicide();
@@ -5041,6 +5071,10 @@ void mons_cast(monster* mons, bolt &pbolt, spell_type spell_cast,
_do_high_level_summon(mons, monsterNearby, spell_cast,
_pick_vermin, one_chance_in(4) ? 3 : 2 , god);
return;
+
+ case SPELL_WATER_HARPOON:
+ cast_water_harpoon(pbolt.ench_power, pbolt, false);
+ return;
}
// If a monster just came into view and immediately cast a spell,
@@ -5545,3 +5579,4 @@ void mons_cast_noise(monster* mons, const bolt &pbolt,
mons_speaks_msg(mons, msg, chan);
}
}
+
diff --git a/crawl-ref/source/mon-data.h b/crawl-ref/source/mon-data.h
index 2ea68e5..fe286dc 100644
--- a/crawl-ref/source/mon-data.h
+++ b/crawl-ref/source/mon-data.h
@@ -4717,6 +4717,19 @@ static monsterentry mondata[] =
MONUSE_WEAPONS_ARMOUR, MONEAT_NOTHING, SIZE_MEDIUM
},
+{
+ MONS_OCTOPODE_CRUSHER, 'x', YELLOW, "octopode crusher",
+ M_NO_SKELETON | M_COLD_BLOOD | M_SPELLCASTER | M_ACTUAL_SPELLS | M_SPEAKS,
+ MR_NO_FLAGS,
+ 700, 13, MONS_OCTOPODE, MONS_OCTOPODE, MH_NATURAL, -3,
+ { {AT_TENTACLE_SLAP, AF_PLAIN, 35}, {AT_CONSTRICT, AF_CRUSH, 10},
+ AT_NO_ATK, AT_NO_ATK },
+ { 12, 5, 4, 0 },
+ 1, 18, MST_OCTOPODE_CRUSHER, CE_CLEAN, Z_SMALL, S_SHOUT,
+ I_NORMAL, HT_AMPHIBIOUS, FL_NONE, 10, DEFAULT_ENERGY,
+ MONUSE_WEAPONS_ARMOUR, MONEAT_NOTHING, SIZE_MEDIUM
+},
+
// lava monsters
{
MONS_LAVA_WORM, 'w', RED, "lava worm",
diff --git a/crawl-ref/source/mon-spll.h b/crawl-ref/source/mon-spll.h
index b58e454..7a0f71d 100644
--- a/crawl-ref/source/mon-spll.h
+++ b/crawl-ref/source/mon-spll.h
@@ -2300,4 +2300,15 @@
SPELL_TELEPORT_OTHER
}
},
+
+ { MST_OCTOPODE_CRUSHER,
+ {
+ SPELL_WATER_HARPOON,
+ SPELL_CORONA,
+ SPELL_NO_SPELL,
+ SPELL_WATER_HARPOON,
+ SPELL_NO_SPELL,
+ SPELL_NO_SPELL,
+ }
+ },
#endif
diff --git a/crawl-ref/source/ouch.h b/crawl-ref/source/ouch.h
index 91c5a45..4cbce5f 100644
--- a/crawl-ref/source/ouch.h
+++ b/crawl-ref/source/ouch.h
@@ -56,7 +56,7 @@ enum kill_method_type
KILLED_BY_MIRROR_DAMAGE,
KILLED_BY_SPINES,
KILLED_BY_FRAILTY,
-
+ KILLED_BY_BEING_THROWN,
NUM_KILLBY
};
diff --git a/crawl-ref/source/spl-damage.cc b/crawl-ref/source/spl-damage.cc
index 604b41d..d4abf9e 100644
--- a/crawl-ref/source/spl-damage.cc
+++ b/crawl-ref/source/spl-damage.cc
@@ -2329,6 +2329,65 @@ spret_type cast_sandblast(int pow, bolt &beam, bool fail)
return ret;
}
+spret_type cast_water_harpoon(int pow, bolt &beam, bool fail)
+{
+ coord_def target = beam.target;
+ coord_def source = beam.source;
+ zap_type zap = ZAP_WATER_HARPOON;
+ const spret_type ret = zapping(zap, pow, beam, true, NULL, fail);
+
+ if (ret != SPRET_SUCCESS)
+ return ret;
+
+ actor *caster = actor_at(source);
+ const string caster_name = caster->name(DESC_THE);
+ actor *victim = actor_at(target);
+
+ // Nothing can get pulled.
+ if (!victim)
+ return ret;
+
+ bool caster_seen = you.can_see(caster);
+ bool victim_was_seen = you.can_see(victim);
+ coord_def landing_pos;
+ vector<coord_def> landing_sites;
+ for (adjacent_iterator ai(source); ai; ai++)
+ {
+ if (victim->is_habitable(*ai))
+ landing_sites.push_back(*ai);
+ }
+ if (!landing_sites.size())
+ die("Buggy target for water harpoon");
+
+ landing_pos = landing_sites[random2(landing_sites.size())];
+ if (victim->is_player())
+ {
+ mprf("You are pulled towards %s!",
+ (caster_seen ? caster_name.c_str() : "something"));
+ move_player_to_grid(landing_pos, false, true);
+ }
+ else
+ {
+ monster *mon = victim->as_monster();
+ const string victim_name = victim->name(DESC_THE);
+
+ if (!(mon->flags & MF_WAS_IN_VIEW))
+ mon->seen_context = SC_PULLED_IN;
+ mon->move_to_pos(landing_pos);
+ mon->apply_location_effects(target);
+ mon->check_redraw(target);
+
+ if (caster_seen || victim_was_seen)
+ {
+ mprf("%s pulls %s %s!",
+ (caster_seen ? caster_name.c_str() : "Something"),
+ (victim_was_seen ? victim_name.c_str() : "something"),
+ (you.can_see(mon) ? "over" : "out of view"));
+ }
+ }
+ return ret;
+}
+
static bool _elec_not_immune(const actor *act)
{
return act->res_elec() < 3;
diff --git a/crawl-ref/source/spl-damage.h b/crawl-ref/source/spl-damage.h
index b70d699..384eb2b 100644
--- a/crawl-ref/source/spl-damage.h
+++ b/crawl-ref/source/spl-damage.h
@@ -35,6 +35,7 @@ bool setup_fragmentation_beam(bolt &beam, int pow, const actor *caster,
spret_type cast_fragmentation(int powc, const actor *caster,
const coord_def target, bool fail);
int wielding_rocks();
+
spret_type cast_sandblast(int powc, bolt &beam, bool fail);
spret_type cast_tornado(int powc, bool fail);
void tornado_damage(actor *caster, int dur);
@@ -59,4 +60,5 @@ void toxic_radiance_effect(actor* agent, int mult);
spret_type cast_searing_ray(int pow, bolt &beam, bool fail);
void handle_searing_ray();
void end_searing_ray();
+spret_type cast_water_harpoon(int powc, bolt &beam, bool fail);
#endif
diff --git a/crawl-ref/source/spl-data.h b/crawl-ref/source/spl-data.h
index cdcb3a2..88b8c0f 100644
--- a/crawl-ref/source/spl-data.h
+++ b/crawl-ref/source/spl-data.h
@@ -3314,6 +3314,19 @@ struct spell_desc
},
{
+ SPELL_WATER_HARPOON, "Water Harpoon",
+ SPTYP_CONJURATION | SPTYP_ICE,
+ SPFLAG_DIR_OR_TARGET | SPFLAG_MONSTER,
+ 4,
+ 100,
+ 6, 6,
+ 0,
+ NULL,
+ true,
+ false
+},
+
+{
SPELL_NO_SPELL, "nonexistent spell",
0,
SPFLAG_TESTING,
diff --git a/crawl-ref/source/spl-zap.cc b/crawl-ref/source/spl-zap.cc
index b1289f0..2703285 100644
--- a/crawl-ref/source/spl-zap.cc
+++ b/crawl-ref/source/spl-zap.cc
@@ -104,6 +104,8 @@ zap_type spell_to_zap(spell_type spell)
return ZAP_DIG;
case SPELL_HOLY_LIGHT:
return ZAP_HOLY_LIGHT;
+ case SPELL_WATER_HARPOON:
+ return ZAP_WATER_HARPOON;
case SPELL_DEBUGGING_RAY:
return ZAP_DEBUGGING_RAY;
default:
diff --git a/crawl-ref/source/zap-data.h b/crawl-ref/source/zap-data.h
index 5f5e8d9..3de428f 100644
--- a/crawl-ref/source/zap-data.h
+++ b/crawl-ref/source/zap-data.h
@@ -1111,3 +1111,19 @@ struct zap_info
false,
2
},
+
+{
+ ZAP_WATER_HARPOON,
+ "harpoon of water",
+ 50,
+ new dicedef_calculator<3, 5, 1, 8>,
+ new tohit_calculator<9, 1, 12>,
+ LIGHTBLUE,
+ false,
+ BEAM_WATER,
+ DCHAR_WAVY,
+ false,
+ false,
+ false,
+ 4
+},
--
1.8.3.2
|