Mod Information - KristalTeam/Kristal GitHub Wiki

File Structure

Files in Kristal mods need to follow a specific structure to be loaded properly. The following folders describe what kinds of files should go where.

assets: All sprites and sounds go in this folder. This folder is subdivided into fonts, music, sounds, and sprites. All textures go inside assets/sprites, regardless of how they're used (with the exception of preview.png and icon.png, which go in the mod base).
scripts: All code files go in this folder. This folder is subdivided into battle, world, shops, objects, drawfx, globals, and data, which can have further subdivision. The rest of the wiki will detail where files go and how to use them, but this is a brief summary of each folder:

  • battle: Divided into cutscenes, bullets, encounters, enemies, and waves. Files defined in these folders will be used for making encounters and defining what happens in them.
  • world: Divided into cutscenes, scripts, events, bullets, maps, and tilesets. Files defined in these folders will be used for making overworld rooms and objects that appear in them.
  • shops: Files defined in this folder will each create a shop.
  • objects: Files defined in this folder will create a global variable named after the file for the Object class that is defined.
  • drawfx: Files defined in this folder will create a global variable named after the file for the DrawFX class that is defined.
  • globals: Files defined in this folder will create global variables named after each file containing whatever data the file returns.
  • data: Divided into actors, party, items, and spells. Files defined in these folders will define data that the mod can use to create objects that it may need.

libraries: All library mods go in this folder. Each library should be either a folder or a .zip. See Libraries below for more detail.

Additionally, some files go in the mod base:

mod.json: Contains data Kristal uses to load the mod.
mod.lua: Defines global functions and variables used throughout the mod.
preview.png: Optional image used on the mod loader screen, when hovering over the mod. Can be a series of images for an animation.
preview.lua: Optional .lua file used on the mod loader screen, when hovering over the mod. See the example preview for an example of what this could look like.
icon.png: Optional image used for the icon of the mod on the mod loader screen. Can be a series of images for an animation.

If neither preview.png or preview.lua are provided, the mod loader will display the usual background instead.

mod.json and mod.lua

mod.json is the file that contains the data relevant to initially loading your mod. See the example mod for an example of how to format it. The following fields are used by Kristal:

name: The name that appears in the mod loader to represent your mod. Defaults to the name of the folder if unspecified.
subtitle: An optional short description of your mod that will display below the name.
id: An optional string defining the ID used for your mod. If undefined, the mod's ID will be its folder's name.
encounter: A string referring to the ID of an encounter. If defined, the mod will immediately start in that encounter when creating a new file. See Encounters for more detail.
map: A string referring to the ID of a map to load upon starting a new file. See Overworld for more detail. Overrides encounter if defined.
shop: A string referring to the ID of a shop to load upon starting a new file. See Shops for more detail. If defined, it will override map, and instead use the map string as the room to place the player when they exit the shop.
party: An array of strings, containing a list of party members that your mod starts with. Each string refers to the ID of a party member (see Party Members for more detail.).
maxFollowers: The max allowed followers in the party for the mod.
inventory: A dictionary defining arrays labeled item, key, armor, or weapon, with each array listing item IDs that the party should start with by default. See Items for more detail.
equipment: A dictionary defining equipment that each party member starts with being equipped by default. Each index of the dictionary should refer to the ID of a party member, and each value should be a dictionary that can define a weapon string, and an armor array.
chapter: The chapter number of the mod. Used to change game behavior (eg. stats are changed, some sprites are different, etc).
transition: A boolean value that determines whether your mod will start with the Kris and Susie falling into the dark world. False by default.
hidden: If true, the mod will not appear in the mod list.
hardReset: Forces LÖVE to restart upon reloading the mod.
config: A table used to change configurable values for either Kristal or libraries installed in a mod. Each index in this table should refer to the ID of a library, and the value of each index should be a dictionary defining specific indexes for that library's config. See Libraries below for more detail.

You can use the config table to alter many properties of the engine for your mod, as well. To do so, you would use kristal as the ID for the index of the config dictionary, like so:

{
  "id": "example_mod_id",
  "config": {
    "kristal": {
      "ralseiStyle": 1
    }
  }
}

The following is a list of all values that can be defined in Kristal's config:

susieStyle: How Susie should look by default if used. Should be a number correlating to a Deltarune chapter (eg. 1 will make her have bangs over her eyes). Defaults to the mod.json's chapter value.
ralseiStyle How Ralsei should look by default if used. Should be a number correlating to a Deltarune chapter. Defaults to the mod.json's chapter value.
growStronger: Whether the party should level up when defeating enemies with violence, and display the "You became stronger" message. Defaults to false.
growStrongerChara: A string defining a character that will be named in the "X became stronger" message if they're in the party. If undefined, the message will always be "You became stronger". Defaults to nil.
smallSaveMenu: Whether the smaller save menu from chapter 1 should be used. Defaults to true if chapter is 1, and false otherwise.
partyActions: Whether the party should have X-Actions in encounters by default. Defaults to false if chapter is 1, and true otherwise.
mercyMessages: Whether mercy percentages should appear above enemies when mercy is added. Defaults to false if chapter is 1, and true otherwise.
mercyBar: Whether enemies should have a mercy bar visible next to their health bar. Defaults to false if chapter is 1, and true otherwise.
enemyBarPercentages: Whether enemies' health and mercy bars should display percentage text. Defaults to false if chapter is 1, and true otherwise.
speechBubble: What speech bubble style should be used for enemy textboxes by default. Defaults to round if chapter is 1, and cyber otherwise.
enableStorage: Whether the inventory should have a storage storage to place extra items in. Defaults to false if chapter is 1, and true otherwise.
enemyAuras: Whether enemies should emit a red aura around them in the overworld by default. Defaults to false if chapter is 1, and true otherwise.
pushBlockInputLock: Whether push blocks should lock player input while the block is moving. Defaults to true.

mod.lua is an optional file that will be run when the mod is loaded. It can define functions inside the global Mod class, which can be called later during specific Kristal functions. A basic mod.lua file looks something like this:

function Mod:init()
  print("mod loaded!")
  -- could set global variables here, or initialize hooks, or whatever you like
end

The following functions are functions that can be defined:

init(): Called before the mod loads.
postInit(new_file): Called after the mod loads. new_file is a boolean that will be true if a new file has been loaded.
save(data): Called when the game is saved. data is a table containing all save data for the save. Editing data allows you to change save data before it's saved to the save file.
load(data, new_file, index): Called when the game loads a save. data is a table containing all save data for the save, new_file is a boolean that will be true if a new file has been loaded, and index is the index of the file chosen, if the mod has save files; if it doesn't, then index will always be 1.
preUpdate(): Called every frame, before anything else updates. If this returns true, then nothing else will update.
postUpdate(): Called every frame, after everything else updates.
preDraw(): Called every frame, before anything else draws. If this returns true, then nothing else will draw.
postDraw(): Called every frame, after everything else draws.
onKeyPressed(key): Called every time a key is pressed.
onKeyReleased(key): Called every time a key is released.
onTextInput(key): Called when text is inputted.
onMousePressed(x, y, button, istouch, presses): Called when a mouse button is pressed. See love.mousepressed for more detail.
onMouseMoved(x, y, dx, dy, istouch): Called every frame the mouse moves. See love.mousemoved for more detail.
onMouseReleated(x, y, button, istouch, presses): Called when a mouse button is released. See love.mousereleased for more detail.
onRegistered(): Called after all mod scripts have been loaded. There are also several similar functions that get called when specific categories of scripts have finished loading. Each type of script is loaded in a particular order; the following is a list of the registered scripts and the functions that get called for them, in order:

  • onRegisterGlobals()
  • onRegisterObjects()
  • onRegisterDrawFX()
  • onRegisterActors()
  • onRegisterPartyMembers()
  • onRegisterItems()
  • onRegisterSpells()
  • onRegisterEncounters()
  • onRegisterEnemies()
  • onRegisterWaves()
  • onRegisterBullets()
  • onRegisterCutscenes()
  • onRegisterEventScripts()
  • onRegisterTilesets()
  • onRegisterMaps()
  • onRegisterEvents()
  • onRegisterControllers()
  • onRegisterShops()

registerDebugOptions(debug): Called when the DebugSystem class is created, to be used to create debug commands that can be used in-game (see Debug Menus for more detail). debug is the DebugSystem instance that should be used to register commands.
regiserTextCommands(text): Called every time Text is created. text is the Text instance that is being created. See Text:registerCommand() for more detail.
loadObject(world, name, data): Called every time an overworld object loads. world refers to the current Game.world instance, name is the name of the object, and data is a table of the object's properties. Should return an object if it successfully loaded; otherwise, return nil. See Events for more detail.
getActionButtons(battler, types): Called for each party member when the game retrieves the list of action buttons for the member for battle. battler is an instance of the PartyBattler, and types is a table of strings referring to the list of actions the battler would normally have. Returning a table of strings will cause the battler passed into the function to have the action buttons specified by the table (eg. returning {"act", "defend"} if battler.chara.id is kris will make it so Kris only has ACT and DEFEND buttons in battles). This is not necessary for basic action bars; see Party Members for more detail.
onFootstep(chara, num): Called each time a character takes a step. chara refers to the Character instance that is stepping, and num is a number that alternates between 1 and 2 each step.
onTextSound(sound, node): Called during dialogue text for character node in the text string. sound is a string referring to the sound path for the character that is currently talking, and node is the current node for the text (see Text Object for more detail). If this function returns true, sound will not be played automatically, and must be played manually in the function; this can be used to override sound behavior for certain characters by returning true if sound is their sound path.
onShadowCrystal(item, light): Called when the player uses either a Shadow Crystal or Glass item in the overworld. item is the Item instance being used, and light is a boolean determining whether the party is in a light world or not. If this function returns true, then the default text will not display when using the item, allowing the coder to define custom text for their mod.
onConvertToLight(inventory): Called when the player transitions from a dark world to a light world. inventory is the light world Inventory instance that is being created. Game.inventory will still refer to the dark world inventory when this is called. Can be used to add certain items to the player's light inventory based on the presence of items in their dark inventory.
onConvertToDark(inventory): Called when the player transitions from a light world to a dark world. inventory is the dark world Inventory instance that is being created. Game.inventory will still refer to the light world inventory when this is called. Can be used to add certain items to the player's dark inventory based on the presence of items in their light inventory.

Libraries

Libraries are essentially small mods that other mods can use, to add objects or functions or any other types of custom stuff to the engine. Libraries can be added to mods by putting them in your mod's libraries folder. They are packaged the same way as normal mods, with assets and scripts folders being in the base folder of the library; the only difference is mod.json and mod.lua should be named lib.json and lib.lua for libraries.

lib.json and lib.lua function differently from mod.json and mod.lua as well. Instead of using a global Mod or Lib variable when defining functions in lib.lua, you instead need to define your own table, like so:

local Lib = {}

function Lib:init()
  print("library loaded")
end

return Lib

The changes to lib.json are much more significant, however. Most values defined in a mod's mod.json are irrelevant to libraries; the only values that should be defined in a lib.json are id and config. id is an optional string defining the ID of the library, defaulting to the library's folder's name if undefined. config is a table that defines values that mod users can configure for their own mod. Each index in this table defines the name of the value, and the value for each index defines the default value for the configuration if the mod using the library does not define it. Library config options can be later retrieved by using Game:getConfig() (see Game functions below for more detail).

Save Data

Every mod keeps track of a play session in a save file. Kristal automatically saves and loads certain information, most of which are kept track of in the global Game class. The fields that the game saves and loads to Game are:

chapter: The chapter number of the mod.
name: The name of the save file.
level: The level of the save file.
playtime: How long the mod has been played.
room_name: The name of the room the save file starts in.
room_id: The internal name of the room the save file starts in, used to load the room (see Overworld for more detail).
gold: The amount of gold on the save file.
xp: The amount of experience on the save file. Unused by default.
level_up_count: The amount of level ups needed for certain party members to get stat boosts from winning battles by fighting.
inventory: A table of the save file's inventory. See Inventory for more information.
flags: A table of any extra information the mod wants to store.

Additionally, the Game table has the following functions available related to save files:

Game:setFlag(flag, value): Sets a save data flag to the specified value.
Game:getFlag(flag, default): Gets a save data flag's value, returning default if the flag doesn't exist.
Game:addFlag(flag, amount): Adds to the value of a flag (assuming an initial value of 0 if the flag is previously undefined). amount is the amount to add to the value, defaulting to 1.
Game:saveQuick(...): Creates a quick save that can be reloaded. ... refers to the position the player will spawn at when the quick save is loaded, and can be used in 2 different ways:

  • If ... is a string, the player will spawn at a marker with the name specified. See Overworld for more detail.
  • If ... is either a table with 2 numbers, or a series of 2 numbers, the player will spawn at the coordinates specified, in pixels from the topleft of the room.

Game:loadQuick(): Loads the most recent quicksave, or most recent save if there is no quicksave.
Game:save(...): Creates a table of all current save data in the game (eg. current equipped armor on party members, current room, etc.), passes it into Mod:save() if it's defined, then returns the table. ... functions the same as for Game:saveQuick(). Does not actually save the game data to a save file; see Kristal.saveGame() below for more detail.
Game:load(data, index): Loads the game with data provided by the data argument. data is a table containing save data to load the game from (see Save Data above for what can be defined), and index is an optional number that will define what save file is currently loaded.

Game and Kristal Variables and Functions

The Game class is the class that keeps track of all gameplay related functions, such as updating the current game state and giving the player control, and it has variables and functions available that are separate from saving the game.

stage: An object that is responsible for being the highest parent object. This object is updated directly by the game, and is the parent of other important objects, such as World and Battle. See Objects for more detail.
world: The current instance of the World object. Will never be nil, even when an encounter is happening. See World for more detail.
battle: The current instance of the Battle object. Will be nil if no encounter is happening. See Battle for more detail.
inventory: The current instance of the Inventory object. See Inventory for more detail.
music: A globally active Music instance.
lock_input: If true, the player will not be able to control the overworld player.
started: Determines whether the mod has started yet. Will be false during the initial transition if transition is true in mod.json.

gameOver(x, y): Creates a game over screen, with a heart sprite breaking at the specified coordinates.
encounter(encounter, transition, enemy): Starts an encounter. encounter can be either the ID of an encounter or an existing Encounter instance, transition is an optional boolean determining whether the game should transition into the encounter or start it immediately (defaulting to true), and enemy is an optional EnemyBattler instance that will be moved to its starting position in the specified encounter.
addPartyMember(chara, index): Adds a party member to the current party. chara is either a string referring to a party member ID or a Character instance, and index is an optional number determining where in the party order the party member should be placed.
removePartyMember(chara): Removes the specified party member from the party. chara is either a string referring to a party member ID or a Character instance.
movePartyMember(chara, index): Moves the specified party member to the specified party position. chara is either a string referring to a party member ID or a Character instance, and index is a number determining where in the party order the party member should be placed.
getSoulPartyMember(): Returns the party member with the highest soul_priority value. Used to determine which party member the Soul object should be associated with.

Kristal is a global table that is used to keep track of data that is more external to gameplay, such as saving and loading data to and from files. This table also has multiple helpful variables and functions available:

Config: A table containing the values for the player's game settings. Each index is a string referring to the internal name of a setting, and the value is the current value the player has set for it. Kristal's config file can be found at either AppData/Roaming/LOVE/kristal or AppData/Roaming/kristal if you want to see all config values possible, but the following is a list of values that may be helpful to check in mods:

  • debug: Whether the player has debug mode enabled. Useful for implementing debug features.
  • autoRun: Whether the player has auto-run enabled.
  • simplifyVFX: Whether the player has Simplify VFX enabled. This is intended to be used by mods to conditionally simplify graphical effects if something may cause lag on lower-end devices.

Version: A table of values, with indexes defining the major, minor, and patch values of the current Kristal version.

returnToMenu(): Exits the mod and returns to the main menu of Kristal. Does not save the game.
saveGame(id, data): Saves the game. id is an optional number defining which save file to save to (defaulting to whichever save file the player is playing on), and data is an optional table of save data to save to the file (defaulting to what Game:save() returns).
loadGame(id): Loads the data saved to the specified file. id is a number referring to the save file index that should be loaded (defaulting to the currently loaded save file if undefined).
getModOption(name): Returns the value for the specified name in mod.json if it is defined.
getLibConfig(lib_id, name, merge): Returns the value defined in the configuration of the specified library for the specified name. If the mod's mod.json defines this value for the library in its config, it will return that value; otherwise, it will return the library's default value. For table values, if merge is false, the value defined in mod.json will completely override the table defined in lib.json; otherwise, it will merge the two tables together (prioritizing mod.json values if two values with the same index are defined).

Important Tips

Some facets of Kristal modding are incredibly important to understanding how to make a mod, but they may not be entirely intuitive to learn. It is very important for modders to read this section to be able to understand both the documentation and the code itself.

Global Tables and Classes

The wiki will frequently denote global variables in paragraphs with code blocks, eg. Object or Utils. These global variables will likely refer to either a table, a class, or an (extension of) Object. These variables, unless otherwise specified, can be used globally within the code. Each type also has specific rules regarding how they can be used in code:

  • Tables, such as Utils or Assets, exist mostly to organize functions or data in an easily accessible manner. To call functions for them, you call the function without passing in the table itself, by using a . before the function (eg. Utils.round()).
  • Classes, such as Music or Encounter, are simple classes that do not extend anything else. They do not contain any functions or variables other than the ones specified by the wiki (though sometimes the wiki will not document variables that are largely intended for internal Kristal use only). The variables themselves cannot be used to reference variables; instead, an "instance" needs to be created, through calling the variable as if it is a function (eg. local mus = Music()). The variable defined by this can then be used to call functions that will apply to that specific instance.
  • Objects, such as Sprite or Bullet, are special classes containing many functions and variables that can be used. See Objects for more details.

Majority of Kristal's global variables are Objects; hence, it is strongly recommended to familiarize yourself with the Objects page, as it defines all relevant functions and variables.

Consistency across Framerates

Kristal, unlike Deltarune, runs at an uncapped framerate; while Deltarune runs at a constant 30 FPS, Kristal can run at any FPS. In order to maintain consistency between framerates, Kristal implements global DT and DTMULT variables. DT represents the amount of time passed since the last update, and DTMULT is a value equal to DT*30, which is used to make objects move at an assumption of 30 FPS, to maintain consistency with Deltarune. When updating anything over a period of time, you should multiply the change values by DTMULT, so that the mod will behave consistently at any framerate (eg. to move an object horizontally at 4 pixels per frame at 30fps, you would do obj.x = obj.x + 4*DTMULT).