Make challenges use the requirements system
This commit is contained in:
parent
eff5852b04
commit
cc1a2998e0
3 changed files with 32 additions and 58 deletions
|
@ -12,10 +12,10 @@ import {
|
|||
Visibility
|
||||
} from "features/feature";
|
||||
import type { GenericReset } from "features/reset";
|
||||
import type { Resource } from "features/resources/resource";
|
||||
import { globalBus } from "game/events";
|
||||
import type { Persistent } from "game/persistence";
|
||||
import { persistent } from "game/persistence";
|
||||
import { maxRequirementsMet, Requirements } from "game/requirements";
|
||||
import settings, { registerSettingField } from "game/settings";
|
||||
import type { DecimalSource } from "util/bignum";
|
||||
import Decimal from "util/bignum";
|
||||
|
@ -36,11 +36,10 @@ export interface ChallengeOptions {
|
|||
visibility?: Computable<Visibility | boolean>;
|
||||
canStart?: Computable<boolean>;
|
||||
reset?: GenericReset;
|
||||
canComplete?: Computable<boolean | DecimalSource>;
|
||||
requirements: Requirements;
|
||||
maximize?: Computable<boolean>;
|
||||
completionLimit?: Computable<DecimalSource>;
|
||||
mark?: Computable<boolean | string>;
|
||||
resource?: Resource;
|
||||
goal?: Computable<DecimalSource>;
|
||||
classes?: Computable<Record<string, boolean>>;
|
||||
style?: Computable<StyleValue>;
|
||||
display?: Computable<
|
||||
|
@ -60,6 +59,7 @@ export interface ChallengeOptions {
|
|||
|
||||
export interface BaseChallenge {
|
||||
id: string;
|
||||
canComplete: Ref<DecimalSource>;
|
||||
completions: Persistent<DecimalSource>;
|
||||
completed: Ref<boolean>;
|
||||
maxed: Ref<boolean>;
|
||||
|
@ -76,10 +76,10 @@ export type Challenge<T extends ChallengeOptions> = Replace<
|
|||
{
|
||||
visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>;
|
||||
canStart: GetComputableTypeWithDefault<T["canStart"], true>;
|
||||
canComplete: GetComputableTypeWithDefault<T["canComplete"], Ref<boolean>>;
|
||||
requirements: GetComputableType<T["requirements"]>;
|
||||
maximize: GetComputableType<T["maximize"]>;
|
||||
completionLimit: GetComputableTypeWithDefault<T["completionLimit"], 1>;
|
||||
mark: GetComputableTypeWithDefault<T["mark"], Ref<boolean>>;
|
||||
goal: GetComputableType<T["goal"]>;
|
||||
classes: GetComputableType<T["classes"]>;
|
||||
style: GetComputableType<T["style"]>;
|
||||
display: GetComputableType<T["display"]>;
|
||||
|
@ -91,7 +91,6 @@ export type GenericChallenge = Replace<
|
|||
{
|
||||
visibility: ProcessedComputable<Visibility | boolean>;
|
||||
canStart: ProcessedComputable<boolean>;
|
||||
canComplete: ProcessedComputable<boolean | DecimalSource>;
|
||||
completionLimit: ProcessedComputable<DecimalSource>;
|
||||
mark: ProcessedComputable<boolean>;
|
||||
}
|
||||
|
@ -105,19 +104,6 @@ export function createChallenge<T extends ChallengeOptions>(
|
|||
return createLazyProxy(() => {
|
||||
const challenge = optionsFunc();
|
||||
|
||||
if (
|
||||
challenge.canComplete == null &&
|
||||
(challenge.resource == null || challenge.goal == null)
|
||||
) {
|
||||
console.warn(
|
||||
"Cannot create challenge without a canComplete property or a resource and goal property",
|
||||
challenge
|
||||
);
|
||||
throw new Error(
|
||||
"Cannot create challenge without a canComplete property or a resource and goal property"
|
||||
);
|
||||
}
|
||||
|
||||
challenge.id = getUniqueID("challenge-");
|
||||
challenge.type = ChallengeType;
|
||||
challenge[Component] = ChallengeComponent;
|
||||
|
@ -137,13 +123,10 @@ export function createChallenge<T extends ChallengeOptions>(
|
|||
const genericChallenge = challenge as GenericChallenge;
|
||||
if (genericChallenge.active.value) {
|
||||
if (
|
||||
unref(genericChallenge.canComplete) !== false &&
|
||||
Decimal.gt(unref(genericChallenge.canComplete), 0) &&
|
||||
!genericChallenge.maxed.value
|
||||
) {
|
||||
let completions: boolean | DecimalSource = unref(genericChallenge.canComplete);
|
||||
if (typeof completions === "boolean") {
|
||||
completions = 1;
|
||||
}
|
||||
const completions = unref(genericChallenge.canComplete);
|
||||
genericChallenge.completions.value = Decimal.min(
|
||||
Decimal.add(genericChallenge.completions.value, completions),
|
||||
unref(genericChallenge.completionLimit)
|
||||
|
@ -163,18 +146,20 @@ export function createChallenge<T extends ChallengeOptions>(
|
|||
genericChallenge.onEnter?.();
|
||||
}
|
||||
};
|
||||
challenge.canComplete = computed(() =>
|
||||
Decimal.max(
|
||||
maxRequirementsMet((challenge as GenericChallenge).requirements),
|
||||
unref((challenge as GenericChallenge).maximize) ? Decimal.dInf : 1
|
||||
)
|
||||
);
|
||||
challenge.complete = function (remainInChallenge?: boolean) {
|
||||
const genericChallenge = challenge as GenericChallenge;
|
||||
let completions: boolean | DecimalSource = unref(genericChallenge.canComplete);
|
||||
const completions = unref(genericChallenge.canComplete);
|
||||
if (
|
||||
genericChallenge.active.value &&
|
||||
completions !== false &&
|
||||
(completions === true || Decimal.neq(0, completions)) &&
|
||||
Decimal.gt(completions, 0) &&
|
||||
!genericChallenge.maxed.value
|
||||
) {
|
||||
if (typeof completions === "boolean") {
|
||||
completions = 1;
|
||||
}
|
||||
genericChallenge.completions.value = Decimal.min(
|
||||
Decimal.add(genericChallenge.completions.value, completions),
|
||||
unref(genericChallenge.completionLimit)
|
||||
|
@ -196,19 +181,6 @@ export function createChallenge<T extends ChallengeOptions>(
|
|||
}
|
||||
return unref(visibility);
|
||||
});
|
||||
if (challenge.canComplete == null) {
|
||||
challenge.canComplete = computed(() => {
|
||||
const genericChallenge = challenge as GenericChallenge;
|
||||
if (
|
||||
!genericChallenge.active.value ||
|
||||
genericChallenge.resource == null ||
|
||||
genericChallenge.goal == null
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return Decimal.gte(genericChallenge.resource.value, unref(genericChallenge.goal));
|
||||
});
|
||||
}
|
||||
if (challenge.mark == null) {
|
||||
challenge.mark = computed(
|
||||
() =>
|
||||
|
@ -219,11 +191,10 @@ export function createChallenge<T extends ChallengeOptions>(
|
|||
|
||||
processComputable(challenge as T, "canStart");
|
||||
setDefault(challenge, "canStart", true);
|
||||
processComputable(challenge as T, "canComplete");
|
||||
processComputable(challenge as T, "maximize");
|
||||
processComputable(challenge as T, "completionLimit");
|
||||
setDefault(challenge, "completionLimit", 1);
|
||||
processComputable(challenge as T, "mark");
|
||||
processComputable(challenge as T, "goal");
|
||||
processComputable(challenge as T, "classes");
|
||||
processComputable(challenge as T, "style");
|
||||
processComputable(challenge as T, "display");
|
||||
|
@ -278,9 +249,9 @@ export function setupAutoComplete(
|
|||
): WatchStopHandle {
|
||||
const isActive = typeof autoActive === "function" ? computed(autoActive) : autoActive;
|
||||
return watch(
|
||||
[challenge.canComplete as Ref<boolean>, isActive as Ref<boolean>],
|
||||
[challenge.canComplete as Ref<DecimalSource>, isActive as Ref<boolean>],
|
||||
([canComplete, isActive]) => {
|
||||
if (canComplete && isActive) {
|
||||
if (Decimal.gt(canComplete, 0) && isActive) {
|
||||
challenge.complete(!exitOnComplete);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,6 +78,11 @@ export interface BaseRepeatable {
|
|||
maxed: Ref<boolean>;
|
||||
/** Whether or not this repeatable can be clicked. */
|
||||
canClick: ProcessedComputable<boolean>;
|
||||
/**
|
||||
* How much amount can be increased by, or 1 if unclickable.
|
||||
* Capped at 1 if {@link RepeatableOptions.maximize} is false.
|
||||
**/
|
||||
amountToIncrease: Ref<DecimalSource>;
|
||||
/** A function that gets called when this repeatable is clicked. */
|
||||
onClick: (event?: MouseEvent | TouchEvent) => void;
|
||||
/** A symbol that helps identify features of the same type. */
|
||||
|
@ -170,6 +175,11 @@ export function createRepeatable<T extends RepeatableOptions>(
|
|||
}
|
||||
return currClasses;
|
||||
});
|
||||
repeatable.amountToIncrease = computed(() =>
|
||||
unref((repeatable as GenericRepeatable).maximize)
|
||||
? maxRequirementsMet(repeatable.requirements)
|
||||
: 1
|
||||
);
|
||||
repeatable.canClick = computed(() => requirementsMet(repeatable.requirements));
|
||||
const onClick = repeatable.onClick;
|
||||
repeatable.onClick = function (this: GenericRepeatable, event?: MouseEvent | TouchEvent) {
|
||||
|
@ -177,12 +187,7 @@ export function createRepeatable<T extends RepeatableOptions>(
|
|||
if (!unref(genericRepeatable.canClick)) {
|
||||
return;
|
||||
}
|
||||
payRequirements(
|
||||
repeatable.requirements,
|
||||
unref(genericRepeatable.maximize)
|
||||
? maxRequirementsMet(genericRepeatable.requirements)
|
||||
: 1
|
||||
);
|
||||
payRequirements(repeatable.requirements, unref(repeatable.amountToIncrease));
|
||||
genericRepeatable.amount.value = Decimal.add(genericRepeatable.amount.value, 1);
|
||||
onClick?.(event);
|
||||
};
|
||||
|
@ -233,9 +238,7 @@ export function createRepeatable<T extends RepeatableOptions>(
|
|||
<br />
|
||||
{displayRequirements(
|
||||
genericRepeatable.requirements,
|
||||
unref(genericRepeatable.maximize)
|
||||
? maxRequirementsMet(genericRepeatable.requirements)
|
||||
: 1
|
||||
unref(repeatable.amountToIncrease)
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -239,7 +239,7 @@ export function maxRequirementsMet(requirements: Requirements): DecimalSource {
|
|||
}
|
||||
const reqsMet = unref(requirements.requirementMet);
|
||||
if (typeof reqsMet === "boolean") {
|
||||
return reqsMet ? Infinity : 0;
|
||||
return reqsMet ? Decimal.dInf : 0;
|
||||
} else if (Decimal.gt(reqsMet, 1) && unref(requirements.canMaximize) !== true) {
|
||||
return 1;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue