All Articles

Adding Gamification to DismalThreads

DismalThreads forum implements a gamified achievement system inspired by Dungeon Crawler Carl's snarky tone. Achievements have five tiers (Common to Legendary) plus secret achievements for specific behaviors.

A buddy recently recommended the series Dungeon Crawler Carl, and I have really enjoyed it so far (I am on book 4 currently). One of the things I particularly enjoy about the series is the author's snarky take on achievements. I thought it would be neat to implement an achievement system on Dismal Threads that follows the tone of the books.

There are only so many different achievements you can get on a forum for making a post or liking a comment, so most achievements come in five different tiers: Common, Uncommon, Rare, Epic, and Legendary. The exceptions to the rule are the "secret" achievements, which are awarded for specific behaviors. The forum already has to track user information like making a comment or bookmarking a post; the fun part is taking these metrics and acting upon them with a gamification system.

The ColdBox scheduler makes it easy to automate the process of evaluating which achievements need to be awarded. This is handled in config/Scheduler.bx.

	function configure(){
		/**
		 * --------------------------------------------------------------------------
		 * Configuration Methods
		 * --------------------------------------------------------------------------
		 * From here you can set global configurations for the scheduler
		 * - setTimezone( ) : change the timezone for ALL tasks
		 * - setExecutor( executorObject ) : change the executor if needed
		 */


		/**
		 * --------------------------------------------------------------------------
		 * Register Scheduled Tasks
		 * --------------------------------------------------------------------------
		 * You register tasks with the task() method and get back a ColdBoxScheduledTask object
		 * that you can use to register your tasks configurations.
		 */

		setTimezone( "UTC" );

		task("evaluate-achievements")
            .call(() => {
                getInstance( "AchievementService" ).evaluateAll();
            })
            .every( 5, "minutes" )
	}

The next step in the process is figuring out which achievements need to be awarded. If a user has not been online in the last 30 days, they are right out — only users active within the last 30 days are eligible to receive an achievement. However, when they do come back, they are eligible to receive whichever achievements they earned.

	function evaluateAll(){
		var users = getAllUsers();
		var achievementMap = getAchievementMap();

		if ( achievementMap.isEmpty() ) {
			logger.warn( "AchievementService: No achievements found in database" );
			return;
		}

		for ( var user in users ) {
			try {
				evaluateUser( user, achievementMap );
			} catch ( any e ) {
				logger.error( "Achievement evaluation failed for user #user.id#: #e.message#", e.stackTrace );
			}
		}
	}

The real magic happens in evaluateUser — that is where current user achievements are loaded and tiers are calculated based on activity. I would post the function, but it has hints on how to trigger specific achievements and I would prefer they be found organically.

Currently there are eight base achievements: six are public and two are hidden, each with five tiers. Feel free to create an account and see how many you can find.