2022-05-24 04:34:59 +00:00
|
|
|
import { BaseLayer } from "game/layers";
|
2022-04-12 03:50:02 +00:00
|
|
|
import { Modifier } from "game/modifiers";
|
2022-03-04 03:39:48 +00:00
|
|
|
import Decimal, { DecimalSource } from "util/bignum";
|
2022-05-02 00:13:41 +00:00
|
|
|
import { WithRequired } from "util/common";
|
2022-01-14 04:25:47 +00:00
|
|
|
import {
|
|
|
|
Computable,
|
2022-05-02 00:23:16 +00:00
|
|
|
convertComputable,
|
2022-01-14 04:25:47 +00:00
|
|
|
GetComputableTypeWithDefault,
|
|
|
|
processComputable,
|
|
|
|
ProcessedComputable
|
2022-03-04 03:39:48 +00:00
|
|
|
} from "util/computed";
|
|
|
|
import { createLazyProxy } from "util/proxies";
|
2022-05-03 01:50:19 +00:00
|
|
|
import { computed, Ref, unref } from "vue";
|
2022-04-11 00:04:56 +00:00
|
|
|
import { OptionsFunc, Replace, setDefault } from "./feature";
|
2022-02-27 22:04:56 +00:00
|
|
|
import { Resource } from "./resources/resource";
|
2022-01-14 04:25:47 +00:00
|
|
|
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* An object that configures a {@link conversion}.
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
export interface ConversionOptions {
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* The scaling function that is used to determine the rate of conversion from one {@link resource} to the other.
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
scaling: ScalingFunction;
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* How much of the output resource the conversion can currently convert for.
|
|
|
|
* Typically this will be set for you in a conversion constructor.
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
currentGain?: Computable<DecimalSource>;
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* The absolute amount the output resource will be changed by.
|
|
|
|
* Typically this will be set for you in a conversion constructor.
|
|
|
|
* This will differ from {@link currentGain} in the cases where the conversion isn't just adding the converted amount to the output resource.
|
|
|
|
*/
|
2022-04-10 22:05:49 +00:00
|
|
|
actualGain?: Computable<DecimalSource>;
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* The amount of the input resource currently being required in order to produce the {@link currentGain}.
|
|
|
|
* That is, if it went below this value then {@link currentGain} would decrease.
|
|
|
|
* Typically this will be set for you in a conversion constructor.
|
|
|
|
*/
|
2022-04-10 22:05:49 +00:00
|
|
|
currentAt?: Computable<DecimalSource>;
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* The amount of the input resource required to make {@link currentGain} increase.
|
|
|
|
* Typically this will be set for you in a conversion constructor.
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
nextAt?: Computable<DecimalSource>;
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* The input {@link resource} for this conversion.
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
baseResource: Resource;
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* The output {@link resource} for this conversion. i.e. the resource being generated.
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
gainResource: Resource;
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* Whether or not to cap the amount of the output resource gained by converting at 1.
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
buyMax?: Computable<boolean>;
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* Whether or not to round up the cost to generate a given amount of the output resource.
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
roundUpCost?: Computable<boolean>;
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* The function that performs the actual conversion from {@link baseResource} to {@link gainResource}.
|
|
|
|
* Typically this will be set for you in a conversion constructor.
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
convert?: VoidFunction;
|
2022-05-24 04:40:26 +00:00
|
|
|
/**
|
|
|
|
* A callback that happens after a conversion has been completed.
|
|
|
|
* Receives the amount gained via conversion.
|
|
|
|
* This will not be called whenever using currentGain without calling convert (e.g. passive generation)
|
|
|
|
*/
|
|
|
|
onConvert?: (amountGained: DecimalSource) => void;
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
2022-05-24 04:06:21 +00:00
|
|
|
* An additional modifier that will be applied to the gain amounts.
|
2022-05-02 00:13:41 +00:00
|
|
|
* Must be reversible in order to correctly calculate {@link nextAt}.
|
|
|
|
* @see {@link createSequentialModifier} if you want to apply multiple modifiers.
|
|
|
|
*/
|
|
|
|
gainModifier?: WithRequired<Modifier, "revert">;
|
2022-05-24 04:06:21 +00:00
|
|
|
/**
|
|
|
|
* A modifier that will be applied to the cost amounts.
|
|
|
|
* That is to say, this modifier will be applied to the amount of baseResource before going into the scaling function.
|
|
|
|
* A cost modifier of x0.5 would give gain amounts equal to the player having half the baseResource they actually have.
|
|
|
|
* Must be reversible in order to correctly calculate {@link nextAt}.
|
|
|
|
* @see {@link createSequentialModifier} if you want to apply multiple modifiers.
|
|
|
|
*/
|
|
|
|
costModifier?: WithRequired<Modifier, "revert">;
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* The properties that are added onto a processed {@link ConversionOptions} to create a {@link Conversion}.
|
|
|
|
*/
|
2022-03-09 01:40:51 +00:00
|
|
|
export interface BaseConversion {
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* The function that performs the actual conversion.
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
convert: VoidFunction;
|
|
|
|
}
|
|
|
|
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* An object that converts one {@link resource} into another at a given rate.
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
export type Conversion<T extends ConversionOptions> = Replace<
|
|
|
|
T & BaseConversion,
|
|
|
|
{
|
|
|
|
currentGain: GetComputableTypeWithDefault<T["currentGain"], Ref<DecimalSource>>;
|
2022-04-10 22:05:49 +00:00
|
|
|
actualGain: GetComputableTypeWithDefault<T["actualGain"], Ref<DecimalSource>>;
|
|
|
|
currentAt: GetComputableTypeWithDefault<T["currentAt"], Ref<DecimalSource>>;
|
2022-01-14 04:25:47 +00:00
|
|
|
nextAt: GetComputableTypeWithDefault<T["nextAt"], Ref<DecimalSource>>;
|
|
|
|
buyMax: GetComputableTypeWithDefault<T["buyMax"], true>;
|
|
|
|
roundUpCost: GetComputableTypeWithDefault<T["roundUpCost"], true>;
|
|
|
|
}
|
|
|
|
>;
|
|
|
|
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* A type that matches any {@link conversion} object.
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
export type GenericConversion = Replace<
|
|
|
|
Conversion<ConversionOptions>,
|
|
|
|
{
|
|
|
|
currentGain: ProcessedComputable<DecimalSource>;
|
2022-04-10 22:05:49 +00:00
|
|
|
actualGain: ProcessedComputable<DecimalSource>;
|
|
|
|
currentAt: ProcessedComputable<DecimalSource>;
|
2022-01-14 04:25:47 +00:00
|
|
|
nextAt: ProcessedComputable<DecimalSource>;
|
|
|
|
buyMax: ProcessedComputable<boolean>;
|
|
|
|
roundUpCost: ProcessedComputable<boolean>;
|
|
|
|
}
|
|
|
|
>;
|
|
|
|
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* Lazily creates a conversion with the given options.
|
|
|
|
* You typically shouldn't use this function directly. Instead use one of the other conversion constructors, which will then call this.
|
|
|
|
* @param optionsFunc Conversion options.
|
|
|
|
* @see {@link createCumulativeConversion}.
|
|
|
|
* @see {@link createIndependentConversion}.
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
export function createConversion<T extends ConversionOptions>(
|
2022-05-24 04:34:59 +00:00
|
|
|
optionsFunc: OptionsFunc<T, BaseConversion>
|
2022-01-14 04:25:47 +00:00
|
|
|
): Conversion<T> {
|
2022-02-27 19:49:34 +00:00
|
|
|
return createLazyProxy(() => {
|
2022-04-11 00:04:56 +00:00
|
|
|
const conversion = optionsFunc();
|
2022-02-27 19:49:34 +00:00
|
|
|
|
|
|
|
if (conversion.currentGain == null) {
|
2022-03-30 02:26:01 +00:00
|
|
|
conversion.currentGain = computed(() => {
|
|
|
|
let gain = conversion.gainModifier
|
|
|
|
? conversion.gainModifier.apply(
|
|
|
|
conversion.scaling.currentGain(conversion as GenericConversion)
|
|
|
|
)
|
|
|
|
: conversion.scaling.currentGain(conversion as GenericConversion);
|
|
|
|
gain = Decimal.floor(gain).max(0);
|
|
|
|
|
2022-04-11 02:22:00 +00:00
|
|
|
if (!unref(conversion.buyMax)) {
|
2022-03-30 02:26:01 +00:00
|
|
|
gain = gain.min(1);
|
|
|
|
}
|
|
|
|
return gain;
|
|
|
|
});
|
2022-02-27 19:49:34 +00:00
|
|
|
}
|
2022-04-10 22:05:49 +00:00
|
|
|
if (conversion.actualGain == null) {
|
|
|
|
conversion.actualGain = conversion.currentGain;
|
|
|
|
}
|
|
|
|
if (conversion.currentAt == null) {
|
|
|
|
conversion.currentAt = computed(() => {
|
|
|
|
let current = conversion.scaling.currentAt(conversion as GenericConversion);
|
|
|
|
if (conversion.roundUpCost) current = Decimal.ceil(current);
|
|
|
|
return current;
|
|
|
|
});
|
|
|
|
}
|
2022-02-27 19:49:34 +00:00
|
|
|
if (conversion.nextAt == null) {
|
2022-03-30 02:26:01 +00:00
|
|
|
conversion.nextAt = computed(() => {
|
|
|
|
let next = conversion.scaling.nextAt(conversion as GenericConversion);
|
|
|
|
if (conversion.roundUpCost) next = Decimal.ceil(next);
|
|
|
|
return next;
|
|
|
|
});
|
2022-02-27 19:49:34 +00:00
|
|
|
}
|
2022-01-14 04:25:47 +00:00
|
|
|
|
2022-02-27 19:49:34 +00:00
|
|
|
if (conversion.convert == null) {
|
|
|
|
conversion.convert = function () {
|
2022-05-24 04:40:26 +00:00
|
|
|
const amountGained = unref((conversion as GenericConversion).currentGain);
|
2022-02-27 19:49:34 +00:00
|
|
|
conversion.gainResource.value = Decimal.add(
|
|
|
|
conversion.gainResource.value,
|
2022-05-24 04:40:26 +00:00
|
|
|
amountGained
|
2022-02-27 19:49:34 +00:00
|
|
|
);
|
|
|
|
// TODO just subtract cost?
|
|
|
|
conversion.baseResource.value = 0;
|
2022-05-24 04:40:26 +00:00
|
|
|
conversion.onConvert?.(amountGained);
|
2022-02-27 19:49:34 +00:00
|
|
|
};
|
|
|
|
}
|
2022-01-14 04:25:47 +00:00
|
|
|
|
2022-02-27 19:49:34 +00:00
|
|
|
processComputable(conversion as T, "currentGain");
|
2022-04-10 22:05:49 +00:00
|
|
|
processComputable(conversion as T, "actualGain");
|
|
|
|
processComputable(conversion as T, "currentAt");
|
2022-02-27 19:49:34 +00:00
|
|
|
processComputable(conversion as T, "nextAt");
|
|
|
|
processComputable(conversion as T, "buyMax");
|
|
|
|
setDefault(conversion, "buyMax", true);
|
|
|
|
processComputable(conversion as T, "roundUpCost");
|
|
|
|
setDefault(conversion, "roundUpCost", true);
|
2022-01-14 04:25:47 +00:00
|
|
|
|
2022-02-27 19:49:34 +00:00
|
|
|
return conversion as unknown as Conversion<T>;
|
|
|
|
});
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* A collection of functions that allow a conversion to scale the amount of resources gained based on the input resource.
|
|
|
|
* This typically shouldn't be created directly. Instead use one of the scaling function constructors.
|
|
|
|
* @see {@link createLinearScaling}.
|
|
|
|
* @see {@link createPolynomialScaling}.
|
|
|
|
*/
|
|
|
|
export interface ScalingFunction {
|
|
|
|
/**
|
|
|
|
* Calculates the amount of the output resource a conversion should be able to currently produce.
|
|
|
|
* This should be based off of `conversion.baseResource.value`.
|
|
|
|
* The conversion is responsible for applying the gainModifier, so this function should be un-modified.
|
|
|
|
* It does not need to be clamped or rounded.
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
currentGain: (conversion: GenericConversion) => DecimalSource;
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* Calculates the amount of the input resource that is required for the current value of `conversion.currentGain`.
|
|
|
|
* Note that `conversion.currentGain` has been modified by `conversion.gainModifier`, so you will need to revert that as appropriate.
|
|
|
|
* The conversion is responsible for rounding up the amount as appropriate.
|
|
|
|
* The returned value should not be below 0.
|
|
|
|
*/
|
2022-04-10 22:05:49 +00:00
|
|
|
currentAt: (conversion: GenericConversion) => DecimalSource;
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* Calculates the amount of the input resource that would be required for the current value of `conversion.currentGain` to increase.
|
|
|
|
* Note that `conversion.currentGain` has been modified by `conversion.gainModifier`, so you will need to revert that as appropriate.
|
|
|
|
* The conversion is responsible for rounding up the amount as appropriate.
|
|
|
|
* The returned value should not be below 0.
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
nextAt: (conversion: GenericConversion) => DecimalSource;
|
2022-05-02 00:13:41 +00:00
|
|
|
}
|
2022-01-14 04:25:47 +00:00
|
|
|
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* Creates a scaling function based off the formula `(baseResource - base) * coefficient`.
|
|
|
|
* If the baseResource value is less than base then the currentGain will be 0.
|
|
|
|
* @param base The base variable in the scaling formula.
|
|
|
|
* @param coefficient The coefficient variable in the scaling formula.
|
|
|
|
* @example
|
|
|
|
* A scaling function created via `createLinearScaling(10, 0.5)` would produce the following values:
|
|
|
|
* | Base Resource | Current Gain |
|
|
|
|
* | ------------- | ------------ |
|
|
|
|
* | 10 | 1 |
|
|
|
|
* | 12 | 2 |
|
|
|
|
* | 20 | 6 |
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
export function createLinearScaling(
|
2022-05-02 00:23:16 +00:00
|
|
|
base: Computable<DecimalSource>,
|
|
|
|
coefficient: Computable<DecimalSource>
|
2022-01-14 04:25:47 +00:00
|
|
|
): ScalingFunction {
|
2022-05-02 00:23:16 +00:00
|
|
|
const processedBase = convertComputable(base);
|
|
|
|
const processedCoefficient = convertComputable(coefficient);
|
2022-01-14 04:25:47 +00:00
|
|
|
return {
|
|
|
|
currentGain(conversion) {
|
2022-05-24 04:06:21 +00:00
|
|
|
let baseAmount: DecimalSource = unref(conversion.baseResource.value);
|
|
|
|
if (conversion.costModifier) {
|
|
|
|
baseAmount = conversion.costModifier.apply(baseAmount);
|
|
|
|
}
|
|
|
|
if (Decimal.lt(baseAmount, unref(processedBase))) {
|
2022-02-27 19:49:34 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-05-24 04:06:21 +00:00
|
|
|
return Decimal.sub(baseAmount, unref(processedBase))
|
2022-02-27 19:49:34 +00:00
|
|
|
.sub(1)
|
2022-05-02 00:23:16 +00:00
|
|
|
.times(unref(processedCoefficient))
|
2022-03-30 02:26:01 +00:00
|
|
|
.add(1);
|
2022-01-14 04:25:47 +00:00
|
|
|
},
|
2022-04-10 22:05:49 +00:00
|
|
|
currentAt(conversion) {
|
|
|
|
let current: DecimalSource = unref(conversion.currentGain);
|
|
|
|
if (conversion.gainModifier) {
|
|
|
|
current = conversion.gainModifier.revert(current);
|
|
|
|
}
|
2022-05-24 04:06:21 +00:00
|
|
|
current = Decimal.max(0, current)
|
|
|
|
.sub(1)
|
2022-05-02 00:23:16 +00:00
|
|
|
.div(unref(processedCoefficient))
|
|
|
|
.add(unref(processedBase));
|
2022-05-24 04:06:21 +00:00
|
|
|
if (conversion.costModifier) {
|
|
|
|
current = conversion.costModifier.revert(current);
|
|
|
|
}
|
|
|
|
return current;
|
2022-04-10 22:05:49 +00:00
|
|
|
},
|
2022-01-14 04:25:47 +00:00
|
|
|
nextAt(conversion) {
|
2022-03-30 02:26:01 +00:00
|
|
|
let next: DecimalSource = Decimal.add(unref(conversion.currentGain), 1);
|
|
|
|
if (conversion.gainModifier) {
|
|
|
|
next = conversion.gainModifier.revert(next);
|
|
|
|
}
|
2022-05-24 04:06:21 +00:00
|
|
|
next = Decimal.max(0, next)
|
|
|
|
.sub(1)
|
2022-05-02 00:23:16 +00:00
|
|
|
.div(unref(processedCoefficient))
|
|
|
|
.add(unref(processedBase))
|
|
|
|
.max(unref(processedBase));
|
2022-05-24 04:06:21 +00:00
|
|
|
if (conversion.costModifier) {
|
|
|
|
next = conversion.costModifier.revert(next);
|
|
|
|
}
|
|
|
|
return next;
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* Creates a scaling function based off the formula `(baseResource / base) ^ exponent`.
|
|
|
|
* If the baseResource value is less than base then the currentGain will be 0.
|
|
|
|
* @param base The base variable in the scaling formula.
|
|
|
|
* @param exponent The exponent variable in the scaling formula.
|
|
|
|
* @example
|
|
|
|
* A scaling function created via `createLinearScaling(10, 0.5)` would produce the following values:
|
|
|
|
* | Base Resource | Current Gain |
|
|
|
|
* | ------------- | ------------ |
|
|
|
|
* | 10 | 1 |
|
|
|
|
* | 40 | 2 |
|
|
|
|
* | 250 | 5 |
|
|
|
|
*/
|
2022-03-06 04:49:27 +00:00
|
|
|
export function createPolynomialScaling(
|
2022-05-02 00:23:16 +00:00
|
|
|
base: Computable<DecimalSource>,
|
|
|
|
exponent: Computable<DecimalSource>
|
2022-01-14 04:25:47 +00:00
|
|
|
): ScalingFunction {
|
2022-05-02 00:23:16 +00:00
|
|
|
const processedBase = convertComputable(base);
|
|
|
|
const processedExponent = convertComputable(exponent);
|
2022-01-14 04:25:47 +00:00
|
|
|
return {
|
|
|
|
currentGain(conversion) {
|
2022-05-24 04:06:21 +00:00
|
|
|
let baseAmount: DecimalSource = unref(conversion.baseResource.value);
|
|
|
|
if (conversion.costModifier) {
|
|
|
|
baseAmount = conversion.costModifier.apply(baseAmount);
|
|
|
|
}
|
|
|
|
if (Decimal.lt(baseAmount, unref(processedBase))) {
|
2022-05-02 00:13:41 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-05-24 04:06:21 +00:00
|
|
|
const gain = Decimal.div(baseAmount, unref(processedBase)).pow(
|
2022-05-02 00:23:16 +00:00
|
|
|
unref(processedExponent)
|
2022-03-30 02:26:01 +00:00
|
|
|
);
|
2022-01-14 04:25:47 +00:00
|
|
|
|
2022-02-27 19:49:34 +00:00
|
|
|
if (gain.isNan()) {
|
|
|
|
return new Decimal(0);
|
|
|
|
}
|
2022-01-14 04:25:47 +00:00
|
|
|
return gain;
|
|
|
|
},
|
2022-04-10 22:05:49 +00:00
|
|
|
currentAt(conversion) {
|
|
|
|
let current: DecimalSource = unref(conversion.currentGain);
|
|
|
|
if (conversion.gainModifier) {
|
|
|
|
current = conversion.gainModifier.revert(current);
|
|
|
|
}
|
2022-05-24 04:06:21 +00:00
|
|
|
current = Decimal.max(0, current)
|
|
|
|
.root(unref(processedExponent))
|
|
|
|
.times(unref(processedBase));
|
|
|
|
if (conversion.costModifier) {
|
|
|
|
current = conversion.costModifier.revert(current);
|
|
|
|
}
|
|
|
|
return current;
|
2022-04-10 22:05:49 +00:00
|
|
|
},
|
2022-01-14 04:25:47 +00:00
|
|
|
nextAt(conversion) {
|
2022-03-30 02:26:01 +00:00
|
|
|
let next: DecimalSource = Decimal.add(unref(conversion.currentGain), 1);
|
|
|
|
if (conversion.gainModifier) {
|
|
|
|
next = conversion.gainModifier.revert(next);
|
|
|
|
}
|
2022-05-24 04:06:21 +00:00
|
|
|
next = Decimal.max(0, next)
|
|
|
|
.root(unref(processedExponent))
|
2022-05-02 00:23:16 +00:00
|
|
|
.times(unref(processedBase))
|
|
|
|
.max(unref(processedBase));
|
2022-05-24 04:06:21 +00:00
|
|
|
if (conversion.costModifier) {
|
|
|
|
next = conversion.costModifier.revert(next);
|
|
|
|
}
|
|
|
|
return next;
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* Creates a conversion that simply adds to the gainResource amount upon converting.
|
|
|
|
* This is similar to the behavior of "normal" layers in The Modding Tree.
|
|
|
|
* This is equivalent to just calling createConversion directly.
|
|
|
|
* @param optionsFunc Conversion options.
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
export function createCumulativeConversion<S extends ConversionOptions>(
|
2022-05-24 04:34:59 +00:00
|
|
|
optionsFunc: OptionsFunc<S, BaseConversion>
|
2022-01-14 04:25:47 +00:00
|
|
|
): Conversion<S> {
|
2022-02-27 19:49:34 +00:00
|
|
|
return createConversion(optionsFunc);
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* Creates a conversion that will replace the gainResource amount with the new amount upon converting.
|
|
|
|
* This is similar to the behavior of "static" layers in The Modding Tree.
|
|
|
|
* @param optionsFunc Converison options.
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
export function createIndependentConversion<S extends ConversionOptions>(
|
2022-05-24 04:34:59 +00:00
|
|
|
optionsFunc: OptionsFunc<S, BaseConversion>
|
2022-01-14 04:25:47 +00:00
|
|
|
): Conversion<S> {
|
2022-02-27 19:49:34 +00:00
|
|
|
return createConversion(() => {
|
|
|
|
const conversion: S = optionsFunc();
|
|
|
|
|
|
|
|
setDefault(conversion, "buyMax", false);
|
|
|
|
|
2022-04-11 03:29:32 +00:00
|
|
|
if (conversion.currentGain == null) {
|
|
|
|
conversion.currentGain = computed(() => {
|
|
|
|
let gain = conversion.gainModifier
|
|
|
|
? conversion.gainModifier.apply(
|
|
|
|
conversion.scaling.currentGain(conversion as GenericConversion)
|
|
|
|
)
|
|
|
|
: conversion.scaling.currentGain(conversion as GenericConversion);
|
|
|
|
gain = Decimal.floor(gain).max(conversion.gainResource.value);
|
|
|
|
|
|
|
|
if (!unref(conversion.buyMax)) {
|
|
|
|
gain = gain.min(Decimal.add(conversion.gainResource.value, 1));
|
|
|
|
}
|
|
|
|
return gain;
|
|
|
|
});
|
|
|
|
}
|
2022-04-10 22:05:49 +00:00
|
|
|
if (conversion.actualGain == null) {
|
2022-04-11 02:22:00 +00:00
|
|
|
conversion.actualGain = computed(() => {
|
|
|
|
let gain = Decimal.sub(
|
2022-02-27 19:49:34 +00:00
|
|
|
conversion.scaling.currentGain(conversion as GenericConversion),
|
|
|
|
conversion.gainResource.value
|
2022-04-11 02:22:00 +00:00
|
|
|
).max(0);
|
|
|
|
|
|
|
|
if (!unref(conversion.buyMax)) {
|
|
|
|
gain = gain.min(1);
|
|
|
|
}
|
|
|
|
return gain;
|
|
|
|
});
|
2022-02-27 19:49:34 +00:00
|
|
|
}
|
|
|
|
setDefault(conversion, "convert", function () {
|
2022-05-24 04:40:26 +00:00
|
|
|
const amountGained = unref((conversion as GenericConversion).actualGain);
|
2022-03-30 02:26:01 +00:00
|
|
|
conversion.gainResource.value = conversion.gainModifier
|
|
|
|
? conversion.gainModifier.apply(
|
|
|
|
unref((conversion as GenericConversion).currentGain)
|
|
|
|
)
|
2022-02-27 19:49:34 +00:00
|
|
|
: unref((conversion as GenericConversion).currentGain);
|
|
|
|
// TODO just subtract cost?
|
|
|
|
// Maybe by adding a cost function to scaling and nextAt just calls the cost function
|
|
|
|
// with 1 + currentGain
|
|
|
|
conversion.baseResource.value = 0;
|
2022-05-24 04:40:26 +00:00
|
|
|
conversion.onConvert?.(amountGained);
|
2022-02-27 19:49:34 +00:00
|
|
|
});
|
2022-01-14 04:25:47 +00:00
|
|
|
|
2022-02-27 19:49:34 +00:00
|
|
|
return conversion;
|
2022-01-14 04:25:47 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* This will automatically increase the value of conversion.gainResource without lowering the value of the input resource.
|
|
|
|
* It will by default perform 100% of a conversion's currentGain per second.
|
|
|
|
* If you use a ref for the rate you can set it's value to 0 when passive generation should be disabled.
|
2022-05-24 04:34:59 +00:00
|
|
|
* @param layer The layer this passive generation will be associated with. Typically `this` when calling this function from inside a layer's options function.
|
2022-05-02 00:13:41 +00:00
|
|
|
* @param conversion The conversion that will determine how much generation there is.
|
|
|
|
* @param rate A multiplier to multiply against the conversion's currentGain.
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
export function setupPassiveGeneration(
|
2022-05-24 04:34:59 +00:00
|
|
|
layer: BaseLayer,
|
2022-01-14 04:25:47 +00:00
|
|
|
conversion: GenericConversion,
|
2022-05-02 00:23:16 +00:00
|
|
|
rate: Computable<DecimalSource> = 1
|
2022-01-14 04:25:47 +00:00
|
|
|
): void {
|
2022-05-02 00:23:16 +00:00
|
|
|
const processedRate = convertComputable(rate);
|
2022-03-11 20:02:41 +00:00
|
|
|
layer.on("preUpdate", diff => {
|
2022-05-02 00:23:16 +00:00
|
|
|
const currRate = unref(processedRate);
|
2022-01-14 04:25:47 +00:00
|
|
|
if (Decimal.neq(currRate, 0)) {
|
|
|
|
conversion.gainResource.value = Decimal.add(
|
2022-02-27 19:49:34 +00:00
|
|
|
conversion.gainResource.value,
|
2022-01-14 04:25:47 +00:00
|
|
|
Decimal.times(currRate, diff).times(unref(conversion.currentGain))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2022-02-27 19:49:34 +00:00
|
|
|
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* Given a value, this function finds the amount above a certain value and raises it to a power.
|
|
|
|
* If the power is <1, this will effectively make the value scale slower after the cap.
|
|
|
|
* @param value The raw value.
|
|
|
|
* @param cap The value after which the softcap should be applied.
|
|
|
|
* @param power The power to raise value above the cap to.
|
|
|
|
* @example
|
|
|
|
* A softcap added via `addSoftcap(scaling, 100, 0.5)` would produce the following values:
|
|
|
|
* | Raw Value | Softcapped Value |
|
|
|
|
* | --------- | ---------------- |
|
|
|
|
* | 1 | 1 |
|
|
|
|
* | 100 | 100 |
|
|
|
|
* | 125 | 105 |
|
|
|
|
* | 200 | 110 |
|
|
|
|
*/
|
|
|
|
export function softcap(
|
2022-02-27 19:49:34 +00:00
|
|
|
value: DecimalSource,
|
|
|
|
cap: DecimalSource,
|
|
|
|
power: DecimalSource = 0.5
|
|
|
|
): DecimalSource {
|
|
|
|
if (Decimal.lte(value, cap)) {
|
|
|
|
return value;
|
|
|
|
} else {
|
|
|
|
return Decimal.pow(value, power).times(Decimal.pow(cap, Decimal.sub(1, power)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* Creates a scaling function based off an existing scaling function, with a softcap applied to it.
|
|
|
|
* The softcap will take any value above a certain value and raise it to a power.
|
|
|
|
* If the power is <1, this will effectively make the value scale slower after the cap.
|
|
|
|
* @param scaling The raw scaling function.
|
|
|
|
* @param cap The value after which the softcap should be applied.
|
|
|
|
* @param power The power to raise value about the cap to.
|
|
|
|
* @see {@link softcap}.
|
|
|
|
*/
|
2022-02-27 19:49:34 +00:00
|
|
|
export function addSoftcap(
|
|
|
|
scaling: ScalingFunction,
|
|
|
|
cap: ProcessedComputable<DecimalSource>,
|
|
|
|
power: ProcessedComputable<DecimalSource> = 0.5
|
|
|
|
): ScalingFunction {
|
|
|
|
return {
|
|
|
|
...scaling,
|
|
|
|
currentGain: conversion =>
|
|
|
|
softcap(scaling.currentGain(conversion), unref(cap), unref(power))
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-05-02 00:13:41 +00:00
|
|
|
/**
|
|
|
|
* Creates a scaling function off an existing function, with a hardcap applied to it.
|
|
|
|
* The harcap will ensure that the currentGain will stop at a given cap.
|
|
|
|
* @param scaling The raw scaling function.
|
|
|
|
* @param cap The maximum value the scaling function can output.
|
|
|
|
*/
|
2022-02-27 19:49:34 +00:00
|
|
|
export function addHardcap(
|
|
|
|
scaling: ScalingFunction,
|
|
|
|
cap: ProcessedComputable<DecimalSource>
|
|
|
|
): ScalingFunction {
|
|
|
|
return {
|
|
|
|
...scaling,
|
|
|
|
currentGain: conversion => Decimal.min(scaling.currentGain(conversion), unref(cap))
|
|
|
|
};
|
|
|
|
}
|