the various chaos effects, to make chaos brand less powerful.
Added berserk and miscast effects for chaos effects, plus chaos weapons will occasionally give a message-only miscast effect if it would otherwise have done nothing.
Added several effects that have a chance of happening to an attacker every time it uses a chaos brand/AF_CHAOS: dropping through temporary a shaft, having the stairs move out from under them, the weapon making a loud noise.
Monsters killed by a chaos weapon/AF_CHAOS have a chance of immediately turning into a zombie (assuming the monster didn't leave a corpse; chaos effects don't (yet) use up corpses).
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@7804 c06c8d41-db1a-0410-9941-cceddc491573
SOCJXX6MMOXLBEWBID4QN5FW2YNYULNNN7K3IRL7RSWK5EUNAZLQC
J4C7374KUQQZXUJNL22HGMZUCISCBLNPGO2FHQKS7STOTPLNY2WAC
HF3CSMAKAOHWJA6CU4PTZSM6G7LVUV5MHE4ZWGEHCCNRBU43UUUQC
43ZYZLF7JFSCKTLQMBYIHNGGDMN3OBDX4X4PLXLIUFD5DPYSDCXQC
BY6T2KQBNMPI54QVEI4QRI6GOCMKAPDEY543VTX7PWXFNNQF25MQC
CCRQESB4ADT4WA7FGLNZZXAJ6G5QMCTYCZIWORBN45P6ZPILC34AC
J4CLLL5AUB264TTFGM66PFWF4KL6LXCQ4TWATOXHNLDQ7OIVTMDQC
K2CS6TCX2NDVL2ASEHGP4J4K4IJ6FP3ANNKTSIWVG43HPYSBX6ZQC
UKN6HTZXDUUOWKNWNKWPHKGUGL474JIAQN5JU3DM3DU26WGMNP4AC
3DQXSE4YGFBBDUWK4YEOFWW4UPWILWELFSLP37SL6BERGAZJC5YAC
AREBCIU2RU2RNHBWD4GARWEBKSL7HDFGDLII22H56OJO2AQUOMLQC
SVY2PTCLXR3KNPQAWXVXTTGCC5DR334HOAKHYO3VDDRWM2BWMALAC
JGTKZP6HCXDHEJLAONL3FNLNIZ7MUBYKXZ4CRTL46YC53TW7CBEAC
5TG5LXU4DX65KMWCZ7YJHOB3VAETQAVBUHEUSQTPMA327XV2HQWAC
RPOZZWKG5GLPHVZZ7ZKMKS64ZMV2LDCQSARBJFJ6FZOTOKCQO7FAC
FLAGBNUNSIQNFDN53CDWABJRTTFWDL4PG34AI474ZKPXDEPYHOAQC
VCG3BRIYRTNNWYC3LOXD6KFGXOX37HAFW2HNV7WXVG2V7EUHLDZQC
BWAQ3FHBBM6G3K3KYP75CRTR343RDQZJRYX5ZGYUEXYBAC3APDLAC
R22TTMI6WXWULC7ODKFF3QCB7MOTETQQ6IR4BUCUPOCQKQNCTT5AC
ACKNLTFL2RI3PMRWLNRVLRWGQAMLRFKNGNS5LED6NFE5GVGFIHFAC
QDTVLBRGHDTRUVT7I3O72K6TMOYAUSAJBZUHGOEFU2RKJNUPWZSQC
JYEEOUYQ7ZPKOGWUV7VCORBVSOLF2UCBFBH3TR75RGOSS6PNKYUAC
77H4BWWPPGLM3PLZH4QTAJRXIZTSDVNCOKZE223I437FN2UJ34RQC
OSRZEPPGBIMSZBWIVBTZTTIMV6TEUGVZRZ5AI2ZJW7CVZZQBUIMQC
IHLGRQOXBGZE3COMNKBKMIDQPJ7HRY4PT74ZUN7HD4ADDPDOX2NQC
OMTU7OMVWDVAGJCQGQJDZ3YU252T6IM2LPS2ZMPWB7MIXCJK62AQC
AS2IQQJNNCEQNXXKTGYHLB7RO3ZKCF4F7GK6FJH66BOOKDDRGNIQC
2WVP47RBNL5OVYMAZH7TKRYD7F2TGSZ5X74PWVGAYCQP26G3JUHQC
FWR2SQFT5VG5BG7DJQQ7NKT2O5OMS3ID2OKLSR4L2CSZUL3RJJYQC
T4FNOPMWYYJHJBTTY33PB43HTJPKEC46L62YERTWIX73HYZSELXQC
KBNY5FWKTEAKABFCLPC3QFKFSVZKAGXINPCIFV6WDSWFO4VCKNTAC
ODQ7LIJ2UROGGENIORRXZFWII3ZM2N45YD53FKWDQB7LLRXR4PHAC
msg = replace_all(msg, "@hand@", target->hand_name(false));
msg = replace_all(msg, "@hands@", target->hand_name(true));
if (hand_str.empty())
{
msg = replace_all(msg, "@hand@", target->hand_name(false));
msg = replace_all(msg, "@hands@", target->hand_name(true));
}
else
{
msg = replace_all(msg, "@hand@", hand_str);
if (can_plural_hand)
msg = replace_all(msg, "@hands@", pluralise(hand_str));
else
msg = replace_all(msg, "@hands@", hand_str);
}
}
std::string melee_attack::wep_name(description_level_type desc,
unsigned long ignore_flags) const
{
ASSERT(weapon != NULL);
if (attacker->atype() == ACT_PLAYER)
return weapon->name(desc, false, false, false, false, ignore_flags);
std::string name;
bool possessive = false;
if (desc == DESC_CAP_YOUR)
{
desc = DESC_CAP_THE;
possessive = true;
}
else if (desc == DESC_NOCAP_YOUR)
{
desc = DESC_NOCAP_THE;
possessive = true;
}
if (possessive)
{
name = atk_name(desc);
name += "'s ";
// Proper English-language possessive.
name = replace_all(name, "s's ", "s' ");
}
name += weapon->name(desc, false, false, false, false, ignore_flags);
return (name);
int clone_chance = can_clone ? 1 : 0;
int poly_chance = can_poly ? 1 : 0;
int poly_up_chance = can_poly && mon ? 1 : 0;
int shifter_chance = can_poly && mon ? 1 : 0;
int clone_chance = can_clone ? 1 : 0;
int poly_chance = can_poly ? 1 : 0;
int poly_up_chance = can_poly && mon ? 1 : 0;
int shifter_chance = can_poly && mon ? 1 : 0;
int rage_chance = can_rage ? 10 : 0;
int miscast_chance = 10;
break;
case CHAOS_MISCAST:
{
int level = defender->get_experience_level();
// At level == 27 there's a 20.3% chance of a level 3 miscast.
int level1_chance = level;
int level2_chance = std::max( 0, level - 7);
int level3_chance = std::max( 0, level - 15);
level = random_choose_weighted(
level1_chance, 1,
level2_chance, 2,
level3_chance, 3,
0);
miscast_level = level;
miscast_type = SPTYP_RANDOM;
miscast_target = coinflip() ? attacker : defender;
static bool _move_stairs(const actor* attacker, const actor* defender)
{
const coord_def orig_pos = attacker->pos();
const dungeon_feature_type stair_feat = grd(orig_pos);
if (grid_stair_direction(stair_feat) == CMD_NO_CMD)
return (false);
// Moving shops is too much trouble, and anyways the player can't
// use them to escape.
if (stair_feat == DNGN_ENTER_SHOP)
return false;
const bool stair_is_marker = env.markers.find(orig_pos, MAT_ANY);
coord_def dest(-1, -1);
// Prefer to send it under the defender.
if (defender->alive() && defender->pos() != attacker->pos())
{
dungeon_feature_type feat = grd(defender->pos());
if (!grid_destroys_items(feat) && !grid_is_solid(feat)
&& !grid_is_water(feat) && !grid_is_trap(feat, true))
{
dest = defender->pos();
}
// Don't try to swap two markers.
if (stair_is_marker && env.markers.find(defender->pos(), MAT_ANY))
dest.set(-1, -1);
}
if (!in_bounds(dest))
{
radius_iterator ri(attacker->pos(), 1, true, false, true);
int squares = 0;
for (; ri; ++ri)
{
// Don't try to swap two markers.
if (stair_is_marker && env.markers.find(*ri, MAT_ANY))
continue;
dungeon_feature_type feat = grd(defender->pos());
if (!grid_destroys_items(feat) && !grid_is_solid(feat)
&& !grid_is_water(feat) && !grid_is_trap(feat, true))
{
if (one_chance_in(++squares))
dest = *ri;
}
}
}
if (!in_bounds(dest))
return (false);
ASSERT(dest != orig_pos);
const bool dest_is_marker = env.markers.find(dest, MAT_ANY);
const dungeon_feature_type dest_feat = grd(defender->pos());
ASSERT(!(dest_is_marker && stair_is_marker));
dungeon_terrain_changed(orig_pos, dest_feat);
dungeon_terrain_changed(dest, stair_feat);
if (stair_is_marker)
{
env.markers.move(orig_pos, dest);
dungeon_events.move_listeners(orig_pos, dest);
}
else if (dest_is_marker)
{
env.markers.move(dest, orig_pos);
dungeon_events.move_listeners(dest, orig_pos);
}
if (!see_grid(orig_pos) && !see_grid(dest))
return (true);
std::string orig_actor, dest_actor;
if (orig_pos == you.pos())
orig_actor = "you";
else if (mgrd(orig_pos) != NON_MONSTER)
{
monsters &mon(menv[mgrd(orig_pos)]);
if (you.can_see(&mon))
orig_actor = mon.name(DESC_NOCAP_THE);
}
if (dest == you.pos())
dest_actor = "you";
else if (mgrd(dest) != NON_MONSTER)
{
monsters &mon(menv[mgrd(dest)]);
if (you.can_see(&mon))
dest_actor = mon.name(DESC_NOCAP_THE);
}
std::string stair_name =
feature_description(dest, false,
see_grid(orig_pos) ? DESC_CAP_THE : DESC_CAP_A,
false);
std::string prep;
if (grid_stair_direction(stair_feat) == CMD_GO_DOWNSTAIRS
&& (stair_name.find("stair") || grid_is_escape_hatch(stair_feat)))
{
prep = "beneath";
}
else if (grid_is_escape_hatch(stair_feat))
prep = "above";
else
prep = "beside";
std::ostringstream str;
str << stair_name << " ";
if (see_grid(orig_pos) && !see_grid(dest))
{
str << "suddenly disappears";
if (!orig_actor.empty())
str << " from " << prep << " " << orig_actor;
}
else if (!see_grid(orig_pos) && see_grid(dest))
{
str << "suddenly appears";
if (!dest_actor.empty())
str << " " << prep << " " << dest_actor;
}
else
{
str << "moves";
if (!orig_actor.empty())
str << " from " << prep << " " << orig_actor;
if (!dest_actor.empty())
str << " to " << prep << " " << dest_actor;
}
str << "!";
mpr(str.str().c_str());
return (true);
}
#define DID_AFFECT() \
{ \
if (miscast_level == 0) \
miscast_level = -1; \
return; \
}
if (miscast_level >= 1 || !attacker->alive())
return;
// Move stairs out from under the attacker.
if (one_chance_in(100) && _move_stairs(attacker, defender))
DID_AFFECT();
// Dump attacker or items under attacker to another level.
if (is_valid_shaft_level()
&& (attacker->will_trigger_shaft()
|| igrd(attacker->pos()) != NON_ITEM)
&& one_chance_in(1000))
{
(void) attacker->do_shaft();
DID_AFFECT();
}
// Make a loud noise.
if (weapon && player_can_hear(attacker->pos())
&& one_chance_in(1000))
{
std::string msg = wep_name(DESC_CAP_YOUR);
msg += " twangs alarmingly!";
if (!you.can_see(attacker))
msg = "You hear a loud twang.";
noisy(15, attacker->pos(), msg.c_str());
DID_AFFECT();
}
return;
static void _find_remains(monsters* mon, int &corpse_class, int &corpse,
int &last_item, std::vector<int> items)
{
for (int i = 0; i < NUM_MONSTER_SLOTS; i++)
{
const int idx = mon->inv[i];
if (idx == NON_ITEM)
continue;
item_def &item(mitm[idx]);
if (!is_valid_item(item) || item.pos != mon->pos())
continue;
items.push_back(idx);
}
corpse = NON_ITEM;
last_item = NON_ITEM;
corpse_class = mons_species(mon->type);
if (corpse_class == MONS_DRACONIAN)
corpse_class = draco_subspecies(mon);
if (mon->has_ench(ENCH_SHAPESHIFTER))
corpse_class = MONS_SHAPESHIFTER;
else if (mon->has_ench(ENCH_GLOWING_SHAPESHIFTER))
corpse_class = MONS_GLOWING_SHAPESHIFTER;
// Stop at first non-matching corpse, since the freshest corpse will
// be at the top of the stack.
for (stack_iterator si(mon->pos()); si; ++si)
{
if (si->base_type == OBJ_CORPSES && si->sub_type == CORPSE_BODY)
{
if (si->plus != corpse_class)
break;
// It should have just been dropped.
if (corpse_freshness(*si) != FRESHEST_CORPSE)
break;
// If there was an opportunity to butcher it then it can't
// have just been dropped.
if (si->plus2 != 0)
break;
// It can't have been just made if it was picked up or
// dropped.
if (si->flags & (ISFLAG_DROPPED | ISFLAG_BEEN_IN_INV))
break;
// If it's a hydra the number of heads must match.
if ((int) mon->number != si->props[MONSTER_NUMBER].get_long())
break;
// Got it!
corpse = si.link();
break;
}
else
{
// Last item which we're sure belonded to the monster.
for (unsigned int i = 0; i < items.size(); i++)
{
if (items[i] == si.link())
last_item = si.link();
}
}
}
}
static bool _make_zombie(monsters* mon, int corpse_class, int corpse,
int last_item)
{
// If the monster dropped a corpse then don't waste it by turning
// it into a zombie.
if (corpse != NON_ITEM || !mons_class_can_be_zombified(corpse_class))
return (false);
int idx = get_item_slot();
if (idx != NON_ITEM && last_item != NON_ITEM)
{
// Fake a corpse
item_def &corpse_item(mitm[idx]);
corpse_item.base_type = OBJ_CORPSES;
corpse_item.sub_type = CORPSE_BODY;
corpse_item.plus = corpse_class;
corpse_item.orig_monnum = mon->type + 1;
corpse_item.pos = mon->pos();
corpse_item.quantity = 1;
corpse_item.props[MONSTER_NUMBER] = short(mon->number);
// Insert it in the item stack right after the monster's
// last item, so it will be equipped with all the monster's
// items.
corpse_item.link = mitm[last_item].link;
mitm[last_item].link = idx;
if (animate_remains(mon->pos(), CORPSE_BODY, mon->behaviour,
mon->foe, mon->god, true, true))
{
if (you.can_see(mon))
simple_monster_message(mon,
" instantly turns into a zombie!");
else if (see_grid(mon->pos()))
mpr("A zombie appears out of nowhere!");
return (true);
}
}
return (false);
}
void melee_attack::chaos_killed_defender(monsters* def_copy)
void melee_attack::chaos_killed_defender(monsters* mon)
{
ASSERT(mon->type != -1 && mon->type != MONS_PROGRAM_BUG);
ASSERT(in_bounds(mon->pos()));
ASSERT(!defender->alive());
if (!attacker->alive())
return;
int corpse_class, corpse, last_item;
std::vector<int> items;
_find_remains(mon, corpse_class, corpse, last_item, items);
if (one_chance_in(100) &&
_make_zombie(mon, corpse_class, corpse, last_item))
{
DID_AFFECT();
}
}
void melee_attack::do_miscast()
if (miscast_level == -1)
return;
ASSERT(miscast_target != NULL);
ASSERT(miscast_level >= 0 && miscast_level <= 3);
ASSERT(count_bits(miscast_type) == 1);
if (!miscast_target->alive())
return;
const bool chaos_brand =
weapon && get_weapon_brand(*weapon) == SPWPN_CHAOS;
// If the miscast is happening on the attacker's side and is due to
// a chaos weapon then make smoke/sand/etc pour out of the weapon
// instead of the attacker's hands.
std::string hand_str;
std::string cause = atk_name(DESC_NOCAP_THE);
int source;
const int ignore_mask = ISFLAG_KNOW_CURSE | ISFLAG_KNOW_PLUSES;
if (attacker->atype() == ACT_PLAYER)
{
source = NON_MONSTER;
if (chaos_brand)
{
cause = "a chaos effect from ";
// Ignore a lot of item flags to make cause as short as possible,
// so it will (hopefully) fit onto a single line in the death
// cause screen.
cause += wep_name(DESC_NOCAP_YOUR,
ignore_mask
| ISFLAG_COSMETIC_MASK | ISFLAG_RACIAL_MASK);
if (miscast_target == attacker)
hand_str = wep_name(DESC_PLAIN, ignore_mask);
}
}
else
{
source = attacker->mindex();
if (chaos_brand && miscast_target == attacker
&& you.can_see(attacker))
{
hand_str = wep_name(DESC_PLAIN, ignore_mask);
}
}
MiscastEffect(miscast_target, source, (spschool_flag_type) miscast_type,
miscast_level, cause, NH_NEVER, hand_str, false);
// Don't do miscast twice for one attack.
miscast_level = -1;
int brands[] = {SPWPN_FLAMING, SPWPN_FREEZING, SPWPN_ELECTROCUTION,
SPWPN_VENOM, SPWPN_DRAINING, SPWPN_VAMPIRICISM,
SPWPN_PAIN, SPWPN_DISTORTION, SPWPN_CONFUSE,
SPWPN_CHAOS, SPWPN_NORMAL};
return (RANDOM_ELEMENT(brands));
return (random_choose_weighted(
15, SPWPN_NORMAL,
10, SPWPN_FLAMING,
10, SPWPN_FREEZING,
10, SPWPN_ELECTROCUTION,
10, SPWPN_VENOM,
10, SPWPN_CHAOS,
5, SPWPN_VORPAL,
5, SPWPN_DRAINING,
5, SPWPN_VAMPIRICISM,
2, SPWPN_CONFUSE,
2, SPWPN_DISTORTION,
0));
if (!attacker->alive() || attacker == defender)
// Also, bail if the monster is attacking itself without a weapon
// since intrinsic monster attack flavours aren't applied for
// self attacks.
if (!attacker->alive() || (attacker == defender && !weapon))
{
if (miscast_target == defender)
do_miscast();