Skip to content

Commit

Permalink
Mind Control: L9 Hex/Tloc spell (WIP)
Browse files Browse the repository at this point in the history
Capstone spell of both hex and translocations schools, allows you to take full physical control of a monster for a few turns (with limitations of course).

You can only make the victim move around and use melee attacks - not even range combat, although this be considered (but I wanted to keep things simple). How many turns it lasts will be reduced dependent upon the puppet's willpower.

While doing this the player's original body is left prone with both EV and SH set to 0, as you are unable to control two bodies at once. Additionally you can't use any spells or abilities (except for one "End Mind Control" ability) as it takes all your concentration to control a third party. If LOS is broken the control will end.

Nevertheless it's an extremely powerful and useful spell; you can start fights, use them as a meat shield, move them away just to get some distance, and even combine with MCC for a powerful combo (requiring extremely high investment in three schools of course).
  • Loading branch information
chucksellick committed Sep 15, 2024
1 parent 74af7b1 commit 27b6654
Show file tree
Hide file tree
Showing 37 changed files with 441 additions and 47 deletions.
2 changes: 2 additions & 0 deletions crawl-ref/source/ability-type.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ enum ability_type
ABIL_MUD_BREATH,
// Coglins
ABIL_INVENT_GIZMO,
// Mind Control
ABIL_END_MIND_CONTROL,

// Note: this is getting dangerously close to ABIL_EVOKE_BERSERK! be careful

Expand Down
23 changes: 23 additions & 0 deletions crawl-ref/source/ability.cc
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,8 @@ static vector<ability_def> &_get_ability_list()
0, 0, 0, -1, {}, abflag::none },
{ ABIL_INVENT_GIZMO, "Invent Gizmo",
0, 0, 0, -1, {}, abflag::none },
{ ABIL_END_MIND_CONTROL, "End Mind Control",
0, 0, 0, -1, {}, abflag::conf_ok },

// EVOKE abilities use Evocations and come from items.
{ ABIL_EVOKE_BLINK, "Evoke Blink",
Expand Down Expand Up @@ -1824,6 +1826,17 @@ static bool _check_ability_possible(const ability_def& abil, bool quiet = false)
if (abil.ability >= ABIL_FIRST_WIZ)
return you.wizard;
#endif

// No other abilities allowed during mind control, you're too busy
if (you.duration[DUR_MIND_CONTROL])
{
if (abil.ability == ABIL_END_MIND_CONTROL)
return true;
if (!quiet)
mprf("It takes all of your concentration to sustain mind control.");
return false;
}

if (you.berserk() && !testbits(abil.flags, abflag::berserk_ok))
{
if (!quiet)
Expand Down Expand Up @@ -2692,6 +2705,7 @@ unique_ptr<targeter> find_ability_targeter(ability_type ability)
case ABIL_EVOKE_TURN_INVISIBLE:
case ABIL_END_TRANSFORMATION:
case ABIL_BEGIN_UNTRANSFORM:
case ABIL_END_MIND_CONTROL:
case ABIL_ZIN_VITALISATION:
case ABIL_TSO_DIVINE_SHIELD:
case ABIL_YRED_RECALL_UNDEAD_HARVEST:
Expand Down Expand Up @@ -3297,6 +3311,11 @@ static spret _do_ability(const ability_def& abil, bool fail, dist *target,
return spret::abort;
break;

case ABIL_END_MIND_CONTROL:
mprf("You return to controlling your own body.");
end_mind_control(true);
break;

// INVOCATIONS:
case ABIL_ZIN_RECITE:
{
Expand Down Expand Up @@ -4253,6 +4272,9 @@ bool player_has_ability(ability_type abil, bool include_unusable)
case ABIL_BEGIN_UNTRANSFORM:
return you.form == you.default_form
&& you.default_form != transformation::none;
// spells
case ABIL_END_MIND_CONTROL:
return you.duration[DUR_MIND_CONTROL];
// TODO: other god abilities
case ABIL_RENOUNCE_RELIGION:
return !you_worship(GOD_NO_GOD);
Expand Down Expand Up @@ -4329,6 +4351,7 @@ vector<talent> your_talents(bool check_confused, bool include_unusable, bool ign
ABIL_EVOKE_TURN_INVISIBLE,
ABIL_EVOKE_DISPATER,
ABIL_EVOKE_OLGREB,
ABIL_END_MIND_CONTROL,
#ifdef WIZARD
ABIL_WIZ_BUILD_TERRAIN,
ABIL_WIZ_SET_TERRAIN,
Expand Down
4 changes: 3 additions & 1 deletion crawl-ref/source/acquire.cc
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,7 @@ static const vector<pair<misc_item_type, int> > _misc_base_weights()
{MISC_PHIAL_OF_FLOODS, 20},
{MISC_CONDENSER_VANE, 20},
{MISC_GRAVITAMBOURINE, 20},
{MISC_VOODOO_PAW, 20},
};
// The player never needs more than one of any of these.
for (auto &p : choices)
Expand Down Expand Up @@ -598,7 +599,8 @@ static int _acquirement_misc_subtype(int & /*quantity*/,
MISC_LIGHTNING_ROD,
MISC_PHIAL_OF_FLOODS,
MISC_CONDENSER_VANE,
MISC_GRAVITAMBOURINE);
MISC_GRAVITAMBOURINE,
MISC_VOODOO_PAW);
}

return *choice;
Expand Down
5 changes: 5 additions & 0 deletions crawl-ref/source/duration-data.h
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,11 @@ static const duration_def duration_data[] =
D_DISPELLABLE | D_EXPIRES, {{ "",
[](){mprf(MSGCH_RECOVERY, "Your spells flood back into your mind.");}}}},

{ DUR_MIND_CONTROL, LIGHTBLUE, "Mind", "using mind control", "mind control",
"You are controlling the mind of another.", D_DISPELLABLE | D_EXPIRES, {{ "",
[](){mind_control_end_effect();}}, "You feel your mind control about to slip"
}, 1},

// The following are visible in wizmode only, or are handled
// specially in the status lights and/or the % or @ screens.

Expand Down
1 change: 1 addition & 0 deletions crawl-ref/source/duration-type.h
Original file line number Diff line number Diff line change
Expand Up @@ -270,5 +270,6 @@ enum duration_type
DUR_EXECUTION,
DUR_GROWING_DESTRUCTION,
DUR_AMNESIA,
DUR_MIND_CONTROL,
NUM_DURATIONS
};
1 change: 1 addition & 0 deletions crawl-ref/source/enchant-type.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ enum enchant_type
ENCH_SHADOWLESS,
ENCH_AMNESIA,
ENCH_CHARMER,
ENCH_MIND_CONTROL,
// Update enchant_names[] in mon-ench.cc when adding or removing
// enchantments.
NUM_ENCHANTMENTS
Expand Down
21 changes: 21 additions & 0 deletions crawl-ref/source/evoke.cc
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,17 @@ static bool _gravitambourine(dist *target)
return true;
}

static bool _voodoo_paw(dist *target)
{
const spret ret = your_spells(SPELL_MIND_CONTROL, _gravitambourine_power(),
false, nullptr, target);

if (ret == spret::abort)
return false;

return true;
}

static transformation _form_for_talisman(const item_def &talisman)
{
if (you.using_talisman(talisman))
Expand Down Expand Up @@ -1173,6 +1184,16 @@ bool evoke_item(item_def& item, dist *preselect)
return false;
break;

case MISC_VOODOO_PAW:
if (_voodoo_paw(preselect))
{
expend_xp_evoker(item.sub_type);
practise_evoking(3);
}
else
return false;
break;

case MISC_HORN_OF_GERYON:
if (!_evoke_horn_of_geryon())
return false;
Expand Down
2 changes: 1 addition & 1 deletion crawl-ref/source/god-abil.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7046,7 +7046,7 @@ spret makhleb_unleash_destruction(int power, bolt& beam, bool fail)
else
{
you.duration[DUR_GROWING_DESTRUCTION] = you.time_taken + 1;
++stacks;
++stacks;
}
}

Expand Down
1 change: 1 addition & 0 deletions crawl-ref/source/item-name.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,7 @@ static string misc_type_name(int type)
case MISC_TIN_OF_TREMORSTONES: return "tin of tremorstones";
case MISC_CONDENSER_VANE: return "condenser vane";
case MISC_GRAVITAMBOURINE: return "Gell's gravitambourine";
case MISC_VOODOO_PAW: return "voodoo paw";

default:
return "buggy miscellaneous item";
Expand Down
2 changes: 2 additions & 0 deletions crawl-ref/source/item-prop-enum.h
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ enum misc_item_type
MISC_TIN_OF_TREMORSTONES,
MISC_CONDENSER_VANE,
MISC_GRAVITAMBOURINE,
MISC_VOODOO_PAW,

NUM_MISCELLANY,
MISC_DECK_UNKNOWN = NUM_MISCELLANY,
Expand Down Expand Up @@ -354,6 +355,7 @@ const vector<misc_item_type> misc_types =
MISC_TIN_OF_TREMORSTONES,
MISC_CONDENSER_VANE,
MISC_GRAVITAMBOURINE,
MISC_VOODOO_PAW,
};

enum missile_type
Expand Down
3 changes: 2 additions & 1 deletion crawl-ref/source/item-prop.cc
Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,8 @@ static const item_set_def item_sets[] =
{ "ally misc", OBJ_MISCELLANY, { MISC_BOX_OF_BEASTS,
MISC_SACK_OF_SPIDERS } },
{ "control misc", OBJ_MISCELLANY, { MISC_PHIAL_OF_FLOODS,
MISC_GRAVITAMBOURINE } },
MISC_GRAVITAMBOURINE,
MISC_VOODOO_PAW } },
};
COMPILE_CHECK(ARRAYSZ(item_sets) == NUM_ITEM_SET_TYPES);

Expand Down
56 changes: 54 additions & 2 deletions crawl-ref/source/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1620,6 +1620,9 @@ static void _take_stairs(bool down)
ASSERT(!crawl_state.game_is_arena());
ASSERT(!crawl_state.arena_suspended);

if (mind_control_prevents_action())
return;

const dungeon_feature_type ygrd = env.grid(you.pos());

const bool shaft = (down && get_trap_type(you.pos()) == TRAP_SHAFT);
Expand Down Expand Up @@ -2017,6 +2020,38 @@ class GameMenu : public Menu
}
};

static bool _mind_control_compatible_command(command_type cmd)
{
// XX: Probably do this in a totally different way. In the end the
// vast majority of commands *should* do something. Just need to
// handle it properly everywhere.

// if (cmd == CMD_DISPLAY_INVENTORY)
// {
// return false;
// }

// Autofight just doesn't work, as the Lua massively assumes it's the
// player fighting. And we want to be careful about our moves anyway.
if (cmd >= CMD_AUTOFIGHT && cmd <= CMD_AUTOFIRE)
return false;

if (cmd >= CMD_MOVE_LEFT && cmd <= CMD_REST
|| cmd >= CMD_CHARACTER_DUMP)
{
return true;
}

switch (cmd)
{
case CMD_USE_ABILITY:
case CMD_LOOK_AROUND:
return true;
default:
return false;
}
}

// Note that in some actions, you don't want to clear afterwards.
// e.g. list_jewellery, etc.
// calling this directly will not record the command for later replay; if you
Expand All @@ -2034,6 +2069,12 @@ void process_command(command_type cmd, command_type prev_cmd)
cmd = m.cmd;
}

if (you.duration[DUR_MIND_CONTROL] && !_mind_control_compatible_command(cmd))
{
mind_control_prevents_action();
return;
}

switch (cmd)
{
#ifdef USE_TILE
Expand Down Expand Up @@ -2146,6 +2187,8 @@ void process_command(command_type cmd, command_type prev_cmd)
break;

case CMD_INSPECT_FLOOR:
if (mind_control_prevents_action())
break;
if (player_on_single_stack() && !you.running)
pickup(true);
else
Expand All @@ -2156,7 +2199,8 @@ void process_command(command_type cmd, command_type prev_cmd)
case CMD_ADJUST_INVENTORY: adjust(); break;

case CMD_SAFE_WAIT:
if (!i_feel_safe(true) && can_rest_here(true))
if (mind_control_prevents_action()
|| !i_feel_safe(true) && can_rest_here(true))
break;
// else fall-through
case CMD_WAIT:
Expand All @@ -2166,6 +2210,8 @@ void process_command(command_type cmd, command_type prev_cmd)

case CMD_PICKUP:
case CMD_PICKUP_QUANTITY:
if (mind_control_prevents_action())
break;
pickup(cmd != CMD_PICKUP);
break;

Expand Down Expand Up @@ -2771,11 +2817,17 @@ static keycode_type _get_next_keycode()
static void _swing_at_target(coord_def move)
{
dist target;
target.target = you.pos() + move;
target.target = you.acting_as_pos() + move;

if (god_protects(monster_at(target.target), false))
return;

if (you.duration[DUR_MIND_CONTROL])
{
mind_control_move(move);
return;
}

// Don't warn the player "too injured to fight recklessly" when they
// explicitly request an attack.
unwind_bool autofight_ok(crawl_state.skip_autofight_check, true);
Expand Down
37 changes: 31 additions & 6 deletions crawl-ref/source/mon-act.cc
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ static bool _handle_pickup(monster* mons);
static bool _monster_move(monster* mons, coord_def& delta);
static bool _monster_swaps_places(monster* mon, const coord_def& delta);
static bool _do_move_monster(monster& mons, const coord_def& delta);
static void _post_monster_move(monster* mons);

/**
* Get the monster's "hit dice".
Expand Down Expand Up @@ -1632,12 +1633,13 @@ static void _pre_monster_move(monster& mons)

// Handle weird stuff like spells/special abilities, item use,
// reaching, swooping, etc.
// Returns true iff the monster used up their turn.
// Returns true if the monster used up their turn.
static bool _mons_take_special_action(monster &mons, int old_energy)
{
if ((mons.asleep() || mons_is_wandering(mons))
// Slime creatures can split while wandering or resting.
&& mons.type != MONS_SLIME_CREATURE)
&& mons.type != MONS_SLIME_CREATURE
|| mons.has_ench(ENCH_MIND_CONTROL))
{
return false;
}
Expand Down Expand Up @@ -1714,14 +1716,33 @@ static bool _mons_take_special_action(monster &mons, int old_energy)
return false;
}

void handle_monster_move(monster* mons)
int force_monster_move(monster* mons, coord_def move)
{
ASSERT(mons);
int start_energy = mons->speed_increment;
handle_monster_move(mons, move);
_post_monster_move(mons);
fire_final_effects();
you.turn_is_over = true;
return start_energy - mons->speed_increment;
}

void handle_monster_move(monster* mons, coord_def force_move)
{
ASSERT(mons);

const monsterentry* entry = get_monster_data(mons->type);
if (!entry)
return;

coord_def mmov;
// Don't make a normal move while mind controlled: only when forced by player
if (mons->has_ench(ENCH_MIND_CONTROL) && force_move.origin())
{
mons->lose_energy(EUT_SPECIAL); // XX: Is it needed?
return;
}

coord_def mmov = force_move;

const bool disabled = crawl_state.disables[DIS_MON_ACT]
&& _unfriendly_or_impaired(*mons);
Expand All @@ -1735,9 +1756,12 @@ void handle_monster_move(monster* mons)
#endif
coord_def old_pos = mons->pos();

if (!mons->has_action_energy())
if (!mons->has_action_energy() && !mons->has_ench(ENCH_MIND_CONTROL))
return;

if (mons->has_ench(ENCH_MIND_CONTROL))
mpr("Here 1");

if (!disabled)
move_solo_tentacle(mons);

Expand Down Expand Up @@ -1975,7 +1999,7 @@ void handle_monster_move(monster* mons)
// Struggling against the net takes time.
_swim_or_move_energy(*mons);
}
else if (!mons->petrified())
else if (!mons->petrified() && force_move.origin())
{
// Calculates mmov based on monster target.
mmov = _find_best_step(mons);
Expand All @@ -1988,6 +2012,7 @@ void handle_monster_move(monster* mons)
mmov = _confused_move_dir(mons);
}
}

if (!mons->asleep())
maybe_mons_speaks(mons);

Expand Down
Loading

0 comments on commit 27b6654

Please sign in to comment.