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; !--------------------------------------------------------------------------------