Add comments for modifiers

This commit is contained in:
thepaperpilot 2022-05-01 14:12:00 -05:00
parent 665ddff196
commit 8f7160c725
2 changed files with 120 additions and 47 deletions

View file

@ -1,17 +1,45 @@
import "components/common/modifiers.css";
import { CoercableComponent, jsx } from "features/feature"; import { CoercableComponent, jsx } from "features/feature";
import Decimal, { DecimalSource, format } from "util/bignum"; import Decimal, { DecimalSource, format } from "util/bignum";
import { WithRequired } from "util/common";
import { Computable, convertComputable, ProcessedComputable } from "util/computed"; import { Computable, convertComputable, ProcessedComputable } from "util/computed";
import { renderJSX } from "util/vue"; import { renderJSX } from "util/vue";
import { computed, unref } from "vue"; import { computed, unref } from "vue";
import "components/common/modifiers.css";
/**
* An object that can be used to apply or unapply some modification to a number.
* Being reversible requires the operation being invertible, but some features may rely on that.
* Descriptions can be optionally included for displaying them to the player.
* The built-in modifier creators are designed to display the modifiers using
* {@link createModifierSection}
*/
export interface Modifier { export interface Modifier {
/**
* Applies some operation on the input and returns the result
*/
apply: (gain: DecimalSource) => DecimalSource; apply: (gain: DecimalSource) => DecimalSource;
revert: (gain: DecimalSource) => DecimalSource; /**
enabled: ProcessedComputable<boolean>; * Reverses the operation applied by the apply property. Required by some features
*/
revert?: (gain: DecimalSource) => DecimalSource;
/**
* Whether or not this modifier should be considered enabled.
* Typically for use with modifiers passed into {@link createSequentialModifier}
*/
enabled?: ProcessedComputable<boolean>;
/**
* A description of this modifier.
* @see {@link createModifierSection}
*/
description?: ProcessedComputable<CoercableComponent>; description?: ProcessedComputable<CoercableComponent>;
} }
/**
* Create a modifier that adds some value to the input value
* @param addend The amount to add to the input value
* @param description Description of what this modifier is doing
* @param enabled A computable that will be processed and passed directly into the returned modifier
*/
export function createAdditiveModifier( export function createAdditiveModifier(
addend: Computable<DecimalSource>, addend: Computable<DecimalSource>,
description?: Computable<CoercableComponent>, description?: Computable<CoercableComponent>,
@ -19,25 +47,34 @@ export function createAdditiveModifier(
): Modifier { ): Modifier {
const processedAddend = convertComputable(addend); const processedAddend = convertComputable(addend);
const processedDescription = convertComputable(description); const processedDescription = convertComputable(description);
const processedEnabled = convertComputable(enabled == null ? true : enabled); const processedEnabled = enabled == null ? undefined : convertComputable(enabled);
return { return {
apply: gain => Decimal.add(gain, unref(processedAddend)), apply: gain => Decimal.add(gain, unref(processedAddend)),
revert: gain => Decimal.sub(gain, unref(processedAddend)), revert: gain => Decimal.sub(gain, unref(processedAddend)),
enabled: processedEnabled, enabled: processedEnabled,
description: jsx(() => ( description:
<div class="modifier-container"> description == null
<span class="modifier-amount">+{format(unref(processedAddend))}</span> ? undefined
{unref(processedDescription) ? ( : jsx(() => (
<span class="modifier-description"> <div class="modifier-container">
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} <span class="modifier-amount">+{format(unref(processedAddend))}</span>
{renderJSX(unref(processedDescription)!)} {unref(processedDescription) ? (
</span> <span class="modifier-description">
) : null} {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
</div> {renderJSX(unref(processedDescription)!)}
)) </span>
) : null}
</div>
))
}; };
} }
/**
* Create a modifier that multiplies the input value by some value
* @param multiplier The value to multiply the input value by
* @param description Description of what this modifier is doing
* @param enabled A computable that will be processed and passed directly into the returned modifier
*/
export function createMultiplicativeModifier( export function createMultiplicativeModifier(
multiplier: Computable<DecimalSource>, multiplier: Computable<DecimalSource>,
description?: Computable<CoercableComponent>, description?: Computable<CoercableComponent>,
@ -45,25 +82,34 @@ export function createMultiplicativeModifier(
): Modifier { ): Modifier {
const processedMultiplier = convertComputable(multiplier); const processedMultiplier = convertComputable(multiplier);
const processedDescription = convertComputable(description); const processedDescription = convertComputable(description);
const processedEnabled = convertComputable(enabled == null ? true : enabled); const processedEnabled = enabled == null ? undefined : convertComputable(enabled);
return { return {
apply: gain => Decimal.times(gain, unref(processedMultiplier)), apply: gain => Decimal.times(gain, unref(processedMultiplier)),
revert: gain => Decimal.div(gain, unref(processedMultiplier)), revert: gain => Decimal.div(gain, unref(processedMultiplier)),
enabled: processedEnabled, enabled: processedEnabled,
description: jsx(() => ( description:
<div class="modifier-container"> description == null
<span class="modifier-amount">x{format(unref(processedMultiplier))}</span> ? undefined
{unref(processedDescription) ? ( : jsx(() => (
<span class="modifier-description"> <div class="modifier-container">
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} <span class="modifier-amount">x{format(unref(processedMultiplier))}</span>
{renderJSX(unref(processedDescription)!)} {unref(processedDescription) ? (
</span> <span class="modifier-description">
) : null} {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
</div> {renderJSX(unref(processedDescription)!)}
)) </span>
) : null}
</div>
))
}; };
} }
/**
* Create a modifier that raises the input value to the power of some value
* @param exponent The value to raise the input value to the power of
* @param description Description of what this modifier is doing
* @param enabled A computable that will be processed and passed directly into the returned modifier
*/
export function createExponentialModifier( export function createExponentialModifier(
exponent: Computable<DecimalSource>, exponent: Computable<DecimalSource>,
description?: Computable<CoercableComponent>, description?: Computable<CoercableComponent>,
@ -71,41 +117,56 @@ export function createExponentialModifier(
): Modifier { ): Modifier {
const processedExponent = convertComputable(exponent); const processedExponent = convertComputable(exponent);
const processedDescription = convertComputable(description); const processedDescription = convertComputable(description);
const processedEnabled = convertComputable(enabled == null ? true : enabled); const processedEnabled = enabled == null ? undefined : convertComputable(enabled);
return { return {
apply: gain => Decimal.pow(gain, unref(processedExponent)), apply: gain => Decimal.pow(gain, unref(processedExponent)),
revert: gain => Decimal.root(gain, unref(processedExponent)), revert: gain => Decimal.root(gain, unref(processedExponent)),
enabled: processedEnabled, enabled: processedEnabled,
description: jsx(() => ( description:
<div class="modifier-container"> description == null
<span class="modifier-amount">^{format(unref(processedExponent))}</span> ? undefined
{unref(processedDescription) ? ( : jsx(() => (
<span class="modifier-description"> <div class="modifier-container">
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} <span class="modifier-amount">^{format(unref(processedExponent))}</span>
{renderJSX(unref(processedDescription)!)} {unref(processedDescription) ? (
</span> <span class="modifier-description">
) : null} {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
</div> {renderJSX(unref(processedDescription)!)}
)) </span>
) : null}
</div>
))
}; };
} }
export function createSequentialModifier(...modifiers: Modifier[]): Required<Modifier> { /**
* Takes an array of modifiers and applies and reverses them in order.
* Modifiers that are not enabled will not be applied nor reversed.
* Also joins their descriptions together.
* @param modifiers The modifiers to perform sequentially
* @see {@link createModifierSection}
*/
export function createSequentialModifier(
...modifiers: Modifier[]
): WithRequired<Modifier, "description"> {
return { return {
apply: gain => apply: gain =>
modifiers modifiers
.filter(m => unref(m.enabled)) .filter(m => unref(m.enabled) !== false)
.reduce((gain, modifier) => modifier.apply(gain), gain), .reduce((gain, modifier) => modifier.apply(gain), gain),
revert: gain => revert: modifiers.every(m => m.revert != null)
modifiers ? gain =>
.filter(m => unref(m.enabled)) modifiers
.reduceRight((gain, modifier) => modifier.revert(gain), gain), .filter(m => unref(m.enabled) !== false)
enabled: computed(() => modifiers.filter(m => unref(m.enabled)).length > 0), // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
.reduceRight((gain, modifier) => modifier.revert!(gain), gain)
: undefined,
enabled: computed(() => modifiers.filter(m => unref(m.enabled) !== false).length > 0),
description: jsx(() => ( description: jsx(() => (
<> <>
{( {(
modifiers modifiers
.filter(m => unref(m.enabled)) .filter(m => unref(m.enabled) !== false)
.map(m => unref(m.description)) .map(m => unref(m.description))
.filter(d => d) as CoercableComponent[] .filter(d => d) as CoercableComponent[]
).map(renderJSX)} ).map(renderJSX)}
@ -114,6 +175,16 @@ export function createSequentialModifier(...modifiers: Modifier[]): Required<Mod
}; };
} }
/**
* Create a JSX element that displays a modifier.
* Intended to be used with the output from {@link createSequentialModifier}
* @param title The header for the section
* @param subtitle Smaller text that appears in the header after the title
* @param modifier The modifier to render
* @param base The base value that'll be passed into the modifier
* @param unit The unit of the value being modified, if any
* @param baseText The label to use for the base value
*/
export function createModifierSection( export function createModifierSection(
title: string, title: string,
subtitle: string, subtitle: string,

View file

@ -1,3 +1,5 @@
export type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
// Reference: // Reference:
// https://stackoverflow.com/questions/7225407/convert-camelcasetext-to-sentence-case-text // https://stackoverflow.com/questions/7225407/convert-camelcasetext-to-sentence-case-text
export function camelToTitle(camel: string): string { export function camelToTitle(camel: string): string {