Skip to content

Profectus / features/challenges/challenge

features/challenges/challenge ​

This is a feature that represents a challenge a player can enter into that then has some requirement to complete. It is not passive like Achievements but rather must be explicitly started by the player.

ts
const challenge = createChallenge(() => ({
    requirements: createCostRequirement(() => ({
        requiresPay: false,
        cost: 100,
        resource: noPersist(points)
    }))
 }));

Sometimes these challenges will involve changing how the game is played, such as by nerfing or disabling certain mechanics. Here's an example of making point gain get square rooted by a challenge:

ts
const points = createResource<DecimalSource>(10);

const pointGainModifier = createSequentialModifier(() => [
    ...,
    createExponentialModifier(() => ({ exponent: 0.5, enabled: challenge.active }))
]);

const pointGain = computed(() => pointGainModifier.apply(1));
layer.on("update", diff => {
    points.value = Decimal.add(points.value, Decimal.times(pointGain.value, diff));
});

If a challenge can be completed multiple times with scaling requirements, make sure to reference the amount of completions already performed! For example, this one makes the exponent more severe per completion:

ts
const pointGainModifier = createSequentialModifier(() => [
    ...,
    createExponentialModifier(() => ({
        exponent: () => Decimal.times(0.5, Decimal.pow(0.9, challenge.completions.value)),
        enabled: challenge.completed
    }))
]);

You can give the player a reward based on completing a challenge in much the same way as you implement the effects of the challenge. You can use the completed property for whether a challenge has been completed any number of times or the completions property for the specific count. For example, something that doubles point gain after each completion:

ts
const pointGainModifier = createSequentialModifier(() => [
    ...,
    createMultiplicativeModifier(() => ({
        multiplier: () => Decimal.pow(2, challenge.completions.value),
        enabled: ach.earned
    }))
]);

If that was a challenge that could only be completed once, the multiplier could have been a flat 2.

It's common to have multiple different challenges the player can be in, and sometimes you'll want to make it so the player can only be in one of them at a time. There's a couple utility functions to help with determining what challenge (if any) is the active one. For example, here's a setup of 4 challenges where only one can be active at a time. If there are other similarities between your challenges (e.g. a style object they all share) it might be worth making a wrapper around createChallenge.

ts
const challenge1 = createChallenge(() => ({
    canStart: computed(() => !anyChallengeActive.value),
    ...
 }));
const challenge2 = createChallenge(() => ({
     canStart: computed(() => !anyChallengeActive.value),
     ...
 }));
 const challenge3 = createChallenge(() => ({
     canStart: computed(() => !anyChallengeActive.value),
     ...
 }));

 const anyChallengeActive = isAnyChallengeActive([challenge1, challenge2, challenge3]);

There's also a utility function for "auto-completing" challenges. By default challenges must be manually exited by the player before being considered completed, but this utility can skip that process, and optionally choose to make the player exit or remain in the challenge after completion (useful for challenges that can be completed multiple times, where the only difference between each completion is the goal).

Index ​

Interfaces ​

Variables ​

Functions ​