Tutorial: Adding Custom Festivals (CP)(1.6)
There are two types of festivals in the game: active festivals (such as the Egg Hunt) and passive festivals (such as the Night Market). Both types can be created using only Content Patcher. This tutorial assumes basic familiarity with Content Patcher, and skills such as mapmaking and event scripting. It is recommended to use this tutorial in conjunction with the festival wiki page, which provides detailed information about festival fields.
Please note: it is recommended to use i18n for all player-visible text in your mod for ease of translation into other languages, but for the purpose of this tutorial, examples shown here will not have i18ned text.
Active Festivals
Basic Data
Creating an active festival isn’t too different from adding custom NPCs to festivals and writing events. It consists of a minimum of two patches: one to edit (or load, but I recommend loading a blank.json and then editing) Data/Festivals/yourfestivaldate, and one to edit Data/FestivalDates. Here is an example for a festival that takes place on the 6th of fall:
{
"Changes": [
{
"Action": "Load",
"Target": "Data/Festivals/fall6",
"FromFile": "data/blank.json",
},
{
"LogName": "Birthday Party - Date",
"Action": "EditData",
"Target": "Data/Festivals/FestivalDates",
"Entries": {
"fall6": "Birthday Party",
},
},
]
}
The first patch loads a blank file to the festival data for your festival date so that we can edit it using tokens in the next step. The second patch makes sure your festival actually exists on that date.
Creating a Custom Festival Map
If you want your festival to take place in a vanilla location without any changes to the map, skip this step. If you want to change a vanilla map for your festival, then create a copy of the vanilla map, edit it however you want, and load it. Here is an example of you might load an edited version of the town map:
{
"Changes": [
{
// loading your new map, birthday variant
"Action": "Load",
"Target": "Maps/{{ModId}}_Town_Birthday",
"FromFile": "assets/maps/Town_Birthday.tmx",
},
]
}
You can change from the regular Town map to your Town Birthday variant map inside the festival event script. If you want your festival to take place in a custom location, then load that map and location like normal and apply a birthday variant like above if desired.
Note: You shouldn’t have to use EditMap for festival maps if you use the event command changeToTemporaryMap during the set-up phase.
Festival Details
Now we’ll handle the actual event itself. For convenience, I recommend opening the festival map in Tiled and adding the characterSheet tileset to the map (located in unpacked content Data/Maps), which you can use to visually place vanilla NPCs on the map where you think they should stand. However, it is not recommended to spawn them from the map, which is fragile. Instead, we’ll use the Set-Up_additionalCharacters field (shown not in this code block, but the next). This is similar to how you might add a custom NPC to an existing festival.
For detailed information on what each field does and requires, visit the main wiki festival page (linked at the top of this tutorial).
{
"Changes": [
{
"Action": "EditData",
"LogName": "Birthday Party - Data",
"Target": "Data/Festivals/fall6",
"Entries": {
"name": "Birthday Party",
"conditions": "Town/0900 1400", // the location and active time for your festival
"mainEvent": "pause 500/playMusic ocean/globalFade/viewport -1000 -1000/loadActors MainEvent/warpFarmers 33 19 0 32 19 0 40 19 0 28 9 1 42 9 3 37 19 0 38 19 0 34 19 0 right 36 6 2/viewport 35 12 true/pause 2000/faceDirection Lewis 3/pause 100/faceDirection Lewis 2/pause 100/faceDirection Lewis 1/pause 100/faceDirection Lewis 2/speak Lewis \"Today we're gathered to celebrate @'s birthday. Let's cut the cake!\"/pause 1000/..." // continue the event script as desired
// ... (continued below)
- name is how your event will appear in places like the calendar.
- conditions are similar to event preconditions, and determine the location and time when your festival will trigger.
- mainEvent is the main event of the festival, such as the soup tasting for the luau. MainEvent is written just like any other event script. You can look at the unpacked festival data for details on how vanilla festivals' main events are handled. Note that more advanced interactions such as the grange fair evaluation or the luau soup tasting will likely require a C# component.
// ... (continued from above)
"shop": "O 628 6000 1", // (this field might be mandatory, but an easier way to set it up is to edit Data/Shops instead and use a tile action to access it, which we’ll go over later)
"set-up": "playful/-1000 -1000/farmer 2 9 1/changeToTemporaryMap {{ModId}}_Town_Birthday/loadActors Set-Up/playerControl luau/advancedMove Vincent true 3 0 3 3000 -3 0 3 3000/advancedMove Jas true 3 0 1 3000 -3 0 1 3000/animate Emily false true 250 16 17 16 17 20 21 20 21 18 19 18 19 22 23 22 23/animate Robin false true 500 20 21 20 22", // use advancedMove and animate to add movement and life to the NPCs
"Set-Up_additionalCharacters{{FestivalYear}}": "Elliott 8 7 right/Emily 10 7 left/Marlon 13 4 down/Pam 19 7 right/{{ModId}}_MyCoolNPC 20 20 down", // {{FestivalYear}} is a dynamic token set to y1 or y2 accordingly, as described in the tutorial linked below
"startedMessage": "Your birthday party has started in the town square.", // this will pop up on the HUD when the clock strikes 9AM on fall 6
"MainEvent_additionalCharacters{{FestivalYear}}": null, // use this to place characters on the map just like during the Set-Up phase
"locationDisplayName": "Town",
// ... (continued below)
- shop is a remnant of previous game versions. In my experience, editing it did not provide the results I wanted. Instead, I chose to implement a custom shop + tile action, which will appear in a later section of this tutorial.
- set-up is how the festival will appear when the player enters the map. This is also similar to normal event scripts. (Note that
{{FestivalYear}}is a dynamic token that was set up as described in this tutorial. You can use it if you don't want NPCs' positions to change year-to-year. If you do want them to change year 1 and year 2, then include both fields"Set-Up_additionalCharacters_y1"and"Set-Up_additionalCharacters_y2".)- The
changeToTemporaryMapcommand replaces the vanilla Town map with the birthday variant that was loaded earlier. - The
loadActors Set-Upcommand (case-sensitive) is what allows NPCs to spawn. playerControl luaugives the player the ability to walk around. Triggering the main event after giving control back to the player is up to you.
- The
Next add in dialogue lines for any NPCs who you want to attend the festival:
// ... (continued from above)
// vanilla dialogue
"Abigail": "Happy birthday, @!",
"Alex": "I hope you get a gridball for your birthday. Then we can play together!",
// ...
"Wizard": "The spirits are confused by the concept of ageing.",
// custom NPC dialogue
"{{ModId}}_MyNPC": "Wow, you’re so old!",
}
},
]
}
You can add dialogue for any NPC, vanilla or modded, as long as the key matches their internal name.
Adding a Festival Shop
You may have noticed that the above patch to Data/Festivals/fall6 includes the Shop field. This is left over from previous game versions, and adding a festival shop can be much more easily implemented another way. This can be done just like you might add a custom shop in other places. Simply edit your festival map to include a tile action that opens one of the entries in Data/Shops (either an existing one or a custom one).
For example, here is a custom entry for a shop run by Pierre that sells saplings and which has him say one of two randomized dialogue lines:
{
"Changes": [
{
"Action": "EditData",
"LogName": "Festival Shop",
"Target": "Data/Shops",
"Entries": {
"Festival_BirthdayParty_Pierre": {
"Currency": 0,
"PriceModifierMode": "Stack",
"Owners": [
{
"Portrait": "Pierre",
"Dialogues": [
{
"Id": "{{ModId}}_RandomFestivalDialogue",
"Dialogue": "For your birthday, I've given you a special discount! 5 percent off!",
"RandomDialogue": [ "Happy birthday, @!", "I hope we cut the cake soon." ] // you don't have to include multiple randomized dialogues, but if you do, he will never say the discount dialogue
}
],
"RandomizeDialogueOnOpen": true,
"Id": "AnyOrNone",
"Name": "AnyOrNone"
}
],
"Items": [
{
"Id": "Cherry Sapling",
"ItemId": "(O)628"
},
{
"Id": "Apricot Sapling",
"ItemId": "(O)629"
}
],
}
}
}
]
}
This is then paired with a map edit (or baked into a custom loaded map) with a tile action on the Buildings layer that opens the shop, like so:
Sending Festival Invitations by Mail
This isn’t required, but can be a good way to immerse the player. Create an entry in Data/Mail with any information you’d like to include about your festival, such as the day and location it occurs, how long it lasts, and how the player should prepare.
Once your letter is complete, create a trigger action to send it before your festival occurs. For vanilla, this is anywhere between one day and one week in advance.