A thorough code review/overhaul of QuickMUD / ROM 2.4b6 for starting your MUD
BaseMUD is a work-in-progress fork of QuickMUD that has been heavily reorganized, refactored, and cleaned-up for clarity and easy extendability. This project has two main goals:
- Provide an easy-to-modify codebase for those who want to play around with their own stock ROM mud, and
- Provide a transitional project for porting the ROM codebase to C++ or another high-level language.
To compile and run:
cd src
make
cd ..
./run.sh
OR using Docker to startup and shutdown:
docker-compose build
docker-compose up
docker-compose down
This will mount the player, log, and json/area directories for easier editing of player and area files (when using OLC in-game).
To play:
telnet localhost 4000
BaseMUD comes packaged with a level 60 "Implementor" account.
The name is Admin
and the password is Admin
. Change the password and,
if you'd like, create your own god character and raise them to level 60.
BaseMUD is based on QuickMUD, which is based on the ROM 2.4 beta version of Merc 2.1 base code, which is in turn based on DikuMUD. Credits in licenses:
ROM 2.4 copyright (c) 1993-1995 Russ Taylor.
MERC 2.1 code by Hatchet, Furey, and Kahn.
DikuMUD by Hans Staerfeldt, Katja Nyboe, Tom Madsen, Michael Seifert, and Sebastian Hammer
If you wish to use BaseMUD, you must comply with the following licenses:
doc/license.doc
: DikuMUD licensedoc/license.txt
: Merc Release 2.1 licensedoc/rom.license
: ROM 2.4 license
As with QuickMUD, BaseMUD is packaged with the following code (which has been integrated into the source code more thoroughly):
- OLC 1.81
- Lope's Color 2.0
- Erwin's Copyover
- Erwin's Noteboard
- Color Login
This project is still very rough. I've been testing things as I go, but I've been haphazardly moving large swaths of code around under the premise that everything will be thoroughly tested later. Hopefully the resulting code will be modular enough that some sort of unit testing environment will be possible.
Despite the heavy refactoring,
BaseMUD strives to be a pure version of ROM 2.4. However, in the process of sifting
through all this code, sometimes I just can't help myself.
Some small changes and additions have been made, which can be enabled/disabled by
uncommenting/commenting the #define BASEMUD_(FEATURE)
lines in src/basemud.h
.
Changes that I've considered bugfixes, typo / spelling error fixes, or patches
to small oversights (i.e, fixing spells that have no casting message) will be
left in.
Most of the code has been manually linted for better readability and brevity. Some changes are subject to taste, but much care has been spent to make the code consistent and easy to read. Here are some general style changes:
- Vertical space is prioritized highly - two-line if() statements are bountiful.
- Horizontal space is also cherished. Some lines exceed the old standard 80 character limit just barely to avoid some ugliness.
- Several giant tables, such as spells, races, and classes, go WAY over the line so they can be read and modified as tabular data.
- Redundant comments have been removed for the most part in favor of renaming functions, parameters, and variables to be more self-evident. The author comments have been preserved out of respect and to comply with licenses.
- All tabs have been removed and replaced with spaces. Everywhere.
Some changes may also be temporarilly regressive as a staging step for some larger refactoring. In other words, some things are a mess because I'm still cleaning it :) The affect code, for example, had all of its easy-to-read assignments replaced with incomprehensible one-liner functions. The long-term plan, in this case, is to move all of the affects to a table, at which point this will be fixed.
Note: OLC and Copyover code is also largely untested, and may even crash! D:
Want to contribute or just chat about the project?
You can locate us at the Mud Coder's Guild Slack; reach out to either Synival or Nikhil for an invite to the private BaseMUD channel.
Some small changes have been made, most of which are small quality-of-life improvements, some of which are personal preferences that will eventually be phased out or at least optional. A handful of changes were just for fun.
Major features:
- The world can now be exported and imported in JSON format.
Deployment:
- The
rom
executable is now in the/bin
directory and run from the root directory. For convenience, you can userun.sh
in the root directory. - The
Dockerfile
has been temporarilly removed until some of the structural changes are accounted for.
Optional Features - enable/disabled by uncommenting/commenting #define BASEMUD_(FEATURE)
in basemud.h
:
BASEMUD_SHOW_DOORS
: Open and closed doors are now visible in auto exits. This reveals some otherwise hidden exits but makes exploring sooo much nicer.BASEMUD_SHOW_ARRIVAL_DIRECTIONS
: When a character arrives in the room via standard movement, you will now see in what direction they came from.BASEMUD_SHOW_OLC_IN_PROMPT
: Added OLC editor and vnum to the front of the prompt by default.BASEMUD_SHOW_ABSOLUTE_HIT_DAMAGE
: Hits and misses have additional adjectives to show their power in addition to the verb that shows percentage of max hit points. Example: "You wound a scary beholder with your excellent slash."BASEMUD_SHOW_ENHANCED_DAMAGE
: Attacks from successful enhanced damage rolls are now displayed as "heavy ". Example: "You scratch a lich with your epic heavy pierce."BASEMUD_SHOW_RECOVERY_RATE
: Thescore
command now shows your current hit/mana/move rates. This was a debug feature that I felt was nice enough to have.BASEMUD_COLOR_STATUS_EFFECTS
: The (Glowing), (White Aura), (Red Aura) etc. tags have appropriate colors.BASEMUD_COLOR_ROOMS_BY_SECTOR
: Room titles are colored based on their terrain type.BASEMUD_MORE_PRECISE_CONDITIONS
: More status notifications in addition to 'excellent', 'nasty wounds', etc. There are now different messages for every interval of 10% of hp.BASEMUD_MORE_PRECISE_RELATIVE_DAMAGE
: More low-level damage verbs during combat. The 1~15% damage range verbs change from "scratch, graze" to "annoy, scratch, graze, scrape, bruise".BASEMUD_MOBS_SAY_SPELLS
: Caster mobs now say their spells when casting just like players.BASEMUD_NO_RECALL_TO_SAME_ROOM
: Recall attempts to the target recall room are now ignored completely.BASEMUD_NO_WORTHLESS_SACRIFICES
: You can no longer sacrifice worthless objects.BASEMUD_GRADUAL_RECOVERY
: Hit / mana / move generation now happens on every pulse rather than in singular giant ticks. This is undoubtedly a CPU hog, and will need to be improved in some way! In the meantime, it's very luxurious :)BASEMUD_CAP_JOINED_AFFECTS
: When affects are joined, the duration will stack as before, but the modifier will cap out at the min/max between the two affects. For example: the 'chill touch' spell will reduce strength, but when stacked, the strength penalty will use the stronger penalty between the old and new affects, rather than add them together as before.BASEMUD_ALLOW_STUNNED_MOBS
: Mobs can now be in stunned, incapacitated, and mortally wounded states.BASEMUD_DETECT_EXTREME_ALIGNMENTS
: Added (Golden Aura) and (Black Aura) for extreme good/evil characters.BASEMUD_MATERIALS_COMMAND
: Object / character materials can now be shown with the 'materials' command.BASEMUD_DISENGAGE_COMMAND
: Added thedisengage
command to let spellcasters stop fighting and recover mana. Doesn't work if they're being targetted by anyone in the room.BASEMUD_ORDER_ALL_COMMAND
: Added@
command as a shortcut fororder all
.BASEMUD_ABILITIES_COMMAND
: Addedabilities
command, which shows both skills and spells.BASEMUD_PIXIE_RACE
: The "pixie" race has been added just for fun. They can fly, and they're small, they detect good/evil, and are extremely frail!BASEMUD_IMPORT_JSON
: Imports all.json
files in thejson/
folder before loading the traditional.are
files.
Quality-of-Life Features:
- Current position can be shown in prompt.
- Exits in the prompt now show open/closed status (requires
#define BASEMUD_FEATURES
). - Stunned / incapacitated / mortally wounded / dead state checks are now based on percentage of max hit points.
- The 'lore' command has been implemented. It works like the 'identify' spell, but hand-picks information based on your skill percentage. What the player knows about is random, seeded by the player's name and the object's vnum. It currently cannot be practiced beyond 75%.
- Some, but not all, simple "Ok." messages have been replaced with useful things.
- Lots and lots of warning messages for mismatched doors or keys when loading zones. This produces some warning messages from the stock zones (which are warranted).
- Added warnings for
'E'
resets with unequippable wear locations for the item or wear locations that are already taken on the mob. Many of these errors are acceptable with the stock world, but many indicate real bugs. - New messages for passing through doors (for both sides).
- Drunkeness, thirst, hunger, and fullness now gives you more useful messages when the state changes via drink/eat.
- If standing on/at/in something, The
stand
command without any argument will now step off/out. - Modified the
dump
command to allowdump stats
ordump world <raw | json>
. Files are dumped to thedump/
directory. - Areas already loaded by JSON importing are now ignored.
- Warnings are now produced if non-doors have keys.
- Warnings are now produced for keys that don't exist.
Gameplay Changes:
- Door open(always) and closed(w/
BASEMUD_FEATURES
) statuses are visible in the "exits" command. - Beds, tents, stools, etc. have automatic flags for
STAND_IN
,STAND_AT
, etc. in addition to some standard hit and move regeneration bonuses. - Bashing / tripping was effectively useless because combatants were
automatically moved back to standing position in
violence_update()
. This is now a bit smarter, with cooldown rates. - You cannot change position while in a daze / cooldown state. You can still issue several commands while knocked down, but you can't get back up!
- Standing up in combat is automatic once the cooldown period has been reached. This is essentially the same as before where you would always stand up during combat when attacked, but now it's no longer instant.
- Form / parts for races have been reviewed and tweaked slightly (consistency between canids). It makes no gameplay difference, but may in the future.
- Tiny balance changes have been made to certain skills and stat regen amounts.
- Fast healing is only active when sitting, resting, or sleeping.
- Meditation is only active when sitting or resting.
Patched Oversights:
scan <dir>
only checks two rooms ahead rather than four. To see that far, you'll need to use the "farsight" spell :)- Tweaked some stat tables very slightly to allow for steady progression in areas that looked like oversights. (Maximum weight capacity did not increase steadily where it easily could have.)
- You can no longer trip targets without feet or legs.
- Looking for doors by name now works the same way as looking for anything else.
You can now find a door by number - for example, if you're carrying "a portable door",
and there are two "door"s in the room, you can now say
open 3.door
to open the room's second door. - Clean-ups and bugfixes to OLC to ensure stock zones are saved exactly as they are loaded.
(Confirmed using
diff
comparisons on.are
files and one gargantuaneverything.json
file containing the entire world in JSON format) - Certain messages from shopkeepers, trainers, etc. would try to look and act like the
tell
command, but it wasn't consistent and generally unecessary. Now they're just simple messages. Hopefully this doesn't break any mob scripting or anything like that. - Area files can now be loaded in any order.
- NPCs can no longer bash, trip, kick, etc. while they're on the ground.
Bug Fixes:
- The
scan
command now checks for mob visibility in rooms based on room light level. scan
doesn't peek through doors anymore.- Doors are now properly closed and locked and both sides when loading zones. This fixes some resets as well.
- Getting hit by fire or ice breath no longer makes you less hungry or thirsty. Whoops!
- 'Word of recall' now writes "Spell failed." to the caster rather than the victim.
- 'Heat metal' would drop an item more if the opponent had more dexterity - this is fixed, but still needs balancing.
- In most cases, finding characters and objects by '1.thing', '2.thing', etc.
will now count correctly when searching multiple locations (e.g, check the room,
then the rest of the world). This is done by a
find_continue_count()
call preceding the next check. - Checks for immune/resistant/vulnerable flags completely ignored default
weapon/magic imm/res/vuln flags, despite existing code with implied behavior.
Resistance no longer overrides default immunity, and vulnerability now
properly downgrades the default status (imm
->
res, res->
normal, normal->
vuln). - When wearing an item, all potential slots are now checked, not just related slots (e.g, a ring can now be held as well as worn on a finger).
- The AREA_LOADING flag is no removed for the last area loaded.
Code Reorganization:
- Files have been split and rearranged so there is (hopefully) less time spent hunting for functions or data tables.
- All
do_*()
functions are in appropriateact_*.c
orwiz_*.c
files. - Introduced
do_filter*()
functions whose role is to break out of code flow indo_*()
functions with appropriate messages. - Several functions have been renamed to follow the same naming scheme for consistency's sake.
- More data has been moved from functions to tables to facilitate easy modification, addition, or removal of features.
- Integrated board and OLC features into the main code.
handler.c
has been seperated out intochars.c
,objs.c
,rooms.c
, andfind.c
.- Mob / object materials are now stored in a table rather than as a simple string.
- Unused flags in flag / type have been added to their respective tables so they're accounted for.
- Moved "day of the week", "months of the year", "weather", and "sun position" values
to a tables in
tables.c
. - Weather change calculations have been made easier to understand (but could still be better).
- Merged
db2.c
intodb.c
and separated old database functions intodb_old.c
. - The
wear
command now uses a table for looking up wear slots, as doeschar_wear_obj()
. This is a big improvement from copy+pasted "You wear ___ on ___" code for each possible slot. - Separated descriptor functions in
comm.c
into new filedescs.c
. - Moved all top-level booting functions to
boot.c
. - Moved mob/obj instantiation and clone functions to
chars.c
andobjs.c
. They are now namedchar_create_mobile()
,char_clone_mobile()
,obj_create()
andobj_clone()
. - Moved all shared global variables to new
globals.c
andglobals.h
. All other global variables used in a single file are now markedstatic
. - Moved all string or memory allocation functions from
utils.c
to newmemory.c
. Functions inmemory.c
have been renamed more appropriately, reviewed, and wiped clean of hungarian notation (yay). - Moved all typedefs to new
typedefs.h
. This way, structures can be used in all headers before they're defined.
Code Reduction:
- LOTS of redundant code has been swapped up with small helper functions or... (gulp) macros. Macros are indeed ugly and bad - they'll be replaced with something better in a later iteration of refactoring, potentially using C++.
- Common shared code between
do_*()
functions have been separated out and moved todo_sub.c
. - Extremely common code patterns were replaced with macros for brevity.
Most significant is
BAIL_IF(cond, msg, ch)
to replaceif (cond) { send_to_char(msg, ch); return; }
found in 99% ofdo_*()
functions. - Common linked-list code has been replaced with helper macros. This is a stepping-stone to a more reliable set of helper structs and functions to manage all lists.
get
,put
,drop
, andgive
have been refactored to use a lot of shared code.- NPC breath attacks now use a shared
perform_breath_attack()
function. - Replaced
is_safe()
andis_safe_spell()
with consolidateddo_filter_can_attack_real()
. Added shorter functions:do_filter_can_attack()
,do_filter_can_attack_spell()
,can_attack()
, andcan_attack_spell()
. - Merged code for displaying exits in
look
,exits
, and exits in the prompt. - Replaced "sprintf(), send_to_char()" with "printf_to_char()" wherever possible
- Replaced "sprintf(), log_string()" with "log_f()" wherever possible
- Replaced "sprintf(), bug()" with "bugf()" wherever possible
- Replaced "sprintf(), wiznet()" with "wiznetf()" wherever possible
Code Changes:
- Several systems have been overhauled completely, namely: the color system, recycleable objects, and the "lookup" functions.
- Object values now use a union with all property types. Nearly all references
to
obj->value[x]
have been replaced byobj->v.<type>.<property>
. Example:obj->value[0]
for weapons is nowobj->v.weapon.weapon_type
. - Reset values now use a union with all property types. Nearly all references
to
reset->value[x]
has been replaced byreset->v.<type>.<property>
. Example:reset->value[3]
for the'E'
(equip) command is nowreset->v.equip.slot
. - Separated mob- and player-specific flags (
ACT_*
) into separateMOB_*
andPLR_*
flags, with separate variables. This should prevent a lot of bugs. act_new()
now uses flags instead of anint type
variable to determine recipients. Target#define
s have been appropriately renamed to be more accurate. Flags could potentially be extended to have flags regarding visibility for blindness, deafness, or other options.- Introduced functions
act2()
(messages to char + room) andact3()
(messages to char + target + room). - Replaced some groups of
act()
to CHAR/NOTCHAR and CHAR/VICT/OTHERS toact2()
andact3()
respectively. - Some spells (like 'blindness') now can be invoked with or without messages so other spells that use cause the affect won't give "spell failed" messages. (A better solution would be a way to 'scope' functions like this to have their messages suppressed. C doesn't have anything like that, so perhaps a global stack-based system?)
- Exits between areas are tracked internally using a "portal" system that link exits/rooms together
with keywords. This doesn't change anything for gameplay or
.are
files, but will (eventually) make building easier by defining links between areas without writing in specific vnums (e.g, linkmidgaard_west
tomidennir_east
). - Introduced
dam_type
classesDAM_PHYSICAL
andDAM_MAGICAL
for determining immunity. - The "TO_XXX" affect flags have been renamed to "AFF_TO_XXX" to avoid conflict with act() flags (TO_CHAR, TO_NOTCHAR, etc)
- Clarified wear flags (head, finger, wrist) vs. wear locations (head, lfinger,
rfinger, lwrist, rwrist). Most code now uses
wear_flag
andwear_loc
to distinguish the two. A few random bugs (likely by me) from this confusion have been patched. - All
do_*()
functions are now defined usingDEFINE_DO_FUNC(do_*)
. This way, if the arguments to a top-level command are changed, every function doesn't need to be modified. - Replaced most character and object macros with small functions. Macros have been left in place as shorthand functions.
- Replaced _app[] lookups with
<attr>_app_get()
,char_get_curr_<attr>()
functions. Added lookup functions for all stat bonuses. - Replaced key 0 and -1 with KEY_NO_KEY and KEY_NOKEYHOLE wherever it was seen.
- Non-doors now always have their key set to KEY_NOKEYHOLE.
- Separated room/mob/obj hash registration process into separate functions.
- Removed
target_name
global variable - it is now explicitly provided to the functions that require it.
There are some higher-level abstractions that have likely caused a slight performance hit or a little more memory usage, but this is hardly an issue in 2019 ;) Some profiling will likely happen later on down the line.
Enjoy!
-- Synival