Bonuses

Bonuses let you express dynamic changes as the player progresses in your game. Bonuses can include:

  • How much damage you do per strength level
  • A base amount of money you get each harvest.
  • How many monsters need to be defeated before you can progress to the next zone

In Ludiek, Bonuses are an advanced abstract Engine concept.

While they are defined by extending from the LudiekModifier, the actual modifying is done by other concepts, such as the Output.

Sounds complicated? It’s really not! Let’s take a look at a SeedOutput for our farming game. It defines an Output which produces some seeds for the Currency Plugin

export interface SeedOutput extends BaseOutput {
  type: '/output/seed';
  plant: PlantId;
  amount: number;
}

interface Dependencies extends LudiekDependencies {
  plugins: [CurrencyPlugin];
}

export class SeedProducer extends LudiekProducer<SeedOutput, Dependencies> {
  readonly type = '/output/seed';

  produce(output: SeedOutput): void {
    this.engine.plugins.currency.gainCurrency({
      amount: output.amount,
      id: output.plant,
    });
  }
}

If we want to introduce a bonus that gives us more seeds, we first have to define two things.

The shape that identifies this bonus

export interface SeedBonus extends BaseBonus {
  type: '/bonus/seed';
  seed: PlantId;
}

And the configuration for this modifier

export class SeedModifier extends LudiekModifier<SeedBonus> {
  readonly type = '/bonus/seed';
  readonly default = 1;
  readonly variant = 'multiplicative';

  stringify(bonus: SeedBonus): string {
    return `${this.type}${bonus.seed}`;
  }
}

And add it to our engine

const engine = new LudiekEngine({
  plugins: [new CurrencyPlugin()],
  outputs: [new SeedOutput()],
  modifiers: [new SeedModifier()],
})

We can update our SeedProducer to make use of the modifier

interface Dependencies extends LudiekDependencies {
  plugins: [CurrencyPlugin];
  modifiers: [SeedModifier];
}

export class SeedProducer extends LudiekProducer<SeedOutput, Dependencies> {

  modify(output: SeedOutput): SeedOutput {
    output.amount *= this.getBonus({ type: '/bonus/seed', seed: output.plant });
    return output;
  }

  // ...
}

From now on, the engine will modify the output automatically whenever it is produced!

Now that we have the SeedModifier declared, you can get its current value with

engine.getBonus({
  type: '/bonus/seed',
  seed: '/plant/sunflower',
});

This value is currently 1, the default we declared earlier.

To calculate the value of a bonus, the engine asks all Plugins and Features for a list of the bonuses they contribute. The Key Item Plugin provides bonuses for unlocked Key Items, which could make engine.activeBonuses looks like this:

{
  "currency": {},
  "keyItem": {
    "/bonus/seed": [
      {
        "type": "/bonus/seed",
        "plant": "/plant/sunflower",
        "amount": 0.1,
        "source": "/key-item/seed-collector"
      },
      {
        "type": "/bonus/seed",
        "plant": "/plant/cauliflower",
        "amount": 0.2,
        "source": "/key-item/watering-can"
      }
    ]
  }
}

All bonus contributions are combined using the strategy defined by the modifier, which gives you the final value!

It might look like a bunch of boilerplate to simply multiply a few values, but consider the impact of this setup. Any piece of content can provide any type of bonus, meaning you can change an item from +10% seeds to +20% plantValue without touching any code!