Game Code Examples
Code Examples For Nerds
Here's some clearcut examples from the old public SMAUG 1.8 release that show how things work on the back end.
Spoiler Alert: It's exactly like D&D
- Skills & Physical attacks do some dice roll to see if they hit. If they do hit, then you roll damage.
- Magical attacks 'just happen' but the defender get's a chance to save their ass by rolling a saving throw. Successful save reduces damage by some factor.
Basic Skill
For the 'hide' skill:
void do_hide( CHAR_DATA *ch, char *argument )
{
if ( IS_NPC(ch) && IS_AFFECTED( ch, AFF_CHARM ) )
{
send_to_char( "You can't concentrate enough for that.\n\r", ch );
return;
}
if ( ch->mount )
{
send_to_char( "You can't do that while mounted.\n\r", ch );
return;
}
send_to_char( "You attempt to hide.\n\r", ch );
if ( IS_AFFECTED(ch, AFF_HIDE) )
xREMOVE_BIT(ch->affected_by, AFF_HIDE);
if ( can_use_skill(ch, number_percent(), gsn_hide ) )
{
xSET_BIT(ch->affected_by, AFF_HIDE);
learn_from_success( ch, gsn_hide );
}
else
learn_from_failure( ch, gsn_hide );
return;
}
- Entry gates
- Charmed NPC — same guard as the other skills; a charmed mob can't concentrate enough to hide.
- Mounted — can't hide while on a mount.
- The attempt
- send_to_char("You attempt to hide.") — you always get this message just for trying, regardless of outcome.
- if ( IS_AFFECTED(ch, AFF_HIDE) ) xREMOVE_BIT(ch->affected_by, AFF_HIDE);
- Clear existing hide first. If you're already hidden, the AFF_HIDE bit is stripped before the roll. This means re-hiding is a fresh attempt — you drop your current hide and have to succeed again to get it back. A failed re-hide leaves you un-hidden. So spamming hide when already hidden can actually expose you.
- The roll
- can_use_skill(ch, number_percent(), gsn_hide)
- rolls 1–100 and checks it against your learned hide percentage - it's a straight skill-adept-percent vs roll check.
- can_use_skill(ch, number_percent(), gsn_hide)
- The outcome
- On success: xSET_BIT(ch->affected_by, AFF_HIDE) sets the hide affect, and learn_from_success gives a chance to improve the skill.
- On failure: just learn_from_failure — no hide, but learn anyway.
Passive Skill
Skill Attack
Here's the super old code for thieve's 'circle' from the public release of SMAUG 1.8.
Actually, the first section is just a bunch of qualifiers I'm going to omit cuz its boring, you can imagine what they do from their messages:
- You can't circle while mounted.
- Circle around whom?
- They aren't here
- How can you sneak up on yourself?
- you need to wield a piercing or stabbing weapon.
- You can't circle when you aren't fighting.
- You can't circle around a person who is not fighting.
- You can't circle around them without a distraction.
Ok, then actual 'hit' code:
percent = number_percent( ) - (get_curr_lck(ch) - 16)
+ (get_curr_lck(victim) - 13);
check_attacker( ch, victim );
WAIT_STATE( ch, skill_table[gsn_circle]->beats );
if ( can_use_skill( ch, percent, gsn_circle ) )
{
learn_from_success( ch, gsn_circle );
WAIT_STATE( ch, 2 * PULSE_VIOLENCE );
global_retcode = multi_hit( ch, victim, gsn_circle );
adjust_favor( ch, 10, 1 );
check_illegal_pk( ch, victim );
}
else
{
learn_from_failure( ch, gsn_circle );
WAIT_STATE( ch, 2 * PULSE_VIOLENCE );
global_retcode = damage( ch, victim, 0, gsn_circle );
The skill roll
- percent = number_percent() - (get_curr_lck(ch) - 16) + (get_curr_lck(victim) - 13);
- number_percent() rolls 1–100. Then luck adjusts the roll:
- Your luck above 16 lowers the number (subtracted), which helps you — lower rolls are better against your skill threshold.
- Victim's luck above 13 raises the number, hurting you.
- So high-luck attacker + low-luck victim = easier success. The 16 and 13 are just the neutral baselines for each side.
- number_percent() rolls 1–100. Then luck adjusts the roll:
- Committing to the attempt
- check_attacker — flags you as the aggressor (PK bookkeeping).
- WAIT_STATE( ch, skill_table[gsn_circle]->beats ) — applies the skill's base lag immediately, whether or not you succeed.
- Success vs. failure
- can_use_skill( ch, percent, gsn_circle ) compares your adjusted roll against your percentage adept in the skill.
- On success:
- learn_from_success — chance to improve the skill.
- Another WAIT_STATE of 2 * PULSE_VIOLENCE (extra lag).
- multi_hit( ch, victim, gsn_circle ) — delivers the actual attack, tagged as circle.
- Likely further modifiers baked into other game functions (apply Damage Roll, etc)
- adjust_favor( ch, 10, 1 ) — deity favor adjustment.
- check_illegal_pk — validates the kill was legal.
- On failure:
- learn_from_failure — you still might learn from botching it.
- Same 2 * PULSE_VIOLENCE lag.
- damage( ch, victim, 0, gsn_circle ) — deals 0 damage but still "connects" as an action, which triggers the miss message and puts you into combat with them.
Spell Attack
Here's a really old example from the public relase of SMAUG 1.8 for mage's 'burning hands' spell. This uses a pre-determined table of damage values, does some rolls on them, applies saving throws, to determine damage. Bear in mind that Resistances are then applied AFTER, and are a different chunk of code/logic.
ch_ret spell_burning_hands( int sn, int level, CHAR_DATA *ch, void *vo )
{
CHAR_DATA *victim = (CHAR_DATA *) vo;
static const sh_int dam_each[] =
{
0,
0, 0, 0, 0, 14, 17, 20, 23, 26, 29,
29, 29, 30, 30, 31, 31, 32, 32, 33, 33,
34, 34, 35, 35, 36, 36, 37, 37, 38, 38,
39, 39, 40, 40, 41, 41, 42, 42, 43, 43,
44, 44, 45, 45, 46, 46, 47, 47, 48, 48,
49, 49, 50, 50, 51, 51, 52, 52, 53, 53,
54, 54, 55, 55, 56, 56, 57, 57, 58, 58
};
int dam;
level = UMIN(level, sizeof(dam_each)/sizeof(dam_each[0]) - 1);
level = UMAX(0, level);
dam = number_range( dam_each[level] / 2, dam_each[level] * 2 );
if ( saves_spell_staff( level, victim ) )
dam /= 2;
return damage( ch, victim, dam, sn );
}
Here's how Claude explains that:
- Setup
- victim is the target, cast from the generic void *vo pointer.
- dam_each[] is a lookup table mapping the caster's level to a base damage value.
- Low levels (0–4) do 0, then it jumps to 14 at level 5 and scales up to 58 at the top.
- Clamping the level
- level = UMIN(level, sizeof(dam_each)/sizeof(dam_each[0]) - 1) caps the damage level at the last valid table index
- (70 here, since the array has 71 entries indexed 0–70). This prevents reading past the end of the array.
- level = UMAX(0, level) ensures level isn't negative. After these two lines, damage level is guaranteed to be a safe index.
- level = UMIN(level, sizeof(dam_each)/sizeof(dam_each[0]) - 1) caps the damage level at the last valid table index
- Rolling damage
- dam = number_range( dam_each[level] / 2, dam_each[level] * 2 ) picks a random integer between half and double the base value for that level.
- So at level 5 (base 14), it rolls between 7 and 28. This gives a wide random spread averaging roughly the base value (slightly above it, since the midpoint of half-to-double is 1.25× base).
- dam = number_range( dam_each[level] / 2, dam_each[level] * 2 ) picks a random integer between half and double the base value for that level.
- Saving throw
- if ( saves_spell_staff( level, victim ) ) dam /= 2; — if the victim successfully makes a saving throw (save vs. spells/staves), damage is halved.
- Applying it
- return damage( ch, victim, dam, sn ) deals the final damage from caster ch to victim, tagged with the spell number sn (used for messages, damage type, resistances, etc.).
Here's how that maps out to actual damage values per level:
- The 'Base' column is the base damage pulled from the index table of values in the spell code before randomization
- The code then operates upon that by doing "number_range(base/2, base*2)" to set the random spread. So base is the center-ish of the roll — the min is half of it, the max is double it.
Lvl Base NoSave(min-max) Saved(min-max) --- ---- --------------- -------------- 0 0 0-0 0-0 1 0 0-0 0-0 2 0 0-0 0-0 3 0 0-0 0-0 4 0 0-0 0-0 5 14 7-28 3-14 6 17 8-34 4-17 7 20 10-40 5-20 8 23 11-46 5-23 9 26 13-52 6-26 10 29 14-58 7-29 11 29 14-58 7-29 12 29 14-58 7-29 13 30 15-60 7-30 14 30 15-60 7-30 15 31 15-62 7-31 16 31 15-62 7-31 17 32 16-64 8-32 18 32 16-64 8-32 19 33 16-66 8-33 20 33 16-66 8-33 21 34 17-68 8-34 22 34 17-68 8-34 23 35 17-70 8-35 24 35 17-70 8-35 25 36 18-72 9-36 26 36 18-72 9-36 27 37 18-74 9-37 28 37 18-74 9-37 29 38 19-76 9-38 30 38 19-76 9-38 31 39 19-78 9-39 32 39 19-78 9-39 33 40 20-80 10-40 34 40 20-80 10-40 35 41 20-82 10-41 36 41 20-82 10-41 37 42 21-84 10-42 38 42 21-84 10-42 39 43 21-86 10-43 40 43 21-86 10-43 41 44 22-88 11-44 42 44 22-88 11-44 43 45 22-90 11-45 44 45 22-90 11-45 45 46 23-92 11-46 46 46 23-92 11-46 47 47 23-94 11-47 48 47 23-94 11-47 49 48 24-96 12-48 50 48 24-96 12-48