summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source
diff options
context:
space:
mode:
authorzelgadis <zelgadis@c06c8d41-db1a-0410-9941-cceddc491573>2008-12-01 04:00:00 +0000
committerzelgadis <zelgadis@c06c8d41-db1a-0410-9941-cceddc491573>2008-12-01 04:00:00 +0000
commit04f5058cac8e12d3b85834bda4589239932f371a (patch)
tree20863dfb1b0a87c9643f0ca98ce2cb43cf195416 /crawl-ref/source
parent6e3985b473d0b26c028820b3d0a3808d3f3dd7ee (diff)
downloadcrawl-ref-04f5058cac8e12d3b85834bda4589239932f371a.tar.gz
crawl-ref-04f5058cac8e12d3b85834bda4589239932f371a.zip
Add some chaos attacks/weapons. Missiles and launchers of chaos fire a bolt of
a random type (including enchantments, which isn't so good since they don't have a visible beam). Might want to add BEAM_CHAOS and make it a beam of that. Weapons of chaos either does a random brand effect (fire, poison, etc) or a random chaos effect (which includes cloning the attack victim). The AF_CHAOS monster attack flavour either does a random monster flavour or chaos effect (same chaos effects as for weapons). The relative frequency of all the different effects/brands/flavours no doubt needs adjustment. All of this is currently only available via Xom. 10% of all common-type demons sent in by Xom will be chaos spawn (the only kind that use AF_CHAOS, and never randomly generated otherwise). All item gifts from Xom which are generated with a brand will have their brand switched to chaos (this should probably be made to happen less than 100% of the time). And finally one of Xom's bad acts is to upgrade a non-branded weapon of a nearby hostile monster to a chaos brand (this might need to be made less (or more) common). Oh, and if a randart has a brand which would be identified if it were merely an ego weapon, it now identifies the RAP_BRAND property of the randart. git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@7704 c06c8d41-db1a-0410-9941-cceddc491573
Diffstat (limited to 'crawl-ref/source')
-rw-r--r--crawl-ref/source/dat/database/godspeak.txt5
-rw-r--r--crawl-ref/source/dat/database/monspeak.txt14
-rw-r--r--crawl-ref/source/dat/descript/monsters.txt5
-rw-r--r--crawl-ref/source/describe.cc20
-rw-r--r--crawl-ref/source/enum.h1
-rw-r--r--crawl-ref/source/fight.cc433
-rw-r--r--crawl-ref/source/fight.h9
-rw-r--r--crawl-ref/source/item_use.cc182
-rw-r--r--crawl-ref/source/item_use.h2
-rw-r--r--crawl-ref/source/itemname.cc6
-rw-r--r--crawl-ref/source/itemprop.h4
-rw-r--r--crawl-ref/source/mon-data.h12
-rw-r--r--crawl-ref/source/mon-util.cc90
-rw-r--r--crawl-ref/source/mon-util.h6
-rw-r--r--crawl-ref/source/mstuff2.cc11
-rw-r--r--crawl-ref/source/religion.cc70
-rw-r--r--crawl-ref/source/religion.h1
-rw-r--r--crawl-ref/source/shopping.cc1
-rw-r--r--crawl-ref/source/xom.cc292
19 files changed, 1080 insertions, 84 deletions
diff --git a/crawl-ref/source/dat/database/godspeak.txt b/crawl-ref/source/dat/database/godspeak.txt
index 299870dd54..382a186f89 100644
--- a/crawl-ref/source/dat/database/godspeak.txt
+++ b/crawl-ref/source/dat/database/godspeak.txt
@@ -260,6 +260,11 @@ Xom casts you into the Abyss!
The world seems to spin as Xom's maniacal laughter rings in your ears.
%%%%
+# Xom upgrades a nearby hostile monster's weapon to a chaos brand
+Xom chaos upgrade
+
+"Have a taste of chaos, mortal."
+%%%%
#####################
# other effects
#####################
diff --git a/crawl-ref/source/dat/database/monspeak.txt b/crawl-ref/source/dat/database/monspeak.txt
index b86a461b0b..80ae1f879d 100644
--- a/crawl-ref/source/dat/database/monspeak.txt
+++ b/crawl-ref/source/dat/database/monspeak.txt
@@ -2218,6 +2218,20 @@ _Xtahua_rare_
# Specific non-unique monsters
##########################################
%%%%
+# Chaos spawns shouldn't have coherent speech, since that would be too
+# orderly for beings of pure chaos.
+chaos spawn
+
+VISUAL:@The_monster@ grows dozens of eye stalks in order to get a better look at you.
+
+VISUAL:@The_monster@ splits into many small globs of multi-coloured light, then recombines.
+
+VISUAL:@The_monster@ breifly grows a face disturbingly similar to your own.
+
+@The_monster@ ululates chillingly with its many mouths.
+
+@The_monster@ gibbers incoherently in a cacophony of voices.
+%%%%
crystal golem
VISUAL:@The_monster@ glitters in the dim dungeon light.
diff --git a/crawl-ref/source/dat/descript/monsters.txt b/crawl-ref/source/dat/descript/monsters.txt
index dd0fbf0a69..5060800209 100644
--- a/crawl-ref/source/dat/descript/monsters.txt
+++ b/crawl-ref/source/dat/descript/monsters.txt
@@ -50,6 +50,11 @@ Cerebov
A violent and wrathful demon, Cerebov appears as a giant human covered in shining golden armour and wielding a huge twisted sword.
%%%%
+Chaos spawn
+
+A being of pure chaos, its form is constantly shifitng, growing and then
+losing eyes, mouths, claws, and tentacles.
+%%%%
Daeva
A divine agent of the Shining One, it is a towering winged figure with an aura of brilliant golden light.
diff --git a/crawl-ref/source/describe.cc b/crawl-ref/source/describe.cc
index b80634e36b..4fd6f9a611 100644
--- a/crawl-ref/source/describe.cc
+++ b/crawl-ref/source/describe.cc
@@ -981,6 +981,14 @@ static std::string _describe_weapon(const item_def &item, bool verbose)
description += "It turns projectiles fired from it into "
"bolts of frost.";
break;
+ case SPWPN_CHAOS:
+ if (is_range_weapon(item))
+ description += "Each time it fires it turns the launched "
+ "projectile into a different, random type of bolt.";
+ else
+ description += "Each time it hits an enemy it has a "
+ "different, random effect.";
+ break;
case SPWPN_VAMPIRICISM:
description += "It inflicts no extra harm, "
"but heals its wielder somewhat when "
@@ -1145,11 +1153,21 @@ static std::string _describe_ammo( const item_def &item )
if (item.special && item_type_known(item))
{
description += "$$";
+ std::string bolt_name;
switch (item.special)
{
case SPMSL_FLAME:
+ bolt_name = "flame";
+ // Intentional fall-through
case SPMSL_ICE:
+ if (bolt_name.empty())
+ bolt_name = "ice";
+ // Intentional fall-through
+ case SPMSL_CHAOS:
+ if (bolt_name.empty())
+ bolt_name = "a random type";
+
description += "When ";
if (can_throw)
@@ -1163,7 +1181,7 @@ static std::string _describe_ammo( const item_def &item )
description += "fired from an appropriate launcher, ";
description += "it turns into a bolt of ";
- description += (item.special == SPMSL_FLAME) ? "flame" : "ice";
+ description += bolt_name;
description += ".";
break;
case SPMSL_POISONED:
diff --git a/crawl-ref/source/enum.h b/crawl-ref/source/enum.h
index 361764cb82..08bd09a537 100644
--- a/crawl-ref/source/enum.h
+++ b/crawl-ref/source/enum.h
@@ -681,6 +681,7 @@ enum conduct_type
DID_EAT_SOULED_BEING, // Zin
DID_DELIBERATE_MUTATING, // Zin
DID_CAUSE_GLOWING, // Zin
+ DID_CHAOS, // Zin (used weapon/magic of chaos)
DID_DESTROY_ORCISH_IDOL, // Beogh
DID_CREATE_LIFE, // unused
diff --git a/crawl-ref/source/fight.cc b/crawl-ref/source/fight.cc
index 465afc5ed3..d7dedf3a99 100644
--- a/crawl-ref/source/fight.cc
+++ b/crawl-ref/source/fight.cc
@@ -1215,7 +1215,7 @@ bool melee_attack::player_apply_aux_unarmed()
}
if (def->hit_points < 1)
- monster_die(def, KILL_YOU, NON_MONSTER);
+ _monster_die(def, KILL_YOU, NON_MONSTER);
return (!def->alive());
}
@@ -1849,6 +1849,25 @@ bool melee_attack::player_monattk_hit_effects(bool mondied)
return (!def->alive());
}
+void melee_attack::_monster_die(monsters* monster, killer_type killer,
+ int killer_index)
+{
+ const bool chaos = damage_brand == SPWPN_CHAOS;
+
+ // Copy defender before it gets reset by monster_die()
+ monsters* def_copy = NULL;
+ if (chaos)
+ def_copy = new monsters(*monster);
+
+ monster_die(monster, killer, killer_index);
+
+ if (chaos)
+ {
+ chaos_killed_defender(def_copy);
+ delete def_copy;
+ }
+}
+
static bool is_boolean_resist(beam_type flavour)
{
switch (flavour)
@@ -2078,13 +2097,17 @@ bool melee_attack::distortion_affects_defender()
emit_nodmg_hit_message();
if (defender->atype() == ACT_PLAYER && attacker_visible
- && weapon != NULL && !is_artefact(*weapon))
+ && weapon != NULL && !is_unrandom_artefact(*weapon)
+ && !is_fixed_artefact(*weapon))
{
// If the player is being sent to the Abyss by being attacked
// with a distortion weapon, then we have to ID it before
// the player goes to Abyss, while the weapon object is
// still in memory.
- set_ident_flags(*weapon, ISFLAG_KNOW_TYPE);
+ if (is_random_artefact(*weapon))
+ randart_wpn_learn_prop(*weapon, RAP_BRAND);
+ else
+ set_ident_flags(*weapon, ISFLAG_KNOW_TYPE);
}
else if (defender_visible)
obvious_effect = true;
@@ -2097,8 +2120,331 @@ bool melee_attack::distortion_affects_defender()
return (false);
}
+static bool _can_clone(const actor *defender, coord_def *pos, int *midx)
+{
+ pos->set(-1, -1);
+ *midx = NON_MONSTER;
+
+ // Maybe create a player ghost?
+ if (defender->atype() == ACT_PLAYER)
+ return (false);
+
+ const monsters* mon = dynamic_cast<const monsters*>(defender);
+
+ // No uniques, pandemonium lords or player ghosts. Also, figuring
+ // out the name for the clone of a named monster isn't worth it.
+ if (mons_is_unique(mon->type) || mon->is_named() || mon->ghost.get())
+ return (false);
+
+ // Holy beings can't be duplicated by chaotic means.
+ if (mons_is_holy(mon))
+ return (false);
+
+ // Is there space for the clone?
+ int squares = 0;
+ for (int i = 0; i < 8; i++)
+ {
+ const coord_def p = mon->pos() + Compass[i];
+
+ if (in_bounds(p) && p != you.pos() && mgrd(p) == NON_MONSTER
+ && monster_habitable_grid(mon, grd(p)))
+ {
+ if (one_chance_in(++squares))
+ *pos = p;
+ }
+ }
+ if (squares == 0)
+ return (false);
+
+ // Is there an open slot in menv?
+ for (int i = 0; i < MAX_MONSTERS; i++)
+ if (menv[i].type == -1)
+ {
+ *midx = i;
+ break;
+ }
+
+ if (*midx == NON_MONSTER)
+ return (false);
+
+ // Is the monster carrying an artefact?
+ for (int i = 0; i < NUM_MONSTER_SLOTS; i++)
+ {
+ const int index = mon->inv[i];
+
+ if (index == NON_ITEM)
+ continue;
+
+ if (is_artefact(mitm[index]))
+ return (false);
+ }
+
+ return (true);
+}
+
+static bool _do_clone(monsters* orig, coord_def pos, int midx)
+{
+ bool obvious = false;
+
+ monsters &mon(menv[midx]);
+
+ mon = *orig;
+
+ mon.position = pos;
+ mgrd(pos) = midx;
+
+ // Duplicate objects, or unequip them if they can't be duplicated.
+ for (int i = 0; i < NUM_MONSTER_SLOTS; i++)
+ {
+ const int old_index = orig->inv[i];
+
+ if (old_index == NON_ITEM)
+ continue;
+
+ const int new_index = get_item_slot(0);
+ if (new_index == NON_ITEM)
+ {
+ mon.unequip(mitm[old_index], i, 0, true);
+ mon.inv[i] = NON_ITEM;
+ continue;
+ }
+
+ mon.inv[i] = new_index;
+ mitm[new_index] = mitm[old_index];
+ }
+
+ // The player shouldn't get new permanent followers from cloning.
+ if (mon.attitude == ATT_FRIENDLY && !mons_is_summoned(&mon))
+ mon.mark_summoned(6, true);
+
+ if (you.can_see(orig) && you.can_see(&mon))
+ {
+ simple_monster_message(orig, " is duplicated!");
+ obvious = true;
+ }
+
+ mark_interesting_monst(&mon, mon.behaviour);
+ if (you.can_see(&mon))
+ {
+ seen_monster(&mon);
+ viewwindow(true, false);
+ }
+
+ return (obvious);
+}
+
+enum chaos_type
+{
+ CHAOS_CLONE,
+ CHAOS_POLY,
+ CHAOS_POLY_UP,
+ CHAOS_MAKE_SHIFTER,
+ CHAOS_HEAL,
+ CHAOS_HASTE,
+ CHAOS_INVIS,
+ CHAOS_SLOW,
+ CHAOS_PARA,
+ CHAOS_PETRIFY,
+ NUM_CHAOS_TYPES
+};
+
+// XXX: We might want to vary the probabilites for the various effects
+// based on whether the source is weapon of chaos or a monster with
+// AF_CHAOS
+void melee_attack::chaos_affects_defender()
+{
+ coord_def clone_pos;
+ int clone_midx;
+ const bool mon = defender->atype() == ACT_MONSTER;
+ const bool immune = mon && mons_immune_magic(def);
+ const bool is_shifter = mon && mons_is_shapeshifter(def);
+ const bool is_chaotic = mon && mons_is_chaotic(def);
+ const bool can_clone = _can_clone(defender, &clone_pos, &clone_midx);
+ const bool can_poly = is_shifter || (defender->can_safely_mutate()
+ && !immune);
+
+ int clone_chance = can_clone ? 1 : 0;
+ int poly_chance = can_poly ? 1 : 0;
+ int poly_up_chance = can_poly ? 1 : 0;
+ int shifter_chance = can_poly ? 1 : 0;
+
+ if (is_chaotic)
+ {
+ // Polymorphing might reduce amount of chaos in the world.
+ poly_chance = 0;
+ poly_up_chance = 0;
+
+ // Chaos wants more chaos.
+ clone_chance *= 2;
+
+ // Chaos loves shifters.
+ if (is_shifter)
+ {
+ clone_chance *= 2;
+ poly_up_chance = 4;
+
+ // Already a shifter
+ shifter_chance = 0;
+ }
+ }
+
+ // NOTE: Must appear in exact same order as in chaos_type enumeration.
+ int probs[NUM_CHAOS_TYPES] =
+ {
+ clone_chance, // CHAOS_CLONE
+ poly_chance, // CHAOS_POLY
+ poly_up_chance, // CHAOS_POLY_UP
+ shifter_chance, // CHAOS_MAKE_SHIFTER
+
+ 5, // CHAOS_HEAL
+ 5, // CHAOS_HASTE
+ 5, // CHAOS_INVIS
+
+ 15, // CHAOS_SLOW
+ 15, // CHAOS_PARA
+ 15, // CHAOS_PETRIFY
+ };
+
+ bolt beam;
+ beam.flavour = BEAM_NONE;
+
+ int choice = choose_random_weighted(probs, probs + NUM_CHAOS_TYPES);
+ switch(static_cast<chaos_type>(choice))
+ {
+ case CHAOS_CLONE:
+ ASSERT(can_clone);
+ ASSERT(defender->atype() == ACT_MONSTER);
+ obvious_effect = _do_clone(def, clone_pos, clone_midx);
+ break;
+
+ case CHAOS_POLY:
+ ASSERT(can_poly);
+ beam.flavour = BEAM_POLYMORPH;
+ break;
+
+ case CHAOS_POLY_UP:
+ ASSERT(can_poly);
+ ASSERT(defender->atype() == ACT_MONSTER);
+
+ obvious_effect = you.can_see(defender);
+ monster_polymorph(def, RANDOM_MONSTER, PPT_MORE, true);
+ break;
+
+ case CHAOS_MAKE_SHIFTER:
+ ASSERT(can_poly);
+ ASSERT(!is_shifter);
+ ASSERT(defender->atype() == ACT_MONSTER);
+
+ obvious_effect = you.can_see(defender);
+ def->add_ench(one_chance_in(3) ?
+ ENCH_GLOWING_SHAPESHIFTER : ENCH_SHAPESHIFTER);
+ // Immediately polymorph monster, just to make the effect obvious.
+ monster_polymorph(def, RANDOM_MONSTER, PPT_SAME, true);
+ break;
+
+ case CHAOS_HEAL:
+ beam.flavour = BEAM_HEALING;
+ break;
+
+ case CHAOS_HASTE:
+ beam.flavour = BEAM_HASTE;
+ break;
+
+ case CHAOS_INVIS:
+ beam.flavour = BEAM_INVISIBILITY;
+ break;
+
+ case CHAOS_SLOW:
+ beam.flavour = BEAM_SLOW;
+ break;
+
+ case CHAOS_PARA:
+ beam.flavour = BEAM_PARALYSIS;
+ break;
+
+ case CHAOS_PETRIFY:
+ beam.flavour = BEAM_PETRIFY;
+ break;
+
+ default:
+ ASSERT(!"Invalid chaos effect type");
+ break;
+ }
+
+ if (beam.flavour != BEAM_NONE)
+ {
+ beam.name = atk_name(DESC_CAP_THE);
+ beam.range = 1;
+ beam.colour = BLACK;
+ beam.is_beam = false;
+ beam.is_explosion = false;
+ beam.is_big_cloud = false;
+ beam.effect_known = false;
+
+ beam.thrower = (attacker->atype() == ACT_PLAYER) ? KILL_YOU
+ : def->confused_by_you() ? KILL_YOU_CONF
+ : KILL_MON;
+ beam.beam_source =
+ (attacker->atype() == ACT_PLAYER) ? MHITYOU : monster_index(atk);
+
+ beam.source = attacker->pos();
+ beam.target = defender->pos();
+ beam.pos = defender->pos();
+
+ beam.damage = dice_def(damage_done + special_damage + aux_damage, 1);
+
+ beam.ench_power = beam.damage.num;
+
+ fire_beam(beam);
+
+ if (you.can_see(defender))
+ obvious_effect = beam.obvious_effect;
+ }
+
+ if (!you.can_see(attacker))
+ obvious_effect = false;
+}
+
+void melee_attack::chaos_affects_attacker()
+{
+}
+
+// NOTE: Isn't called if monster dies from poisoning caused by chaos.
+void melee_attack::chaos_killed_defender(monsters* def_copy)
+{
+}
+
+// NOTE: random_chaos_brand() and random_chaos_attack_flavour() should
+// return a set of effects that are roughly the same, to make it easy
+// for chaos_affects_defender() not to do duplicate effects caused
+// by the non-chaos brands/flavours they return.
+int melee_attack::random_chaos_brand()
+{
+ int brands[] = {SPWPN_FLAMING, SPWPN_FREEZING, SPWPN_ELECTROCUTION,
+ SPWPN_VENOM, SPWPN_DRAINING, SPWPN_VAMPIRICISM,
+ SPWPN_PAIN, SPWPN_DISTORTION, SPWPN_CONFUSE,
+ SPWPN_CHAOS};
+ return (RANDOM_ELEMENT(brands));
+}
+
+mon_attack_flavour melee_attack::random_chaos_attack_flavour()
+{
+ mon_attack_flavour flavours[] =
+ {AF_FIRE, AF_COLD, AF_ELEC, AF_POISON_NASTY, AF_VAMPIRIC, AF_DISTORT,
+ AF_CONFUSE, AF_CHAOS};
+ return (RANDOM_ELEMENT(flavours));
+}
+
bool melee_attack::apply_damage_brand()
{
+ bool brand_was_known = false;
+
+ if (weapon)
+ if (is_random_artefact(*weapon))
+ brand_was_known = randart_known_wpn_property(*weapon, RAP_BRAND);
+ else
+ brand_was_known = item_type_known(*weapon);
+
bool ret = false;
// Monster resistance to the brand.
@@ -2106,7 +2452,14 @@ bool melee_attack::apply_damage_brand()
special_damage = 0;
obvious_effect = false;
- switch (damage_brand)
+
+ int brand;
+ if (damage_brand == SPWPN_CHAOS)
+ brand = random_chaos_brand();
+ else
+ brand = damage_brand;
+
+ switch (brand)
{
case SPWPN_FLAMING:
res = fire_res_apply_cerebov_downgrade( defender->res_fire() );
@@ -2306,8 +2659,6 @@ bool melee_attack::apply_damage_brand()
{
emit_nodmg_hit_message();
- // FIXME Currently Confusing Touch is the *only* way to get
- // here. Generalise.
const int hdcheck =
(defender->holiness() == MH_NATURAL? random2(30) : random2(22));
@@ -2323,26 +2674,42 @@ bool melee_attack::apply_damage_brand()
(attacker->atype() == ACT_PLAYER) ? MHITYOU
: monster_index(atk);
mons_ench_f2( def, beam_temp );
+ obvious_effect = beam_temp.obvious_effect;
}
- if (attacker->atype() == ACT_PLAYER)
+ if (attacker->atype() == ACT_PLAYER && damage_brand == SPWPN_CONFUSE)
{
+ ASSERT(you.duration[DUR_CONFUSING_TOUCH]);
you.duration[DUR_CONFUSING_TOUCH] -= roll_dice(3, 5);
if (you.duration[DUR_CONFUSING_TOUCH] < 1)
you.duration[DUR_CONFUSING_TOUCH] = 1;
+ obvious_effect = false;
}
break;
}
+
+ case SPWPN_CHAOS:
+ chaos_affects_defender();
+ break;
}
+ if (attacker->atype() == ACT_PLAYER && damage_brand == SPWPN_CHAOS)
+ // If your god objects to using chaos then it makes the
+ // brand obvious.
+ if (did_god_conduct(DID_CHAOS, 2 + random2(3), brand_was_known))
+ obvious_effect = true;
+
if (!obvious_effect)
obvious_effect = !special_damage_message.empty();
if (obvious_effect && attacker_visible && weapon != NULL
- && !is_artefact(*weapon))
+ && !is_unrandom_artefact(*weapon) && !is_fixed_artefact(*weapon))
{
- set_ident_flags(*weapon, ISFLAG_KNOW_TYPE);
+ if (is_random_artefact(*weapon))
+ randart_wpn_learn_prop(*weapon, RAP_BRAND);
+ else
+ set_ident_flags(*weapon, ISFLAG_KNOW_TYPE);
}
return (ret);
@@ -2621,7 +2988,7 @@ bool melee_attack::player_check_monster_died()
player_monattk_hit_effects(true);
- monster_die(def, KILL_YOU, NON_MONSTER);
+ _monster_die(def, KILL_YOU, NON_MONSTER);
return (true);
}
@@ -3571,7 +3938,11 @@ void melee_attack::mons_apply_attack_flavour(const mon_attack_def &attk)
{
// Most of this is from BWR 4.1.2.
- switch (attk.flavour)
+ mon_attack_flavour flavour = attk.flavour;
+ if (flavour == AF_CHAOS)
+ flavour = random_chaos_attack_flavour();
+
+ switch (flavour)
{
default:
break;
@@ -3811,6 +4182,10 @@ void melee_attack::mons_apply_attack_flavour(const mon_attack_def &attk)
case AF_NAPALM:
mons_do_napalm();
break;
+
+ case AF_CHAOS:
+ chaos_affects_defender();
+ break;
}
}
@@ -3822,6 +4197,7 @@ void melee_attack::mons_perform_attack_rounds()
// Melee combat, tell attacker to wield its melee weapon.
atk->wield_melee_weapon();
+ monsters* def_copy = NULL;
for (attack_number = 0; attack_number < nrounds; ++attack_number)
{
// Monster went away?
@@ -3855,6 +4231,13 @@ void melee_attack::mons_perform_attack_rounds()
mons_set_weapon(attk);
to_hit = mons_to_hit();
+ const bool chaos_attack = attk.flavour == AF_CHAOS
+ || damage_brand == SPWPN_CHAOS;
+
+ // Make copy of monster before monster_die() resets it.
+ if (chaos_attack && defender->atype() == ACT_MONSTER && !def_copy)
+ def_copy = new monsters(*def);
+
final_attack_delay = mons_attk_delay();
if (damage_brand == SPWPN_SPEED)
final_attack_delay = final_attack_delay / 2 + 1;
@@ -3945,13 +4328,17 @@ void melee_attack::mons_perform_attack_rounds()
defender->hurt(attacker, damage_done + special_damage);
- // Yredelemnul's injury mirroring can kill the attacker.
- if (!attacker->alive() || !defender->alive()
- || attacker == defender)
+ if (!defender->alive())
{
- return;
+ if (chaos_attack && defender->atype() == ACT_MONSTER)
+ chaos_killed_defender(def_copy);
+ break;
}
+ // Yredelemnul's injury mirroring can kill the attacker.
+ if (!attacker->alive() || attacker == defender)
+ break;
+
special_damage = 0;
special_damage_message.clear();
apply_damage_brand();
@@ -3962,18 +4349,28 @@ void melee_attack::mons_perform_attack_rounds()
if (special_damage > 0)
defender->hurt(attacker, special_damage);
+ if (!defender->alive())
+ {
+ if (chaos_attack && defender->atype() == ACT_MONSTER)
+ chaos_killed_defender(def_copy);
+ break;
+ }
+
// Yredelemnul's injury mirroring can kill the attacker.
if (!attacker->alive())
- return;
+ break;
}
item_def *weap = atk->mslot_item(MSLOT_WEAPON);
- if (weap && weap->cursed() && is_range_weapon(*weap)
- && !(weap->flags & ISFLAG_KNOW_CURSE))
+ if (weap && you.can_see(atk) && weap->cursed()
+ && is_range_weapon(*weap))
{
set_ident_flags( *weap, ISFLAG_KNOW_CURSE );
}
}
+
+ if (def_copy)
+ delete def_copy;
}
bool melee_attack::mons_perform_attack()
diff --git a/crawl-ref/source/fight.h b/crawl-ref/source/fight.h
index 4ac3b0051d..8df5ec790d 100644
--- a/crawl-ref/source/fight.h
+++ b/crawl-ref/source/fight.h
@@ -13,6 +13,7 @@
#include "externs.h"
#include "randart.h"
+#include "mon-util.h"
enum unarmed_attack_type
{
@@ -198,6 +199,11 @@ private:
// Returns true if the defender is banished.
bool distortion_affects_defender();
+ void chaos_affects_defender();
+ void chaos_affects_attacker();
+ void chaos_killed_defender(monsters* def_copy);
+ int random_chaos_brand();
+
private:
// Monster-attack specific stuff
bool mons_attack_you();
@@ -222,6 +228,8 @@ private:
std::string mons_defender_name();
void wasp_paralyse_defender();
+ mon_attack_flavour random_chaos_attack_flavour();
+
private:
// Player-attack specific stuff
bool player_attack();
@@ -261,6 +269,7 @@ private:
std::string player_why_missed();
void player_warn_miss();
void player_check_weapon_effects();
+ void _monster_die(monsters *monster, killer_type killer, int killer_index);
};
#endif
diff --git a/crawl-ref/source/item_use.cc b/crawl-ref/source/item_use.cc
index 198fd61ad0..6f300fffd2 100644
--- a/crawl-ref/source/item_use.cc
+++ b/crawl-ref/source/item_use.cc
@@ -140,9 +140,8 @@ bool can_wield(item_def *weapon, bool say_reason,
return (false);
}
- int weap_brand = get_weapon_brand(*weapon);
if ((you.is_undead || you.species == SP_DEMONSPAWN)
- && (weap_brand == SPWPN_HOLY_WRATH || is_blessed_blade(*weapon)))
+ && is_holy_item(*weapon))
{
if (say_reason)
{
@@ -634,6 +633,11 @@ void wield_effects(int item_wield_2, bool showMsgs)
mpr("A searing pain shoots up your arm!");
break;
+ case SPWPN_CHAOS:
+ mpr("It is briefly surrounded by a scintillating arua "
+ "of random colours.");
+ break;
+
case SPWPN_SINGING_SWORD:
if (!was_known)
{
@@ -1649,12 +1653,15 @@ int launcher_final_speed(const item_def &launcher, const item_def *shield)
// positive: frost, negative: flame, zero: neither
bool elemental_missile_beam(int launcher_brand, int ammo_brand)
{
+ if (launcher_brand == SPWPN_CHAOS || ammo_brand == SPMSL_CHAOS)
+ return (true);
+
int element = (launcher_brand == SPWPN_FROST
+ ammo_brand == SPMSL_ICE
- launcher_brand == SPWPN_FLAME
- ammo_brand == SPMSL_FLAME);
- return (element);
+ return (element != 0);
}
// XXX This is a bit too generous, as it lets the player determine
@@ -1668,6 +1675,8 @@ static bool determines_ammo_brand(int bow_brand, int ammo_brand)
return (false);
if (bow_brand == SPWPN_VENOM && ammo_brand == SPMSL_POISONED)
return (false);
+ if (bow_brand == SPWPN_CHAOS && ammo_brand == SPMSL_CHAOS)
+ return (false);
return (true);
}
@@ -1739,6 +1748,140 @@ void _merge_ammo_in_inventory(int slot)
}
}
+std::string setup_chaos_ammo(bolt &pbolt, item_def ammo)
+{
+ ASSERT(!is_artefact(ammo));
+
+ const bool poisoned = (get_ammo_brand(ammo) == SPMSL_POISONED);
+
+ // Don't choose BEAM_POISON or BEAM_HEALING if we have poisoned ammo.
+ const int pois_weight = poisoned ? 0 : 10;
+ const int heal_weight = poisoned ? 0 : 10;
+
+ const beam_type flavour = static_cast<beam_type>(
+ random_choose_weighted( pois_weight, BEAM_POISON,
+ heal_weight, BEAM_HEALING,
+
+ 10, BEAM_FIRE,
+ 10, BEAM_COLD,
+ 10, BEAM_ELECTRICITY,
+ 10, BEAM_NEG,
+ 10, BEAM_ACID,
+ 10, BEAM_HELLFIRE,
+ 10, BEAM_NAPALM,
+ 10, BEAM_HELLFROST,
+ 10, BEAM_SLOW,
+ 10, BEAM_HASTE,
+ 10, BEAM_PARALYSIS,
+ 10, BEAM_CONFUSION,
+ 10, BEAM_INVISIBILITY,
+ 10, BEAM_POLYMORPH,
+ 10, BEAM_BANISH,
+ 10, BEAM_DISINTEGRATION,
+ 0 ));
+
+ std::string name;
+ int colour;
+
+ if (poisoned)
+ name = "poison ";
+
+ switch(flavour)
+ {
+ case BEAM_POISON:
+ name += "poison";
+ colour = EC_POISON;
+ break;
+ case BEAM_HEALING:
+ name += "healing";
+ colour = EC_HEAL;
+ break;
+ case BEAM_FIRE:
+ name += "flame";
+ colour = EC_FIRE;
+ break;
+ case BEAM_COLD:
+ name += "frost";
+ colour = EC_ICE;
+ break;
+ case BEAM_ELECTRICITY:
+ name += "lightning";
+ colour = EC_ELECTRICITY;
+ break;
+ case BEAM_NEG:
+ name += "negative energy";
+ colour = EC_NECRO;
+ break;
+ case BEAM_ACID:
+ name += "acid";
+ colour = YELLOW;
+ break;
+ case BEAM_HELLFIRE:
+ name += "hellfire";
+ colour = EC_FIRE;
+ break;
+ case BEAM_NAPALM:
+ name += "sticky fire";
+ colour = EC_FIRE;
+ break;
+ case BEAM_HELLFROST:
+ name += "hellfrost";
+ colour = EC_ICE;
+ break;
+ case BEAM_SLOW:
+ name += "slowing";
+ colour = EC_ENCHANT;
+ break;
+ case BEAM_HASTE:
+ name += "hasting";
+ colour = EC_ENCHANT;
+ break;
+ case BEAM_PARALYSIS:
+ name += "paralysis";
+ colour = EC_ENCHANT;
+ break;
+ case BEAM_CONFUSION:
+ name += "confusion";
+ colour = EC_ENCHANT;
+ break;
+ case BEAM_INVISIBILITY:
+ name += "invisibility";
+ colour = EC_ENCHANT;
+ break;
+ case BEAM_POLYMORPH:
+ name += "polymorphing";
+ colour = EC_MUTAGENIC;
+ break;
+ case BEAM_BANISH:
+ name += "banishment";
+ colour = EC_WARP;
+ break;
+ case BEAM_DISINTEGRATION:
+ name += "disintegration";
+ colour = EC_DEATH;
+ break;
+ default:
+ ASSERT(!"Invalid chaos ammo flavour.");
+ break;
+ }
+
+ pbolt.name = "bolt of ";
+ pbolt.name += name;
+
+ pbolt.flavour = flavour;
+ pbolt.colour = colour;
+ pbolt.type = dchar_glyph(DCHAR_FIRED_BOLT);
+
+ // Get name for a plain arrow/bolt/dart/needle.
+ ammo.special = 0;
+
+ std::string ammo_name = ammo.name(DESC_NOCAP_A);
+ ammo_name += " of ";
+ ammo_name += name;
+
+ return ammo_name;
+}
+
// throw_it - currently handles player throwing only. Monster
// throwing is handled in mstuff2:mons_throw()
// Note: If teleport is true, assume that pbolt is already set up,
@@ -1785,6 +1928,9 @@ bool throw_it(bolt &pbolt, int throw_2, bool teleport, int acc_bonus,
item_def& thrown = you.inv[throw_2];
+ // Did we know the ammo's brand before throwing it?
+ const bool ammon_brand_known = item_type_known(thrown);
+
// Get the ammo/weapon type. Convenience.
const object_class_type wepClass = thrown.base_type;
const int wepType = thrown.sub_type;
@@ -1888,6 +2034,7 @@ bool throw_it(bolt &pbolt, int throw_2, bool teleport, int acc_bonus,
// Now start real firing!
origin_set_unknown(item);
+ std::string ammo_name;
if (is_blood_potion(item) && thrown.quantity > 1)
{
@@ -2389,8 +2536,19 @@ bool throw_it(bolt &pbolt, int throw_2, bool teleport, int acc_bonus,
// and vice versa.
// Note that bow_brand is known since the bow is equipped.
- if ((bow_brand == SPWPN_FLAME || ammo_brand == SPMSL_FLAME)
- && ammo_brand != SPMSL_ICE && bow_brand != SPWPN_FROST)
+
+ // Chaos overides flame and frost/ice.
+ if (bow_brand == SPWPN_CHAOS || ammo_brand == SPMSL_CHAOS)
+ {
+ ammo_name = setup_chaos_ammo(pbolt, item);
+
+ // [dshaligram] Branded arrows are much stronger.
+ dice_mult = (dice_mult * 150) / 100;
+
+ pbolt.effect_known = false;
+ }
+ else if ((bow_brand == SPWPN_FLAME || ammo_brand == SPMSL_FLAME)
+ && ammo_brand != SPMSL_ICE && bow_brand != SPWPN_FROST)
{
// [dshaligram] Branded arrows are much stronger.
dice_mult = (dice_mult * 150) / 100;
@@ -2407,9 +2565,8 @@ bool throw_it(bolt &pbolt, int throw_2, bool teleport, int acc_bonus,
pbolt.thrower = KILL_YOU_MISSILE;
pbolt.aux_source.clear();
}
-
- if ((bow_brand == SPWPN_FROST || ammo_brand == SPMSL_ICE)
- && ammo_brand != SPMSL_FLAME && bow_brand != SPWPN_FLAME)
+ else if ((bow_brand == SPWPN_FROST || ammo_brand == SPMSL_ICE)
+ && ammo_brand != SPMSL_FLAME && bow_brand != SPWPN_FLAME)
{
// [dshaligram] Branded arrows are much stronger.
dice_mult = (dice_mult * 150) / 100;
@@ -2522,12 +2679,15 @@ bool throw_it(bolt &pbolt, int throw_2, bool teleport, int acc_bonus,
}
}
+ if (ammo_name.empty())
+ ammo_name = ammo.name(DESC_NOCAP_A);
+
// Create message.
mprf( "%s %s%s %s.",
teleport ? "Magically, you" : "You",
projected ? "" : "awkwardly ",
projected == LRET_LAUNCHED ? "shoot" : "throw",
- ammo.name(DESC_NOCAP_A).c_str() );
+ ammo_name.c_str() );
// Ensure we're firing a 'missile'-type beam.
pbolt.is_beam = false;
@@ -2559,6 +2719,10 @@ bool throw_it(bolt &pbolt, int throw_2, bool teleport, int acc_bonus,
did_return = false;
}
+ if (bow_brand == SPWPN_CHAOS || ammo_brand == SPMSL_CHAOS)
+ did_god_conduct(DID_CHAOS, 2 + random2(3),
+ bow_brand == SPWPN_CHAOS || ammon_brand_known);
+
if (did_return)
{
// Fire beam in reverse.
diff --git a/crawl-ref/source/item_use.h b/crawl-ref/source/item_use.h
index 31009b522e..c6f5820b18 100644
--- a/crawl-ref/source/item_use.h
+++ b/crawl-ref/source/item_use.h
@@ -160,6 +160,8 @@ bool puton_item(int slot, bool prompt_finger = true);
bool enchant_weapon(enchant_stat_type which_stat, bool quiet, item_def &wpn);
bool enchant_armour(int &ac_change, bool quiet, item_def &arm);
+std::string setup_chaos_ammo(bolt &pbolt, item_def item);
+
bool throw_it(bolt &pbolt, int throw_2, bool teleport = false,
int acc_bonus = 0, dist *target = NULL);
diff --git a/crawl-ref/source/itemname.cc b/crawl-ref/source/itemname.cc
index a12e1c7804..feb61f56cd 100644
--- a/crawl-ref/source/itemname.cc
+++ b/crawl-ref/source/itemname.cc
@@ -305,6 +305,9 @@ const char* weapon_brand_name(const item_def& item, bool terse)
case SPWPN_FLAME: return ((terse) ? " (flame)" : " of flame");
case SPWPN_FROST: return ((terse) ? " (frost)" : " of frost");
+ // both ranged and non-ranged
+ case SPWPN_CHAOS: return ((terse) ? " (chaos)" : " of chaos");
+
// randart brands
default: return "";
}
@@ -1089,6 +1092,9 @@ std::string item_def::name_aux( description_level_type desc,
case SPMSL_RETURNING:
buff << ((terse) ? " (return)" : " of returning");
break;
+ case SPMSL_CHAOS:
+ buff << ((terse) ? " (chaos)" : " of chaos");
+ break;
default:
buff << " (buggy)";
}
diff --git a/crawl-ref/source/itemprop.h b/crawl-ref/source/itemprop.h
index 36a3e12c4f..2723fc4057 100644
--- a/crawl-ref/source/itemprop.h
+++ b/crawl-ref/source/itemprop.h
@@ -98,6 +98,7 @@ enum brand_type // equivalent to (you.inv[].special or mitm[].special) % 30
MAX_PAN_LORD_BRANDS = SPWPN_RETURNING,
SPWPN_CONFUSE,
+ SPWPN_CHAOS,
SPWPN_RANDART_I = 25, // 25
SPWPN_RANDART_II,
SPWPN_RANDART_III,
@@ -354,7 +355,8 @@ enum special_missile_type // to separate from weapons in general {dlb}
SPMSL_POISONED, // 3
SPMSL_POISONED_II, // 4 - unused
SPMSL_CURARE, // 5
- SPMSL_RETURNING // 6
+ SPMSL_RETURNING, // 6
+ SPMSL_CHAOS // 7
};
enum special_ring_type // jewellery mitm[].special values
diff --git a/crawl-ref/source/mon-data.h b/crawl-ref/source/mon-data.h
index 8c466dfaca..91f3c46a5b 100644
--- a/crawl-ref/source/mon-data.h
+++ b/crawl-ref/source/mon-data.h
@@ -3448,13 +3448,13 @@ static monsterentry mondata[] = {
{
MONS_CHAOS_SPAWN, '3', EC_RANDOM, "chaos spawn",
- M_SEE_INVIS | M_EVIL,
+ M_SEE_INVIS | M_EVIL | M_INSUBSTANTIAL,
MR_NO_FLAGS,
- 0, 10, MONS_CHAOS_SPAWN, MONS_CHAOS_SPAWN, MH_NATURAL, -3,
- { AT_NO_ATK, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK },
- { 0, 0, 0, 0 },
- 0, 0, MST_NO_SPELLS, CE_MUTAGEN_RANDOM, Z_NOZOMBIE, S_RANDOM, I_NORMAL,
- HT_LAND, 0, DEFAULT_ENERGY, MONUSE_NOTHING, SIZE_BIG
+ 0, 12, MONS_CHAOS_SPAWN, MONS_CHAOS_SPAWN, MH_DEMONIC, -7,
+ { {AT_RANDOM, AF_CHAOS, 21}, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK },
+ { 6, 3, 5, 0 },
+ 7, 12, MST_NO_SPELLS, CE_MUTAGEN_RANDOM, Z_NOZOMBIE, S_RANDOM, I_ANIMAL,
+ HT_LAND, 11, DEFAULT_ENERGY, MONUSE_NOTHING, SIZE_BIG
},
// reaper etc. ('2')
diff --git a/crawl-ref/source/mon-util.cc b/crawl-ref/source/mon-util.cc
index 841ba05762..2e73ae5c9f 100644
--- a/crawl-ref/source/mon-util.cc
+++ b/crawl-ref/source/mon-util.cc
@@ -575,7 +575,8 @@ bool mons_is_chaotic(const monsters *mon)
return (true);
const int attk_flavour = mons_attack_spec(mon, 0).flavour;
- return (attk_flavour == AF_MUTATE || attk_flavour == AF_ROT);
+ return (attk_flavour == AF_MUTATE || attk_flavour == AF_ROT
+ || attk_flavour == AF_CHAOS);
}
bool mons_is_poisoner(const monsters *mon)
@@ -738,14 +739,19 @@ int get_shout_noise_level(const shout_type shout)
}
}
-// Only the beast uses S_RANDOM for noise type.
+// Only beasts and chaos spawns uses S_RANDOM for noise type.
// Pandemonium lords can also get here but this mostly used for the
// "says" verb used for insults.
static bool _shout_fits_monster(int type, int shout)
{
- if (shout == NUM_SHOUTS || shout >= NUM_LOUDNESS)
+ if (shout == NUM_SHOUTS || shout >= NUM_LOUDNESS || shout == S_SILENT)
return (false);
+ // Chaos spawns can do anything but demon taunts, since they're
+ // not coherenet enough to actually say words.
+ if (type == MONS_CHAOS_SPAWN)
+ return (shout != S_DEMON_TAUNT);
+
// For demon lords almost everything is fair game.
// It's only used for the shouting verb ("say", "bellow", "roar", ...)
// anyway.
@@ -760,8 +766,6 @@ static bool _shout_fits_monster(int type, int shout)
case S_WHINE:
// The beast cannot speak.
case S_DEMON_TAUNT:
- // Silent is boring.
- case S_SILENT:
return (false);
default:
return (true);
@@ -1018,17 +1022,16 @@ mon_attack_def mons_attack_spec(const monsters *mon, int attk_number)
ASSERT(smc);
mon_attack_def attk = smc->attack[attk_number];
+ if (attk.type == AT_RANDOM)
+ attk.type = static_cast<mon_attack_type>(random_range(AT_HIT,
+ AT_BUTT));
+
if (attk.flavour == AF_KLOWN)
{
- switch (random2(6))
- {
- case 0: attk.flavour = AF_POISON_NASTY; break;
- case 1: attk.flavour = AF_ROT; break;
- case 2: attk.flavour = AF_DRAIN_XP; break;
- case 3: attk.flavour = AF_FIRE; break;
- case 4: attk.flavour = AF_COLD; break;
- case 5: attk.flavour = AF_BLINK; break;
- }
+ mon_attack_flavour flavours[] =
+ {AF_POISON_NASTY, AF_ROT, AF_DRAIN_XP, AF_FIRE, AF_COLD, AF_BLINK};
+
+ attk.flavour = RANDOM_ELEMENT(flavours);
}
return (zombified ? downscale_zombie_attack(mon, attk) : attk);
@@ -3637,6 +3640,10 @@ void monsters::equip_weapon(item_def &item, int near, bool msg)
case SPWPN_DISTORTION:
mpr("Its appearance distorts for a moment.");
break;
+ case SPWPN_CHAOS:
+ mpr("It is briefly surrounded by a scintillating arua of "
+ "random colours.");
+ break;
default:
// A ranged weapon without special message is known to be unbranded.
@@ -3645,7 +3652,12 @@ void monsters::equip_weapon(item_def &item, int near, bool msg)
}
if (message_given)
- set_ident_flags(item, ISFLAG_KNOW_TYPE);
+ {
+ if (is_random_artefact(item))
+ randart_wpn_learn_prop(item, RAP_BRAND);
+ else
+ set_ident_flags(item, ISFLAG_KNOW_TYPE);
+ }
}
}
@@ -3739,7 +3751,12 @@ void monsters::unequip_weapon(item_def &item, int near, bool msg)
message_given = false;
}
if (message_given)
- set_ident_flags(item, ISFLAG_KNOW_TYPE);
+ {
+ if (is_random_artefact(item))
+ randart_wpn_learn_prop(item, RAP_BRAND);
+ else
+ set_ident_flags(item, ISFLAG_KNOW_TYPE);
+ }
}
}
@@ -4687,6 +4704,8 @@ std::string monsters::hand_name(bool plural, bool *can_plural) const
std::string str;
char ch = mons_char(type);
+ const bool rand = (type == MONS_CHAOS_SPAWN);
+
switch(get_mon_shape(this))
{
case MON_SHAPE_CENTAUR:
@@ -4705,7 +4724,7 @@ std::string monsters::hand_name(bool plural, bool *can_plural) const
case MON_SHAPE_QUADRUPED_TAILLESS:
case MON_SHAPE_QUADRUPED_WINGED:
case MON_SHAPE_ARACHNID:
- if (type == MONS_SCORPION)
+ if (type == MONS_SCORPION || rand && one_chance_in(4))
str = "pincer";
else
{
@@ -4738,7 +4757,7 @@ std::string monsters::hand_name(bool plural, bool *can_plural) const
break;
case MON_SHAPE_MISC:
- if (ch == 'x' || ch == 'X')
+ if (ch == 'x' || ch == 'X' || rand)
{
str = "tentacle";
break;
@@ -4768,14 +4787,25 @@ std::string monsters::hand_name(bool plural, bool *can_plural) const
case MONS_GIANT_ORANGE_BRAIN:
default:
- str = "body";
- can_plural = false;
+ if (rand)
+ str = "rhizome";
+ else
+ {
+ str = "body";
+ can_plural = false;
+ }
break;
}
}
if (str.empty())
+ {
+ // Reduce the chance of a random-shaped monster having hands.
+ if (rand && coinflip())
+ return (hand_name(plural, can_plural));
+
str = "hand";
+ }
if (plural && *can_plural)
str = pluralise(str);
@@ -4793,6 +4823,8 @@ std::string monsters::foot_name(bool plural, bool *can_plural) const
std::string str;
char ch = mons_char(type);
+ const bool rand = (type == MONS_CHAOS_SPAWN);
+
switch(get_mon_shape(this))
{
case MON_SHAPE_INSECT:
@@ -4823,7 +4855,12 @@ std::string monsters::foot_name(bool plural, bool *can_plural) const
case MON_SHAPE_QUADRUPED:
case MON_SHAPE_QUADRUPED_TAILLESS:
case MON_SHAPE_QUADRUPED_WINGED:
- if (ch == 'h')
+ if (rand)
+ {
+ const char* feet[] = {"paw", "talon", "hoof"};
+ str = RANDOM_ELEMENT(feet);
+ }
+ else if (ch == 'h')
str = "paw";
else if (ch == 'l' || ch == 'D')
str = "talon";
@@ -4862,7 +4899,7 @@ std::string monsters::foot_name(bool plural, bool *can_plural) const
break;
case MON_SHAPE_MISC:
- if (ch == 'x' || ch == 'X')
+ if (ch == 'x' || ch == 'X' || rand)
{
str = "tentacle";
break;
@@ -4877,7 +4914,13 @@ std::string monsters::foot_name(bool plural, bool *can_plural) const
}
if (str.empty())
+ {
+ // Reduce the chance of a random-shaped monster having feet.
+ if (rand && coinflip())
+ return (foot_name(plural, can_plural));
+
return (plural ? "feet" : "foot");
+ }
if (plural && *can_plural)
str = pluralise(str);
@@ -7679,6 +7722,9 @@ mon_body_shape get_mon_shape(const monsters *mon)
mon_body_shape get_mon_shape(const int type)
{
+ if (type == MONS_CHAOS_SPAWN)
+ return static_cast<mon_body_shape>(random2(MON_SHAPE_MISC + 1));
+
switch(mons_char(type))
{
case 'a': // ants and cockroaches
diff --git a/crawl-ref/source/mon-util.h b/crawl-ref/source/mon-util.h
index 2526ab012c..add15efa74 100644
--- a/crawl-ref/source/mon-util.h
+++ b/crawl-ref/source/mon-util.h
@@ -49,7 +49,8 @@ enum mon_attack_type
AT_TAIL_SLAP,
AT_BUTT,
- AT_SHOOT // Attack representing missile damage for M_ARCHER.
+ AT_SHOOT, // Attack representing missile damage for M_ARCHER.
+ AT_RANDOM // Anything but AT_SHOOT
};
enum mon_attack_flavour
@@ -79,7 +80,8 @@ enum mon_attack_flavour
AF_KLOWN,
AF_DISTORT,
AF_RAGE,
- AF_NAPALM
+ AF_NAPALM,
+ AF_CHAOS
};
// properties of the monster class (other than resists/vulnerabilities)
diff --git a/crawl-ref/source/mstuff2.cc b/crawl-ref/source/mstuff2.cc
index 18da736352..a6e5c5f5fb 100644
--- a/crawl-ref/source/mstuff2.cc
+++ b/crawl-ref/source/mstuff2.cc
@@ -1040,9 +1040,16 @@ bool mons_throw(struct monsters *monster, struct bolt &pbolt, int hand_used)
monster->speed_increment += speed_delta;
}
+ // Chaos overides flame and frost
+ if (bow_brand == SPWPN_CHAOS || ammo_brand == SPMSL_CHAOS)
+ {
+ (void) setup_chaos_ammo(pbolt, item);
+ baseHit += 2;
+ exDamBonus += 6;
+ }
// WEAPON or AMMO of FIRE
- if (bow_brand == SPWPN_FLAME && ammo_brand != SPMSL_ICE
- || ammo_brand == SPMSL_FLAME && bow_brand != SPWPN_FROST)
+ else if (bow_brand == SPWPN_FLAME && ammo_brand != SPMSL_ICE
+ || ammo_brand == SPMSL_FLAME && bow_brand != SPWPN_FROST)
{
baseHit += 2;
exDamBonus += 6;
diff --git a/crawl-ref/source/religion.cc b/crawl-ref/source/religion.cc
index a949d74a73..96f45320d3 100644
--- a/crawl-ref/source/religion.cc
+++ b/crawl-ref/source/religion.cc
@@ -742,6 +742,7 @@ std::string get_god_dislikes(god_type which_god, bool /*verbose*/)
"have been avoided");
dislikes.push_back("you polymorph monsters");
dislikes.push_back("you eat the flesh of sentient beings");
+ dislikes.push_back("you use weapons or missiles of chaos");
break;
case GOD_SHINING_ONE:
@@ -2952,6 +2953,21 @@ bool did_god_conduct(conduct_type thing_done, int level, bool known,
}
break;
+ case DID_CHAOS:
+ if (you.religion == GOD_ZIN)
+ {
+ retval = true;
+ if (!known)
+ {
+ simple_god_message(" forgives your inadvertent chaotic "
+ "act, just this once.");
+ break;
+ }
+ piety_change = -level;
+ penance = level;
+ }
+ break;
+
case DID_DESTROY_ORCISH_IDOL:
if (you.religion == GOD_BEOGH)
{
@@ -2975,6 +2991,7 @@ bool did_god_conduct(conduct_type thing_done, int level, bool known,
else
_dock_piety(-piety_change, penance);
+#define DEBUG_DIAGNOSTICS 1
#if DEBUG_DIAGNOSTICS
if (retval)
{
@@ -2992,7 +3009,7 @@ bool did_god_conduct(conduct_type thing_done, int level, bool known,
"Servant Kill Holy", "Spell Memorise", "Spell Cast",
"Spell Practise", "Spell Nonutility", "Cards", "Stimulants",
"Drink Blood", "Cannibalism", "Eat Meat", "Eat Souled Being",
- "Deliberate Mutation", "Cause Glowing",
+ "Deliberate Mutation", "Cause Glowing", "Use Chaos",
"Destroy Orcish Idol", "Create Life"
};
@@ -3261,7 +3278,7 @@ bool is_holy_item(const item_def& item)
{
const int item_brand = get_weapon_brand(item);
- retval = (item_brand == SPWPN_HOLY_WRATH);
+ retval = (item_brand == SPWPN_HOLY_WRATH || is_blessed_blade(item));
break;
}
case OBJ_SCROLLS:
@@ -3329,6 +3346,40 @@ bool is_evil_item(const item_def& item)
return (retval);
}
+bool is_chaotic_item(const item_def& item)
+{
+ bool retval = false;
+
+ switch (item.base_type)
+ {
+ case OBJ_WEAPONS:
+ {
+ const int item_brand = get_weapon_brand(item);
+ retval = (item_brand == SPWPN_CHAOS);
+ }
+ break;
+ case OBJ_MISSILES:
+ {
+ const int item_brand = get_ammo_brand(item);
+ retval = (item_brand == SPMSL_CHAOS);
+ }
+ break;
+ case OBJ_WANDS:
+ retval = (item.sub_type == WAND_POLYMORPH_OTHER);
+ break;
+ case OBJ_POTIONS:
+ retval = (item.sub_type == POT_MUTATION);
+ break;
+ default:
+ break;
+ }
+
+ if (is_random_artefact(item) && randart_wpn_property(item, RAP_MUTAGENIC))
+ retval = true;
+
+ return (retval);
+}
+
bool good_god_dislikes_item_handling(const item_def &item)
{
return (is_good_god(you.religion) && is_evil_item(item)
@@ -3346,19 +3397,8 @@ bool god_dislikes_item_handling(const item_def &item)
if (you.religion == GOD_ZIN)
{
- if (((item.base_type == OBJ_POTIONS && item.sub_type == POT_MUTATION)
- || (item.base_type == OBJ_WANDS
- && item.sub_type == WAND_POLYMORPH_OTHER))
- && item_type_known(item))
- {
+ if (item_type_known(item) && is_chaotic_item(item))
return (true);
- }
-
- if (is_random_artefact(item)
- && randart_known_wpn_property(item, RAP_MUTAGENIC))
- {
- return (true);
- }
}
if (you.religion == GOD_SHINING_ONE)
@@ -3378,7 +3418,9 @@ bool god_dislikes_item_handling(const item_def &item)
const int item_brand = get_ammo_brand(item);
if (item_brand == SPMSL_POISONED || item_brand == SPMSL_CURARE)
+ {
return (true);
+ }
}
else if (item.base_type == OBJ_STAVES
&& (item.sub_type == STAFF_POISON
diff --git a/crawl-ref/source/religion.h b/crawl-ref/source/religion.h
index dd72be5c46..99f585b283 100644
--- a/crawl-ref/source/religion.h
+++ b/crawl-ref/source/religion.h
@@ -105,6 +105,7 @@ void beogh_convert_orc(monsters *orc, bool emergency,
bool converted_by_follower = false);
bool is_holy_item(const item_def& item);
bool is_evil_item(const item_def& item);
+bool is_chaotic_item(const item_def& item);
bool good_god_dislikes_item_handling(const item_def &item);
bool god_dislikes_item_handling(const item_def &item);
bool trog_burn_spellbooks();
diff --git a/crawl-ref/source/shopping.cc b/crawl-ref/source/shopping.cc
index 33ca97f4f9..40cfd757df 100644
--- a/crawl-ref/source/shopping.cc
+++ b/crawl-ref/source/shopping.cc
@@ -830,6 +830,7 @@ unsigned int item_value( item_def item, bool ident )
valued *= 50;
break;
+ case SPWPN_CHAOS:
case SPWPN_SPEED:
valued *= 40;
break;
diff --git a/crawl-ref/source/xom.cc b/crawl-ref/source/xom.cc
index fdd3dabe2f..3c0224fe9f 100644
--- a/crawl-ref/source/xom.cc
+++ b/crawl-ref/source/xom.cc
@@ -202,6 +202,43 @@ static void _xom_makes_you_cast_random_spell(int sever)
your_spells(spell, sever, false);
}
+static void _try_brand_switch(const int item_index)
+{
+ if (item_index == NON_ITEM)
+ return;
+
+ item_def &item(mitm[item_index]);
+
+ if (is_unrandom_artefact(item) || is_fixed_artefact(item))
+ return;
+
+ if (item.base_type != OBJ_WEAPONS && item.base_type != OBJ_MISSILES)
+ return;
+
+ int brand;
+ if (item.base_type == OBJ_WEAPONS)
+ {
+ // Only switch already branded items.
+ if (get_weapon_brand(item) == SPWPN_NORMAL)
+ return;
+
+ brand = (int) SPWPN_CHAOS;
+ }
+ else
+ {
+ // Only switch already branded items.
+ if (get_ammo_brand(item) == SPWPN_NORMAL)
+ return;
+
+ brand = (int) SPMSL_CHAOS;
+ }
+
+ if (is_random_artefact(item))
+ randart_set_property(item, RAP_BRAND, brand);
+ else
+ item.special = brand;
+}
+
static void _xom_make_item(object_class_type base, int subtype, int power)
{
int thing_created =
@@ -213,6 +250,8 @@ static void _xom_make_item(object_class_type base, int subtype, int power)
return;
}
+ _try_brand_switch(thing_created);
+
god_acting gdact(GOD_XOM);
move_item_to_grid(&thing_created, you.pos());
@@ -223,6 +262,24 @@ static void _xom_make_item(object_class_type base, int subtype, int power)
origin_acquired(mitm[thing_created], GOD_XOM);
}
+static void _xom_acquirement(object_class_type force_class)
+{
+ god_acting gdact(GOD_XOM);
+
+ int item_index = NON_ITEM;
+
+ if (!acquirement(force_class, GOD_XOM, false, &item_index)
+ || item_index == NON_ITEM)
+ {
+ god_speaks(GOD_XOM, "\"No, never mind.\"");
+ return;
+ }
+
+ _try_brand_switch(item_index);
+
+ stop_running();
+}
+
static object_class_type _get_unrelated_wield_class(object_class_type ref)
{
object_class_type objtype = OBJ_WEAPONS;
@@ -269,7 +326,7 @@ static bool _xom_annoyance_gift(int power)
// For added humour, give the same sub-type.
_xom_make_item(weapon->base_type, weapon->sub_type, power * 3);
else
- acquirement(weapon->base_type, GOD_XOM);
+ _xom_acquirement(weapon->base_type);
return (true);
}
@@ -315,7 +372,7 @@ static bool _xom_annoyance_gift(int power)
_get_unrelated_wield_class(weapon->base_type);
if (x_chance_in_y(power, 256))
- acquirement(objtype, GOD_XOM);
+ _xom_acquirement(objtype);
else
_xom_make_item(objtype, OBJ_RANDOM, power * 3);
return (true);
@@ -365,7 +422,7 @@ static bool _xom_gives_item(int power)
god_acting gdact(GOD_XOM);
- acquirement(objtype, GOD_XOM);
+ _xom_acquirement(objtype);
}
else
{
@@ -384,6 +441,185 @@ static bool _choose_mutatable_monster(const monsters* mon)
&& !mons_is_submerged(mon));
}
+static bool _is_chaos_upgradeable(const item_def &item,
+ const monsters* mon)
+{
+ // Since Xom is a god he is capable of changing randarts, but not
+ // other artifacts.
+ if (is_artefact(item) && !is_random_artefact(item))
+ return (false);
+
+ // Only upgrade permanent items, since the player should get a
+ // chance to use the item if s/he can defeat the monster.
+ if (item.flags & ISFLAG_SUMMONED)
+ return (false);
+
+ // Don't know how to downgrade blessed blades to normal blades.
+ // Can be justified as good gods protecting blessed blades.
+ if (is_blessed_blade(item))
+ return (false);
+
+ // God gifts from good gods are protected. Also, Beogh hates all
+ // the other gods so he'll protect his gifts as well.
+ if (item.orig_monnum < 0)
+ {
+ god_type iorig = static_cast<god_type>(-item.orig_monnum - 2);
+ if ((iorig > GOD_NO_GOD && iorig < NUM_GODS)
+ && (is_good_god(iorig) || iorig == GOD_BEOGH))
+ {
+ return (false);
+ }
+ }
+
+ // Leave branded items alone, since this is supposed to be an
+ // upgrade.
+ if (item.base_type == OBJ_MISSILES)
+ {
+ // Don't make boulders or throwing nets of chaos.
+ if (item.sub_type == MI_LARGE_ROCK
+ || item.sub_type == MI_THROWING_NET)
+ {
+ return (false);
+ }
+
+ if (get_ammo_brand(item) == SPMSL_NORMAL)
+ return (true);
+ }
+ else
+ {
+ // If the weapon is a launcher and the monster is either out
+ // of ammo or is carrying javelins then don't bother upgrading
+ // launcher.
+ if (is_range_weapon(item)
+ && (mon->inv[MSLOT_MISSILE] == NON_ITEM
+ || !has_launcher(mitm[mon->inv[MSLOT_MISSILE]])))
+ {
+ return (false);
+ }
+ if (get_weapon_brand(item) == SPWPN_NORMAL)
+ return (true);
+ }
+
+ return (false);
+}
+
+static bool _choose_chaos_upgrade(const monsters* mon)
+{
+ // Only choose monsters that will attack.
+ if (!mon->alive() || mons_attitude(mon) != ATT_HOSTILE
+ || mons_is_fleeing(mon) || mons_is_panicking(mon))
+ {
+ return (false);
+ }
+
+ if (mons_itemuse(mon) < MONUSE_STARTING_EQUIPMENT)
+ return (false);
+
+ // Holy beings are presumably protected by another god, unless they're
+ // gifts from Xom.
+ if (mons_is_holy(mon) && mon->god != GOD_XOM)
+ return (false);
+
+ // God gifts from good gods will be protected by their god from being
+ // given chaos weapons, while other gods won't mind the help in their
+ // servants killing the player.
+ if (mon->god != GOD_NO_GOD && is_good_god(mon->god))
+ return (false);
+
+ // Beogh presumably doesn't want Xom messing with his orcs, even if
+ // it would give them a better weapon.
+ if (mons_genus(mon->type) == MONS_ORC)
+ return (false);
+
+ mon_inv_type slots[] = {MSLOT_WEAPON, MSLOT_ALT_WEAPON, MSLOT_MISSILE};
+
+ // NOTE: Code assumes that the monster will only be carrying one
+ // missile launcher at a time.
+ bool special_launcher = false;
+ for (int i = 0; i < 3; i++)
+ {
+ const mon_inv_type slot = slots[i];
+ const int midx = mon->inv[slot];
+
+ if (midx == NON_ITEM)
+ continue;
+ const item_def &item(mitm[midx]);
+
+ // Monster already has a chaos weapon, give upgrade to a different
+ // monster.
+ if (is_chaotic_item(item))
+ return (false);
+
+ if (_is_chaos_upgradeable(item, mon))
+ {
+ if (item.base_type != OBJ_MISSILES)
+ return (true);
+
+ // If for some weird reason a monster is carrying a bow
+ // and javelins then branding the javelins is okay since
+ // they won't be fired by the bow.
+ if (!special_launcher || !has_launcher(item))
+ return (true);
+ }
+
+ if (is_range_weapon(item))
+ {
+ // If the launcher alters its ammo then branding the monster's
+ // ammo won't be an upgrade.
+ int brand = get_weapon_brand(item);
+ if (brand == SPWPN_FLAME || brand == SPWPN_FROST
+ || brand == SPWPN_VENOM)
+ {
+ special_launcher = true;
+ }
+ }
+ }
+
+ return (false);
+}
+
+static void _do_chaos_upgrade(item_def &item, const monsters* mon)
+{
+ ASSERT(item.base_type == OBJ_MISSILES
+ || item.base_type == OBJ_WEAPONS);
+ ASSERT(!is_unrandom_artefact(item) && !is_fixed_artefact(item));
+
+ bool seen = false;
+ if (mon && you.can_see(mon) && item.base_type == OBJ_WEAPONS)
+ {
+ seen = true;
+
+ description_level_type desc = mons_friendly(mon) ? DESC_CAP_YOUR :
+ DESC_CAP_THE;
+ std::string msg = mon->name(desc);
+ msg += "'s ";
+ msg = replace_all(msg, "s's", "s'"); // Proper posessive.
+
+ msg += item.name(DESC_PLAIN, false, false, false);
+
+ msg += " is briefly surrounded by a scintillating arua of "
+ "random colours.";
+
+ mpr(msg.c_str());
+ }
+
+ const int brand = (item.base_type == OBJ_WEAPONS) ? (int) SPWPN_CHAOS :
+ (int) SPMSL_CHAOS;
+
+ if (is_random_artefact(item))
+ {
+ randart_set_property(item, RAP_BRAND, brand);
+ if (seen)
+ randart_wpn_learn_prop(item, RAP_BRAND);
+ }
+ else
+ {
+ item.special = brand;
+ if (seen)
+ set_ident_flags(item, ISFLAG_KNOW_TYPE);
+ }
+}
+
static monster_type _xom_random_demon(int sever, bool use_greater_demons = true)
{
const int roll = random2(1000 - (27 - you.experience_level) * 10);
@@ -402,8 +638,15 @@ static monster_type _xom_random_demon(int sever, bool use_greater_demons = true)
if (dct == DEMON_GREATER && coinflip())
demon = summon_any_holy_being(HOLY_BEING_WARRIOR);
else
- demon = summon_any_demon(
- (use_greater_demons || dct != DEMON_GREATER) ? dct : DEMON_COMMON);
+ {
+ const demon_class_type dct2 =
+ (!use_greater_demons && dct == DEMON_GREATER) ? DEMON_COMMON : dct;
+
+ if (dct2 == DEMON_COMMON && one_chance_in(10))
+ demon = MONS_CHAOS_SPAWN;
+ else
+ demon = summon_any_demon(dct2);
+ }
return (demon);
}
@@ -772,6 +1015,37 @@ static bool _xom_is_bad(int sever)
}
else if (x_chance_in_y(7, sever))
{
+ monsters *mon =
+ choose_random_nearby_monster(0, _choose_chaos_upgrade);
+
+ if (!mon)
+ continue;
+
+ god_speaks(GOD_XOM, _get_xom_speech("chaos upgrade").c_str());
+
+ mon_inv_type slots[] = {MSLOT_WEAPON, MSLOT_ALT_WEAPON,
+ MSLOT_MISSILE};
+ for (int i = 0; i < 3; i++)
+ {
+ int idx = mon->inv[slots[i]];
+ if (idx == NON_ITEM)
+ continue;
+
+ item_def &item(mitm[idx]);
+ if (!_is_chaos_upgradeable(item, mon))
+ continue;
+
+ _do_chaos_upgrade(item, mon);
+ done = true;
+ break;
+ }
+ ASSERT(done);
+
+ // Wake the monster up.
+ behaviour_event( mon, ME_ALERT, MHITYOU );
+ }
+ else if (x_chance_in_y(8, sever))
+ {
if (you.can_safely_mutate()
&& player_mutation_level(MUT_MUTATION_RESISTANCE) < 3)
{
@@ -793,7 +1067,7 @@ static bool _xom_is_bad(int sever)
}
}
}
- else if (x_chance_in_y(8, sever))
+ else if (x_chance_in_y(9, sever))
{
if (there_are_monsters_nearby(false, false))
{
@@ -819,7 +1093,7 @@ static bool _xom_is_bad(int sever)
}
}
}
- else if (x_chance_in_y(9, sever))
+ else if (x_chance_in_y(10, sever))
{
std::string speech = _get_xom_speech("draining or torment");
@@ -850,7 +1124,7 @@ static bool _xom_is_bad(int sever)
}
}
}
- else if (x_chance_in_y(10, sever))
+ else if (x_chance_in_y(11, sever))
{
std::string speech = _get_xom_speech("hostile monster");
@@ -890,7 +1164,7 @@ static bool _xom_is_bad(int sever)
}
}
}
- else if (x_chance_in_y(11, sever))
+ else if (x_chance_in_y(12, sever))
{
god_speaks(GOD_XOM, _get_xom_speech("major miscast effect").c_str());