Skip to content

Commit

Permalink
Revise Beastly Appendange
Browse files Browse the repository at this point in the history
Beastly Appendage has two issues at present.

The first is a charms-like "always cast this low level spell for
marginal benefit", the condition isn't quite always, but any melee
character without boots or a helmet who isn't using other forms should
use this (as should any Op).

The second is that the possibilities of Horns and Talons are not
balanced against eachother, so the player is incentivized to re-cast
until the better aux is granted.

This commit addresses both of those, while buffing the spell slightly to
address concerns that Tm is too weak without Sticks to Snakes as a
crutch. Beastly appendage now melds aux armour slots, weapons, and
shields and gives Horns 2 and Talons 2 (Op keep tentacle spike 3). Two
level 2 aux attacks was selected over a single level 3 so that the spell
synergizes better with Wereblood.
  • Loading branch information
ebering committed Jul 15, 2020
1 parent e385806 commit 1ed4c44
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 106 deletions.
4 changes: 3 additions & 1 deletion crawl-ref/source/attribute-type.h
Expand Up @@ -36,7 +36,9 @@ enum attribute_type
ATTR_PERM_FLIGHT, // Tengu flight or boots of flying are on.
ATTR_SEEN_INVIS_TURN, // Last turn you saw something invisible.
ATTR_SEEN_INVIS_SEED, // Random seed for invis monster positions.
ATTR_APPENDAGE, // eq slot of Beastly Appendage
#if TAG_MAJOR_VERSION == 34
ATTR_UNUSED3, // old Beastly Appendage
#endif
ATTR_TITHE_BASE, // Remainder of untithed gold.
ATTR_EVOL_XP, // XP gained since last evolved mutation
ATTR_LIFE_GAINED, // XL when a felid gained a life.
Expand Down
7 changes: 5 additions & 2 deletions crawl-ref/source/dat/descript/spells.txt
Expand Up @@ -105,12 +105,15 @@ Beastly Appendage spell
"attacks. It is not powerful enough to affect tentacles " ..
"which are already mutated."
else
return "Causes monstrous horns or talons to grow from the caster's " ..
return "Causes monstrous horns and talons to grow from the caster's " ..
"body, granting them a chance of making an extra attack in " ..
"melee. It is not powerful enough to meld armour or enhance " ..
"melee. It is not powerful enough to enhance " ..
"appendages that are already nonhuman."
end
}}

While transformed, any equipped weapons, shields, and auxilliary armour are
melded.
%%%%
Berserker Rage spell

Expand Down
4 changes: 2 additions & 2 deletions crawl-ref/source/form-data.h
Expand Up @@ -149,9 +149,9 @@ static const form_entry formdata[] =
},

{
transformation::appendage, MONS_PLAYER, "App", "appendage", "appendage",
transformation::appendage, MONS_PLAYER, "App", "appendages", "appendages",
"",
EQF_NONE, MR_NO_FLAGS,
EQF_HANDS | EQF_AUXES, MR_NO_FLAGS,
FormDuration(10, PS_DOUBLE, 60), 0, 0, SIZE_CHARACTER, 10,
0, 0, 0, true, 0, 0, 3,
SPWPN_NORMAL, LIGHTGREY, "", DEFAULT_VERBS,
Expand Down
10 changes: 0 additions & 10 deletions crawl-ref/source/item-use.cc
Expand Up @@ -1006,16 +1006,6 @@ bool can_wear_armour(const item_def &item, bool verbose, bool ignore_temporary)
return false;
}

if (you.form == transformation::appendage
&& ignore_temporary
&& slot == beastly_slot(you.attribute[ATTR_APPENDAGE])
&& you.has_mutation(static_cast<mutation_type>(you.attribute[ATTR_APPENDAGE])))
{
unwind_var<uint8_t> mutv(you.mutation[you.attribute[ATTR_APPENDAGE]], 0);
// disable the mutation then check again
return can_wear_armour(item, verbose, ignore_temporary);
}

if (sub_type == ARM_GLOVES)
{
if (you.has_claws(false) == 3)
Expand Down
12 changes: 10 additions & 2 deletions crawl-ref/source/mutation.cc
Expand Up @@ -1909,6 +1909,14 @@ static bool _delete_single_mutation_level(mutation_type mutat,
return true;
}

static bool _is_appendage_mutation(mutation_type mut)
{
for (auto app : you.props[APPENDAGE_KEY].get_vector())
if (mut == static_cast<mutation_type>(app.get_int()))
return true;
return false;
}

/*
* Delete a mutation level, accepting random mutation types and checking mutation resistance.
* This will not delete temporary or innate mutations.
Expand Down Expand Up @@ -1986,7 +1994,7 @@ bool delete_mutation(mutation_type which_mutation, const string &reason,
continue;

// MUT_ANTENNAE is 0, and you.attribute[] is initialized to 0.
if (mutat && mutat == you.attribute[ATTR_APPENDAGE])
if (mutat && _is_appendage_mutation(mutat))
continue;

const mutation_def& mdef = _get_mutation_def(mutat);
Expand Down Expand Up @@ -2303,7 +2311,7 @@ string mutation_desc(mutation_type mut, int level, bool colour,
colourname = "darkgrey";
else if (partially_active)
colourname = "brown";
else if (you.form == transformation::appendage && you.attribute[ATTR_APPENDAGE] == mut)
else if (_is_appendage_mutation(mut) && you.form == transformation::appendage)
colourname = "lightgreen";
else if (is_slime_mutation(mut))
colourname = "green";
Expand Down
1 change: 1 addition & 0 deletions crawl-ref/source/tag-version.h
Expand Up @@ -248,6 +248,7 @@ enum tag_minor_version
TAG_MINOR_LOAF_BUST, // Remove rations, eating, and hunger mechanics
TAG_MINOR_REVEALED_TRAPS, // No skill check to spot traps
TAG_MINOR_BARDING_MERGE, // Merge naga and centaur bardings.
TAG_MINOR_APPENDAGE, // Change beastly appendage
#endif
NUM_TAG_MINORS,
TAG_MINOR_VERSION = NUM_TAG_MINORS - 1
Expand Down
18 changes: 18 additions & 0 deletions crawl-ref/source/tags.cc
Expand Up @@ -3844,6 +3844,24 @@ static void _tag_read_you(reader &th)
you.props[NEMELEX_STACK_KEY].get_vector().push_back(card);
}
}

// Appendage changed to meld, so let's untransform players who were using
// the old one
if (th.getMinorVersion() < TAG_MINOR_APPENDAGE
&& you.form == transformation::appendage)
{
you.form = transformation::none;
you.duration[DUR_TRANSFORMATION] = 0;
const mutation_type app = static_cast<mutation_type>(you.attribute[ATTR_UNUSED3]);
const int levels = you.get_base_mutation_level(app);
const int beast_levels = app == MUT_HORNS ? 2 : 3;
// Preserve extra mutation levels acquired after transforming.
const int extra = max(0, levels - you.get_innate_mutation_level(app)
- beast_levels);
you.mutation[app] = you.get_innate_mutation_level(app) + extra;
you.attribute[ATTR_UNUSED3] = 0;
}

#endif
}

Expand Down
175 changes: 86 additions & 89 deletions crawl-ref/source/transform.cc
Expand Up @@ -44,6 +44,9 @@ static const int EQF_NONE = 0;
// "hand" slots (not rings)
static const int EQF_HANDS = SLOTF(EQ_WEAPON) | SLOTF(EQ_SHIELD)
| SLOTF(EQ_GLOVES);
// "aux" body slots (beastly appendage);
static const int EQF_AUXES = SLOTF(EQ_GLOVES) | SLOTF(EQ_BOOTS)
| SLOTF(EQ_CLOAK) | SLOTF(EQ_HELMET);
// core body slots (statue form)
static const int EQF_STATUE = SLOTF(EQ_GLOVES) | SLOTF(EQ_BOOTS)
| SLOTF(EQ_BODY_ARMOUR);
Expand Down Expand Up @@ -778,35 +781,55 @@ class FormAppendage : public Form

string get_description(bool past_tense) const override
{
if (you.attribute[ATTR_APPENDAGE] == MUT_TENTACLE_SPIKE)
ostringstream desc;
for (auto app : you.props[APPENDAGE_KEY].get_vector())
{
return make_stringf("One of your tentacles %s a temporary spike.",
past_tense ? "had" : "has");
mutation_type mut = static_cast<mutation_type>(app.get_int());
if (mut == MUT_TENTACLE_SPIKE)
{
string tense = past_tense ? "had" : "has";
desc << "One of your tentacles " << tense;
desc << " a temporary spike. ";

}
else
{
string tense = past_tense ? "had" : "have";
desc << "You " << tense << " grown temporary ";
desc << mutation_name(mut) << ". ";
}
}

return make_stringf("You %s grown temporary %s.",
past_tense ? "had" : "have",
mutation_name((mutation_type)
you.attribute[ATTR_APPENDAGE]));
return trimmed_string(desc.str());
}

/**
* Get a message for transforming into this form.
*/
string transform_message(transformation /*previous_trans*/) const override
{
// ATTR_APPENDAGE must be set earlier!
switch (you.attribute[ATTR_APPENDAGE])
ostringstream msg;
for (auto app : you.props[APPENDAGE_KEY].get_vector())
{
case MUT_HORNS:
return "You grow a pair of large bovine horns.";
case MUT_TENTACLE_SPIKE:
return "One of your tentacles grows a vicious spike.";
case MUT_TALONS:
return "Your feet morph into talons.";
default:
die("Unknown beastly appendage.");
mutation_type mut = static_cast<mutation_type>(app.get_int());
switch (mut)
{
case MUT_HORNS:
msg << "You grow a pair of large bovine horns. ";
break;
case MUT_TENTACLE_SPIKE:
msg << "One of your tentacles grows a vicious spike. ";
break;
case MUT_TALONS:
msg << "Your feet morph into talons. ";
break;
default:
die("Unknown appendage type");
break;
}
}

return trimmed_string(msg.str());
}

/**
Expand Down Expand Up @@ -1382,45 +1405,15 @@ static mutation_type appendages[] =
MUT_TALONS,
};

static bool _slot_conflict(equipment_type eq)
{
// Choose uncovered slots only. Melding could make people re-cast
// until they get something that doesn't conflict with their randart
// of überness.
if (you.equip[eq] != -1)
{
// Horns + hat is fine.
if (eq != EQ_HELMET
|| you.melded[eq]
|| is_hard_helmet(*(you.slot_item(eq))))
{
return true;
}
}

for (int mut = 0; mut < NUM_MUTATIONS; mut++)
if (you.has_mutation(static_cast<mutation_type>(mut)) && eq == beastly_slot(mut))
return true;

return false;
}

static mutation_type _beastly_appendage()
static int _beastly_level(mutation_type mut)
{
mutation_type chosen = NUM_MUTATIONS;
int count = 0;

for (mutation_type app : appendages)
switch (mut)
{
if (_slot_conflict(beastly_slot(app)))
continue;
if (physiology_mutation_conflict(app))
continue;

if (one_chance_in(++count))
chosen = app;
case MUT_TENTACLE_SPIKE:
return 3;
default:
return 2;
}
return chosen;
}

static bool _transformation_is_safe(transformation which_trans,
Expand Down Expand Up @@ -1478,15 +1471,6 @@ static int _transform_duration(transformation which_trans, int pow)
return get_form(which_trans)->get_duration(pow);
}

static int _beastly_appendage_level(int appendage)
{
switch (appendage)
{
case MUT_HORNS: return 2;
default: return 3;
}
}

/**
* Print an appropriate message when the number of heads the player has
* changes during a refresh of hydra form.
Expand Down Expand Up @@ -1686,18 +1670,27 @@ bool transform(int pow, transformation which_trans, bool involuntary,

if (which_trans == transformation::appendage)
{
const mutation_type app = _beastly_appendage();
if (app == NUM_MUTATIONS)
// Need to set the appendages here for messaging
for (mutation_type app : appendages)
{
if (physiology_mutation_conflict(app))
continue;
you.props[APPENDAGE_KEY].get_vector().push_back(app);
dprf("Setting appendage mutation %s.", mutation_name(app));
}

if (you.props[APPENDAGE_KEY].get_vector().empty())
{
msg = "You have no appropriate body parts free.";
success = false; // XXX: VERY dubious, since an untransform occurred
}

if (!just_check)
if (just_check || !success)
{
you.attribute[ATTR_APPENDAGE] = app; // need to set it here so
// the message correlates
you.props.erase(APPENDAGE_KEY);
dprf("Erasing, just check");
}
dprf("Set appendages");
}

if (!success)
Expand Down Expand Up @@ -1812,10 +1805,12 @@ bool transform(int pow, transformation which_trans, bool involuntary,

case transformation::appendage:
{
int app = you.attribute[ATTR_APPENDAGE];
ASSERT(app != NUM_MUTATIONS);
ASSERT(beastly_slot(app) != EQ_NONE);
you.mutation[app] = _beastly_appendage_level(app);
auto& apps = you.props[APPENDAGE_KEY].get_vector();
for (auto app : apps)
{
const mutation_type mut = static_cast<mutation_type>(app.get_int());
you.mutation[mut] = _beastly_level(mut);
}
}
break;

Expand Down Expand Up @@ -1929,26 +1924,28 @@ void untransform(bool skip_move)

if (old_form == transformation::appendage)
{
mutation_type app = static_cast<mutation_type>(you.attribute[ATTR_APPENDAGE]);
ASSERT(beastly_slot(app) != EQ_NONE);
const int levels = you.get_base_mutation_level(app);
// Preserve extra mutation levels acquired after transforming.
const int beast_levels = _beastly_appendage_level(app);
const int extra = max(0, levels - you.get_innate_mutation_level(app)
- beast_levels);
you.mutation[app] = you.get_innate_mutation_level(app) + extra;
you.attribute[ATTR_APPENDAGE] = 0;

// The mutation might have been removed already by a conflicting
// demonspawn innate mutation; no message then.
if (levels)
const auto& apps = you.props[APPENDAGE_KEY].get_vector();
for (auto mut : apps)
{
const char * const verb = you.has_mutation(app) ? "shrink"
: "disappear";
mprf(MSGCH_DURATION, "Your %s %s%s.",
mutation_name(app), verb,
app == MUT_TENTACLE_SPIKE ? "s" : "");
const mutation_type app = static_cast<mutation_type>(mut.get_int());
const int levels = you.get_base_mutation_level(app);
// Preserve extra mutation levels acquired after transforming.
const int extra = max(0, levels - you.get_innate_mutation_level(app)
- _beastly_level(app));
you.mutation[app] = you.get_innate_mutation_level(app) + extra;

// The mutation might have been removed already by a conflicting
// demonspawn innate mutation; no message then.
if (levels)
{
const char * const verb = you.has_mutation(app) ? "shrink"
: "disappear";
mprf(MSGCH_DURATION, "Your %s %s%s.",
mutation_name(app), verb,
app == MUT_TENTACLE_SPIKE ? "s" : "");
}
}
you.props.erase(APPENDAGE_KEY);
}

calc_hp(true, false);
Expand Down
3 changes: 3 additions & 0 deletions crawl-ref/source/transform.h
Expand Up @@ -13,6 +13,9 @@
#define HYDRA_FORM_HEADS_KEY "hydra_form_heads"
#define MAX_HYDRA_HEADS 20

#define APPENDAGE_KEY "beastly_appendages"
#define APPENDAGE_LEVEL 2

enum form_capability
{
FC_DEFAULT,
Expand Down

0 comments on commit 1ed4c44

Please sign in to comment.