Skip to content

Commit

Permalink
Player Charm: new effect (WIP)
Browse files Browse the repository at this point in the history
Allow monsters to finally get to use a wand of charming on the player.

Previously monsters could use the wand but would never use it on the
player; only on their allies. This made it useless against anyone not
using allies, and even then exploitable by just leaving allies behind
when facing one. Consequently it was not such a high-tier threat even
though it could be (compared to how useful it is to a player).

The new effect is designed to be a serious threat, especially from a
monster that already has a pack of their own allies, as well as
attempting to be mechanically distinct from other debuffs that tread
similar "mind control" territory (e.g. Mesmerisation or Fear).

A successful use will make all *same-species* (TBD) monsters in LOS gain a
"charmer" status, with a new pink halo as it's fundamentally an
alignment change (from the player's perspective).

You are tricked into thinking these monsters are your friends, and your
allies will now also consider them aligned (TBD). You can attempt to
attack them but will now receive a warning much like when attempting
to attack allies, as 25% (TBD) of any damage you cause them will now be
reflected back sympathetically onto yourself, although this will not
kill you directly.

On attacking (or being attacked by) any "charmer" monster you will make
another willpower check (with considerably worse chances than the
initial enchantment roll) to break free of their glamour. This simulates
you fighting against your own will to regain control of your faculties.

This gives the player rather more agency and decision-making in dealing
with the threat (it's even worth taking a resistance potion *after*
being charmed as it will help with subsequent will checks).
(TBD how curing / quicksilver / yara's work).
  • Loading branch information
chucksellick committed Sep 8, 2024
1 parent 83ad1f2 commit 59b4a54
Show file tree
Hide file tree
Showing 29 changed files with 279 additions and 96 deletions.
9 changes: 6 additions & 3 deletions crawl-ref/source/beam.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3722,9 +3722,11 @@ void bolt::affect_player_enchantment(bool resistible)

case BEAM_CHARM:
mprf(MSGCH_WARN, "Your will is overpowered!");
confuse_player(5 + random2(3));
// Double original effective spell power to be used on escape checks
// (degree will decay with the enchantment though)
you.charm(agent(), ench_power * 2);
obvious_effect = true;
break; // charming - confusion?
break;

case BEAM_BANISH:
if (YOU_KILL(thrower))
Expand Down Expand Up @@ -4690,7 +4692,8 @@ void bolt::handle_stop_attack_prompt(monster* mon)
{
string adj, suffix;
bool penance;
if (bad_attack(mon, adj, suffix, penance, target))
bool self_hurt;
if (bad_attack(mon, adj, suffix, penance, self_hurt, target))
friendly_past_target = true;
return;
}
Expand Down
1 change: 1 addition & 0 deletions crawl-ref/source/enchant-type.h
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ enum enchant_type
ENCH_CHANGED_APPEARANCE, // Visual change for player shadow during Shadowslip
ENCH_SHADOWLESS,
ENCH_AMNESIA,
ENCH_CHARMER,
// Update enchant_names[] in mon-ench.cc when adding or removing
// enchantments.
NUM_ENCHANTMENTS
Expand Down
23 changes: 17 additions & 6 deletions crawl-ref/source/fight.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1233,7 +1233,8 @@ int mons_weapon_damage_rating(const item_def &launcher)
}

bool bad_attack(const monster *mon, string& adj, string& suffix,
bool& would_cause_penance, coord_def attack_pos)
bool& would_cause_penance, bool& could_self_hurt,
coord_def attack_pos)
{
ASSERT(mon); // XXX: change to const monster &mon
ASSERT(!crawl_state.game_is_arena());
Expand All @@ -1247,10 +1248,18 @@ bool bad_attack(const monster *mon, string& adj, string& suffix,
adj.clear();
suffix.clear();
would_cause_penance = false;
could_self_hurt = false;

if (is_sanctuary(mon->pos()) || is_sanctuary(attack_pos))
suffix = ", despite your sanctuary";

if (mon->has_ench(ENCH_CHARMER))
{
adj = "your \"friend\" ";
could_self_hurt = true;
return true;
}

if (mon->friendly())
{
// There's not really any harm in attacking your own spectral weapon.
Expand All @@ -1271,7 +1280,6 @@ bool bad_attack(const monster *mon, string& adj, string& suffix,
adj += "the ";

would_cause_penance = true;

}
else
{
Expand Down Expand Up @@ -1313,6 +1321,7 @@ bool stop_attack_prompt(const monster* mon, bool beam_attack,
{
ASSERT(mon); // XXX: change to const monster &mon
bool penance = false;
bool self_hurt = false;

if (prompted)
*prompted = false;
Expand All @@ -1327,7 +1336,7 @@ bool stop_attack_prompt(const monster* mon, bool beam_attack,
return false;

string adj, suffix;
if (!bad_attack(mon, adj, suffix, penance, attack_pos))
if (!bad_attack(mon, adj, suffix, penance, self_hurt, attack_pos))
return false;

// We have already determined this attack *would* prompt, so stop here
Expand Down Expand Up @@ -1356,9 +1365,10 @@ bool stop_attack_prompt(const monster* mon, bool beam_attack,
else
verb = "attack ";

const string prompt = make_stringf("Really %s%s%s?%s",
const string prompt = make_stringf("Really %s%s%s?%s%s",
verb.c_str(), mon_name.c_str(), suffix.c_str(),
penance ? " This attack would place you under penance!" : "");
penance ? " This attack would place you under penance!" : "",
self_hurt ? " You might inflict damage on yourself!" : "");

if (prompted)
*prompted = true;
Expand Down Expand Up @@ -1407,7 +1417,8 @@ bool stop_attack_prompt(targeter &hitfunc, const char* verb,

string adjn, suffixn;
bool penancen = false;
if (bad_attack(mon, adjn, suffixn, penancen))
bool self_hurt = false;
if (bad_attack(mon, adjn, suffixn, penancen, self_hurt))
{
// record the adjectives for the first listed, or
// first that would cause penance
Expand Down
2 changes: 1 addition & 1 deletion crawl-ref/source/fight.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ bool player_unrand_bad_attempt(const item_def &weapon,
bool check_only);

bool bad_attack(const monster *mon, string& adj, string& suffix,
bool& would_cause_penance,
bool& would_cause_penance, bool& could_self_hurt,
coord_def attack_pos = coord_def(0, 0));

bool stop_attack_prompt(const monster* mon, bool beam_attack,
Expand Down
99 changes: 99 additions & 0 deletions crawl-ref/source/fineff.cc
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,18 @@ bool deferred_damage_fineff::mergeable(const final_effect &fe) const
&& attacker_effects == o->attacker_effects && fatal == o->fatal;
}

bool charmer_damage_share_fineff::mergeable(const final_effect &fe) const
{
const charmer_damage_share_fineff *o = dynamic_cast<const charmer_damage_share_fineff *>(&fe);
return o && att == o->att && def == o->def;
}

bool charmer_escape_fineff::mergeable(const final_effect &fe) const
{
const charmer_escape_fineff *o = dynamic_cast<const charmer_escape_fineff *>(&fe);
return o && att == o->att && def == o->def;
}

bool starcursed_merge_fineff::mergeable(const final_effect &fe) const
{
const starcursed_merge_fineff *o = dynamic_cast<const starcursed_merge_fineff *>(&fe);
Expand Down Expand Up @@ -202,6 +214,24 @@ void deferred_damage_fineff::merge(const final_effect &fe)
damage += ddamfe->damage;
}

void charmer_damage_share_fineff::merge(const final_effect &fe)
{
const charmer_damage_share_fineff *ddamfe =
dynamic_cast<const charmer_damage_share_fineff *>(&fe);
ASSERT(ddamfe);
ASSERT(mergeable(*ddamfe));
damage += ddamfe->damage;
}

void charmer_escape_fineff::merge(const final_effect &fe)
{
const charmer_escape_fineff *ddamfe =
dynamic_cast<const charmer_escape_fineff *>(&fe);
ASSERT(ddamfe);
ASSERT(mergeable(*ddamfe));
damage += ddamfe->damage;
}

void shock_discharge_fineff::merge(const final_effect &fe)
{
const shock_discharge_fineff *ssdfe =
Expand Down Expand Up @@ -432,6 +462,75 @@ void deferred_damage_fineff::fire()
true, attacker_effects);
}

void charmer_damage_share_fineff::fire()
{
// The player has attacked a charming monster takes sympathetic damage
actor *source = defender();
ASSERT(!source || source->is_monster());
monster *charmer = source ? source->as_monster() : nullptr;

if (!charmer || !charmer->alive())
return;

actor *target = attacker();
if (!target || !target->alive())
return;
ASSERT(target->is_player());

player *player = attacker()->as_player();

// Reduce to 1 HP but don't directly kill
// XX: Move flavour texts to monster speech
damage = min(damage, player->hp - 1);
if (charmer)
{
// Chance to end charming based on your willpower
if (player->check_willpower(charmer,
charmer->get_ench(ENCH_CHARMER).degree))
{
mprf("You break free of %s mind games!",
apostrophise(charmer->name(DESC_THE)).c_str());
charmer->del_ench(ENCH_CHARMER);
return;
}
mprf("You share in the suffering of your friend %s!",
charmer->name(DESC_THE).c_str());
}
else
mpr("You anguish over the death of your friend!");

// Register as player hurting themselves to avoid triggering a bonus
// escape fineff (and really the damage was self-inflicted anyway)
// XX: Or should the damage be attributable to the monster and use a kill type to prevent recursion?
player->hurt(player, damage);
}

void charmer_escape_fineff::fire()
{
actor *source = attacker();
monster *charmer = source ? source->as_monster() : nullptr;

if (!charmer || !charmer->alive())
return;

actor *target = defender();
if (!target || !target->alive())
return;
ASSERT(target->is_player());

player *player = target->as_player();

// Chance to end charming based on your willpower
if (player->check_willpower(charmer,
charmer->get_ench(ENCH_CHARMER).degree))
{
mprf("You break free of %s mind games!",
apostrophise(charmer->name(DESC_THE)).c_str());
charmer->del_ench(ENCH_CHARMER);
return;
}
}

static void _do_merge_masses(monster* initial_mass, monster* merge_to)
{
// Combine enchantment durations.
Expand Down
42 changes: 42 additions & 0 deletions crawl-ref/source/fineff.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,48 @@ class deferred_damage_fineff : public final_effect
bool fatal;
};

class charmer_damage_share_fineff : public final_effect
{
public:
bool mergeable(const final_effect &a) const override;
void merge(const final_effect &a) override;
void fire() override;

static void schedule(const monster *charmer, int dam)
{
final_effect::schedule(
new charmer_damage_share_fineff(charmer, dam));
}
protected:
charmer_damage_share_fineff(const monster *charmer, int dam)
: final_effect(&you, charmer, coord_def()),
damage(dam)
{
}
int damage;
};

class charmer_escape_fineff : public final_effect
{
public:
bool mergeable(const final_effect &a) const override;
void merge(const final_effect &a) override;
void fire() override;

static void schedule(const monster *charmer, int dam)
{
final_effect::schedule(
new charmer_escape_fineff(charmer, dam));
}
protected:
charmer_escape_fineff(const monster *charmer, int dam)
: final_effect(charmer, &you, coord_def()),
damage(dam)
{
}
int damage;
};

class starcursed_merge_fineff : public final_effect
{
public:
Expand Down
1 change: 1 addition & 0 deletions crawl-ref/source/god-abil.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2529,6 +2529,7 @@ void spare_beogh_convert()

++witc;
orc->del_ench(ENCH_CHARM);
orc->del_ench(ENCH_CHARMER);
mons_pacify(*orc, ATT_GOOD_NEUTRAL, true);
}

Expand Down
4 changes: 2 additions & 2 deletions crawl-ref/source/item-prop.cc
Original file line number Diff line number Diff line change
Expand Up @@ -914,8 +914,8 @@ struct item_set_def
};
static const item_set_def item_sets[] =
{
{ "hex wand", OBJ_WANDS, { WAND_CHARMING, WAND_PARALYSIS } },
{ "beam wand", OBJ_WANDS, { WAND_ACID, WAND_LIGHT, WAND_QUICKSILVER } },
{ "hex wand", OBJ_WANDS, { WAND_CHARMING /*, WAND_PARALYSIS */ } },
{ "beam wand", OBJ_WANDS, { /* WAND_ACID, */ WAND_LIGHT /*, WAND_QUICKSILVER */ } },
{ "blast wand", OBJ_WANDS, { WAND_ICEBLAST, WAND_ROOTS, WAND_WARPING } },
{ "ally scroll", OBJ_SCROLLS, { SCR_SUMMONING, SCR_BUTTERFLIES } },
{ "area misc", OBJ_MISCELLANY, { MISC_CONDENSER_VANE,
Expand Down
2 changes: 2 additions & 0 deletions crawl-ref/source/menu.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2412,6 +2412,8 @@ bool MonsterMenuEntry::get_tiles(vector<tile_def>& tileset) const
tileset.emplace_back(TILE_HALO_GD_NEUTRAL);
else if (m->neutral())
tileset.emplace_back(TILE_HALO_NEUTRAL);
else if (m->has_trivial_ench(ENCH_CHARMER))
tileset.emplace_back(TILE_HALO_CHARMER);
else if (Options.tile_show_threat_levels.find("unusual") != string::npos
&& m->has_unusual_items())
tileset.emplace_back(TILE_THREAT_UNUSUAL);
Expand Down
Loading

0 comments on commit 59b4a54

Please sign in to comment.