Guides

Combat Systems with open-source tools

This guide outlines the implementation of a deterministic round-based combat system for text-based MUDs. It focuses on the 'Pulse-Violence' architecture common in Diku-derivatives, ensuring that combat remains synchronized across all players while allowing for variable attack speeds and skill interruptions.

6-10 hours6 steps
Combat Systems with open-source tools hero illustration
1

Define Combat State in Character Structures

Add pointers and state flags to your character structure to track who is fighting whom. Avoid storing combat state globally; instead, each character should know their primary target. This allows for multi-party combat and easier cleanup.

structs.h
struct char_data {
    struct char_data *fighting; /* Pointer to current target */
    short position;            /* POS_FIGHTING, POS_STANDING, etc */
    short wait_state;          /* Lag/Cooldown timer in pulses */
    int hitroll;               /* Accuracy modifier */
    int damroll;               /* Damage modifier */
};

⚠ Common Pitfalls

  • Memory leaks: Failing to nullify the 'fighting' pointer when a target is freed from memory.
  • Dangling pointers: A target logs out or is deleted while still being referenced by an attacker.
2

Implement the Violence Pulse Hook

The main game loop must trigger a combat update at a fixed interval (usually every 2-3 seconds). Iterate through the global descriptor list and process combat for any character currently in the POS_FIGHTING state.

fight.c
void perform_violence(void) {
    struct char_data *ch;
    for (ch = character_list; ch; ch = ch->next) {
        if (ch->fighting && ch->position == POS_FIGHTING) {
            hit(ch, ch->fighting, TYPE_UNDEFINED);
        }
    }
}

⚠ Common Pitfalls

  • Processing characters twice if they move during the list iteration.
  • CPU spikes if the character list is massive; consider a separate 'active_combat_list'.
3

Develop the To-Hit and Damage Formulas

Create a tiered calculation that determines if an attack connects and how much damage it deals. Use a linear or bell-curve (3d6) distribution for hit checks to prevent high-level characters from becoming untouchable.

combat_math.c
int calculate_damage(struct char_data *ch, struct char_data *victim) {
    int dam = dice(1, 8) + ch->damroll;
    /* Apply damage reduction based on victim's Armor Class */
    dam -= (victim->armor / 10);
    if (dam < 0) dam = 0;
    return dam;
}

⚠ Common Pitfalls

  • Stat overflow: If damage variables are short integers, high-level buffs may wrap values to negative.
  • Division by zero errors in complex scaling formulas.
4

Integrate Command Lag (Wait State)

Combat feels trivial if players can spam skills. Implement a 'wait_state' check in your command interpreter. When a skill is used, set the character's wait_state to a value (e.g., PULSE_VIOLENCE * 2). The character cannot act until this timer reaches zero.

act_skills.c
void do_kick(struct char_data *ch, char *argument) {
    if (ch->wait_state > 0) {
        send_to_char("You are too busy to kick!\n\r", ch);
        return;
    }
    /* Perform kick logic */
    ch->wait_state = 12; /* Approx 2 rounds of lag */
}

⚠ Common Pitfalls

  • Stun-locking: Allowing skills to add wait_state to the victim without diminishing returns.
  • Lagging NPCs: Forgetting to apply wait_state logic to mob AI, giving them infinite actions.
5

Handle Combat Exit Conditions

Define clear logic for ending combat. This includes death, fleeing, or room movement. When a character leaves the room or dies, you must iterate through the character list (or a list of attackers) and clear their 'fighting' pointers to prevent 'ghost fighting'.

fight.c
void stop_fighting(struct char_data *ch) {
    ch->fighting = NULL;
    ch->position = POS_STANDING;
    update_pos(ch);
}

⚠ Common Pitfalls

  • Aggressive mobs re-initiating combat instantly after a flee if the 'aggro' flag isn't cleared.
  • Players 'flee-spamming' to avoid death; implement a failure chance or movement point cost.
6

Implement Damage Messaging and Feedback

Text-based combat relies on descriptive strings to convey impact. Map damage ranges to specific verbs (e.g., 1-5: 'scratch', 20-30: 'MAIM'). Use color codes to highlight critical hits or health status.

msg_utils.py
def get_damage_msg(amount):
    if amount == 0:
        return "{Wmisses{n"
    elif amount < 10:
        return "{Gscratches{n"
    elif amount < 50:
        return "{RMAIMS{n"
    return "{YDEMOLISHES{n"

⚠ Common Pitfalls

  • Mismatched pronouns (He hits herself) in the string parser.
  • Spamming the buffer: Ensure multi-hit attacks are condensed into a single output block where possible.

What you built

A functional combat system requires strict pointer management and a predictable update loop. Once the core hit/miss/damage cycle is stable, focus on balancing the 'wait_state' across different classes to ensure that fast, weak attackers and slow, heavy hitters are equally viable.