Skip to content

Tutorial: Adding a new Mod Symbol

ValgoBoi edited this page Jun 11, 2021 · 12 revisions

Mod symbols are one of the integral parts to many LuckyAPI mods. In order to create your mod symbol, you will need to create a new GDscript file in your mod folder. I think it'll be easiest to give a full example of a mod symbol's code from my own mod, and then break the example down.

### mods/<mod-id>/mod.gd
extends Reference

# Called when the mod is loaded.
func load(modloader: Reference, mod_info, tree: SceneTree):
    
    # ...
    # Load a mod symbol from the file symbols/YourModSymbol.gd, and add it to the game.
    modloader.add_mod_symbol("res://<mod-id>/symbols/YourModSymbol.gd")
    # ...
### mods/<mod-id>/symbols/YourModSymbol.gd
extends "res://modloader/ModSymbol.gd"

# Called when the symbol is first initialized.
func init(modloader: Reference, params):
    # Set this symbol's reference to the modloader. Always include this line.
    self.modloader = modloader
    
    # The symbol's internal identifier. This should be unique to your symbol. You may even want to prefix
    # it with your mod id.
    self.id = "cloud"
    # The symbol's base value, without any multipliers/bonuses/etc. Generally this shouldn't be above 5,
    # even for very rare symbols.
    self.value = 2
    # The symbol's values array, which is used to determine things such as percent chances of your symbol
    # doing something, or how much you multiply another symbol's value by. This will be explained in
    # greater detail later.
    self.values = [2, 10]
    # The symbol's rarity. Valid values are "common", "uncommon", "rare", or "very_rare".
    self.rarity = "uncommon"
    # The symbol's groups. Groups are used by other symbols and items to apply effects to all symbols in
    # a group. For example, Mrs. Fruit eats symbols in the group "fruitlikes", and no other symbols. This
    # symbol isn't in any groups, but a symbol can be in as few or as many groups as you like.
    self.groups = []
    # This essentially adds sound effects to a symbol from another pre-existing symbol. Whenever this symbol
    # does something, the sound effect from the symbol id specified here will be played.
    add_sfx_redirect("mrs_fruit")
    # Internally, symbols can have multiple sound effects with different names. By default, a symbol uses the
    # sound effect named "default". The 2nd argument is the old symbol's sound effect name. The 3rd parameter
    # is this symbol's effect name. Essentially, we are making it so that the rain symbol's "default" sound
    # effect is used for this symbol's "transform" sound effect.
    add_sfx_redirect("rain", "default", "transform")
    
    # This sets the symbol's texture to a loaded texture. The path can be anything you like, including a path
    # to a vanilla symbol texture, but I recommend using a custom texture for your symbol. This path loads an
    # image with the name "your_mod_symbol.png" from the symbols folder (that this script is also in) as the
    # symbol texture.
    self.texture = load_texture("res://<mod-id>/symbols/your_mod_symbol.png")
    # The symbol's display name. This is shown to the player when on the symbol selection screen, as well as
    # when your symbol is being hovered over in a menu.
    self.name = "Cloud"
    # This symbol's description, including formatting tags and all. The <icon_???> represents another symbol or
    # item in the description that can be hovered over. The <color_???> changes the text's color, which is
    # reverted by a <end>. The <value_?> represents a value from the value array, defined above.
    self.description = "Adjacent <icon_rainbow> give <color_E14A68><value_1>x<end> more <icon_coin>. Has a <color_E14A68><value_2>%<end> chance of transforming into <icon_thunder_cloud>."

# This is called whenever your symbol needs to add effects. An effect is basically an action the symbol can
# perform - boosting an adjacent symbol, destroying an adjacent symbol, or adding a new symbol into your inventory.
func add_conditional_effects(symbol, adjacent):
    # Loop over every adjacent symbol, and add an effect for that symbol.
    for i in adjacent:
        # Add an effect to this symbol. This uses LuckyAPI's effect builder API, which automatically handles
        # all the hassle of dealing with internal weirdness. Essentially, if the symbol we are looking at is
        # a rainbow, then change its value multiplier by the value stored in values index 0 (which is 2), and
        # animate it along with the original symbol with a bouncing animation, as well as playing the "default"
        # sound effect.
        symbol.add_effect_for_symbol(i, effect().if_type("rainbow").change_value_multiplier(values[0]).animate("bounce", "default", [symbol, i]))
    
    # Another effect, this time applied to the symbol itself. Essentially, if a randomly generated value (0 to 100)
    # is less than index 1 of our values array (which is 10), then transform the symbol into a thunder cloud symbol,
    # and animate this symbol with a "shake" animation, with the "transform" sound effect. In other words, there is
    # a 10% chance that the symbol will change into a thunder cloud and shake.
    symbol.add_effect(effect().if_value_random(1).change_type("thunder_cloud").animate("shake", "transform"))

To begin with, create a new file in your mod called symbols/<SymbolID>.gd. In your mod.gd file, make sure to register your symbol: modloader.add_mod_symbol("res://<mod-id>/symbols/<SymbolID>.gd"). This will add your symbol to the game, and integrate it with all of the game's internal gameplay systems. Now, it's time to actually make your symbol do something. In your <SymbolID>.gd file, you'll want to paste this boilerplate code:

extends "res://modloader/ModSymbol.gd"

# Called when the symbol is first initialized.
func init(modloader: Reference, params):
    # Set this symbol's reference to the modloader. Always include this line.
    self.modloader = modloader
    
    self.id = ""
    self.value = 1
    self.values = []
    self.rarity = "common"
    self.groups = []
    
    self.texture = load_texture("res://<mod-id>/symbols/<symbol_id>.png")
    self.name = "Display Name"
    self.description = "This symbol does something really cool, though we're not sure what that is."

First, you'll want to decide on the internal ID for your symbol, and set the symbol ID to that ID. For example, self.id = "symbol_id". Make your ID all lowercase, with underscores for spaces. Next, you'll want to decide how many coins your symbol will give by default, without any buffs or effects. Generally, common symbols give 1 coin, uncommons give 2, rares give 3, and very rares give 4-5. There are some symbols that don't follow this pattern, so this is just a rule of thumb. For example, an uncommon symbol might use self.value = 2. We can ignore the values array for now. You will also want to choose a rarity for your symbol: "common", "uncommon", "rare", or "very_rare". This should correlate with your symbol value from earlier. You can also add your symbol to any groups in the self.groups array.

The next 3 properties are display-based, as they aren't used for gameplay. First is the symbol's texture. You can simply replace the <mod-id> with your mod's ID, and the <symbol_id> with your symbol's ID. If you are using another path for your symbol texture, use that instead. The name is pretty self-explanatory, it's just the display name for your symbol in the Symbol Selection menu, as well as in tooltips. Finally, the description is the symbol's description. You can leave it blank if your symbol doesn't have any special effects.

If you want your symbol to have any effects, you'll need to add a new function to your symbol file:

func add_conditional_effects(symbol, adjacent):
    # ...

The arguments to this function are the symbol that the effects are being added to, and the symbols adjacent to that symbol. In order to add an effect to the symbol itself, use symbol.add_effect(<effect>). If you want to add an effect to another symbol, eg. an adjacent symbol, use symbol.add_effect_for_symbol(<other symbol>, <effect>). It is highly recommended for you to use LuckyAPI's effect builder API, but doing so is not required. Explaining how that works is beyond the scope of this tutorial.

It is also possible to modify the "corner text" of your modded symbols. Think the text that shows how many symbols a geologist has consumed, or how many turns until a golem is destroyed. To modify this text, you'll need to add a new function to your symbol file:

func update_value_text(symbol, values):
   # ...

Generally, there are 2 main things this number displays: The permanent bonus of a symbol (such as a geologist or diver), or the time until a symbol is automatically destroyed (such as a golem or coal). I've provided two basic templates for you to use to display these values, just copy and paste this into your code:

# Display the permanent bonus for a symbol. This one is pretty basic.
func update_value_text(symbol, values):
    symbol.value_text = symbol.permanent_bonus
    symbol.value_text_color = "<color_FBF236>"

# Display the amount of spins until a symbol is destroyed. Replace the `lifetime` with how many spins your symbol lasts for:
func update_value_text(symbol, values):
    symbol.value_text = lifetime - symbol.times_displayed
    symbol.value_text_color = "<color_E14A68>"

It is also possible to modify whether a symbol can be found, either from the symbol selection menu or by other means, using the can_find_symbol function. If you simply want to make a symbol "unfindable", you can just set the symbol's findable property to false:

func init(modloader: Reference, params):
    self.modloader = modloader

    # ...
    self.findable = false
    # ...

For finer control over whether a symbol can be found, you must override the can_find_symbol function. This function should return true when a symbol should be findable, and false when it shouldn't be findable. For example, in order to make a symbol behave like the highlander, which can only be found if there are no highlanders in your inventory already, you can use this code:

func can_find_symbol(symbol_grid):
   # This call to count_symbols counts the amount of symbols with id "highlander_clone". If there are 0 of these symbols, we return true, allowing for our highlander clone to be found. Otherwise, we return false, preventing it from being found.
   return count_symbols("inventory", { "type": "highlander_clone" }) == 0

You can put just about any condition you like in can_find_symbol, which can make for some interesting gameplay.

Remember, if you ever need help with making mods, feel free to join our Discord Server and ask for help! This should be enough information for you to add any modded symbols you want to add. Now go ahead and make something cool!

Clone this wiki locally