Combat (Inform 6 example)
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.
- combat_inflict(defense, damage)
! 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;
!--------------------------------------------------------------------------------