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.

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.
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.
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.
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'.
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.
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.
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.
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.
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'.
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.
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.
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.