Free-to-Play MUDs with Donation platforms
Running a MUD on donations requires balancing server sustainability against the tradition of free, open gameplay. This guide covers the technical implementation of donation platforms, entitlement tracking, and perk delivery systems that respect your community's expectations while covering hosting costs.

Map Pay-for-Perk Integration Points in Your Codebase
Identify every location where player capabilities are checked or modified. In Diku-derived MUDs, examine struct char_data in structs.h. For LPMuds, review the player object inheritance chain. Document which fields control inventory expansion, command access, and cosmetic displays. Create a spreadsheet categorizing each capability as cosmetic, convenience, informational, or gameplay-affecting. Only the first three categories are safe for monetization.
⚠ Common Pitfalls
- •Modifying combat statistics or experience gain rates
- •Locking essential questing commands behind paywalls
- •Failing to version control before adding perk checks
Design the Entitlements Schema
Create a database table to track player perks independently from character stats. The table must handle temporary entitlements, renewal dates, and grace periods. Use a many-to-many relationship between player accounts and perk types to support tiered donations.
CREATE TABLE player_entitlements (
id INT AUTO_INCREMENT PRIMARY KEY,
player_id VARCHAR(64) NOT NULL,
perk_code VARCHAR(32) NOT NULL,
granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NULL,
source ENUM('stripe', 'paypal', 'manual', 'volunteer') NOT NULL,
transaction_ref VARCHAR(128),
INDEX idx_player (player_id),
INDEX idx_expiry (expires_at)
);⚠ Common Pitfalls
- •Using character names instead of stable account IDs
- •Storing credit card data in your MUD database
- •Forgetting to index expiration dates for daily cleanup jobs
Implement Webhook Handlers for Donation Platforms
Configure endpoints to receive Instant Payment Notifications from PayPal or Stripe webhooks. Verify signatures before updating player records. Place webhook handlers outside your main game loop to prevent lag during payment processing. Use a message queue or flatfile intermediary if your MUD cannot accept HTTP requests directly.
# webhook_listener.py
import stripe
import subprocess
endpoint_secret = 'whsec_your_key'
def handle_donation(event):
if event['type'] == 'checkout.session.completed':
session = event['data']['object']
player_id = session['client_reference_id']
# Call MUD's CLI tool to grant perk
subprocess.run([
'./grant_perk',
player_id,
'supporter_badge',
'30d'
])⚠ Common Pitfalls
- •Processing payments synchronously in the game loop
- •Failing to handle webhook retries for duplicate grants
- •Not validating IP addresses against platform whitelists
Build In-Game Perk Verification
Modify command interpreters to check entitlements before executing restricted features. Create a centralized function `has_entitlement(player, perk_code)` that queries your database and caches results for the session duration. For LPMuds, implement this as a security daemon; for Diku derivatives, add to act.other.c or similar.
bool has_entitlement(struct char_data *ch, const char *perk) {
struct entitlement_data *ent;
// Check memory cache first
if (ch->player.entitlements_cached)
return hash_find(ch->player.entitlements, perk);
// Query database (pseudo-code)
ent = db_query(
"SELECT 1 FROM player_entitlements "
"WHERE player_id='%s' AND perk_code='%s' "
"AND (expires_at > NOW() OR expires_at IS NULL)",
GET_ID(ch), perk
);
return ent != NULL;
}⚠ Common Pitfalls
- •Querying the database on every command tick
- •Hardcoding perk codes as magic strings
- •Ignoring timezone differences in expiration checks
Create the Community Transparency Interface
Generate a public HTML page displaying anonymized funding goals, current server costs, and donor counts. Pull data from your entitlements table aggregating monthly totals without exposing individual donor amounts. Host this outside your MUD server to prevent game lag during page loads.
-- Monthly transparency query
SELECT
DATE_FORMAT(granted_at, '%Y-%m') as month,
COUNT(DISTINCT player_id) as donors,
SUM(CASE WHEN source='stripe' THEN 1 ELSE 0 END) as stripe_donations
FROM player_entitlements
WHERE granted_at > DATE_SUB(NOW(), INTERVAL 12 MONTH)
GROUP BY month;⚠ Common Pitfalls
- •Displaying specific donor names without opt-in
- •Showing real-time balance updates that reveal player activity patterns
- •Using game server CPU to generate reports
Implement Volunteer Credit Parity
Establish a secondary entitlement source for non-monetary contributions. Create entries with source='volunteer' for builders, coders, and event organizers. Ensure these credits appear identical to donation perks in-game to prevent tier stratification. Track volunteer hours separately for administrative purposes.
⚠ Common Pitfalls
- •Creating visually distinct 'volunteer' badges that stigmatize non-payers
- •Requiring monetary donations for builder positions
- •Failing to expire volunteer perks when contributions stop
Configure Retention Analytics
Set up tracking to compare 30-day retention between donors and non-donors without violating privacy. Export anonymized player IDs and last_login timestamps weekly to a separate analytics database. Calculate if perk acquisition correlates with playtime increases to verify that monetization enhances rather than replaces gameplay value.
⚠ Common Pitfalls
- •Tracking individual player behavior without consent
- •Using analytics that slow down game server
- •Drawing causation from correlation without A/B testing
Handle Edge Cases and Chargebacks
Write procedures for payment disputes. When Stripe sends a chargeback webhook, immediately suspend associated perks without banning the player character. Create a manual review queue for contested volunteer credits. Implement a grace period buffer (72 hours) before perk expiration to handle payment processing delays.
# chargeback_handler.py
def handle_chargeback(event):
charge = event['data']['object']
player_id = charge['metadata']['player_id']
# Suspend but don't delete
db.execute(
"UPDATE player_entitlements "
"SET suspended_at = NOW() "
"WHERE player_id = %s AND transaction_ref = %s",
(player_id, charge['id'])
)
notify_immortals(f"Perks suspended for {player_id} due to dispute")⚠ Common Pitfalls
- •Immediately deleting player accounts on chargeback
- •Not preserving audit trails for financial records
- •Allowing perk usage during the dispute window
Establish Staff Monetization Policy
Document hard rules for administrators regarding perk grants. Prohibit staff from gifting entitlements to friends, selling perks for personal gain, or using donor status as a moderation qualification. Publish this policy alongside your transparency page. Schedule quarterly audits comparing staff-granted perks against donation records.
⚠ Common Pitfalls
- •Allowing wizards to grant themselves unlimited perks
- •Using donation history as a criteria for staff recruitment
- •Keeping policies in private staff channels instead of public view
Deploy Gradual Rollout and Feedback Collection
Launch the system with a soft opening offering only cosmetic perks. Monitor for database performance issues during peak hours. Create an in-game command (e.g., 'monetization feedback') that logs player concerns to a separate file for review. After two weeks of stable operation, enable convenience perks based on initial feedback.
⚠ Common Pitfalls
- •Launching all perk tiers simultaneously
- •Ignoring performance degradation in MySQL connection pooling
- •Failing to announce the system in-game before activation
What you built
Free-to-play MUD monetization succeeds when technical implementation supports community trust. By separating entitlement tracking from character data, validating payments asynchronously, and treating volunteer contributions equal to monetary ones, you create sustainable funding without compromising the collaborative spirit of text-based gaming. Review your analytics quarterly to ensure perks enhance rather than replace player engagement.