Tutorial - MemeMayhem/ModExamples GitHub Wiki

中文版

This is a step-by-step guide to help you create your first Meme Mayhem mod.

Create a mod

Every Meme Mayhem mod is stored in a folder that contains data files, image files, and script files. To create a new mod, simply create a new folder under %USERPROFILE%\AppData\LocalLow\Cr3 Studio\Meme Mayhem\Mods (if you are on Linux, e.g., Steam Deck, use the path mentioned in this issue) and add the necessary files. If the Mods folder does not exist, create it first.

In this tutorial, we will name our mod "First Mod". To create it, we need to create a new folder called First Mod and add a manifest.json file with the following content:

{
    "name": "First Mod",
    "description": "This is our first Meme Mayhem Mod."
}

Create/find a preview image for your mod, save it as thumbnail.png in the mod folder. This image will be displayed on the mod's front page when published to the Steam Workshop.

The mod folder now contains the following files:

Meme Mayhem\Mods
  + First Mod
    + manifest.json
    + thumbnail.png

That's it. Our mod is ready to be loaded by the game. It doesn't have any functionality yet, but we will address that later on.

Restart the game and open the mod manager UI by pressing "Ctrl + F8". Our First Mod should be listed in the UI, and you can view the mod folder by clicking the "View files" button.

Mod Manager UI

If the mod manager does not list your mod, click the "Open local mods folder" button and check if your mod is placed in the right location. After correcting any errors, restart the game to reload the mods. After the mod is correctly loaded by the mod manager, you can use Ctrl + F5 to reload mods after making script changes. This is faster than restarting the game but only works after your mod is correctly loaded.

Add a character

To make the mod functional, you need to add a mod script. The mod script is written in Lua and defines all features of the mod.

Under the mod folder, create a text file at the path additions\scripts\Trigger.lua.txt with the following content:

MOD_MANAGER:AddMod(function(Api)
    Api:CopyCharacter("naysayer", {
        id = "my_character",
        display_name = "First Mod Character",
        short_description = "This is a character created by my first mod.",
    })
end)

The script registers the mod by calling MOD_MANAGER:AddMod and adds a new character in its implementation function. The new character is a copy of the existing "naysayer" character but with a different name.

Reload mods by pressing Ctrl + F5 in the game, and click "Play Mods". You should see our "First Mod Character" character as a selectable option.

First Mod Character

The character still looks and plays the same as "naysayer". Next, we will customize it to make it a unique character.

Character Skin

To customize the looks of your character, you need to add images to your mod.

Under the mod folder, create an image folder at the path additions\textures and add image files to it. It's recommended to only use lower-case letters and '_' for the file name. PNG and JPG files are supported.

Update the mod script to replace the character skin with these images:

MOD_MANAGER:AddMod(function(Api)
    Api:CopyCharacter("naysayer", {
        id = "my_character",
        display_name = "First Mod Character",
        short_description = "This is a character created by my first mod.",
+       icon = DCEI.Texture("my_character_small"),
+       icon_low_resolution = DCEI.Texture("my_character_small"),
+       icon_high_resolution = DCEI.Texture("my_character_large"),
    })
end)

Two image files are added: my_character_small.png and my_character_large.png. Note that file extensions are omitted when they are referenced in Lua.

Reload mods with Ctrl + F5 and your character will now have a new look:

New Character Look

However, if you start a battle, you will notice the character will still look the same as before:

alt text

To customize the character's look in battle, we will need to add 2 JSON file at the path additions\data\Unit.json and additions\data\Actor.json:

The content of additions\data\Unit.json:

{
    "units": {
        "COMBAT Unit First Mod Character":
        {
            "baseUnit": "_COMBAT Missile"
        }
    }
}

The content of additions\data\Actor.json:

{
    "actors": {
        "COMBAT Unit First Mod Character" : {
            "parent": "_COMBAT Unit Mob",
            "unitActor": {
                "resource": {
                    "name": "my_character_large"
                },
                "modelScale": 0.5
            }
        }
    }
}

Then update the mod script to reference the new data:

MOD_MANAGER:AddMod(function(Api)
    Api:CopyCharacter("naysayer", {
        id = "my_character",
        display_name = "First Mod Character",
        short_description = "This is a character created by my first mod.",
        icon = DCEI.Texture("my_character_small"),
        icon_low_resolution = DCEI.Texture("my_character_small"),
        icon_high_resolution = DCEI.Texture("my_character_large"),
+       unit = DCEI.Unit("COMBAT Unit First Mod Character"),
    })
end)

Now the character in battle will also have the new look:

New Character Battle

Add an emoji

In Meme Mayhem, a character's primary weapon is emojis. You can add emojis to give your character special attacks.

Add an image file my_power_rock.png for your emoji to additions\textures and update the mod script to register the new emoji:

MOD_MANAGER:AddMod(function(Api)
+   Api:RegisterMissile("my_power_rock", {
+       id = "my_power_rock",
+       display_name = "My Powerful Rock",
+       icon = DCEI.Texture("my_power_rock"),
+       damage = function(attack_data, caster)
+           return 200
+       end
+   })
    Api:CopyCharacter("naysayer", {
        id = "my_character",
        display_name = "First Mod Character",
        short_description = "This is a character created by my first mod.",
        icon = DCEI.Texture("my_character_small"),
        icon_low_resolution = DCEI.Texture("my_character_small"),
        icon_high_resolution = DCEI.Texture("my_character_large"),
        unit = DCEI.Unit("COMBAT Unit First Mod Character"),
+       attack_ids = {
+           "my_power_rock",
+           "my_power_rock",
+       },
    })
end)

The updated mod script introduces a new emoji named "My Powerful Rock", which inflicts significant damage per hit. This emoji is also set as the default in the starting emoji collection for our mod character. You can find a list of all available emoji missiles here: Missiles

Reload mods with Ctrl + F5 and play the mod character:

My Power Rock

The character in battle still throws regular rocks instead of our upgraded one. Similar to characters, we need to add some extra data.

Add the following to additions/data/Unit.json:

{
    "units": {
+       "COMBAT Missile My Powerful Rock":
+       {
+           "baseUnit": "_COMBAT Missile"
+       },
        "COMBAT Unit First Mod Character":
        {
            "baseUnit": "_COMBAT Missile"
        }
    }
}

And add the following to additions/data/Actor.json:

    "actors": {
+       "COMBAT Missile My Powerful Rock": {
+           "parent": "_COMBAT Missile Simple",
+           "unitActor": {
+               "resource": {
+                   "name": "my_power_rock"
+               },
+               "modelScale": 2
+           }
+       },
        "COMBAT Unit First Mod Character" : {

Then update the mod script to reference the added data:

    Api:RegisterMissile("my_power_rock", {
        id = "my_power_rock",
        display_name = "My Powerful Rock",
        icon = DCEI.Texture("my_power_rock"),
+       missile = DCEI.SimpleUnit("COMBAT Missile My Powerful Rock"),
        damage = function(attack_data, caster)
            return 100
        end
    })

Now we are throwing big black rocks at our opponent:

Big Black Rock

To make it more impactful, you can change the sound effect by adding a .wav file to additions/sounds and reference it in the mod script:

    Api:RegisterMissile("my_power_rock", {
        id = "my_power_rock",
        display_name = "My Powerful Rock",
        icon = DCEI.Texture("my_power_rock"),
        missile = DCEI.SimpleUnit("COMBAT Missile My Powerful Rock"),
+       sounds = {
+           DCEI.Sound("my_power_rock_hit")
+       },
        damage = function(attack_data, caster)
            return 200
        end
    })

While powerful, you will only have a fixed amount with a fixed damage and that won't get you very far. To make it a viable option, you will need to add perks and relics to work with the emoji.

Add a perk

After beating each enemy in Meme Mayhem, the player gets to select a perk from 3 random perk options. To add more "My Powerful Rock" to your collection, you need to create a perk that awards these emojis.

To create a new perk, add the following in the mod script:

+   Api:RegisterPerk("my_power_perk", {
+       id = "my_power_perk",
+       display_name = "My Powerful Perk",
+       description = "Adds one My Powerful Rock.",
+       icon = DCEI.Texture("my_power_rock"),
+       perk_type = "missile",
+       rarity = "legendary",
+       attacks = {
+           my_power_rock = 1,
+       },
+   })
    Api:CopyCharacter("naysayer", {
        id = "my_character",
        display_name = "First Mod Character",
        short_description = "This is a character created by my first mod.",
        icon = DCEI.Texture("my_character_small"),
        icon_low_resolution = DCEI.Texture("my_character_small"),
        icon_high_resolution = DCEI.Texture("my_character_large"),
        unit = DCEI.Unit("COMBAT Unit First Mod Character"),
        attack_ids = {
            "my_power_rock",
            "my_power_rock",
        },
+       perk_chains = {
+           {
+               "my_power_perk",
+           },
+           {
+               "gain_attack",
+               "attack_quest",
+               "damage_to_health",
+               "flex_on_flex",
+           },
+       },

Note that you need to add at least 4 perks for the character or the game won't work. Usually you want to have way more to enable different strategies. A complete list of built-in perks can be found at Perks.

Now you will be able to add more "My Powerful Rock" by selecting "My Powerful Perk":

My Powerful Perk

Perk Behavior

Besides modifying character attributes and awarding emojis, perks can have more complex behavior by defining Lua functions that are run on certain events.

Here we add a new perk that makes our "My Powerful Rock" even stronger with a behavior function:

    Api:RegisterPerk("small_rock_on_power_rock", {
        id = "small_rock_on_power_rock",
        display_name = "Rock in Rock",
        description = "Throws a regular rock when My Powerful Rock hits its target.",
        icon = DCEI.Texture("my_power_rock"),
        perk_type = "perk",
        rarity = "common",
    }, function(combat_unit)
        local name = "small_rock_on_power_rock"
        combat_unit.Attack:RegisterOnMissileImpactCallback(name, function(level, attack_data, caster, target)
            if attack_data.missile_id == "my_power_rock" then
                for i = 1, level do
                    combat_unit.Attack:NewMissileAttack(target, "attack_rock")
                end
            end
        end)
    end)

This new perk registers a Lua function for on-missile-impact events. When a power rock thrown by the character hits its target, the character will throw a regular rock.

Don't forget to add this perk to the character's perk_chains to make it available in the random selection pool:

        perk_chains = {
            {
                "my_power_perk",
+               "small_rock_on_power_rock",
            },
            {
                "gain_attack",
                "attack_quest",
                "damage_to_health",
                "flex_on_flex",
            },
        },

Add a relic

Relics are very similar to perks in what they can do. The main difference lies in how they are acquired in the game. Perks are picked from 3 options before each battle, whereas relics are brought along with the character or picked from 2 options after a boss battle.

The following code defines a relic and adds it to our character's initial relic set:

+   Api:RegisterRelic("my_power_cookie", {
+       id = "my_power_cookie",
+       display_name = "Overpowered Cookie",
+       description = "When hit by any emoji, gain 1 " .. Api.GameMechanicTags.TAG.attack .. ". Attack speed is reduced by 50%.",
+       icon = DCEI.Texture("smh_cookie"),
+       rarity = "permanent",
+       modify_attributes = {
+           attack_speed = -0.5,
+       },
+   }, function(combat_unit)
+       local name = "my_power_cookie"
+       combat_unit.Attack:RegisterOnMissileHitCallback(name, function(level, attack_data, caster, target)
+           for i = 1, level do
+               combat_unit:ModifyAttribute("attack", 1, false)
+           end
+       end)
+   end)
    Api:CopyCharacter("naysayer", {
        id = "my_character",
        display_name = "First Mod Character",
        short_description = "This is a character created by my first mod.",
        icon = DCEI.Texture("my_character_small"),
        icon_low_resolution = DCEI.Texture("my_character_small"),
        icon_high_resolution = DCEI.Texture("my_character_large"),
        unit = DCEI.Unit("COMBAT Unit First Mod Character"),
+       relics = {
+           "my_power_cookie",
+       },

A complete list of built-in relics can be found at Relics.

With emojis, perks, and relics, we now have a complete (albeit simple) character.

If you want to add the relic to relic pool to make it appear in shop or boss drop, you can set the forth parameter of RegisterRelic to true

For example, Api:RegisterRelic(name, config, callback, true)

Overpowered Cookie

Add a boss

To add a custom boss to your mod, register a new boss with the following change:

    Api:RegisterBoss({
        id = "my_boss",
        display_name = "My Boss",
        unit = DCEI.Unit("COMBAT Unit First Mod Character"),
        icon = DCEI.Texture("my_character_small"),
        icon_high_resolution = DCEI.Texture("my_character_large"),
        cosmetic_data = {
            ultimate_id = "ultimate_basketball",
        },
        attributes = {
            health_maximum = 10000,
            attack = 40
        },
        perks = {
            attack_rock = 30,
            attack_speed = 2,
        },
        lines = {
            battle_start = {
                {
                    "Hey, prepare to be crushed!",
                    "You're no match for me!",
                }
            },
            on_start = {
                {
                    "Let the battle begin!",
                    "Time to show you what I'm made of!",
                }
            },
            on_ultimate = {
                {
                    "Witness my ultimate power!",
                    "Feel the wrath of my ultimate attack!",
                }
            },
            on_win = {
                {
                    "Haha, victory is mine!",
                    "You never stood a chance!",
                }
            },
            on_lose = {
                {
                    "I'll get my revenge next time!",
                    "This is just a temporary setback!",
                }
            },
        }
    })

A boss is similar to a character and has many of the same attributes. For each boss, you also need to add some lines that will popup as text bubbles when the battle starts/ends. To make it simple, the same image from our mod character is used for the boss.

The registered boss won't appear in the campaign yet. We need to update the campaign boss list to include our custom boss:

    Api:SetCampaignBossPool("my_character", {
        "my_boss",
        "miniboss_pop_kat",
        "miniboss_kideo",
        "miniboss_stone",
        "miniboss_zuck",
        "my_boss",
    })

Here you need to specify exactly 6 bosses. We reuse existing bosses for the middle 4 and our custom boss is the first and last. A complete list of built-in bosses can be found at Bosses.

Reload mods with Ctrl + F5 and you will be able to fight against your custom boss.

My Boss

Add a choice event

Events randomly happen from time to time in a game run. You need to add an image my_choice_dog.png to additions/textures, then can add custom events:

    Api:RegisterChoice({
        id = "my_choice",
        display_name = "Dog",
        image = DCEI.Texture("my_choice_dog"),
        description = "A dog is running towards you and looks like it's going to bite you. What do you do?",
        options = {
            {
                flavor = "run away",
                description = "+200 " .. Api.GameMechanicTags.TAG.health,
                aftermath_narrative = "You escaped from the dog!",
                aftermath_description = "You haven't run that fast in years and it feels great. +200 " .. Api.GameMechanicTags.TAG.health,
                modify_attributes = {
                    health_maximum = 200,
                },
            },
            {
                flavor = "throw a rock at it",
                description = "+1 " .. Api.GameMechanicTags.ATTACK_ICON.rock .. ", -100 " .. Api.GameMechanicTags.TAG.health,
                aftermath_narrative = "You picked up a rock but the dog bit you before you could throw it.",
                aftermath_description = "It hurts but at least you have a " .. Api.GameMechanicTags.ATTACK_ICON.rock .. " ready for the next dog encounter.",
                modify_attributes = {
                    health_maximum = -100,
                },
                gain_relics = {
                    single_rock = 1,
                },
            }
        }
    })

This code registers a new choice event with two options. Each option has a flavor text, description, and aftermath narrative. You can modify attributes or gain relics based on the chosen option.

Once you have added the event, you can include it in your campaign by updating the campaign event list:

    Api:SetChoicePool("my_character", {
        "my_choice",
    })

Reload mods with Ctrl + F5 and the event will be randomly triggered during the campaign.

Remember to customize the event details and options to fit your mod's theme and gameplay.

A complete list of built-in choices can be found at Chocies.

Advanced

You can custom the choice to only appear in certain waves by using

Api:RegisterWaveRequiredChoices(choice_id, min_wave, max_wave)

where min_wave and max_wave (inclusive) are the range of waves, can be set to nil to indicate no lower or upper limit

You can also custom the choice to only appear in certain waves by using

Api:RegisterWaveSpecialChoices(choice_id, min_wave, max_wave, limit_characters:table<string>)

where min_wave and max_wave (inclusive) are the range of waves (they can't be nil). If they are equal, the event will only appear in this wave, otherwise it will appear in the range of waves.

limit_characters is a list of character ids that can trigger this event, if empty then all characters can trigger this event (not recommended as it will affect other mods)

If more than one RegisterWaveSpecialChoices is registered in the same wave, the game will randomly select one of them.

Check out Advanced-Mod1 of our mod examples

Requirement config for choice event

requirement can be set in the choice event options

There are the requirements that we support now:

options = {
    ...
    {
        ...
        -- limit relic
        requirement = {
            relic = "trolley_switch",
        },
    },
    {
        ...
        -- limit perk
        requirement = {
            perk = "gain_attack",
        },
    },
    {
        ...
        -- limit relic rarity
        requirement = {
            relic_rarity = "epic",
        },
    },
    {
        ...
        -- limit attributes, you can set multiple attributes, all of them must be satisfied to unlock
        requirement = {
            attributes = {
                attack = 30,
            },
        },
    },
    {
        ...
        -- limit selected options in previous events, all of them must be satisfied to unlock
        requirement = {
            choice_selected = {
                ["my_choice_id"] = { 1 },
            },
        },
    },
}

Change Avatar During Battle

You can ad more textures to change avatar during battle (Time Traveler's mood switch, PreBandleader's face for examples)

Open additions\textures and put your extra textures there.

Let's add 2 new textures: my_character_angry.png and my_character_cry.png.

We also need to modify Actor json file to define some event:

Open additions\data\Actor.json:

{
    "actors": {
        "COMBAT Unit First Mod Character" : {
            "parent": "_COMBAT Unit Mob",
            "unitActor": {
                "resource": {
                    "name": "my_character_large"
                },
                "events": [
                    {
                        "actorTerm": {
                            "onCustomEvent": {
                                "identifier": "Transfer_Angry"
                            }
                        },
                        "actions": [
                            {
                                "setModel": {
                                    "type": "Sprite",
                                    "name": "my_character_angry"
                                }
                            }
                        ]
                    },
                    {
                        "actorTerm": {
                            "onCustomEvent": {
                                "identifier": "Transfer_Cry"
                            }
                        },
                        "actions": [
                            {
                                "setModel": {
                                    "type": "Sprite",
                                    "name": "my_character_cry"
                                }
                            }
                        ]
                    },
                ],
                "modelScale": 0.5
            }
        }
    }
}

Then you can call send actor event to change the sprite during battle!

-- event_name is what we have just definied in actor "Transfer_Angry" or "Transfer_Cry"
-- notice the unit is the actual unit, so use caster.unit
Api:SendActorEvent(unit, event_name)  

Put everything together

Your mod now has everything. Let's check out how it plays:

first_mod.mp4

Pretty nice, isn't it?

If you encounter problems when following this guide, you can download our completed tutorial mod and use it as a reference.

Publish to Steam workshop

The easiest way to share your mod with other players is to publish it to Steam Workshop. To do that, follow these steps:

  1. Open the mod manager UI by pressing "Ctrl + F8" in the game.
  2. Click on the "Upload to Steam" button.
  3. A popup will appear, allowing you to add change notes for your mod update. Fill in the necessary information.
  4. Click "Start upload" to begin the upload process.
  5. Once the upload is complete, a unique ID for your mod will be generated by Steam and saved in the manifest.json file.
  6. Make sure to save the updated manifest.json file, as you will need the Steam ID to publish future updates for the same mod.
  7. If the manifest.json file contains a Steam ID, the mod manager UI will display a "View on Steam" button. Clicking this button will open the Steam page for your mod.
  8. As the mod author, you can add additional screenshots for your mod on the Steam page.
  9. On the Steam page, change the mod's visibilty from private to public.

Congratulations! Your mod is now published on the Steam Workshop and available for other players to enjoy.

Learn More about Meme Mayhem Modding

We also provide a set of APIs to retrieve data used in the base game, allowing mode developers to reuse them. The following APIs will print data to the log, which is stored in the mods' root directory (Press Ctrl + F8, then click "Open local mods folder").

MOD_MANAGER:AddMod(function(Api)
    Api:PrintCharacters()
    Api:PrintAttributes()
    Api:PrintMissiles()
    Api:PrintPerks()
    Api:PrintRelics()
    Api:PrintChoices()
    Api:PrintBosses()
end)

This tutorial only covers the modding basics to create a functional mod. To learn more about how to make Meme Mayhem mods, check out our mod examples here.

When createing your own mod, you may encounter more problems. Check out our FAQ page for the most commonly asked questions. If it still doesn't solve your problem, feel free to ask questions by opening issues.

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