Back to Editor's Guide


Haunts in Betrayal


Designing a Haunt

Haunts are the most complicated piece of content that can be easily added to Betrayal--and also the one which affords the greatest creative freedom.  But you should probably expect to take at least a few hours to construct a Haunt.

The Traitor

Typically, when the Haunt begins, one of the players turns Traitor, and the rest of the game pits that player against everyone else.  However, you don't have to do it exactly like this--you could perhaps have two traitors, or none, or you could have the heroes slowly get turned into traitors one-by-one (be careful with that last one, though, as players who know they're going to change teams later on tend to show very little loyalty).

You should also give some thought to how you pick the traitor.  The easiest option is to pick whoever triggered the Haunt, but you don't want to make things too predictable, and sometimes your story may require something else.  Try to use a criteria that will encourage people to play well and that will give the traitor a good chance--maybe you'll want to pick the highest-level hero, or the hero with the highest value in a particular attribute, or the hero with the most gold.  If you can't decide, you can also just pick the traitor at random, but that's kind of uninteresting.

Objectives

Part of the fun of the map is that your goal can be different each game, so you don't end up playing the same way every time.  Giving the heroes and the traitor different goals is encouraged.

You can introduce new rules as appropriate.  You can introduce new entities, or assign new functions to existing ones.  Maybe the traitor turns into a giant monsters; maybe everything  that dies now becomes undead; maybe there's a cursed zone slowly spreading through the forest; be creative!

Remember that there are (usually) several players on the hero team, so you'll need to give them a goal (or goals) that allows them all to contribute.

You should probably shoot for a play time of around 5-10 minutes after the Haunt begins, but you can go shorter or longer if you like.

Haunt Balance

Regardless of how you pick the Traitor, you're probably going to end up with a very asymmetric scenario, and you should keep in mind that not every game is played with max players.  Therefore, you'll probably need one or more scaling mechanisms to keep things even  for different numbers of players.  Some typical examples include:

Anatomy of a Haunt

Organization

I recommend that you create a new trigger category for all the triggers specific to your particular Haunt--put it at the bottom of the list, and give it a title matching your Haunt.

I also recommend that all your Haunt-specific triggers be given names that start with some abbreviation of your Haunt's title, followed by two spaces.  This helps avoid naming conflicts, and causes all of your triggers to be grouped together in an alphabetical list.

This ends up looking something like this:

Additionally, it's a good idea to turn off any Haunt-specific triggers that don't absolutely need to be on by default--just to make sure they can't accidentally go off or slow things down during other Haunts.  You can turn on all your triggers in your Haunt initialization trigger.

You should also take note that there's a bunch of variables designed to be reused by different Haunts, so that we don't clutter the global variable list too much.  You can still make any new ones you need, but all of the following are available for your use:

There's also several variables which have defined meanings, but which may still be useful in constructing your Haunt:

Haunt Selection

At the start of each game, at the start of the random terrain generation, the game selects the Haunt to be played.  This is done in the "Select Haunt" trigger (Random Forest Generation category).

As you can see, this trigger initializes some basic data for each haunt, then picks one.  You'll want to copy a five line block starting with "Set tempInt = (tempInt + 1)" and paste it into the sequence, then modify it as appropriate to your haunt.

So, to begin with, create a new integer variable named HAUNT_<TITLE>.  Set this to the value of tempInt inside your block.  This allows you to do comparisons on the HauntSelected variable to determine which Haunt is being played, without hard-coding a bunch of magic numbers all over the place.

You then get to specify 3 special triggers for your Haunt: the zone reservation trigger, the Haunt initialization trigger, and the victory conditions trigger.  Your Haunt may not actually need all of these triggers; any that you don't need, just set to the pre-created trigger "Do Nothing <gen>" (which does exactly what you think).  In fact, you may want to set them all to Do Nothing to start with, then go back and update them as you make all the necessary pieces of your Haunt.

Finally, for testing purposes, you'll probably want to make sure your Haunt is selected every time.  To do this, enable the trigger action near the end of the trigger, and change it to set HauntSelected to your Haunt.  (When you're done testing and ready to release the map, make sure to remove or delete this line again).

Zone Reservation

At the end of the "Finalize Forest Pathing" trigger, the selected Haunt's "Zone Reservation" trigger gets called.  This allows you to insert Haunt-specific content into any zones you want before all the usual stuff gets filled in.  This trigger may look something like this:

This reserves one zone for each hero in the game.  The first 5 lines inside the loop select a random zone in the deep forest and remove it from the list of zones to be assigned.  The loop then creates a Fount of Defilement in the zone, adds it to the HauntUnits unit group (so it'll be easy to find later), and adds a zone event to the zone, which will set off the Haunt.  In this case, the Haunt begins as soon as a hero enters any of these zones, and then the content in the rest of them is slightly altered before anyone sees it.

If you don't want to put anything obvious in a zone you reserve, but want to be able to find it again easily, you might find it convenient to create a "Haunt Placeholder" unit at that location.  This unit has basically all the same properties you'd give to a dummy caster, so players won't be able to tell it's there, but it does have vision and true sight, so don't make it owned by a human player.

Haunt Initialization

Once the forest is completely generated and filled in, the Haunt Initialization trigger will be called.  This is just a general place where you can put anything that you want to do at the start of the map, given that your Haunt is being played.  You'll probably want to turn on any other triggers the Haunt will be needing, and maybe add information on extra hero types used in the Haunt, or things like that.

Victory Conditions

If your Haunt specifies a victory conditions trigger, it will be run whenever a hero dies, and possibly when certain other major events occur.  You can also add whatever other triggers you'll need to enforce proper victory conditions--just make sure they don't interfere with any other Haunts.

Note that the general engine should only run this trigger after the Haunt has actually begun, so you don't need to worry about what will happen if it hasn't (though adding a "HauntHasBegun = True" condition certainly wouldn't do any harm).

However, this trigger should check whether VictoryDecided is true (unless it's OK for it to run after the game is over).  Also, if a victory condition is actually fulfilled, you should set VictoryDecided to true.

The Haunt Begins...

Starting Condition

The first 3 Haunts all begin when a hero enters a particular zone, but this is in no way a necessity.  You could start the Haunt after a certain length of time has passed, or when a certain number of zones have been explored, or when someone first reaches level 3.

However, you should be careful to make sure that your Haunt does actually start, even in weird cases.  A "start the Haunt the first time a hero dies" rule would be problematic, because it's entirely possible for the heroes to explore the whole forest and kill every monster without any of them dying.  However, you could fix this with an additon like "...or after 5 minutes, if no one has died yet."

In any event, you probably want to define a trigger to be called when the Haunt begins, and give it whatever events and conditions are appropriate.  I call this the "Haunt Activated" trigger (not to be confused with the "Haunt Started" trigger, which is run after the cinematic).  In this trigger, you'll probably want to decide who the Traitor is (updating various variables to reflect this), and maybe finish fleshing out the Haunt's reserved zones.  You can look at the triggers for some existing Haunts for examples.

If you do want the Haunt to trigger when a hero enters a zone, you should probably make it an official zone event, so that you can easily get the zone that was entered.

The Cinematic

As soon as the Haunt's been activated, it is traditional to show a cinematic, in order to get everyone's attention, introduce the scenario, and stuff like that.  There's a little bit of boilerplate already in the map to make setting this up slightly less painful.

First of all, there's a standard mechanism by which players can vote to skip the cinematic.  However, in order for this to work, you need to specify the trigger that should if the cinematic gets skipped.  Set the variable "CinematicEndTrigger" to this.

After that, the first thing you should do is probably to run "Standard Cinematic SetUp" (from the Util category).  This will enter cinematic mode, fade out, set up the voting, and temporarily make all units on the map paused and invulnerable, so they won't kill each other while the cinematic is running.  Remember to unpause any units that you actually need to have moving around during the cinematic.

You'll still need to display the Haunt title, fade in, play music, and provide the actual content of the cinematic yourself.  Also, the cinematic won't actually be skippable until you "Set CinematicHasBeenSkipped = False" and "Trigger - Turn on Skip Cinematic <gen>".  After this point, you'll want to check the value of CinematicHasBeenSkipped after every wait in your cinematic trigger, so you can abort the cinematic if necessary.

When the cinematic's over (probably in your CinematicEndTrigger), you'll most likely want to run "Standard Cinematic CleanUp" (from the Util category), which will undo most of the stuff from the standard set-up--clear the screen, turn off the skip-voting, return units to normal status, etc.  Note that this contains a wait statement (it fades out before doing noticeable changes, then fades back in afterwards), so be careful of race conditions.  This will also remove every unit in CinematicDisposableUnits, so if your movie includes any units that you don't want to stick around when the show's over, just throw them into this group.

In fact, there are several variables precreated for your cinematic convenience:

Beginning Haunt Gameplay

When the movie's over and you're ready to start playing, there are several more things you should do.

First off, you should create any units and initialize any variables needed for the Haunt (if you haven't already).  This is the time to do things like spawning initial monsters, starting timers, resetting counters, buffing up the Traitor, etc.

Next, you'll want to make sure the players know what's going on.  Start by updating the quests.  You should mark the ExploreQuest (and its requirement) as completed.  There's an undiscovered HauntQuest ready for your use, but there doesn't appear to be any way to change a quest's icon after it's been created, so you'll probably want to destroy it and create a new one.  Also, add as many requirements as you need, and generate the HintStrings for each side (set HintStringHeroes and HintStringTraitor).  The HintStrings tend to be long, so use "Convert Externalized String" to make sure they don't get cut off.  If you've got hints that are relevant only for particular hero types, you can check the HeroTypeCount array to see if you should add them.

Creating quests for only the local player is reportedly hazardous, but changing the text for the local player works fine, so you'll want to fill in the description of the quest and the quest objectives for each team based on comparisons to the local player.  You should end up with something like this:

Notice how the HintStrings are concatenated onto the quest descriptions.

Now, players often aren't very proactive about reading their quest log (and besides, doing so prevents you from controlling your hero), so we want to display the key information in text messages:

The first section (after the 4-second wait) points out that the quest is there, and also lists the main objective, so the player knows what he should focus on without needing to read any further.

Next (after the 8-second wait), we display the list of hints for each side.

Fi