2022-05-01 19:12:00 +00:00
|
|
|
import "components/common/modifiers.css";
|
2022-04-16 05:56:37 +00:00
|
|
|
import { CoercableComponent, jsx } from "features/feature";
|
|
|
|
import Decimal, { DecimalSource, format } from "util/bignum";
|
2022-05-01 19:12:00 +00:00
|
|
|
import { WithRequired } from "util/common";
|
2022-04-16 05:56:37 +00:00
|
|
|
import { Computable, convertComputable, ProcessedComputable } from "util/computed";
|
|
|
|
import { renderJSX } from "util/vue";
|
|
|
|
import { computed, unref } from "vue";
|
|
|
|
|
2022-05-01 19:12:00 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
2022-05-02 00:13:21 +00:00
|
|
|
* The built-in modifier creators are designed to display the modifiers using.
|
|
|
|
* {@link createModifierSection}.
|
2022-05-01 19:12:00 +00:00
|
|
|
*/
|
2022-04-16 05:56:37 +00:00
|
|
|
export interface Modifier {
|
2022-05-01 19:12:00 +00:00
|
|
|
/**
|
2022-05-02 00:13:21 +00:00
|
|
|
* Applies some operation on the input and returns the result.
|
2022-05-01 19:12:00 +00:00
|
|
|
*/
|
2022-04-16 05:56:37 +00:00
|
|
|
apply: (gain: DecimalSource) => DecimalSource;
|
2022-05-01 19:12:00 +00:00
|
|
|
/**
|
2022-05-02 00:13:21 +00:00
|
|
|
* Reverses the operation applied by the apply property. Required by some features.
|
2022-05-01 19:12:00 +00:00
|
|
|
*/
|
|
|
|
revert?: (gain: DecimalSource) => DecimalSource;
|
|
|
|
/**
|
|
|
|
* Whether or not this modifier should be considered enabled.
|
2022-05-02 00:13:21 +00:00
|
|
|
* Typically for use with modifiers passed into {@link createSequentialModifier}.
|
2022-05-01 19:12:00 +00:00
|
|
|
*/
|
|
|
|
enabled?: ProcessedComputable<boolean>;
|
|
|
|
/**
|
|
|
|
* A description of this modifier.
|
2022-05-02 00:13:21 +00:00
|
|
|
* @see {@link createModifierSection}.
|
2022-05-01 19:12:00 +00:00
|
|
|
*/
|
2022-04-16 05:56:37 +00:00
|
|
|
description?: ProcessedComputable<CoercableComponent>;
|
|
|
|
}
|
|
|
|
|
2022-05-01 22:25:10 +00:00
|
|
|
/**
|
|
|
|
* Utility type used to narrow down a modifier type that will have a description and/or enabled property based on optional parameters, T and S (respectively).
|
|
|
|
*/
|
|
|
|
export type ModifierFromOptionalParams<T, S> = T extends undefined
|
|
|
|
? S extends undefined
|
|
|
|
? Omit<WithRequired<Modifier, "revert">, "description" | "enabled">
|
|
|
|
: Omit<WithRequired<Modifier, "revert" | "enabled">, "description">
|
|
|
|
: S extends undefined
|
|
|
|
? Omit<WithRequired<Modifier, "revert" | "description">, "enabled">
|
|
|
|
: WithRequired<Modifier, "revert" | "enabled" | "description">;
|
|
|
|
|
2022-05-01 19:12:00 +00:00
|
|
|
/**
|
2022-05-02 00:13:21 +00:00
|
|
|
* 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.
|
2022-05-01 19:12:00 +00:00
|
|
|
*/
|
2022-05-01 22:25:10 +00:00
|
|
|
export function createAdditiveModifier<
|
|
|
|
T extends Computable<CoercableComponent> | undefined,
|
|
|
|
S extends Computable<boolean> | undefined,
|
|
|
|
R = ModifierFromOptionalParams<T, S>
|
|
|
|
>(addend: Computable<DecimalSource>, description?: T, enabled?: S): R {
|
2022-04-16 05:56:37 +00:00
|
|
|
const processedAddend = convertComputable(addend);
|
|
|
|
const processedDescription = convertComputable(description);
|
2022-05-01 19:12:00 +00:00
|
|
|
const processedEnabled = enabled == null ? undefined : convertComputable(enabled);
|
2022-04-16 05:56:37 +00:00
|
|
|
return {
|
2022-05-01 22:25:10 +00:00
|
|
|
apply: (gain: DecimalSource) => Decimal.add(gain, unref(processedAddend)),
|
|
|
|
revert: (gain: DecimalSource) => Decimal.sub(gain, unref(processedAddend)),
|
2022-04-16 05:56:37 +00:00
|
|
|
enabled: processedEnabled,
|
2022-05-01 19:12:00 +00:00
|
|
|
description:
|
|
|
|
description == null
|
|
|
|
? undefined
|
|
|
|
: jsx(() => (
|
|
|
|
<div class="modifier-container">
|
|
|
|
<span class="modifier-amount">+{format(unref(processedAddend))}</span>
|
|
|
|
{unref(processedDescription) ? (
|
|
|
|
<span class="modifier-description">
|
|
|
|
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
|
|
|
{renderJSX(unref(processedDescription)!)}
|
|
|
|
</span>
|
|
|
|
) : null}
|
|
|
|
</div>
|
|
|
|
))
|
2022-05-01 22:25:10 +00:00
|
|
|
} as unknown as R;
|
2022-04-16 05:56:37 +00:00
|
|
|
}
|
|
|
|
|
2022-05-01 19:12:00 +00:00
|
|
|
/**
|
2022-05-02 00:13:21 +00:00
|
|
|
* 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.
|
2022-05-01 19:12:00 +00:00
|
|
|
*/
|
2022-05-01 22:25:10 +00:00
|
|
|
export function createMultiplicativeModifier<
|
|
|
|
T extends Computable<CoercableComponent> | undefined,
|
|
|
|
S extends Computable<boolean> | undefined,
|
|
|
|
R = ModifierFromOptionalParams<T, S>
|
|
|
|
>(multiplier: Computable<DecimalSource>, description?: T, enabled?: S): R {
|
2022-04-16 05:56:37 +00:00
|
|
|
const processedMultiplier = convertComputable(multiplier);
|
|
|
|
const processedDescription = convertComputable(description);
|
2022-05-01 19:12:00 +00:00
|
|
|
const processedEnabled = enabled == null ? undefined : convertComputable(enabled);
|
2022-04-16 05:56:37 +00:00
|
|
|
return {
|
2022-05-01 22:25:10 +00:00
|
|
|
apply: (gain: DecimalSource) => Decimal.times(gain, unref(processedMultiplier)),
|
|
|
|
revert: (gain: DecimalSource) => Decimal.div(gain, unref(processedMultiplier)),
|
2022-04-16 05:56:37 +00:00
|
|
|
enabled: processedEnabled,
|
2022-05-01 19:12:00 +00:00
|
|
|
description:
|
|
|
|
description == null
|
|
|
|
? undefined
|
|
|
|
: jsx(() => (
|
|
|
|
<div class="modifier-container">
|
|
|
|
<span class="modifier-amount">x{format(unref(processedMultiplier))}</span>
|
|
|
|
{unref(processedDescription) ? (
|
|
|
|
<span class="modifier-description">
|
|
|
|
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
|
|
|
{renderJSX(unref(processedDescription)!)}
|
|
|
|
</span>
|
|
|
|
) : null}
|
|
|
|
</div>
|
|
|
|
))
|
2022-05-01 22:25:10 +00:00
|
|
|
} as unknown as R;
|
2022-04-16 05:56:37 +00:00
|
|
|
}
|
|
|
|
|
2022-05-01 19:12:00 +00:00
|
|
|
/**
|
2022-05-02 00:13:21 +00:00
|
|
|
* 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.
|
2022-05-01 19:12:00 +00:00
|
|
|
*/
|
2022-05-01 22:25:10 +00:00
|
|
|
export function createExponentialModifier<
|
|
|
|
T extends Computable<CoercableComponent> | undefined,
|
|
|
|
S extends Computable<boolean> | undefined,
|
|
|
|
R = ModifierFromOptionalParams<T, S>
|
|
|
|
>(exponent: Computable<DecimalSource>, description?: T, enabled?: S): R {
|
2022-04-16 05:56:37 +00:00
|
|
|
const processedExponent = convertComputable(exponent);
|
|
|
|
const processedDescription = convertComputable(description);
|
2022-05-01 19:12:00 +00:00
|
|
|
const processedEnabled = enabled == null ? undefined : convertComputable(enabled);
|
2022-04-16 05:56:37 +00:00
|
|
|
return {
|
2022-05-01 22:25:10 +00:00
|
|
|
apply: (gain: DecimalSource) => Decimal.pow(gain, unref(processedExponent)),
|
|
|
|
revert: (gain: DecimalSource) => Decimal.root(gain, unref(processedExponent)),
|
2022-04-16 05:56:37 +00:00
|
|
|
enabled: processedEnabled,
|
2022-05-01 19:12:00 +00:00
|
|
|
description:
|
|
|
|
description == null
|
|
|
|
? undefined
|
|
|
|
: jsx(() => (
|
|
|
|
<div class="modifier-container">
|
|
|
|
<span class="modifier-amount">^{format(unref(processedExponent))}</span>
|
|
|
|
{unref(processedDescription) ? (
|
|
|
|
<span class="modifier-description">
|
|
|
|
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
|
|
|
{renderJSX(unref(processedDescription)!)}
|
|
|
|
</span>
|
|
|
|
) : null}
|
|
|
|
</div>
|
|
|
|
))
|
2022-05-01 22:25:10 +00:00
|
|
|
} as unknown as R;
|
2022-04-16 05:56:37 +00:00
|
|
|
}
|
|
|
|
|
2022-05-01 19:12:00 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
2022-05-02 00:13:21 +00:00
|
|
|
* @param modifiers The modifiers to perform sequentially.
|
|
|
|
* @see {@link createModifierSection}.
|
2022-05-01 19:12:00 +00:00
|
|
|
*/
|
2022-05-01 22:25:10 +00:00
|
|
|
export function createSequentialModifier<
|
|
|
|
T extends Modifier[],
|
|
|
|
S = T extends WithRequired<Modifier, "revert">[]
|
|
|
|
? WithRequired<Modifier, "description" | "revert">
|
|
|
|
: Omit<WithRequired<Modifier, "description">, "revert">
|
|
|
|
>(...modifiers: T): S {
|
2022-04-16 05:56:37 +00:00
|
|
|
return {
|
2022-05-01 22:25:10 +00:00
|
|
|
apply: (gain: DecimalSource) =>
|
2022-04-16 05:56:37 +00:00
|
|
|
modifiers
|
2022-05-01 19:12:00 +00:00
|
|
|
.filter(m => unref(m.enabled) !== false)
|
2022-04-16 05:56:37 +00:00
|
|
|
.reduce((gain, modifier) => modifier.apply(gain), gain),
|
2022-05-01 19:12:00 +00:00
|
|
|
revert: modifiers.every(m => m.revert != null)
|
2022-05-01 22:25:10 +00:00
|
|
|
? (gain: DecimalSource) =>
|
2022-05-01 19:12:00 +00:00
|
|
|
modifiers
|
|
|
|
.filter(m => unref(m.enabled) !== false)
|
|
|
|
// 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),
|
2022-04-16 05:56:37 +00:00
|
|
|
description: jsx(() => (
|
|
|
|
<>
|
|
|
|
{(
|
|
|
|
modifiers
|
2022-05-01 19:12:00 +00:00
|
|
|
.filter(m => unref(m.enabled) !== false)
|
2022-04-16 05:56:37 +00:00
|
|
|
.map(m => unref(m.description))
|
|
|
|
.filter(d => d) as CoercableComponent[]
|
|
|
|
).map(renderJSX)}
|
|
|
|
</>
|
|
|
|
))
|
2022-05-01 22:25:10 +00:00
|
|
|
} as unknown as S;
|
2022-04-16 05:56:37 +00:00
|
|
|
}
|
2022-04-23 20:21:12 +00:00
|
|
|
|
2022-05-01 19:12:00 +00:00
|
|
|
/**
|
|
|
|
* Create a JSX element that displays a modifier.
|
2022-05-02 00:13:21 +00:00
|
|
|
* 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.
|
2022-05-01 19:12:00 +00:00
|
|
|
*/
|
2022-04-23 20:21:12 +00:00
|
|
|
export function createModifierSection(
|
|
|
|
title: string,
|
|
|
|
subtitle: string,
|
2022-05-03 23:42:39 +00:00
|
|
|
modifier: WithRequired<Modifier, "description">,
|
2022-04-24 02:23:34 +00:00
|
|
|
base: DecimalSource = 1,
|
2022-04-27 04:06:19 +00:00
|
|
|
unit = "",
|
|
|
|
baseText: CoercableComponent = "Base"
|
2022-04-23 20:21:12 +00:00
|
|
|
) {
|
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<h3>
|
|
|
|
{title}
|
|
|
|
{subtitle ? <span class="subtitle"> ({subtitle})</span> : null}
|
|
|
|
</h3>
|
|
|
|
<br />
|
|
|
|
<div class="modifier-container">
|
|
|
|
<span class="modifier-amount">
|
2022-04-24 02:23:34 +00:00
|
|
|
{format(base)}
|
2022-04-23 20:21:12 +00:00
|
|
|
{unit}
|
|
|
|
</span>
|
2022-04-27 04:06:19 +00:00
|
|
|
<span class="modifier-description">{renderJSX(baseText)}</span>
|
2022-04-23 20:21:12 +00:00
|
|
|
</div>
|
|
|
|
{renderJSX(unref(modifier.description))}
|
|
|
|
<hr />
|
|
|
|
Total: {format(modifier.apply(base))}
|
|
|
|
{unit}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|