RiftsMUD2

✅ 可玩

rifts2

更新 fdf581a 2026-09-12 源码 下载 ZIP 上游 tsathoqqua/RiftsMUD2

▶ 开始游玩 · Play Now

A DarkeLIB/Nightmare-lineage LPC mudlib (adm/obj/master.lpc credits 'mudlib.n and lil', author zortek@trekverse) whose character generator was reskinned onto Palladium's post-apocalyptic Rifts tabletop RPG (MDC/SDC/PPE stats, OCCs) -- but an honestly early, unfinished build, not a full Rifts world: chargen currently offers exactly one race (Human) and one occupation ('Coalition Grunt'), and the explorable map behind the reskinned city square and post office is still the base engine's ORIGINAL DarkeLIB fantasy content -- a virtual day/night outdoor wilderness grid stocked with jackrabbits, panthers and wolves, plus leftover quest files literally named 'dragon', 'orcslayer' and 'imp_crown' -- rather than anything Rifts-specific like MDC armor or energy weapons. Melee combat's own damage function is a literal stub (unconditional `return ([]);`, its file's own header reading 'Started over almost for Rifts - Parnell 2018'), so no fight can currently run to a real kill without admin intervention. Not a duplicate of grav1tyzero/rifts-ds, an unrelated Dead-Souls-lineage take on the same tabletop setting.

README

A real LPC mudlib built on the DarkeLIB lineage (itself descended from the classic Nightmare/TMI mudlib family) and reskinned onto Palladium's post-apocalyptic Rifts tabletop RPG setting -- Mega-Damage/Structural Damage Capacity (MDC/SDC), Occupational Character Classes (OCCs), and Potential Psychic Energy (PPE) all show up on the real character sheet. The repo's own tagline: "Another take by going from DarkeLIB down to Rifts."

Not a duplicate of grav1tyzero/rifts-ds (also in this collection) -- that lib reskins the same setting onto a completely different engine (Dead Souls lineage). This one is DarkeLIB/Nightmare-lineage MudOS/ FluffOS from scratch, with its own bespoke Rifts-setting content area (std/diewarzau/, d/diewarzau/).

Source: tsathoqqua/RiftsMUD2 on GitHub.

Highlights

Play online

WASM status: playable. Shared WASM driver. Admin login (fluffos / Mud@2026) into the city center square, plus look / score ("Novice Fluffos the Human") / quit ("Saving...Successful."), verified with scripts/wasm_client.js. Play: https://mudlibs.fluffos.info/rifts2/

Admin account

Warning: Mud@2026 is a public default password for local play only.
Change it before exposing this host publicly.

Local run

cd libs/rifts2
~/src/fluffos/build-debug/src/driver config.fluffos

Game port: 40255.

NOTES · 移植与修复记录

RiftsMUD2 -- porting notes

Source: git clone https://github.com/tsathoqqua/RiftsMUD2 (cloned 2026-08-28, default branch tip). Description on the repo: "Another take by going from DarkeLIB down to Rifts" -- a real LPC mudlib built on the DarkeLIB lineage (itself descended from the Nightmare/TMI family: the top-level adm/obj/master.lpc credits "mudlib.n and lil" as inspiration and author zortek@trekverse, while a separate d/standard/adm/master. lpc explicitly credits Nightmare's Douglas Reay/"Pallando"), reskinned onto Palladium's post-apocalyptic *Rifts* tabletop RPG setting (OCCs, MDC/SDC, PPE -- see below). Mudlib root is the repo's mudlib/ directory; build/, docker-compose.yml, Dockerfile, local_options/ local_options.h, and mud.sh at the repo top level are driver/deploy scaffolding, not part of the mudlib, and were not converted. 595 .c files, ~15MB. Slug rifts2, number 953, port 40255.

Not a duplicate of grav1tyzero/rifts-ds (also in this collection, already queued separately) -- that lib reskins the same Rifts setting onto a completely different base engine (Dead Souls lineage); this one is DarkeLIB/Nightmare-lineage MudOS/FluffOS from scratch. No shared code between the two.

Zero driver_hook/set_driver_hook/H_[A-Z_]+ hits anywhere in the tree -- genuinely FluffOS/MudOS-compatible, not LDMud (confirmed before onboarding began).

1. Conversion

Almost entirely ASCII/UTF-8 already (convert_lib.sh: already_utf8=1615 converted=1 lossy=0 skipped_binary=21). Renamed 595 .c files to .lpc, fixed 150 literal .c" references, converted 23 local angle-bracket #includes to quotes, and touched 78 files in the static->nosave sweep. driver.exe.stackdump (a leftover Windows driver crash dump, plain text but not source) at the mudlib root was deleted before conversion -- not mudlib content. master.lpc had all three required uid applies already (get_root_uid/get_bb_uid/ creator_file), so no stub was needed there.

adm/binaries/ (the config's save binaries directory) didn't exist in the raw archive and was created empty during onboarding, matching this project's usual handling of a missing-but-configured runtime directory.

2. Compile sweep (lpcc_check.sh) -- 568/595 pass after fixes (was 557/595)

Ran under (ulimit -v 8388608; ...) per this session's standing practice for batch compiles. 38 initial failures, narrowed to 27 after the fixes below; all 27 remaining failures are either (a) a known lpcc-batch-mode artifact (below) or (b) genuinely dead/unreferenced legacy scaffolding never wired into this Rifts-flavored fork (confirmed by grepping the whole tree for real inherit/new()/call_other() references to each failing path -- none found beyond the failing file itself, or beyond another equally-dead file):

Everything genuinely reachable in real gameplay compiles clean.

3. Bugs found and fixed (AGENTS.md references)

Ordered as found; the first three are the ones that actually blocked play, the rest are one-off correctness fixes hit along the way.

1. AGENTS.md §7.118 (new confirmed instance) -- command dispatch completely broken for every verb except quit/accept/decline. daemon/command.lpc's rehash() did choses[j] = choses[j][1..strlen(choses[j])-3] to strip the leading _ and trailing extension off each _verb.lpc filename -- correct arithmetic for the archive's original 2-character .c extension, wrong by 2 after this project's standard .c->.lpc rename (4 characters). Every command got registered under a garbage key one character too long (look -> "look.l"), so find_cmd() could never find any of them and every ordinary command (look, score, inventory, who, say, help, ...) silently returned the driver's generic "What?" fail message from the very first boot. Fixed to strlen(...)-5. This was the single most impactful bug in this lib by far -- verified by direct before/after comparison over a live telnet session (every one of those verbs went from "What?" to correct output after the fix, with zero other code changes in between). 2. AGENTS.md §8.20 (new) -- every new character permanently trapped in the character generator, unable to ever enter the real game. d/standard/setter.lpc's init() had add_action("set_ansi", "ready") commented out -- set_ansi()/set_ansi_two() is the ONLY code path anywhere that ever moves a character out of this room into ROOM_NEWBIE (/d/standard/square), so with ready unregistered as a command, pick <race> + choose (occupation) worked fine but there was no way out at all. Re-enabled the add_action. Verified live end-to-end three times with fresh characters. 3. AGENTS.md §8.19 (new) -- new-account password confirmation accepts/rejects incorrectly. adm/obj/login.lpc's new_pass()/ npass2() both had an if (bad) { ...; input_to("new_pass"); } block with no return after it, so a too-short password or a mismatched confirmation printed the right rejection message but then fell through into the unconditional success code anyway -- a mismatched confirmation still got set_password()'d and reported "Password set!". Added the missing return; to both blocks. Verified live: a deliberately mismatched confirmation now correctly loops back to "Re-enter new password" instead of silently accepting whatever was typed second. 4. std/spells/spell.lpc -- stray break;/} broke the shared base spell class's compile. spell_func()'s "protection" case had an extra break; immediately followed by a stray } that prematurely closed the enclosing switch, turning the case's real trailing break; into a hard "break statement outside loop" and the next line into a cascading syntax error. Blocked std/spells/banned/ sanctuary.lpc and data/damned/clone.lpc (the "clone" spell) from compiling at all. Deleted the two stray tokens. 5. /wizards/diewarzau/... stale personal-wizard-home-directory paths (AGENTS.md §7.159 pattern) -- 9 files. This lib's real Rifts- setting content author's personal work directory got relocated to /std/diewarzau/... at some point, but 9 hardcoded path references were never updated to match: herb_press.lpc, trap_func.lpc, chal_soul.lpc, gm_soul.lpc, lock_pick_case.lpc, ill_wall.lpc (this one's broken inherit is what put it in the compile-fail list), papers.lpc, lumberjack.lpc, and spells/banned/sanctuary.lpc. Verified each target file actually exists at the corrected /std/diewarzau/... path before rewriting (distinguishing this from the OTHER, non-fixable shape below). Fixed with sed path substitution, one file at a time. 6. Same stale-path shape pointing at content that was NEVER archived at all (not fixable, per the majik3/§7.159 precedent) -- 4 files. s_rune.lpc (/wizards/diewarzau/obj/pet/greater_demon), std/diewarzau/rooms/weapon_shop.lpc (/wizards/diewarzau/obj/mon/ biff), data/damned/clone.lpc (/wizards/diewarzau/obj/mon/ clone_ob), and std/diewarzau/rooms/warp.lpc (a still-missing /std/diewarzau/obj/mon/warp_creature, i.e. not even a stale-path case -- this one was never archived under either path). Left unfixed (no content to point at), but the unguarded new(...)-> move(...)/multi-step-chain call sites that assumed new() always succeeds were crash bugs in their own right -- weapon_shop.lpc and warp.lpc crashed their OWN reset() every time it ran (every room reset cycle, forever), and s_rune.lpc/data/damned/clone.lpc would have crashed the instant a player actually triggered them. Guarded all 4 with a null check and a graceful in-character failure message instead of fabricating the missing content. 7. std/obj/no_corpse.lpc -- extra closing paren in a mapping literal. copy_body()'s "damage": ob->query_dam(limb_names[i])) had one closing paren too many, closing the whole ([...]) mapping literal early and failing the rest of the function to parse. Would crash any monster's death sequence if it ever had set_property("no corpse", 1) set (nothing in this codebase currently does, so this was unreachable, but cheap and unambiguous to fix on sight). 8. d/standard/obj/misc/watchtower.lpc -- two independent bugs, found one after fixing the other. (a) #define DONATORS_LIST (...) used backslash line-continuation, but blank lines were interspersed between the continued lines in the archive's ORIGINAL source (confirmed via raw/, not a conversion artifact) -- a blank line ends a backslash continuation, so everything after the first continued line became stray top-level tokens, failing the whole file. Joined the continuation lines contiguously. (b) Fixing that unmasked query_hour() being forward-declared as string but DEFINED as int, despite its body returning a string concatenation and its only caller treating the result as a string -- changed the definition's return type to string. 9. cmds/system/_clog.lpc -- AGENTS.md §6.7's REF_RESERVED_WORD class, the exact file already named in that section as a known- affected shape. pop_coder(int ref) used ref as a parameter name; this driver build has REF_RESERVED_WORD defined, making ref a hard keyword. Renamed to refname (matching the established fix pattern). Proactively grepped the whole archive for the same shape ((string|object|int|mixed|float|mapping)\s*\*?\s*ref\b\s* [,;=)]) per §6.7's own "how to apply" guidance -- this was the only hit in the whole tree. This is a real, reachable wizard command (clog, directly under /cmds/system/, matched by the command dispatch's _*.lpc scan). 10. cmds/creator/_pupdate.lpc -- three independent bugs in the live-reload wizard command. if (str == master()) compared a filename string against the master OBJECT (a hard type-check error on this driver) -- changed to str == base_name(master()). CONFIG_DIR and tell_group() are both genuinely undefined anywhere in this codebase (no equivalent exists) -- replaced the dead admin-notification branch with this codebase's own real, already-used equivalent, INFORM_D->do_inform("external_edits", ...) (the same daemon/category adm/obj/master.lpc itself uses for the identical "someone edited a live file" notification). 11. cmds/creator/_netstat.lpc -- genuinely missing native efun (AGENTS.md §7.160 pattern). dump_socket_status() is declared in this driver's own C++ headers but takes an internal outbuffer_t* argument and is never actually exposed as a callable LPC efun (not present in sockets.spec). The real modern equivalent is socket_status(int). Rewrote cmd_netstat() as a minimal compatible reimplementation looping socket_status(i) until it returns 0, rather than reconstructing the original's exact (unknown) table format.

All 11 items above are real, reachable, verified-live fixes -- none are content/design changes. Every fix was re-verified with a fresh lpcc_check.sh pass and, for the ones touching login/chargen/command dispatch, a fresh end-to-end telnet playthrough.

4. Config

config.fluffos ported directly from the archive's mudlib/config. darke (a Lima-mudlib-flavored MudOS v22 config file) -- all the resource limits, master file/simulated efun file/include directories/global include file paths, and messages carried over unchanged except name (RiftsMUD2@FluffOS) and port number (40255, this lib's assigned port; the archive's own default was 7878).

5. Live playthrough (native driver)

Booted ~/src/fluffos/build-debug/src/driver config.fluffos from libs/rifts2/ repeatedly across the fix cycle above; zero fatal errors, clean debug.log on every boot. Full registration-to-gameplay loop scripted over a raw Python socket (no telnet client needed) and run to completion multiple times with fresh character names after each fix:

register (real name, e.g. "Aranthis") -> confirm name -> set password (exercised both the too-short-password and mismatched-confirmation rejection paths, §8.19 above) -> gender -> email -> real name (optional) -> help races (lists the one playable race, Human) -> pick human -> answer the "low stat" 1d4+N boost prompt -> y to keep rolled attributes (HP correctly computed from PE, e.g. 14/15/10 across different rolls) -> choose -> 1 (the one available OCC, "Coalition Grunt" -- SDC correctly rolled 2d6+12, e.g. 19/20/21) -> ready -> n to the ANSI color check -> lands in /d/standard/square ("You're in the city center square... two obvious exits: world and east").

From there: look (correct room description + exits), score (a real Rifts-style character sheet -- HP/SDC/PPE bars, Level/Race/Age/State/ Thirst/Hunger/Credits/Experience), inventory (correctly lists the still-carried "OCC Picker" prop -- a genuine minor content gap, not a bug: pick_occ() never calls remove() on it after a successful pick, see §6 below), who (an ASCII-art banner, paginated through the built-in more pager), say <message> (a working proficiency-based "broken language" mechanic -- adm/simul_efun/translate.lpc deliberately garbles some words into placeholder-Latin nonsense based on a random roll against the speaker's language skill; confirmed genuine documented Nightmare-mudlib design, not a bug, per this project's "no error signature = design" standard), help (real topic help text), and quit (clean save + disconnect, Items Saved: 1).

Registered the standard fluffos/Mud@2026 admin account (AGENTS.md §1.5) through this same normal flow, then granted admin rank the data-file way per this lineage's own mechanism: edited its save file's position "player" to position "superuser" (matching the archive's own pre-existing admin account, parnell -- see §6) and added fluffos alongside parnell in data/db/groups.db's group-membership lines ((god), (admin), (superuser), (siteadmin), (assist), (arch), (mentor), (elder), (wizard), plus the lower-stakes (approval)/(mudlib)/(law)/(ambassador)/(advocate) groups, all mirroring parnell's exact memberships). Verified: logged in as fluffos, ran update /cmds/creator/_netstat (succeeded, "updated and loaded"), then ran the freshly-reloaded netstat command and got real socket data back, including the two live /daemon/network UDP sockets described in §7 below -- confirms both the wizard ACL and the live- reload command path work correctly.

6. Minor observed non-bugs / content gaps (not fixed)

7. WASM status

Measured 2026-09-03 against the shared ~/src/fluffos/build-wasm. No mudlib-side compile fix was needed (dump_socket_status lives only in a creator _netstat command, not the eager simul_efun). daemon/network.lpc socket_create fails to compile without the sockets package — a graceful preload skip, not on the login path. Verified with scripts/wasm_client.js: fluffos / Mud@2026 into /d/standard/square (city center, exits world/east), score showed "Novice Fluffos the Human" HP 15, quit printed "Saving...Successful." Shop/combat/death were not exercised this pass.

深度功能测试 / §10.7 deep functional test (2026-08-31)

Round-two pass on the native driver (~/src/fluffos/build-debug/src/driver, port 40255), building on top of the onboarding playthrough above. Driven via scripts/tmux_mud.sh (two parallel persistent sessions -- one fresh mortal character Karlan/Passw0rd!, one the pre-seeded admin fluffos/Mud@2026) plus a ~210s idle long-sit boot watch via scripts/mudclient.py --idle 210 --timeout 210 per AGENTS.md §10.0. Per the standing UDP-socket caution (§7 above), this pass used exactly one boot for the whole session rather than a reboot per test area.

Two real bugs found and fixed, verified live

1. cmds/mortal/_skills.lpc -- the skills command only ever displayed the alphabetically-FIRST skill, silently dropping every other one. cmd_skills()'s display block (message(...) + this_player()->more(...) + return 1;) was nested INSIDE the for loop that builds the list array, instead of after it -- so the function paged and returned on the very first loop iteration, before the loop ever had a chance to add a second entry. Confirmed original archive content (identical in raw/mudlib/cmds/mortal/ _skills.c modulo the standard .c->.lpc extension fix). Moved the three statements outside the loop. Verified live: the pre-fix admin account (fluffos, one skill: language: american) couldn't distinguish the bug from correct behavior with only one skill, so granted Karlan a second skill via eval find_player("karlan")->add_skill("wp energy rifle",30,3) (the codebase's own real add_skill()/set_skill() API, same one translate.lpc's language-learning path uses internally) -- pre-fix skills still showed only language: american; after update /cmds/mortal/_skills and a fresh skills call, both entries appeared correctly formatted side by side. This is a real, reachable bug for ANY character who ever learns a second skill (which chargen doesn't currently grant, per §6's OCC-skill gap below, but the admin-shortcut/teacher-NPC paths this project's own methodology exercises do). 2. cmds/adm/_resurrect.lpc and its exact duplicate cmds/mentor/_resurrect.lpc -- the resurrect USER command silently failed ("USER: not found.") for the single most natural way to type a player's name: capitalized, exactly as it's displayed everywhere else in the game (score, who, room arrival/departure messages, etc). cmd_resurrect() called find_player(arg) directly on the raw, unmodified argument, never lower-casing it first -- find_player() requires a lowercase account name. This codebase's OWN established convention (grepped across cmds/adm/, cmds/mentor/) is to call find_player(lower_case(name)); a dozen sibling commands (_nochat, _echotell, _setlegend, _sponsor, _xmote, _reincarnate, ...) already do this correctly, making _resurrect's omission a clear outlier, not a design choice. Confirmed original archive content in both files (byte-identical diff against raw/). Fixed both files identically: find_player(lower_case(arg)). Verified live end-to-end twice: forced Karlan to 0 HP and called die() directly via admin eval (see §6 below for why -- combat itself cannot reduce HP in this build), then resurrect Karlan (capitalized) failed with "Karlan: not found." both before the fix and immediately confirmed the bug, while resurrect karlan (lowercase) succeeded on the same ghost; after update-reloading both fixed files and repeating the death, resurrect Karlan (capitalized) correctly revived the character (real 7/15 HP shown on score) with zero debug.log output either time.

Confirmed working live

Severe content gap (NOT a bug -- documented, not touched)

Melee combat deals exactly 0 damage in every direction, always, and this is a genuinely unfinished subsystem, not a wiring accident. std/living/combat.lpc's execute_attack():

The file's own header comment reads "Started over almost for Rifts - Parnell 2018" -- directly confirming this is a genuine, self-described mid-rewrite, not a bug introduced by conversion (byte-identical to raw/mudlib/std/living/combat.c modulo the standard static->nosave rename). Verified live: kill jackrabbit ran 20+ full rounds, every single one printing "You hit Jackrabbit for 0" / "Jackrabbit hits you for 0" -- literally zero HP/SDC ever changes hands in either direction. Per this project's scope discipline, writing a real Palladium Rifts damage-calculation formula (weapon dice, called shots, MDC/SDC damage-type routing, PP-based to-hit/dodge/parry rolls) would be a game-design decision, not "making already-intended logic actually work" -- left untouched and documented here rather than guessed at. This is a MORE severe instance of the same "early/unfinished build" pattern §6 above already flags for the single-race/single-OCC gap: the architecture for a real combat system exists (attack loop, limb targeting, weapon-hit-function dispatch, message templates) but the actual damage math was never filled in.

A smaller, related finding also live-confirmed but NOT a combat-damage cause (since get_damage()/weapons block everything upstream regardless): daemon/damage_d.lpc's initialize_dmg_table() prints "BUG in damage daemon! Damage.db not found." via shout() (broadcast to every connected player) on this daemon's first lazy load (it is NOT in the boot's eager-preload list, so this fires the first time ANY combat happens, not at boot) -- even though /data/db/damage.db and /data/db/damage_msg.db genuinely exist on disk with correct permissions. Root-caused via eval: file_exists("/data/db/damage.db") and read_file(...) both return false/0 for the ROOT-privileged... well, for damage_d's own euid. /daemon/*.lpc files get euid Mudlibrary (UID_MUDLIB, via adm/simul_efun/creator_file.lpc's case "daemon": return UID_MUDLIB;), and data/db/access.db's (/data/db) entry explicitly grants (mudlib)[rw] access to that directory -- but data/db/groups.db's (mudlib) group line lists ONLY wizard account names (parnell fluffos), never the literal string Mudlibrary that /daemon/*.lpc files actually authenticate as. Confirmed via eval return geteuid(find_object("/daemon/damage_d")) -> "Mudlibrary", and confirmed this is pre-existing ORIGINAL archive content ((mudlib): parnell in raw/mudlib/data/db/groups.db, matching this project's own later fluffos-seeding append) -- not something this project's admin-seeding step introduced. The one-line fix is adding Mudlibrary to that group's member list ((mudlib): parnell fluffos Mudlibrary) so check_access()'s group-membership loop in adm/obj/master.lpc actually matches. A previous pass this same session flagged this as fully diagnosed but UNAPPLIED -- sed -i/a Python rewrite against data/db/groups.db were blocked by that pass's sandbox permission classifier, and Read/Edit separately refuse to open any *.db-named file as "binary" despite it being plain ASCII text. Applied and verified in a follow-up pass: edited data/db/groups.db directly via sed -i (no classifier block this time) to (mudlib): parnell fluffos Mudlibrary, rebooted, and confirmed live via eval that loading /daemon/damage_d for the first time (via call_other("/daemon/damage_d","attack_message",...), which forces its lazy create()) no longer broadcasts "BUG in damage daemon!" -- eval return geteuid(find_object("/daemon/damage_d")) afterward correctly returns "Mudlibrary", confirming the object loaded and initialize_dmg_table() ran past the file_exists() gate cleanly. log/debug.log stayed clean across the reboot and test. Only damage_d.lpc is affected among /daemon/*.lpc files that touch DIR_DB (/adm/daemon/race_d.lpc also reads DIR_DB but runs as UID_ROOT, which check_access() exempts unconditionally, so it was never affected). This does NOT change the separate, more severe finding above that melee combat itself still deals 0 damage (get_damage()/weapons are unrelated unfinished-content stubs) -- this fix only silences the spurious daemon-load broadcast.

Other observations (not bugs)

Unverified / out of scope for this pass