-
Notifications
You must be signed in to change notification settings - Fork 8
Adding Custom Objects to Ahorn
Ahorn 🔗 is a visual level maker and editor for Celeste, based on the
Maple 🔗 wrapper, written in Julia.
This tutorial will explain how to integrate your custom Entities and Triggers with Ahorn, in order to access and place them from within the editor.
Every distinct entity or trigger that you want to add to Ahorn needs to be defined in a .jl
Julia language file within the Ahorn
subfolder of your mod folder.
Within the Ahorn
folder, entities must be placed in an entities
folder, and triggers in triggers
. Additionally, a lang
folder can be used to provide tooltips 🔗 for various options.
Every .jl
file needs to start with the following code:
module YourModnameYourEntity
using ..Ahorn, Maple
This, similarly to C#, defines a "namespace" for your entity/trigger, and imports the Ahorn and Maple libraries for use.
Naming your module YourModnameYourEntity
ensures that your module name will be unique across all mods. For example, the Bubble Push Field entity in Spring Collab 2020 🔗 has a module named SpringCollab2020BubblePushField
.
Defining a custom entity or trigger for Ahorn will always look similar to the following:
@mapdef Entity "YourMod/YourEntity" YourEntity(x::Integer, y::Integer,
attr1::String="default1", attr2::String="default2")
or
@mapdef Trigger "YourMod/YourTrigger" YourTrigger(x::Integer, y::Integer,
width::Integer=Maple.defaultTriggerWidth, height::Integer=Maple.defaultTriggerHeight)
The string (in quotes) should be the name of your Custom Entity.
The Entity/Trigger constructor can take any number of arguments, which are then accessible from the EntityData object that is passed to your C# object's constructor. Depending on the Type of the argument, it will automatically be displayed in the configuration panel for your entity/trigger within Ahorn.
Note
If one of your attributes is a float (Number
), be sure to give it a float default value as well. That is, instead of using foo::Number=2
, use foo::Number=2.0
.
Ahorn gets any placeable entities and triggers from the placements
constant; an Ahorn.PlacementDict
, which is a series of String
=>Ahorn.EntityPlacement
pairs.
The String
is what will show up in the Ahorn selection menu, and the EntityPlacement
is a struct containing the information on the entity/trigger.
An EntityPlacement
struct can be created using the Ahorn.EntityPlacement
function, which takes four arguments:
# Required
func::Union{Function, Type},
# Optional
placement::String,
data::Dict{String, Any},
finalizer::Union{Function, Nothing}
-
func
in most cases will be YourEntity or YourTrigger. -
placement
will be one of "point"(default), "rectangle", or "line", depending on which best suits your needs. -
data
allows for placement-specific attributes to be added to the EntityData. This is useful if you want to have multiple placements with different "default values" for your entity. For example, Ahorn uses this to allow placing moon berries and winged strawberries directly 🔗. -
finalizer
allows for adding a function that is run only when the entity is placed, or the preview is updated.
Ex:
const placements = Ahorn.PlacementDict(
"Your Entity (AhornDemo)" => Ahorn.EntityPlacement(
YourEntity,
"point",
Dict{String, Any}(
"attr1" => "foo"
),
function(entity)
entity.data["attr2"] = "bar"
end
)
)
In order to define dropdown menus for some attributes, you can define Ahorn.editingOptions
for your entity (simplified example from Jump Throughs in Ahorn 🔗):
Ahorn.editingOptions(entity::Maple.JumpThru) = Dict{String, Any}(
"texture" => String["wood", "dream", "temple", "templeB"],
"surfaceIndex" => Dict{String, Int}(
"Default" => -1,
"Null" => 0,
"Asphalt" => 1,
"Car" => 2,
"Dirt" => 3
)
)
This makes the "texture" attribute a dropdown where you can select "wood", "dream", "temple" or "templeB", and where you can still type in custom values.
The "surfaceIndex" attribute is a dropdown as well, with values "Default", "Null", "Asphalt", "Car" and "Dirt". The difference is that selecting "Dirt" will set the "surfaceIndex" attribute to 3. This is a way to show more easily understandable options to the user ("Dirt" is more explicit than "3").
Note
Maple already defines some lists for the base game (surface sound IDs, spike directions, position modes for triggers, etc). They are defined here 🔗.
You can directly use them if needed:
Ahorn.editingOptions(entity::MyEntity) = Dict{String, Any}(
"surfaceIndex" => Maple.tileset_sound_ids
)
Providing additional information to Ahorn on the placement of your entity can be done by overriding the Ahorn.selection
function.
This function recieves the selected entity, and can be used to return an Ahorn.Rectangle that will be used to draw the selection.
Ex:
function Ahorn.selection(entity::YourEntity)
x, y = Ahorn.position(entity)
width = 10
height = 10
return Ahorn.Rectangle(x, y, width, height)
end
Other functions that can be overridden to affect the placement of your entity include Ahorn.minimumSize
and Ahorn.resizable
.
The main way to set how your entity is displayed within Ahorn is by overriding the Ahorn.render
function.
This allows you to draw sprites, rectangles, lines, and other graphics through the
Cairo 🔗 graphics library for julia.
Examples:
sprite = "collectables/strawberry/normal00"
Ahorn.render(ctx::Ahorn.Cairo.CairoContext, entity::YourEntity, room::Maple.Room) = Ahorn.drawSprite(ctx, sprite, 0,0)
function Ahorn.render(ctx::Ahorn.Cairo.CairoContext, entity::YourEntity, room::Maple.Room)
x = Int(get(entity.data, "x", 0))
y = Int(get(entity.data, "y", 0))
width = Int(get(entity.data, "width", 32))
height = Int(get(entity.data, "height", 32))
Ahorn.drawRectangle(ctx, 0, 0, width, height)
end
A .zip file containing all graphics currently in the base game can be found here.
module YourModule
using ..Ahorn, Maple
@mapdef Entity "YourMod/YourEntity" YourEntity(x::Integer, y::Integer)
const placements = Ahorn.PlacementDict(
"Your Entity (AhornDemo)" => Ahorn.EntityPlacement(
YourEntity
)
)
end
Many more examples of various custom Entity and Trigger files can be found in the Spring Collab 2020 repo 🔗.
For questions and feedback, please contact @coloursofnoise on the Celeste Discord 🔗.
Home
Contributing
FAQ
Useful Links
Your First Custom Map
Your First Texture Pack
Mod Setup
Custom Maps
Texture Packs
Uploading Mods
Generated Dialog Keys
Reference Code Mod🔗
Vanilla Audio IDs
Vanilla Decal Registry Reference
Character Portraits
Mod Structure
Debug Mode
Debug Map
Command Line Arguments
Environment Variables
Install Issues
Common Crashes
Latency Guide
everest.yaml Setup
Mapping FAQ
Map Metadata
Vanilla Metadata Reference
Adding Custom Dialogue
Overworld Customisation
Entity & Trigger Documentation
Custom Entity List🔗
Camera
Ahorn Scripts
Custom Tilesets
Tileset Format Reference
Stylegrounds
Reskinning Entities
Skinmods
Decal Registry
Chapter Complete Screen
Custom Portraits
Adding Custom Audio
Advanced Custom Audio
Code Mod Setup
Making Code Mods
Settings, SaveData and Session
Everest Events
Understanding Input
Logging
Cross-Mod Functionality
Recommended Practices
Core Migration Guide
Lönn Integration🔗
Custom Events
Adding Sprites
Adding Preexisting Audio