2022-03-04 03:39:48 +00:00
|
|
|
import { GenericLayer } from "game/layers";
|
|
|
|
import Decimal, { DecimalSource } from "util/bignum";
|
2022-01-14 04:25:47 +00:00
|
|
|
import {
|
|
|
|
Computable,
|
2022-03-30 02:26:01 +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-01-14 04:25:47 +00:00
|
|
|
import { computed, isRef, 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
|
|
|
|
|
|
|
export interface ConversionOptions {
|
|
|
|
scaling: ScalingFunction;
|
|
|
|
currentGain?: Computable<DecimalSource>;
|
2022-04-10 22:05:49 +00:00
|
|
|
actualGain?: Computable<DecimalSource>;
|
|
|
|
currentAt?: Computable<DecimalSource>;
|
2022-01-14 04:25:47 +00:00
|
|
|
nextAt?: Computable<DecimalSource>;
|
|
|
|
baseResource: Resource;
|
|
|
|
gainResource: Resource;
|
|
|
|
buyMax?: Computable<boolean>;
|
|
|
|
roundUpCost?: Computable<boolean>;
|
|
|
|
convert?: VoidFunction;
|
2022-03-30 02:26:01 +00:00
|
|
|
gainModifier?: GainModifier;
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
|
2022-03-09 01:40:51 +00:00
|
|
|
export interface BaseConversion {
|
2022-01-14 04:25:47 +00:00
|
|
|
convert: VoidFunction;
|
|
|
|
}
|
|
|
|
|
|
|
|
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>;
|
|
|
|
}
|
|
|
|
>;
|
|
|
|
|
|
|
|
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-03-30 02:26:01 +00:00
|
|
|
export interface GainModifier {
|
|
|
|
apply: (gain: DecimalSource) => DecimalSource;
|
|
|
|
revert: (gain: DecimalSource) => DecimalSource;
|
|
|
|
}
|
|
|
|
|
2022-01-14 04:25:47 +00:00
|
|
|
export function createConversion<T extends ConversionOptions>(
|
2022-04-11 00:04:56 +00:00
|
|
|
optionsFunc: OptionsFunc<T, Conversion<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 () {
|
|
|
|
conversion.gainResource.value = Decimal.add(
|
|
|
|
conversion.gainResource.value,
|
2022-03-30 02:26:01 +00:00
|
|
|
unref((conversion as GenericConversion).currentGain)
|
2022-02-27 19:49:34 +00:00
|
|
|
);
|
|
|
|
// TODO just subtract cost?
|
|
|
|
conversion.baseResource.value = 0;
|
|
|
|
};
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
|
|
|
export type ScalingFunction = {
|
|
|
|
currentGain: (conversion: GenericConversion) => DecimalSource;
|
2022-04-10 22:05:49 +00:00
|
|
|
currentAt: (conversion: GenericConversion) => DecimalSource;
|
2022-01-14 04:25:47 +00:00
|
|
|
nextAt: (conversion: GenericConversion) => DecimalSource;
|
|
|
|
};
|
|
|
|
|
2022-02-27 19:49:34 +00:00
|
|
|
// Gain formula is (baseResource - base) * coefficient
|
|
|
|
// e.g. if base is 10 and coefficient is 0.5, 10 points makes 1 gain, 12 points is 2
|
2022-01-14 04:25:47 +00:00
|
|
|
export function createLinearScaling(
|
|
|
|
base: DecimalSource | Ref<DecimalSource>,
|
|
|
|
coefficient: DecimalSource | Ref<DecimalSource>
|
|
|
|
): ScalingFunction {
|
|
|
|
return {
|
|
|
|
currentGain(conversion) {
|
2022-02-27 19:49:34 +00:00
|
|
|
if (Decimal.lt(conversion.baseResource.value, unref(base))) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-03-30 02:26:01 +00:00
|
|
|
return Decimal.sub(conversion.baseResource.value, unref(base))
|
2022-02-27 19:49:34 +00:00
|
|
|
.sub(1)
|
|
|
|
.times(unref(coefficient))
|
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-04-11 03:28:39 +00:00
|
|
|
current = Decimal.max(0, current);
|
2022-04-10 22:05:49 +00:00
|
|
|
return Decimal.times(current, unref(coefficient)).add(unref(base));
|
|
|
|
},
|
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-04-11 03:28:39 +00:00
|
|
|
next = Decimal.max(0, next);
|
2022-03-30 02:26:01 +00:00
|
|
|
return Decimal.times(next, unref(coefficient)).add(unref(base)).max(unref(base));
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-02-27 19:49:34 +00:00
|
|
|
// Gain formula is (baseResource / base) ^ exponent
|
|
|
|
// e.g. if exponent is 0.5 and base is 10, then having 10 points makes gain 1, and 40 points is 2
|
2022-03-06 04:49:27 +00:00
|
|
|
export function createPolynomialScaling(
|
2022-01-14 04:25:47 +00:00
|
|
|
base: DecimalSource | Ref<DecimalSource>,
|
|
|
|
exponent: DecimalSource | Ref<DecimalSource>
|
|
|
|
): ScalingFunction {
|
|
|
|
return {
|
|
|
|
currentGain(conversion) {
|
2022-03-30 02:26:01 +00:00
|
|
|
const gain = Decimal.div(conversion.baseResource.value, unref(base)).pow(
|
|
|
|
unref(exponent)
|
|
|
|
);
|
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-04-11 03:28:39 +00:00
|
|
|
current = Decimal.max(0, current);
|
2022-04-10 22:05:49 +00:00
|
|
|
return Decimal.root(current, unref(exponent)).times(unref(base));
|
|
|
|
},
|
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-04-11 03:28:39 +00:00
|
|
|
next = Decimal.max(0, next);
|
2022-03-30 02:26:01 +00:00
|
|
|
return Decimal.root(next, unref(exponent)).times(unref(base)).max(unref(base));
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createCumulativeConversion<S extends ConversionOptions>(
|
2022-04-11 00:04:56 +00:00
|
|
|
optionsFunc: OptionsFunc<S, Conversion<S>>
|
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
|
|
|
}
|
|
|
|
|
|
|
|
export function createIndependentConversion<S extends ConversionOptions>(
|
2022-04-11 00:04:56 +00:00
|
|
|
optionsFunc: OptionsFunc<S, Conversion<S>>
|
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-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-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-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
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
export function setupPassiveGeneration(
|
|
|
|
layer: GenericLayer,
|
|
|
|
conversion: GenericConversion,
|
2022-02-27 19:49:34 +00:00
|
|
|
rate: ProcessedComputable<DecimalSource> = 1
|
2022-01-14 04:25:47 +00:00
|
|
|
): void {
|
2022-03-11 20:02:41 +00:00
|
|
|
layer.on("preUpdate", diff => {
|
2022-01-14 04:25:47 +00:00
|
|
|
const currRate = isRef(rate) ? rate.value : rate;
|
|
|
|
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
|
|
|
|
|
|
|
function softcap(
|
|
|
|
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)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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))
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export function addHardcap(
|
|
|
|
scaling: ScalingFunction,
|
|
|
|
cap: ProcessedComputable<DecimalSource>
|
|
|
|
): ScalingFunction {
|
|
|
|
return {
|
|
|
|
...scaling,
|
|
|
|
currentGain: conversion => Decimal.min(scaling.currentGain(conversion), unref(cap))
|
|
|
|
};
|
|
|
|
}
|
2022-03-30 02:26:01 +00:00
|
|
|
|
|
|
|
export function createAdditiveModifier(addend: Computable<DecimalSource>): GainModifier {
|
|
|
|
const processedAddend = convertComputable(addend);
|
|
|
|
return {
|
|
|
|
apply: gain => Decimal.add(gain, unref(processedAddend)),
|
|
|
|
revert: gain => Decimal.sub(gain, unref(processedAddend))
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createMultiplicativeModifier(multiplier: Computable<DecimalSource>): GainModifier {
|
|
|
|
const processedMultiplier = convertComputable(multiplier);
|
|
|
|
return {
|
|
|
|
apply: gain => Decimal.times(gain, unref(processedMultiplier)),
|
|
|
|
revert: gain => Decimal.div(gain, unref(processedMultiplier))
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createExponentialModifier(exponent: Computable<DecimalSource>): GainModifier {
|
|
|
|
const processedExponent = convertComputable(exponent);
|
|
|
|
return {
|
|
|
|
apply: gain => Decimal.pow(gain, unref(processedExponent)),
|
|
|
|
revert: gain => Decimal.root(gain, unref(processedExponent))
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createSequentialModifier(...modifiers: GainModifier[]): GainModifier {
|
|
|
|
return {
|
|
|
|
apply: gain => modifiers.reduce((gain, modifier) => modifier.apply(gain), gain),
|
|
|
|
revert: gain => modifiers.reduceRight((gain, modifier) => modifier.revert(gain), gain)
|
|
|
|
};
|
|
|
|
}
|