Magic Systems with open-source tools
This guide outlines the technical process for implementing a robust, school-based magic system within a DikuMUD or ROM derivative. It focuses on moving beyond hardcoded damage values toward a modular architecture that supports varied resource costs, casting delays, and strategic resistances.

Define Magic Schools and Resource Types
Modify the skill_table or create a new spell_type structure to categorize spells into schools (e.g., Evocation, Abjuration, Illusion). This allows for global modifiers based on player specialization and school-specific resistances.
struct spell_type {
char *name;
sh_int level[MAX_CLASS];
sh_int school; /* 0: None, 1: Evocation, 2: Abjuration, etc. */
sh_int mana_cost;
sh_int cast_time; /* Pulses required before spell fires */
SPELL_FUN *spell_fun;
};⚠ Common Pitfalls
- •Hardcoding school IDs instead of using an enumeration, making future additions difficult.
- •Overcomplicating the system with too many schools before balancing the core four.
Implement Casting Latency and Interrupts
Add a 'wait state' or 'casting_timer' to the char_data structure. Modify the 'do_cast' command to set this timer based on the spell's complexity. If the player is hit or moves during this time, trigger a check to see if the spell is interrupted.
if (ch->casting_timer > 0) {
ch->casting_timer--;
if (ch->casting_timer == 0) {
finish_spell_cast(ch);
}
return;
}⚠ Common Pitfalls
- •Failing to clear the casting timer upon death, leading to 'ghost' spells.
- •Not providing feedback to the player during the casting duration, making the game feel laggy.
Refactor Spell Scaling Formulas
Replace static damage dice (e.g., 3d8+10) with dynamic formulas that incorporate level, intelligence, and school proficiency. This ensures spells remain relevant as players progress without requiring manual updates to every spell function.
int calculate_spell_power(CHAR_DATA *ch, int spell_school) {
int power = ch->level * 2;
power += get_curr_stat(ch, STAT_INT) * 5;
power += ch->pcdata->school_proficiency[spell_school];
return power;
}⚠ Common Pitfalls
- •Linear scaling that makes high-level players one-shot low-level NPCs.
- •Ignoring the impact of equipment-based 'spell power' modifiers.
Integrate Multi-Layered Resistances
Implement a check in the damage calculation that looks for specific school resistances (RES_FIRE, RES_HOLY, etc.) rather than just a generic 'save vs spell'. This forces players to diversify their spell choices in combat.
int apply_resistance(CHAR_DATA *victim, int damage, int school) {
if (IS_SET(victim->res_flags, school_to_res(school))) {
damage /= 2;
send_to_char("Your spell is partially resisted!\n\r", ch);
}
return damage;
}⚠ Common Pitfalls
- •Allowing resistances to stack to 100%, making certain mobs or players impossible to damage.
- •Forgetting to update the 'mset' and 'stat' commands to reflect new resistance flags.
Add Reagent and Component Verification
For high-impact spells, implement a requirement check for physical items (reagents) in the player's inventory. This balances powerful spells with economic costs and inventory management.
OBJ_DATA *reagent = get_obj_carry(ch, "black pearl", ch);
if (reagent == NULL) {
send_to_char("You lack the black pearl required for this spell.\n\r", ch);
return;
}
extract_obj(reagent);⚠ Common Pitfalls
- •Setting reagent costs too high for common spells, causing player frustration.
- •Not providing a 'reagent bag' or similar storage, leading to inventory clutter.
Establish Textual Feedback Loops
Update the combat messaging system to provide specific text for different outcomes: full resist, partial resist, spell failure, and critical hits. This is essential for players to understand mechanics in a text-only environment.
⚠ Common Pitfalls
- •Using generic 'You missed' messages that don't explain if the cause was a saving throw, a resistance, or a low skill roll.
- •Spamming too many lines of text per spell, which can scroll past important combat data.
What you built
By moving magic logic into a structured framework with casting times, school-based scaling, and reagent costs, you create a system that rewards strategy over simple macroing. Always test new spell formulas against a range of levels in a test harness before deploying to the live production port.