Code Mod Setup - coloursofnoise/Resources GitHub Wiki

Table of Contents

Project setup

Prerequisites

  • Everest: https://everestapi.github.io/
  • Visual Studio 2015 or newer or another IDE with C# language support, or the cross-platform dotnet cli:link:. You need to have the .NET Framework >=4.5.2 targeting pack installed. If you are using Visual Studio 2022 (latest), either install the .NET Framework 4.5.2 Targeting Pack manually or install the .NET Framework 4.6 targeting pack and make your library target that instead (if you are using a template, this may require manually editing the TargetFramework in your .csproj file).

Every Everest code mod starts out as a C# (.NET Framework) class library targeting the .NET Framework 4.5.2 (same as Celeste itself).


Templates

The simplest way to set up a code mod is using a template. Some community created options include:

Manual Setup

This setup doesn't require NuGet or git, but if you're a Windows user, you'll need to switch to the OpenGL branch. Linux and macOS users are already using FNA. Everest will relink your mod from FNA to XNA at runtime.

  • In Steam, right-click the game in your library, select "Properties" and select the opengl "beta".
  • In itch, simply install the Celeste Windows OpenGL version.
  • Epic Games is already using FNA

Note that it is not necessary to play on the OpenGL version to use mods, so a copy of the game files can be used solely for building mods.

After the update has finished, make sure to reinstall Everest.

  • Open your C# IDE (f.e. Visual Studio 2015).
  • Create a new project.
  • In the top bar, select .NET Framework 4.5.2
  • In the left bar, select Installed > Visual C#
  • Select the template Class Library or Class Library (.NET Framework) (not Standard, Core, Portable, Universal Windows, ...).
  • Create your mod in Celeste/Mods

The dialog should look similar to this:

1-newproj

  • Create a new everest.yaml text file with the following content:
- Name: YourMod
  Version: 1.0.0
  DLL: Code/bin/Debug/YourMod.dll
  Dependencies:
    - Name: Everest
      Version: 1.0.0

NOTE

For more info about the mod structure, the everest.yaml format, how to add extra content and on how to zip up your mod, read the mod structure page.


  • Right-click your project's "References" and select "Add Reference...", then "Browse..." into your Celeste installation directory and setup your references like on the following screenshot (add the relevant references and remove extra ones that got added automatically):

2-refs

You'll find most of those references as dlls in your Celeste installation directory. "Celeste" is Celeste.exe itself.


IMPORTANT

Make sure to select all those references, right-click > "Properties" and set "Copy Local" to "False", or you will accidentally include copies of those files in your mod!



IMPORTANT

If you want to maintain cross-platform compatibility, make sure to only use .NET Framework system libraries (dependencies, not namespaces) from this list.

  • System
  • System.Configuration
  • System.Core
  • System.Data
  • System.Drawing (available, but behaves unpredictably)
  • System.Runtime.Serialization
  • System.Security
  • System.Xml
  • System.Xml.Linq

This means: Microsoft.CSharp, System.Windows.Anything, System.IO.Compression and other libraries must be removed from your references.
For an up-to-date list, check the list of precompiled MonoKickstart libraries:link:, as Celeste uses them for Linux / macOS.


Module class

For your mod to load, you have to create a class extending EverestModule in your project. (You can leave Load() and Unload() methods empty if you don't need them though.)

Your module class should look similar to the example.


NOTE

This example only shows a subset of Everest's capabilities.
See ExampleMod/ExampleModule for more of what Everest can do.🔗


// Example usings.
using Celeste.Mod.UI;
using FMOD.Studio;
using Microsoft.Xna.Framework;
using Monocle;
using Celeste;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Celeste.Mod.Example {
    public class ExampleModule : EverestModule {

        // Only one alive module instance can exist at any given time.
        public static ExampleModule Instance;

        public ExampleModule() {
            Instance = this;
        }

        // Check the next section for more information about mod settings, save data and session.
        // Those are optional: if you don't need one of those, you can remove it from the module.

        // If you need to store settings:
        public override Type SettingsType => typeof(ExampleModuleSettings);
        public static ExampleModuleSettings Settings => (ExampleModuleSettings) Instance._Settings;

        // If you need to store save data:
        public override Type SaveDataType => typeof(ExampleModuleSaveData);
        public static ExampleModuleSaveData SaveData => (ExampleModuleSaveData) Instance._SaveData;

        // If you need to store session data:
        public override Type SessionType => typeof(ExampleModuleSession);
        public static ExampleModuleSession Session => (ExampleModuleSession) Instance._Session;

        // Set up any hooks, event handlers and your mod in general here.
        // Load runs before Celeste itself has initialized properly.
        public override void Load() {
        }

        // Optional, initialize anything after Celeste has initialized itself properly.
        public override void Initialize() {
        }

        // Optional, do anything requiring either the Celeste or mod content here.
        public override void LoadContent(bool firstLoad) {
        }

        // Unload the entirety of your mod's content. Free up any native resources.
        public override void Unload() {
        }

    }
}

Mod settings, session and save data

Mods can define several classes to save extra data:

  • Mod settings (EverestModuleSettings): for global data (for example settings). Those can appear in Mod Options, and are saved in Saves/modsettings-[modname].celeste.
  • Mod save data (EverestModuleSaveData): for data that is specific to a save file (if the player loads another save, other save data will be used). Can be useful to save stats or the player's progress for example. Saved in Saves/[savenumber]-modsave-[modname].celeste.
  • Mod session (EverestModuleSession): for data that is specific to a play session. This is reset each time the player starts a level, so this is reset if the player restarts the chapter, but is saved if they choose to save & quit. Can be useful to save the state of an entity for example. Saved in Saves/[savenumber]-modsession-[modname].celeste.

Your settings class should look similar to the example.

Save data and session classes are very similar, but inherit from EverestModuleSaveData / EverestModuleSession and the Setting* attributes are ignored.


NOTE

All entries must be properties, unless you're overriding Load/SaveSettings, Read/Write/Deserialize/SerializeSaveData and Read/Write/Deserialize/SerializeSession to bypass YamlDotNet's restrictions.

This example only shows a subset of Everest's capabilities.
See the Mod Settings wiki page and ExampleMod/ExampleModuleSettings🔗 for more of what can be done with mod settings.


// Example usings.
using Celeste;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YamlDotNet.Serialization;

namespace Celeste.Mod.Example {
    // If no SettingName is applied, it defaults to
    // modoptions_[typename without settings]_title
    // The value is then used to look up the UI text in the dialog files.
    // If no dialog text can be found, Everest shows a prettified mod name instead.
    [SettingName("modoptions_examplemodule_title")]
    public class ExampleModuleSettings : EverestModuleSettings { 

        // SettingName also works on props, defaulting to
        // modoptions_[typename without settings]_[propname]

        // Example ON / OFF property with a default value.
        public bool ExampleSwitch { get; set; } = false;

        [SettingIgnore] // Hide from the options menu, but still load / save it.
        public string ExampleHidden { get; set; } = "";

        [SettingRange(0, 10)] // Allow choosing a value from 0 (inclusive) to 10 (inclusive).
        public int ExampleSlider { get; set; } = 5;

        [SettingRange(0, 10)]
        [SettingInGame(false)] // Only show this in the main menu.
        public int ExampleMainMenuSlider { get; set; } = 5;

        [SettingRange(0, 10)]
        [SettingInGame(true)] // Only show this in the in-game menu.
        public int ExampleInGameSlider { get; set; } = 5;

        [YamlIgnore] // Don't load / save it, but show it in the options menu.
        [SettingNeedsRelaunch] // Tell the user to restart for changes to take effect.
        public bool LaunchInDebugMode {
            get {
                return Settings.Instance.LaunchInDebugMode;
            }
            set {
                Settings.Instance.LaunchInDebugMode = value;
            }
        }

        // Example string property. Selecting it will show a file naming-like menu.
        // Max length defaults to 12 if the attribute is not set.
        [SettingMaxLength(40)]
        public string ExampleString { get; set; } = "test";

        public int SomethingWeird { get; set; } = 42;

        // Custom entry creation methods are always called Create[propname]Entry
        // and offer an alternative to overriding CreateModMenuSection in your module class.
        public void CreateSomethingWeirdEntry(TextMenu menu, bool inGame) {
            // Create your own menu entry here.
            // Maybe you want to create a toggle for an int property?
        }

    }
}
⚠️ **GitHub.com Fallback** ⚠️