Skip to content

Commit

Permalink
Tortle as a playable race.
Browse files Browse the repository at this point in the history
Another new race for EvilHack, added at the request of amateurhour.
Tortles are humanoid tortoise-like beings. They are large, and have a
hard shell on their back which they can retreat into when threatened.
They have several armor restrictions due to their size and body shape.
Tortles are typically lawful in alignment, but are sometimes neutral as
well. To get an idea of what a tortle looks like, along with several
other characteristics, check out their Forgotten Realms entry online.

The specifications:

* Tortles can play as either an Archeologist, Barbarian, Healer, Monk,
Priest, Tourist, or a Wizard
* They can max their strength to 19 and wisdom to 20. Dexterity is
capped at 10, charisma is capped at 14
* They can be either gender, alignment choices are lawful or neutral
* Tortles start with the ability to swim, and have magical breathing
(can hold their breath for a ridiculously long time). They gain warning
at experience level 5, and hungerless regeneration at experience level
12
* Tortles start with a naked AC of 0 due to their hard shell, but are
very limited in what kind of armor they can wear
* Tortles cannot wear body armor, cloaks, t-shirts or boots (body
size/shape). They can wear gloves and helms, but only if their material
is made of something that isn't hard or rigid. Any type of shield is
fine, as are rings and amulets
* Tortles are the slowest of the player races, with a speed of 8
* A new type of helm was created - the toque (think Jayne from the show
'Firefly'). Like the fedora, it is made of cloth, does not grant any
extra AC, does not spawn randomly, and will only appear as a tortles
starting gear depending on what role they choose. It can also be wished
for. One special quality it has - it can protect the wearer from sonic
attacks (those goofy earflaps have a function after all)

There's more to do with the Tortle, this is just the initial commit to
get it in. What still needs to be done: giving the tortle the ability to
hide in its own shell, and figure out and implement the pros and cons to
doing that - player monster tortles - stand alone tortles as a monster
the player could encounter. And of course other little tweaks and
changes that haven't been thought of yet.

This should be a fun but challenging race to play once it's complete.
  • Loading branch information
k21971 committed Mar 28, 2022
1 parent 0af6485 commit fb5a531
Show file tree
Hide file tree
Showing 15 changed files with 198 additions and 83 deletions.
1 change: 1 addition & 0 deletions doc/evilhack-changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2198,4 +2198,5 @@ The following changes to date are:
- Implement new dragon armor system: DSM replaced by scaled armor (dtsund)
- Fix: a couple small issues with dtsund-DSM implementation
- Some minor player monster armor tweaks
- Tortle as a playable race

4 changes: 4 additions & 0 deletions include/mondata.h
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@
((((ptr)->mhflags & MH_ILLITHID) != 0L) \
|| ((ptr) == youmonst.data && !Upolyd && Race_if(PM_ILLITHID)))
#define racial_illithid(mon) mon_has_race(mon, MH_ILLITHID)
#define is_tortle(ptr) \
((((ptr)->mhflags & MH_TORTLE) != 0L) \
|| ((ptr) == youmonst.data && !Upolyd && Race_if(PM_TORTLE)))
#define racial_tortle(mon) mon_has_race(mon, MH_TORTLE)
#define your_race(ptr) (((ptr)->mhflags & urace.selfmask) != 0L)
#define racial_match(mon) mon_has_race(mon, urace.selfmask)
#define is_bat(ptr) \
Expand Down
19 changes: 10 additions & 9 deletions include/monflag.h
Original file line number Diff line number Diff line change
Expand Up @@ -179,16 +179,17 @@
#define MH_HOBBIT 0x00000040L
#define MH_CENTAUR 0x00000080L
#define MH_ILLITHID 0x00000100L
#define MH_TORTLE 0x00000200L
/* Flags below not used as a player race */
#define MH_UNDEAD 0x00000200L
#define MH_WERE 0x00000400L
#define MH_DEMON 0x00000800L
#define MH_DRAGON 0x00001000L
#define MH_ANGEL 0x00002000L
#define MH_OGRE 0x00004000L
#define MH_TROLL 0x00008000L
#define MH_GNOLL 0x00010000L
#define MH_JABBERWOCK 0x00020000L
#define MH_UNDEAD 0x00000400L
#define MH_WERE 0x00000800L
#define MH_DEMON 0x00001000L
#define MH_DRAGON 0x00002000L
#define MH_ANGEL 0x00004000L
#define MH_OGRE 0x00008000L
#define MH_TROLL 0x00010000L
#define MH_GNOLL 0x00020000L
#define MH_JABBERWOCK 0x00040000L

#define MH_ANY 0x80000000L

Expand Down
18 changes: 17 additions & 1 deletion src/attrib.c
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ static const struct innate {
{ 0, 0, 0, 0 } },

cen_abil[] = { { 1, &(HFast), "", "" },
/* use EJumping here, otherwise centaurs would only
be able to jump the same way as knights */
{ 5, &(EJumping), "light on your hooves", "weighted down" },
{ 10, &(HWarning), "sensitive", "" },
{ 0, 0, 0, 0 } },
Expand All @@ -147,6 +149,12 @@ static const struct innate {
/* also inediate */
{ 0, 0, 0, 0 } },

trt_abil[] = { { 1, &(HSwimming), "", "" },
{ 1, &(HMagical_breathing), "", "" },
{ 5, &(HWarning), "sensitive", "" },
{ 12, &(HRegeneration), "resilient", "less resilient" },
{ 0, 0, 0, 0 } },

hum_abil[] = { { 0, 0, 0, 0 } };

STATIC_DCL void NDECL(exerper);
Expand Down Expand Up @@ -245,7 +253,9 @@ boolean givemsg;
{
int num = incr;

if ((!num) || (((Race_if(PM_GIANT)) || (Race_if(PM_CENTAUR))) && (!(otmp && otmp->cursed)))) {
if ((!num) || ((Race_if(PM_GIANT)
|| Race_if(PM_CENTAUR) || Race_if(PM_TORTLE))
&& (!(otmp && otmp->cursed)))) {
if (ABASE(A_STR) < 18)
num = (rn2(4) ? 1 : rnd(6));
else if (ABASE(A_STR) < STR18(85))
Expand Down Expand Up @@ -840,6 +850,9 @@ long frommask;
case PM_DEMON:
abil = dem_abil;
break;
case PM_TORTLE:
abil = trt_abil;
break;
case PM_HUMAN:
abil = hum_abil;
break;
Expand Down Expand Up @@ -1031,6 +1044,9 @@ int oldlevel, newlevel;
case PM_ILLITHID:
rabil = ill_abil;
break;
case PM_TORTLE:
rabil = trt_abil;
break;
case PM_HUMAN:
case PM_DWARF:
case PM_GNOME:
Expand Down
34 changes: 32 additions & 2 deletions src/do_wear.c
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,7 @@ Helmet_on(VOID_ARGS)

switch (uarmh->otyp) {
case FEDORA:
case TOQUE:
case HELMET:
case DENTED_POT:
case ELVEN_HELM:
Expand Down Expand Up @@ -622,6 +623,7 @@ Helmet_off(VOID_ARGS)

switch (uarmh->otyp) {
case FEDORA:
case TOQUE:
case HELMET:
case DENTED_POT:
case ELVEN_HELM:
Expand Down Expand Up @@ -2184,6 +2186,13 @@ boolean noisy;
helm_simple_name(otmp),
plur(num_horns(youmonst.data)));
err++;
} else if (Race_if(PM_TORTLE) && is_hard(otmp)) {
/* Tortles can't retreat back into their shells
whilst wearing rigid head gear */
if (noisy)
pline_The("%s is too rigid to wear.",
helm_simple_name(otmp));
err++;
} else
*mask = W_ARMH;
} else if (is_shield(otmp)) {
Expand Down Expand Up @@ -2222,6 +2231,14 @@ boolean noisy;
c_boots); /* makeplural(body_part(FOOT)) yields
"rear hooves" which sounds odd */
err++;
} else if (Race_if(PM_TORTLE)) {
/* Tortles can't retreat back into their shells
whilst wearing footwear, plus their shape is
all wrong */
if (noisy)
pline("Your %s are not shaped correctly to wear %s.",
makeplural(body_part(FOOT)), c_boots);
err++;
} else if (u.utrap
&& (u.utraptype == TT_BEARTRAP || u.utraptype == TT_INFLOOR
|| u.utraptype == TT_LAVA
Expand Down Expand Up @@ -2258,6 +2275,13 @@ boolean noisy;
Your("%s are too slippery to pull on %s.",
fingers_or_gloves(FALSE), gloves_simple_name(otmp));
err++;
} else if (Race_if(PM_TORTLE) && is_hard(otmp)) {
/* Tortles can't retreat back into their shells
whilst wearing rigid gauntlets */
if (noisy)
pline_The("%s are too rigid to wear.",
gloves_simple_name(otmp));
err++;
} else
*mask = W_ARMG;
} else if (is_shirt(otmp)) {
Expand Down Expand Up @@ -2596,9 +2620,15 @@ find_ac()
* Giants can't wear body armor, t-shirt or cloaks,
* but they do have thick skin. So they get a little bit
* of love in the AC department to compensate somewhat.
*/
*
* Tortles have even more armor restrictions than giants.
* The large shell that makes up a good portion of their
* body provides exceptional protection */
int uac = maybe_polyd(mons[u.umonnum].ac,
Race_if(PM_GIANT) ? 6 : mons[u.umonnum].ac);
Race_if(PM_GIANT)
? 6 : Race_if(PM_TORTLE)
? 0 : mons[u.umonnum].ac);


/* armor class from worn gear */

Expand Down
3 changes: 2 additions & 1 deletion src/mhitm.c
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,8 @@ int target, roll;
fmt = "%s %s %s";
Sprintf(buf, fmt, s_suffix(Monnam(mdef)),
(is_dragon(mdef->data) ? "scaly hide"
: mdef->data == &mons[PM_GIANT_TURTLE]
: (mdef->data == &mons[PM_GIANT_TURTLE]
|| is_tortle(mdef->data))
? "protective shell"
: "thick hide"),
(rn2(2) ? "blocks" : "deflects"));
Expand Down
103 changes: 59 additions & 44 deletions src/mhitu.c
Original file line number Diff line number Diff line change
Expand Up @@ -157,16 +157,16 @@ struct attack *mattk;
/* get object responsible,
work from the closest to the skin outwards */

/* Try undershirt */
if (uarmu && !uarm && !uarmc
&& target <= roll) {
/* Try undershirt */
target += armor_bonus(uarmu);
if (target > roll)
blocker = uarmu;
}

/* Try body armour */
if (uarm && !uarmc && target <= roll) {
/* Try body armour */
target += armor_bonus(uarm);
if (target > roll)
blocker = uarm;
Expand Down Expand Up @@ -218,35 +218,38 @@ struct attack *mattk;
if (!canspotmon(mtmp))
map_invisible(mtmp->mx, mtmp->my);

if (could_seduce(mtmp, &youmonst, mattk) && !mtmp->mcan)
if (could_seduce(mtmp, &youmonst, mattk) && !mtmp->mcan) {
pline("%s pretends to be friendly.", Monnam(mtmp));
else {
if (!flags.verbose || (!nearmiss && !blocker))
} else {
if (!flags.verbose || (!nearmiss && !blocker)) {
pline("%s misses.", Monnam(mtmp));
else if (nearmiss || !blocker) {
if (thick_skinned(youmonst.data) && rn2(2)) {
} else if (nearmiss || !blocker) {
if ((thick_skinned(youmonst.data) || Race_if(PM_TORTLE))
&& rn2(2)) {
Your("%s %s %s attack.",
(is_dragon(youmonst.data) ? "scaly hide"
: youmonst.data == &mons[PM_GIANT_TURTLE]
? "protective shell"
: "thick hide"),
(is_dragon(youmonst.data) ? "scaly hide"
: (youmonst.data == &mons[PM_GIANT_TURTLE]
|| Race_if(PM_TORTLE))
? "protective shell"
: "thick hide"),
(rn2(2) ? "blocks" : "deflects"),
s_suffix(mon_nam(mtmp)));
} else {
rn2(2) ? You("dodge %s attack!", s_suffix(mon_nam(mtmp)))
: rn2(2) ? You("evade %s attack!", s_suffix(mon_nam(mtmp)))
: pline("%s narrowly misses!", Monnam(mtmp));
}
} else if (blocker == &zeroobj)
} else if (blocker == &zeroobj) {
pline("%s is stopped by your golden haze.", Monnam(mtmp));
else
} else {
Your("%s %s%s %s attack.",
blocker->oartifact ? xname(blocker)
: simple_typename(blocker->otyp),
rn2(2) ? "block" : "deflect",
((blocker == uarmg && blocker->oartifact != ART_DRAGONBANE)
|| blocker == uarmf) ? "" : "s",
s_suffix(mon_nam(mtmp)));
}
if (!blocker)
goto end;
/* called if attacker hates the material of the armor
Expand Down Expand Up @@ -3839,15 +3842,18 @@ int dmg;
|| !m_canseeu(mtmp) || mtmp->mspec_used)
return FALSE;

if (canseemon(mtmp) && (Deaf))
if (canseemon(mtmp) && Deaf) {
pline("It looks as if %s is yelling at you.",
mon_nam(mtmp));
if (!cancelled && ((m_canseeu(mtmp) && Blind && Deaf)))
} else if (!cancelled && m_canseeu(mtmp)
&& Blind && Deaf) {
You("sense a disturbing vibration in the air.");
else if (m_canseeu(mtmp) && canseemon(mtmp) && !Deaf && cancelled)
} else if (m_canseeu(mtmp) && canseemon(mtmp)
&& !Deaf && cancelled) {
pline("%s croaks hoarsely.", Monnam(mtmp));
else if (cancelled && !Deaf)
} else if (cancelled && !Deaf) {
You_hear("a hoarse croak nearby.");
}

/* Set mspec->mused */
mtmp->mspec_used = mtmp->mspec_used + (dmg + rn2(6));
Expand All @@ -3857,38 +3863,47 @@ int dmg;

/* scream attacks */
switch (mattk->adtyp) {
case AD_LOUD:
if (m_canseeu(mtmp))
pline("%s lets out a bloodcurdling scream!", Monnam(mtmp));
else if (u.usleep && m_canseeu(mtmp) && (!Deaf))
unmul("You are frightened awake!");
case AD_LOUD:
if (m_canseeu(mtmp))
pline("%s lets out a bloodcurdling scream!", Monnam(mtmp));
else if (u.usleep && m_canseeu(mtmp) && (!Deaf))
unmul("You are frightened awake!");

if (uarmh && uarmh->otyp == TOQUE && !Deaf) {
pline("Your %s protects your ears from the sonic onslaught.",
helm_simple_name(uarmh));
break;
} else {
Your("mind reels from the noise!");
make_stunned((HStun & TIMEOUT) + (long) dmg, TRUE);
stop_occupation();
}

if (!rn2(6))
erode_armor(&youmonst, ERODE_FRACTURE);
if (!rn2(5))
erode_obj(uwep, (char *) 0, ERODE_FRACTURE, EF_DESTROY);
if (!rn2(6))
erode_obj(uswapwep, (char *) 0, ERODE_FRACTURE, EF_DESTROY);
if (rn2(2))
destroy_item(POTION_CLASS, AD_LOUD);
if (!rn2(4))
destroy_item(RING_CLASS, AD_LOUD);
if (!rn2(4))
destroy_item(TOOL_CLASS, AD_LOUD);
if (!rn2(3))
destroy_item(WAND_CLASS, AD_LOUD);

if (u.umonnum == PM_GLASS_GOLEM) {
You("shatter into a million pieces!");
rehumanize();
break;
}
break;
default:
/* being deaf won't protect objects in inventory,
or being made of glass */
if (!rn2(6))
erode_armor(&youmonst, ERODE_FRACTURE);
if (!rn2(5))
erode_obj(uwep, (char *) 0, ERODE_FRACTURE, EF_DESTROY);
if (!rn2(6))
erode_obj(uswapwep, (char *) 0, ERODE_FRACTURE, EF_DESTROY);
if (rn2(2))
destroy_item(POTION_CLASS, AD_LOUD);
if (!rn2(4))
destroy_item(RING_CLASS, AD_LOUD);
if (!rn2(4))
destroy_item(TOOL_CLASS, AD_LOUD);
if (!rn2(3))
destroy_item(WAND_CLASS, AD_LOUD);

if (u.umonnum == PM_GLASS_GOLEM) {
You("shatter into a million pieces!");
rehumanize();
break;
}
break;
default:
break;
}
return TRUE;
}
Expand Down
3 changes: 3 additions & 0 deletions src/mondata.c
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,9 @@ struct monst *mon;
if (racial_centaur(mon))
return FALSE;

if (racial_tortle(mon))
return TRUE;

return (boolean) (r_bigmonst(mon)
|| (ptr->msize > MZ_SMALL && !humanoid(ptr))
/* special cases of humanoids that cannot wear suits */
Expand Down
2 changes: 1 addition & 1 deletion src/monmove.c
Original file line number Diff line number Diff line change
Expand Up @@ -2115,7 +2115,7 @@ struct monst *mtmp;
return TRUE;
if (obj->oclass != GEM_CLASS && !(typ >= ARROW && typ <= BOOMERANG)
&& !(typ >= DAGGER && typ <= CRYSKNIFE) && typ != SLING
&& !is_cloak(obj) && typ != FEDORA && !is_gloves(obj)
&& !is_cloak(obj) && typ != FEDORA && typ != TOQUE && !is_gloves(obj)
&& typ != JACKET && typ != CREDIT_CARD && !is_shirt(obj)
&& !(typ == CORPSE && verysmall(&mons[obj->corpsenm]))
&& typ != FORTUNE_COOKIE && typ != CANDY_BAR && typ != PANCAKE
Expand Down
14 changes: 10 additions & 4 deletions src/monst.c
Original file line number Diff line number Diff line change
Expand Up @@ -1979,8 +1979,7 @@ struct permonst _mons2[] = {
SIZ(2250, 750, MS_BOAST, MZ_HUGE), 0, 0, M1_HUMANOID | M1_CARNIVORE,
M2_STRONG | M2_ROCKTHROW | M2_NASTY | M2_COLLECT | M2_JEWELS,
M3_INFRAVISIBLE | M3_INFRAVISION, 0, MH_GIANT, 8, CLR_GRAY),
/* From SporkHack.
*/
/* From SporkHack */
MON("hill giant shaman", S_GIANT, LVL(7, 10, 4, 0, -3), (G_GENO | 2),
A(ATTK(AT_MAGC, AD_CLRC, 0, 0),
NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK),
Expand Down Expand Up @@ -3653,8 +3652,15 @@ struct permonst _mons2[] = {
M1_SWIM | M1_AMPHIBIOUS | M1_ANIMAL | M1_THICK_HIDE | M1_NOHANDS
| M1_OVIPAROUS | M1_CARNIVORE,
M2_STRONG | M2_HOSTILE, 0, 0, 0, 7, CLR_BROWN),
/* From SporkHack.
*/
MON("tortle", S_LIZARD, LVL(0, 8, 0, 0, 3), G_NOGEN, /* placeholder */
A(ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK,
NO_ATTK),
SIZ(1200, 300, MS_HUMANOID, MZ_LARGE), 0, 0,
M1_HUMANOID | M1_THICK_HIDE | M1_OMNIVORE | M1_AMPHIBIOUS
| M1_SWIM,
M2_NOPOLY | M2_STRONG | M2_COLLECT, M3_INFRAVISIBLE, 0,
MH_TORTLE, 2, CLR_BRIGHT_GREEN),
/* From SporkHack */
MON("giant turtle", S_LIZARD, LVL(7, 2, -8, 10, 0), (G_GENO | 1),
A(ATTK(AT_BITE, AD_PHYS, 4, 6),
NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK),
Expand Down

0 comments on commit fb5a531

Please sign in to comment.