Make challenges use the requirements system

This commit is contained in:
thepaperpilot 2023-04-02 19:22:23 -05:00
parent eff5852b04
commit cc1a2998e0
3 changed files with 32 additions and 58 deletions

View file

@ -12,10 +12,10 @@ import {
Visibility Visibility
} from "features/feature"; } from "features/feature";
import type { GenericReset } from "features/reset"; import type { GenericReset } from "features/reset";
import type { Resource } from "features/resources/resource";
import { globalBus } from "game/events"; import { globalBus } from "game/events";
import type { Persistent } from "game/persistence"; import type { Persistent } from "game/persistence";
import { persistent } from "game/persistence"; import { persistent } from "game/persistence";
import { maxRequirementsMet, Requirements } from "game/requirements";
import settings, { registerSettingField } from "game/settings"; import settings, { registerSettingField } from "game/settings";
import type { DecimalSource } from "util/bignum"; import type { DecimalSource } from "util/bignum";
import Decimal from "util/bignum"; import Decimal from "util/bignum";
@ -36,11 +36,10 @@ export interface ChallengeOptions {
visibility?: Computable<Visibility | boolean>; visibility?: Computable<Visibility | boolean>;
canStart?: Computable<boolean>; canStart?: Computable<boolean>;
reset?: GenericReset; reset?: GenericReset;
canComplete?: Computable<boolean | DecimalSource>; requirements: Requirements;
maximize?: Computable<boolean>;
completionLimit?: Computable<DecimalSource>; completionLimit?: Computable<DecimalSource>;
mark?: Computable<boolean | string>; mark?: Computable<boolean | string>;
resource?: Resource;
goal?: Computable<DecimalSource>;
classes?: Computable<Record<string, boolean>>; classes?: Computable<Record<string, boolean>>;
style?: Computable<StyleValue>; style?: Computable<StyleValue>;
display?: Computable< display?: Computable<
@ -60,6 +59,7 @@ export interface ChallengeOptions {
export interface BaseChallenge { export interface BaseChallenge {
id: string; id: string;
canComplete: Ref<DecimalSource>;
completions: Persistent<DecimalSource>; completions: Persistent<DecimalSource>;
completed: Ref<boolean>; completed: Ref<boolean>;
maxed: Ref<boolean>; maxed: Ref<boolean>;
@ -76,10 +76,10 @@ export type Challenge<T extends ChallengeOptions> = Replace<
{ {
visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>; visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>;
canStart: GetComputableTypeWithDefault<T["canStart"], true>; canStart: GetComputableTypeWithDefault<T["canStart"], true>;
canComplete: GetComputableTypeWithDefault<T["canComplete"], Ref<boolean>>; requirements: GetComputableType<T["requirements"]>;
maximize: GetComputableType<T["maximize"]>;
completionLimit: GetComputableTypeWithDefault<T["completionLimit"], 1>; completionLimit: GetComputableTypeWithDefault<T["completionLimit"], 1>;
mark: GetComputableTypeWithDefault<T["mark"], Ref<boolean>>; mark: GetComputableTypeWithDefault<T["mark"], Ref<boolean>>;
goal: GetComputableType<T["goal"]>;
classes: GetComputableType<T["classes"]>; classes: GetComputableType<T["classes"]>;
style: GetComputableType<T["style"]>; style: GetComputableType<T["style"]>;
display: GetComputableType<T["display"]>; display: GetComputableType<T["display"]>;
@ -91,7 +91,6 @@ export type GenericChallenge = Replace<
{ {
visibility: ProcessedComputable<Visibility | boolean>; visibility: ProcessedComputable<Visibility | boolean>;
canStart: ProcessedComputable<boolean>; canStart: ProcessedComputable<boolean>;
canComplete: ProcessedComputable<boolean | DecimalSource>;
completionLimit: ProcessedComputable<DecimalSource>; completionLimit: ProcessedComputable<DecimalSource>;
mark: ProcessedComputable<boolean>; mark: ProcessedComputable<boolean>;
} }
@ -105,19 +104,6 @@ export function createChallenge<T extends ChallengeOptions>(
return createLazyProxy(() => { return createLazyProxy(() => {
const challenge = optionsFunc(); 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.id = getUniqueID("challenge-");
challenge.type = ChallengeType; challenge.type = ChallengeType;
challenge[Component] = ChallengeComponent; challenge[Component] = ChallengeComponent;
@ -137,13 +123,10 @@ export function createChallenge<T extends ChallengeOptions>(
const genericChallenge = challenge as GenericChallenge; const genericChallenge = challenge as GenericChallenge;
if (genericChallenge.active.value) { if (genericChallenge.active.value) {
if ( if (
unref(genericChallenge.canComplete) !== false && Decimal.gt(unref(genericChallenge.canComplete), 0) &&
!genericChallenge.maxed.value !genericChallenge.maxed.value
) { ) {
let completions: boolean | DecimalSource = unref(genericChallenge.canComplete); const completions = unref(genericChallenge.canComplete);
if (typeof completions === "boolean") {
completions = 1;
}
genericChallenge.completions.value = Decimal.min( genericChallenge.completions.value = Decimal.min(
Decimal.add(genericChallenge.completions.value, completions), Decimal.add(genericChallenge.completions.value, completions),
unref(genericChallenge.completionLimit) unref(genericChallenge.completionLimit)
@ -163,18 +146,20 @@ export function createChallenge<T extends ChallengeOptions>(
genericChallenge.onEnter?.(); 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) { challenge.complete = function (remainInChallenge?: boolean) {
const genericChallenge = challenge as GenericChallenge; const genericChallenge = challenge as GenericChallenge;
let completions: boolean | DecimalSource = unref(genericChallenge.canComplete); const completions = unref(genericChallenge.canComplete);
if ( if (
genericChallenge.active.value && genericChallenge.active.value &&
completions !== false && Decimal.gt(completions, 0) &&
(completions === true || Decimal.neq(0, completions)) &&
!genericChallenge.maxed.value !genericChallenge.maxed.value
) { ) {
if (typeof completions === "boolean") {
completions = 1;
}
genericChallenge.completions.value = Decimal.min( genericChallenge.completions.value = Decimal.min(
Decimal.add(genericChallenge.completions.value, completions), Decimal.add(genericChallenge.completions.value, completions),
unref(genericChallenge.completionLimit) unref(genericChallenge.completionLimit)
@ -196,19 +181,6 @@ export function createChallenge<T extends ChallengeOptions>(
} }
return unref(visibility); 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) { if (challenge.mark == null) {
challenge.mark = computed( challenge.mark = computed(
() => () =>
@ -219,11 +191,10 @@ export function createChallenge<T extends ChallengeOptions>(
processComputable(challenge as T, "canStart"); processComputable(challenge as T, "canStart");
setDefault(challenge, "canStart", true); setDefault(challenge, "canStart", true);
processComputable(challenge as T, "canComplete"); processComputable(challenge as T, "maximize");
processComputable(challenge as T, "completionLimit"); processComputable(challenge as T, "completionLimit");
setDefault(challenge, "completionLimit", 1); setDefault(challenge, "completionLimit", 1);
processComputable(challenge as T, "mark"); processComputable(challenge as T, "mark");
processComputable(challenge as T, "goal");
processComputable(challenge as T, "classes"); processComputable(challenge as T, "classes");
processComputable(challenge as T, "style"); processComputable(challenge as T, "style");
processComputable(challenge as T, "display"); processComputable(challenge as T, "display");
@ -278,9 +249,9 @@ export function setupAutoComplete(
): WatchStopHandle { ): WatchStopHandle {
const isActive = typeof autoActive === "function" ? computed(autoActive) : autoActive; const isActive = typeof autoActive === "function" ? computed(autoActive) : autoActive;
return watch( return watch(
[challenge.canComplete as Ref<boolean>, isActive as Ref<boolean>], [challenge.canComplete as Ref<DecimalSource>, isActive as Ref<boolean>],
([canComplete, isActive]) => { ([canComplete, isActive]) => {
if (canComplete && isActive) { if (Decimal.gt(canComplete, 0) && isActive) {
challenge.complete(!exitOnComplete); challenge.complete(!exitOnComplete);
} }
} }

View file

@ -78,6 +78,11 @@ export interface BaseRepeatable {
maxed: Ref<boolean>; maxed: Ref<boolean>;
/** Whether or not this repeatable can be clicked. */ /** Whether or not this repeatable can be clicked. */
canClick: ProcessedComputable<boolean>; 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. */ /** A function that gets called when this repeatable is clicked. */
onClick: (event?: MouseEvent | TouchEvent) => void; onClick: (event?: MouseEvent | TouchEvent) => void;
/** A symbol that helps identify features of the same type. */ /** A symbol that helps identify features of the same type. */
@ -170,6 +175,11 @@ export function createRepeatable<T extends RepeatableOptions>(
} }
return currClasses; return currClasses;
}); });
repeatable.amountToIncrease = computed(() =>
unref((repeatable as GenericRepeatable).maximize)
? maxRequirementsMet(repeatable.requirements)
: 1
);
repeatable.canClick = computed(() => requirementsMet(repeatable.requirements)); repeatable.canClick = computed(() => requirementsMet(repeatable.requirements));
const onClick = repeatable.onClick; const onClick = repeatable.onClick;
repeatable.onClick = function (this: GenericRepeatable, event?: MouseEvent | TouchEvent) { repeatable.onClick = function (this: GenericRepeatable, event?: MouseEvent | TouchEvent) {
@ -177,12 +187,7 @@ export function createRepeatable<T extends RepeatableOptions>(
if (!unref(genericRepeatable.canClick)) { if (!unref(genericRepeatable.canClick)) {
return; return;
} }
payRequirements( payRequirements(repeatable.requirements, unref(repeatable.amountToIncrease));
repeatable.requirements,
unref(genericRepeatable.maximize)
? maxRequirementsMet(genericRepeatable.requirements)
: 1
);
genericRepeatable.amount.value = Decimal.add(genericRepeatable.amount.value, 1); genericRepeatable.amount.value = Decimal.add(genericRepeatable.amount.value, 1);
onClick?.(event); onClick?.(event);
}; };
@ -233,9 +238,7 @@ export function createRepeatable<T extends RepeatableOptions>(
<br /> <br />
{displayRequirements( {displayRequirements(
genericRepeatable.requirements, genericRepeatable.requirements,
unref(genericRepeatable.maximize) unref(repeatable.amountToIncrease)
? maxRequirementsMet(genericRepeatable.requirements)
: 1
)} )}
</div> </div>
)} )}

View file

@ -239,7 +239,7 @@ export function maxRequirementsMet(requirements: Requirements): DecimalSource {
} }
const reqsMet = unref(requirements.requirementMet); const reqsMet = unref(requirements.requirementMet);
if (typeof reqsMet === "boolean") { if (typeof reqsMet === "boolean") {
return reqsMet ? Infinity : 0; return reqsMet ? Decimal.dInf : 0;
} else if (Decimal.gt(reqsMet, 1) && unref(requirements.canMaximize) !== true) { } else if (Decimal.gt(reqsMet, 1) && unref(requirements.canMaximize) !== true) {
return 1; return 1;
} }