Skip to content

Adding New Cards

Robert Konigsberg edited this page May 10, 2022 · 5 revisions

Adding new cards to TfM is a bit of an art. This is an incomplete set of steps which might guide you into doing this.

Cards are stored in src/cards and each module has its own directory (Corp Era and Base are in one directory, the only exception.) So for example, the cards that come with Prelude are in src/cards/prelude. In most cases, each card is its own source file (eg src/cards/base/MicroMills.ts.)

Which directory should I put my card in? Well, the simplest thing to do is add your card to src/cards/community as that's the directory where all fan-made cards that aren't clustered with any other expansion. Full fan expansions can be in their own directory, like src/cards/ares and src/cards/moon. But I'm going to assume you don't want to write your own expansion. If you do, there's going to be more work that I won't go into.

Here is a very abbreviated set of steps for adding a new card.

  1. Add the name of the card to src/CardName.ts. Take a moment to ensure that string does not already exist in that file. We don't have a way of preventing that (we really should, but Typescript doesn't help us.)
  2. Open src/cards/community/CommunityCardManifest.ts. Add the entry in one of the three sections. Is it a project card? Add it to the projectCards array. If it's a corporation card or prelude, add the entry to those sections.
  • Each entry is a three-element object, cardName, which will contain the enium in CardName you just added, Factory, which will contain a reference to the new source file that will represent your card, and optionally, compatibility. That last one is optional, and will be discussed later. (TODO)
  1. Go find an example card that does exactly what you want yours to do. Are you creating a card that adds adds microbes to another card? Look at Extremeophiles. Do you want to create a card that steals MC from another player? Look up Hired Raiders.
  2. Copy that other card, and put it in src/cards/commuinity with the new name.
  3. Make the card look like what you want it to. This is going to be the very hard part. But, you can see how the card looks by starting a server and opening your browser to http://localhost:8080/cards and type in your card name.
  4. Make the card behave like you want it to. If you thought the last part was hard, this is harder.
  5. If you want to be able to test your card in action, you can force a card into a player's hand. See https://github.com/bafolts/terraforming-mars/wiki/Changing-game-data-for-local-testing for guidance here.
  6. At this point you have a working card. But if you really want to make sure your card works, you'll need to write a unit test for it. This page won't address the details, but you would add your test to tests/cards/community/

Example

Let's say you wanted to create something just like Micro Mills, but raises your MC production one step. Let's call it Micro Credits. You would:

  1. Create an entry in CardName.ts that reads like this:
MICRO_CREDITS = 'Micro Credits',
  1. Create an entry in CommunityCardManifest that reads like this:
projectCards: [
{cardName: CardName.MICRO_CREDITS , Factory: MicroCredits},
],
  1. Create src/card/community/MicroCredits.ts, which will read like this:
export class MicroCredits extends Card implements IProjectCard {
  constructor() {
    super({
      cardType: CardType.AUTOMATED,
      name: CardName.MICRO_CREDITS,
      cost: 3,

      metadata: {
        cardNumber: 'X01',
        renderData: CardRenderer.builder((b) => {
          b.production((pb) => pb.megacredits(1));
        }),
        description: 'Increase your MC production 1 step.',
      },
    });
  }

  public play(player: Player) {
    player.addProduction(Resources.MEGACREDITS, 1);
    return undefined;
  }
}
  1. Create a unit test at tests/cards/community/MicroCredits.spec.ts

  2. To see what the card looks like without having to go through dozens of plays hoping to see it, build your project, start it locally, and see it at http://localhost:8080/cards?search=micro%20money

Other notes

Some things are just going to be tricky to do. For example, if you want to create a card that allows you to spend some new resource type as MC, you'll have to do a lot of plumbing through the code just to make that work.

  • Does your card store resources on it? If so, add resourceType and resourceCount. If you are planning to add a brand new resource type, that can be tricky and require several steps that are not documented here.

  • Does your card score victory points? If so, override add the attribute victoryPoints which will either be a number or a predefined value. For example

    • If the card scores 3 points, you would write victoryPoints: 3,
    • If the card scores 1 point per 2 resources, you would write victoryPoints: VictoryPoints.resource(1, 2),
  • Is your card replacing another card? If so, add the replaced card in cardsToRemove. To see a great example of this, look at how the Ares version of cards replaces the core cards. The Ares card names have :ares as a suffix, etc etc. This probably needs expansion, but there you go.

  • Does your card require another expanion to play? If so, you'll need to use the compatibility attribute. For example, the Pathfinders prelude VitalColony places a colony, and so the entry in the manifest reads

    • {cardName: CardName.VITAL_COLONY, Factory: VitalColony, compatibility: 'colonies'},

    • A special note about Corporation Cards: Since it's possible for someone to create a game using corporation cards from other expansions, it's important to annotate compatibility for coprorations that can't be played without that expansion. For example the Turmoil corporation Septum Tribus has an action related to your delegates. Not only is playing that corp without Turmoil reasonable, we make no real effort for the card to work without it. That corporation card is also annotated, even though it's in the Turmoil manifest as:

      {cardName: CardName.SEPTUM_TRIBUS, Factory: SeptumTribus, compatibility: 'turmoil'},

  • Is it impacted by Robotic Workforce? If it is a card with production box and a building tag, then you have to take extra card to make the card compatible witih Robotic Worforce. Fortunately there are tests that, when run, will tell you if your card is properly compatible. TODO: talk about productionBox and produce and those variations.

  • Is it impacted by Celestic? Celestic applies to cards with a "floater icon" on it, which applies to cards that don't just have a floater tag, but also any icon on the card (Stratospheric Birds is such a case.) If so, add your card to Celestic's list of special cards.