Skip to content

Latest commit

 

History

History
208 lines (130 loc) · 10.8 KB

MyFirstCard.md

File metadata and controls

208 lines (130 loc) · 10.8 KB

First Card

Stuck? MyFirstCard.md is outdated yet again? Join and get help in modding Discord

Prerequisites: basic C#, basic Object Oriented Programming, resolve to debug errors (and ability to read the bloody error messages).

  • [One time] Get Visual Studio 2022.

    • Open Visual Studio Installer and make sure you have .NET desktop development packages

      image

    • You don't need all of them, these should be enough

      image

  • [One time] Setup BepInEx with Sideloader.

  • [One time] After installing Sideloader run the game once and close it after it loads to main menu. There should .cfg file generated BepInEx\config\neo.lbol.frameworks.entitySideloader.cfg. Open it with a text editor and change DevMode to true. Else hot-reloading and extra logging will be disabled.

  • [One time] Setup scriptengine and create BepInEx/scripts folder.

  • [One time] Get DebugMode

  • [One time] Get MODDED Watermark

  • [One time] Setup VS project template.

  • Create new project using Sidleoader template. Wait a moment for nuget packages to finish downloading. If GameFolder was set correctly there should be no errors. Else adjust the GameFolder and clean the project.

image

  • Create a new class, call it FirstCardDef. Make it public sealed and extend CardTemplate.

image

image

  • Class name should be underlined red. Alt+Enter while cursor is on class name, select 'Implement abstract class'.

image

image

  • This should have generated a bunch of methods. These methods are like slots to be filled up with components which define a card. Disregard them for the moment.
  • Create a new class for convenience placed in the same file: public sealed class FirstCard : Card. This class is going to define card's behavior.

image

  • Put [EntityLogic(typeof(FirstCardDef))] on top of FirstCard class.

image

  • Now come back to the generated methods. Make GetId() return name of the entity logic class return nameof(FirstCard) (string). For now it's a strict requirement that Id is the same as the logic type name.

image

  • Crop image to 743x512 , call it same as the Id, FirstCard.png, paste it into the Resources folder. Any files or folder in the Resources will be automatically embedded in the compiled dll. It's the most convenient way to include files, for now.

image

  • In LoadCardImages method create a new instance of CardImages specifying the embedded source. It acts as container to hold any images a card might need and eventually will be passed to Sideloader.

  • Call imgs.AutoLoad method and specify ".png" extension. AutoLoad is possible because the card image is named the same as the Id. Return images. Alternatively, return null can be used skip loading images for now and leave the card without an image.

public override CardImages LoadCardImages()
{
    var imgs = new CardImages(BepinexPlugin.embeddedSource);
    imgs.AutoLoad(this, extension: ".png");
    return imgs;
}
  • In Resources folder create CardsEn.yaml file.

image

  • LoadLocalization can return either LocalizationFiles or GlobalLocalization. GlobalLocalization is just a better choice. When setting up global localization only one CardTemplate should specify the localization files with loc.LocalizationFiles.AddLocaleFile method. Other cards should just return new GlobalLocalization(BepinexPlugin.embeddedSource); to indicate that they use global localization.
public override LocalizationOption LoadLocalization()
{
    var loc = new GlobalLocalization(BepinexPlugin.embeddedSource);
    loc.LocalizationFiles.AddLocaleFile(LBoL.Core.Locale.En, "CardsEn.yaml");
    return loc;
}
  • Open CardsEn.yaml. In the end it, should look very similar to game's localization files, found at <LBoL_install_folder>\LBoL_Data\StreamingAssets\Localization\en. Make sure yaml is formatted legally.
CardId:
  Name: SomeName
  Description: "Description"
  • For now make MakeConfig() return DefaultConfig();. You can worry about specifics in a bit.

  • If everything was done correctly, you should have a skeleton of the card. Build the project, the dll should be automatically copied to BepInEx/scripts folder, run the game.

image

  • If BepInEx has [Logging.Console] enabled in BepInEx\config\BepInEx.cfg you should see various messages (or errors) related to sideloader.

  • In game, go to collection, scroll to the very bottom without filtering and there should be your card, sitting, not doing much.

image

  • Now comes the difficult part, actually implementing the card. Let's start small and say we want to create a stronger version of Youkai Buster.

  • Go back to FirstCardDef.MakeConfig. You don't have to close the game just close the collection.

  • Unfortunately, for now, the fastest way to fill out config is to copy code. Copy CardConfig constructor with named parameters from here.

  • The more detailed explanation of each config property can be found in Entity Reference. For now change Index, GunName, GunNameBurst, Type, IsPooled, TargetType, Colors, Cost and Damage. sequenceTable.Next is convenient way to keep track of indexes but any positive number can be assigned as index as well.

Index: BepinexPlugin.sequenceTable.Next(typeof(CardConfig)),
...
GunName: "Simple1",
GunNameBurst: "Simple2",
...
IsPooled: true,
...
Rarity: Rarity.Common,
Type: CardType.Attack,
TargetType: TargetType.SingleEnemy,
Colors: new List<ManaColor>() { ManaColor.Red, ManaColor.White },
...
Cost: new ManaGroup() { Red = 1, White = 1 },
...
Damage: 7,
...

  • Compile the project again and go back to the game. Close Collection, press F6 to refresh the mods. The card should be an Attack and have cost and colors.

image

  • [One time] Create 'Debug' profile in-game for testing stuff.

  • [One time] Download dnSpyEx.

  • Open these LBoL assemblies in dnSpy. LBoL.EntityLib.dll is the one where the game entities are implemented.

image

  • Find the Id of Youkai Buster. To do that either search Card.yaml localization files or use DebugMode menu option Output All Config and check CardConfig.txt. Id of Youkai Buster is YaoguaiBuster.

  • In dnSpy search for a Type named YaoguaiBuster. Make sure Files in Same Folder option is selected.

image

  • Investigating YaoguaiBuster type we can see that it extends Card. It seems that SetGuns method can be used to increase number of hits performed. Let's copy the method and paste it in our own FirstCard class.

  • Let's increase number of hits performed to 3/4 by adding an item to each array.

[EntityLogic(typeof(FirstCardDef))]
public sealed class FirstCard : Card
{
    protected override void SetGuns()
    {
        if (this.IsUpgraded)
        {
            CardGuns = new Guns(new string[]
            {
            Config.GunNameBurst,
            Config.GunName,
            Config.GunNameBurst,
            Config.GunName,
            });
            return;
        }
        CardGuns = new Guns(new string[]
        {
            Config.GunName,
            Config.GunNameBurst,
            Config.GunName,

        });
    }
}
  • Time to test it! Compile the project, reload mods and start a debug run with F5 (while in main menu). After adding the cards to the deck, giving yourself some shining exhibits you can click one of these buttons to quickly advance the map node. This will save the game and you won't have to redo your setup when restarting the level.

image

  • Both upgraded and unupgraded card versions seems to function as intended and console is clean of errors. The only thing left to do is to finish writing the description. I've chosen to differentiate upgraded card description by using 'UpgradedDescription:' node.

What's next?

Check out example project/template by rmrfmaxxc. The intended way to organize Sidloader templates for larger projects, i.e., new characters. Also contains example code for many generic card types like Teammates, Abilities, XCost etc.

Join modding Discord