Combat (Inform 6 example)

From IFWiki

This code provides a simplified 'd20' system of combat for Inform programs. It offers the following features:

First, there's a BareHands object that is used as a default weapon. This was done so that the default 'selfobj' doesn't need to be changed to reflect the player's fighting abilities. As a weapon, BareHands has two attributes: hit_bonus and hit_dice. The former is a value added to any attack roll to reflect the ease or difficulty of using the weapon, while the latter is a triplet representing the number of dice to roll, the number of sides of those dice, and a damage bonus. So, '2 6 2' means '2d6+2' which means that the weapon inflicts 4 to 14 points of damage. BareHands also has an ext_initialise property so it becomes a part of the selfobj when the game starts, and an armour_class property, which isn't needed here but has to be defined somewhere. This property should be used by armor, which when worn will effect the odds of an attack succeeding. A positive value (typically < 10) will make attacks more likely to fail, while negative values typically indicate "cursed" armor.

The 'get_armour_class' routine adds together a creature's armour_class (if any; it defaults to 10 for selfobj) and the armour_class of everything being worn. Note that the game author needs to prevent the player from, e.g., donning three breastplates.

The 'calculate_damage' routine rolls a weapon's hit dice and returns the sum.

The 'roll_the_dice' routine uses both of the above routines to carry out a single attack. It can be used by player and NPCs alike. For example, a bear with three attacks per round (two claw, one bite) could use this code:

 roll_the_dice(GrizzlyBear, player, BearClaw);
 roll_the_dice(GrizzlyBear, player, BearClaw);
 roll_the_dice(GrizzlyBear, player, BearTeeth);

Finally, the 'attack' action is extended to allow ATTACK CREATURE WITH WEAPON, and AttackSub is rewritten to invoke 'roll_the_dice'.

Program notes:

  • We don't actually keep track of the damage that's being inflicted. Since there seemed to be many ways to do that, I left those implementation details up to the game author.
  • The d20 system defines a natural roll of '1' to always be a miss, and a natural '20' to always be a hit. Some people like to define other special effects in one or both of those cases.
  • Keeping the above in mind, there are the three optional subroutines that an author can define:
    • combat_inflict(defense, damage)
      • Only invoked when an attack succeeds.
      • 'defense' is the object being damaged
      • 'damage' is the number of hit-points of damage
      • Returning a true (non-zero) value supresses the normal message.
    • combat_critical(defense, damage)
      • Only invoked when a natural '20' is rolled on a d20.
      • 'defense' is the object being damaged
      • 'damage' is the number of hit-points of damage
      • Returning a false (zero) value causes combat_inflict to be called.
    • combat_fumble(offense)
      • Only invoked when a natural '1' is rolled on a d20.
      • 'offense' is the object that was attacking
      • Returning a true (non-zero) value supresses the normal message.

! d20combat.h -- provides a 'd20 system' style of combat for Inform programs.
!
! To use this extension, include it four times in your main program.  The
! author finds the following formatting to be useful.
!
!                    #Include "combat.h";
! #Include "parser";
!                    #Include "combat.h";
! #Include "verblib";
!                    #Include "combat.h";
! #Include "grammar";
!                    #Include "combat.h";
!
!
! Created by Sam Denton (samwyse)
! 
! License: 
! This work is licensed under the Creative Commons Attribution-ShareAlike
! License (http://creativecommons.org/licenses/by-sa/1.0/).
!
! In summary: you must credit the original author(s); if you alter, transform,
! or build upon this software, you may distribute the SOURCE FORM of the
! resulting work only under a license identical to this one. Note that the
! ShareAlike clause does not affect the way in which you distribute the
! COMPILED FORM of works built upon this software. Copyright remains with the
! original author(s), from whom you must seek permission if you wish to vary
! any of the terms of this license.
!
! The author(s) would also welcome bug reports and enhancement suggestions.

!-------------------------------------------------------------------------------
#Ifndef LIBRARY_PARSER;
!     1) The REPLACE section - appears prior to the inclusion of 
!          Parser. REPLACE directives, used when modifying system 
!          routines, go here.
!--------------------------------------------------------------------------------


Replace AttackSub;


!--------------------------------------------------------------------------------
#Ifnot;#Ifndef LIBRARY_VERBLIB;
!     2) The MESSAGE section - appears between the Parser and Verblib 
!          includes. Definitions of the LibraryMessages object, 
!          SACK_OBJECT, and the task_scores array goes here.
!--------------------------------------------------------------------------------


! This section is intentionally left blank.
 

!--------------------------------------------------------------------------------
#Ifnot;#Ifndef LIBRARY_GRAMMAR;
!     3) The CODE section - appears between the Verblib and Grammar 
!          includes.  Attribute, Property, Classes, and Object 
!          implementation goes here. Most implementation code is
!          placed in this section.
!--------------------------------------------------------------------------------


! Define a default weapon.

Object BareHands "your bare hands" LibraryExtensions
  with
    name 'bare' 'hands' 'hand' 'fists' 'fist',
    hit_bonus 0,
    hit_dice 1 4 0,  ! 1d4+0
    ext_initialise [ ;
      selfobj.add_to_scope = self;
    ],
    armour_class,  ! just so it's defined somewhere
  has pluralname proper scenery
  ;


! This can be beefed up, but it provides the basics.
! It's up to the author to control how armor is worn.

[ get_armour_class creature  ac item;
  if (creature provides armour_class)
    ac = creature.armour_class;
  else
    ac = 10;
  objectloop (item in creature) {
    if (item has worn)
      if (item provides armour_class)
        ac = ac + item.armour_class;
  }
  return ac;
];


! This can be beefed up, but it provides the basics.

[ calculate_damage weapon victim  dice inflict;
  if (weapon.#hit_dice ~= 6)
    "[ coding error: ",(the)weapon," has invalid hit_dice ]";
  for (dice = 0 : dice < weapon.&hit_dice-->0 : dice++) {
    inflict = inflict + random(weapon.&hit_dice-->1);
  }
  return inflict + weapon.&hit_dice-->2;
];

! This provides the grunt work for a phase of combat.

[ roll_the_dice offense defense weapon  roll damage;
  roll = random(20);
  switch (roll) {
    1:
#Ifdef combat_fumble;
      if (combat_fumble(offense)) rtrue;
#Endif;
      if (offense == player)
        print "You attack";
      else
        print (The)offense, " attacks";
      " only air!";

    20:
      damage = calculate_damage(weapon, noun);
#Ifdef combat_critical;
      if (combat_critical(defense, damage)) rtrue;
#Endif;

    default:
      if (weapon provides hit_bonus)
        roll = roll + weapon.hit_bonus;

      if (roll < get_armour_class(noun)) {
        if (offense == player)
          print "You miss ";
        else
          print (The)offense, " misses ";
        if (defense == player)
          "you!";
        else
          print_ret (the)defense, "!";
      }

      damage = calculate_damage(weapon, noun);
  }
#Ifdef combat_inflict;
  if (combat_inflict(defense, damage)) rtrue;
#Endif;
  if (offense == player)
    print "You inflict ";
  else
    print (The)offense, " inflicts ";
  print damage, " points of damage";
  if (offense ~= weapon)
    print " with ",(the)weapon;
  ".";
];


! Figure out what's going on.

[ AttackSub weapon;
  if (second ~= 0 && second provides hit_dice)
    weapon = second;
  else
    weapon = BareHands;
  roll_the_dice(player, noun, weapon);
];


!--------------------------------------------------------------------------------
#Ifnot;
!     4) The GRAMMAR section - appears following the Grammar include.
!          All new Verb and Extend directives go here.
!--------------------------------------------------------------------------------


Extend 'attack'
  * noun 'with' noun -> Attack;


!--------------------------------------------------------------------------------
#Endif;#Endif;#Endif;
!--------------------------------------------------------------------------------