21 March 2019

Limb System

I was thinking about limb systems and how interesting it could be to include. It adds detail and complexity that makes the game world feel extra alive. And it’s fun reading Dwarf Fortress logs about fights where arms are ripped off and used as weapons. Or getting electrocuted and burning off your hands during a failed hacking attempt in Space Station 13.

There’s also potential for interesting monster spawning behaviour. Monsters like zombies could be generated with randomly weakened body parts. So you might occasionally run into a zombie whose legs disintegrates after the first hit or was crippled after some offscreen altercation. Or monsters with insignificant appendages that soak up damage. Like greeble bits on robots/mechanical units or legs on spiders/arachnids.

One of the immediate problems I think is how to integrate limbs into the combat system. Fallout had the VATS system allowing you to choose your target limb. This could be a bit slow-paced for most roguelikes, though. It also emphasizes combat which changes the game feel.

You could randomly assign a limb based on a weighted probability of maximum HP. Or just pick uniformly from the list of available body parts. The one thing I love about this is how it supports a scenario similar to Space Station 13. In that case, failing a hack attempt caused an electrical counter-attack. The body part is selected at random and your hands are chosen. The attack overwhelmed the defense + HP of your hands and they became disabled. Incineration is part of the status effects from successful critical hits.

It does give a certain extra randomness to combat. I think a little GUI manikin that shows the limb that you damaged during that turn would be a nice touch in place of an HP bar. Three colours roughly show healthy, damaged, and disabled limbs.

manikin

Mechanics

Each limb has its own HP. 50-100% the limb is healthy. 26-49% is damaged with penalties depending on the limb type. anything 25% or less is disabled

Each monster has a global HP that tracks the accumulated damage across limbs. Depending on the monster, Each limb will have its own defense ratings, armor type and HP. Once the accumulated damage exceeds the global HP, the monster dies.

TODO:

  • recovery mechanics
  • limb targeting mechanics
  • should a limb be chosen randomly each attack or be preserved for each additional attack after?

References

Fallout

Finding it hard to get exact info. Each limbs has its own HP. There is a global HP. If the accumulated damage is > global HP then the monster dies. Also seems to be different defense ratings per body part and a host of other systems integrate like morale.

Limb/Body Part% of your total HP
Head75%
Torso255%
Left Arm100%
Right Arm100%
Left Leg150%
Right Leg150%

Cataclysm Dark Days Ahead

Each body part has HP base HP of 84, with additional HP for each point in strength. Body and head are critical parts. if either go to zero then you die. Monsters don’t seem to have a body part system, only the player can take damage to their limbs. I haven’t found anything on what determines which limb is struck.

IVAN

Taken from source code here

Roughly, it looks like all the body parts are put into a queue sorted by the strength and remaining HP. For each body part in the queue, it calculates the toHitPercentage and determines if the player successfully hits the limb. If not, it tries again with the remaining limbs.

int character::ChooseBodyPartToReceiveHit(double ToHitValue, double DodgeValue)
{
  if(BodyParts == 1)
    return 0;

  std::priority_queue<svpriorityelement> SVQueue;

  for(int c = 0; c < BodyParts; ++c)
  {
    bodypart* BodyPart = GetBodyPart(c);

    if(BodyPart
       && (BodyPart->GetHP() != 1
       || BodyPart->CanBeSevered(PHYSICAL_DAMAGE)))
      SVQueue.push(svpriorityelement(c, ModifyBodyPartHitPreference(c,
                   BodyPart->GetStrengthValue() + BodyPart->GetHP())));
  }

  while(SVQueue.size())
  {
    svpriorityelement E = SVQueue.top();
    int ToHitPercentage = int(GLOBAL_WEAK_BODYPART_HIT_MODIFIER
                  * ToHitValue
                  * GetBodyPart(E.BodyPart)->GetBodyPartVolume()
                  / (DodgeValue * GetBodyVolume()));
    ToHitPercentage = ModifyBodyPartToHitChance(E.BodyPart, ToHitPercentage);

    if(ToHitPercentage < 1)
      ToHitPercentage = 1;
    else if(ToHitPercentage > 95)
      ToHitPercentage = 95;

    if(ToHitPercentage > RAND()% 100)
      return E.BodyPart;

    SVQueue.pop();
  }

  return 0;
}