Merge remote-tracking branch 'template/main'
This commit is contained in:
commit
df26b9b756
33 changed files with 1372 additions and 1142 deletions
36
CHANGELOG.md
36
CHANGELOG.md
|
@ -6,6 +6,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.3.0] - 2022-04-10
|
||||||
|
### Added
|
||||||
|
- conversion.currentAt [#4](https://github.com/profectus-engine/Profectus/pull/4)
|
||||||
|
- OptionsFunc utility type, improving type inferencing in feature types
|
||||||
|
- minimumGain property to ResetButton, defaulting to 1
|
||||||
|
### Changed
|
||||||
|
- **BREAKING** Major persistence rework
|
||||||
|
- Removed makePersistent
|
||||||
|
- Removed old Persistent, and renamed PersistentRef to Persistent
|
||||||
|
- createLazyProxy now takes optional base object (replacing use cases for makePersistent)
|
||||||
|
- Added warnings when creating refs outside a layer
|
||||||
|
- Added warnings when persistent refs aren't included in their layer object
|
||||||
|
- **BREAKING** createLayer now takes id as the first param, rather than inside the option function
|
||||||
|
- resetButton now shows "Req:" instead of "Next:" when conversion.buyMax is false
|
||||||
|
- Conversion nextAt and currentAt now cap at 0 after reverting modifier
|
||||||
|
### Fixed
|
||||||
|
- Independent conversion gain calculation [#4](https://github.com/profectus-engine/Profectus/pull/4)
|
||||||
|
- Persistence issue when loading layer dynamically
|
||||||
|
- resetButton's gain and requirement display being incorrect when conversion.buyMax is false
|
||||||
|
- Independent conversions with buyMax false capping incorrectly
|
||||||
|
|
||||||
|
## [0.2.2] - 2022-04-01
|
||||||
|
Unironically posting an update on April Fool's Day ;)
|
||||||
|
### Changed
|
||||||
|
- **BREAKING** Replaced tsparticles with pixi-emitter. Different options, and behaves differently.
|
||||||
|
- Print key and value in lazy proxy's setter message
|
||||||
|
- Update bounding boxes after web fonts load in
|
||||||
|
### Removed
|
||||||
|
- safff.txt
|
||||||
|
|
||||||
|
## [0.2.1] - 2022-03-29
|
||||||
|
### Changed
|
||||||
|
- **BREAKING** Reworked conversion.modifyGainAmount into conversion.gainModifier, with several utility functions. This makes nextAt accurate with modified gain
|
||||||
|
### Fixed
|
||||||
|
- Made overlay nav not overlap leftmost layer
|
||||||
|
|
||||||
## [0.2.0] - 2022-03-27
|
## [0.2.0] - 2022-03-27
|
||||||
### Added
|
### Added
|
||||||
- Particles feature
|
- Particles feature
|
||||||
|
|
1550
package-lock.json
generated
1550
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "profectus",
|
"name": "profectus",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vue-cli-service serve",
|
"start": "vue-cli-service serve",
|
||||||
|
@ -9,11 +9,11 @@
|
||||||
"lint": "vue-cli-service lint"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@pixi/particle-emitter": "^5.0.4",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"nanoevents": "^6.0.2",
|
"nanoevents": "^6.0.2",
|
||||||
"particles.vue3": "^2.0.3",
|
"pixi.js": "^6.3.0",
|
||||||
"tsparticles": "^2.0.3",
|
|
||||||
"vue": "^3.2.26",
|
"vue": "^3.2.26",
|
||||||
"vue-next-select": "^2.10.2",
|
"vue-next-select": "^2.10.2",
|
||||||
"vue-panzoom": "^1.1.6",
|
"vue-panzoom": "^1.1.6",
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
eyJpZCI6InRtdC14LTEwNSIsIm5hbWUiOiJEZWZhdWx0IFNhZmZmZiAtIHNvbWV0aGluZyBlbHNlIiwidGFicyI6WyJtYWluIiwiYyJdLCJ0aW1lIjoxNjI0MjQ1MjYxMDg3LCJhdXRvc2F2ZSI6dHJ1ZSwib2ZmbGluZVByb2QiOnRydWUsInRpbWVQbGF5ZWQiOiIzNDQ4LjYxNTc4MTcwOTAxIiwia2VlcEdvaW5nIjpmYWxzZSwibGFzdFRlblRpY2tzIjpbMC4wNTEsMC4wNSwwLjA0OSwwLjA1LDAuMDUsMC4wNTEsMC4wNDksMC4wNSwwLjA1LDAuMDUxXSwic2hvd1RQUyI6dHJ1ZSwibXNEaXNwbGF5IjoiYWxsIiwiaGlkZUNoYWxsZW5nZXMiOmZhbHNlLCJ0aGVtZSI6InBhcGVyIiwic3VidGFicyI6e30sIm1pbmltaXplZCI6e30sIm1vZElEIjoidG10LXgiLCJtb2RWZXJzaW9uIjoiMC4wIiwicG9pbnRzIjoiMzMwMC4zNzc3NzM4NTkwNTUiLCJtYWluIjp7InVwZ3JhZGVzIjpbXSwiYWNoaWV2ZW1lbnRzIjpbXSwibWlsZXN0b25lcyI6W10sImluZm9ib3hlcyI6e319LCJmIjp7InVwZ3JhZGVzIjpbXSwiYWNoaWV2ZW1lbnRzIjpbXSwibWlsZXN0b25lcyI6W10sImluZm9ib3hlcyI6e30sImNsaWNrYWJsZXMiOnsiMTEiOiJTdGFydCJ9LCJ1bmxvY2tlZCI6ZmFsc2UsInBvaW50cyI6IjAiLCJib29wIjpmYWxzZX0sImMiOnsidXBncmFkZXMiOlsiMTEiXSwiYWNoaWV2ZW1lbnRzIjpbXSwibWlsZXN0b25lcyI6W10sImluZm9ib3hlcyI6e30sImJ1eWFibGVzIjp7IjExIjoiMCJ9LCJjaGFsbGVuZ2VzIjp7IjExIjoiMCJ9LCJ1bmxvY2tlZCI6dHJ1ZSwicG9pbnRzIjoiMCIsImJlc3QiOiIxIiwidG90YWwiOiIwIiwiYmVlcCI6ZmFsc2UsInRoaW5neSI6InBvaW50eSIsIm90aGVyVGhpbmd5IjoxMCwic3BlbnRPbkJ1eWFibGVzIjoiMCJ9LCJhIjp7InVwZ3JhZGVzIjpbXSwiYWNoaWV2ZW1lbnRzIjpbIjExIl0sIm1pbGVzdG9uZXMiOltdLCJpbmZvYm94ZXMiOnt9LCJ1bmxvY2tlZCI6dHJ1ZSwicG9pbnRzIjoiMCJ9LCJnIjp7InVwZ3JhZGVzIjpbXSwiYWNoaWV2ZW1lbnRzIjpbXSwibWlsZXN0b25lcyI6W10sImluZm9ib3hlcyI6e319LCJoIjp7InVwZ3JhZGVzIjpbXSwiYWNoaWV2ZW1lbnRzIjpbXSwibWlsZXN0b25lcyI6W10sImluZm9ib3hlcyI6e319LCJzcG9vayI6eyJ1cGdyYWRlcyI6W10sImFjaGlldmVtZW50cyI6W10sIm1pbGVzdG9uZXMiOltdLCJpbmZvYm94ZXMiOnt9fSwib29tcHNNYWciOjAsImxhc3RQb2ludHMiOiIzMzAwLjM3Nzc3Mzg1OTA1NSJ9
|
|
|
@ -24,7 +24,7 @@
|
||||||
import projInfo from "data/projInfo.json";
|
import projInfo from "data/projInfo.json";
|
||||||
import { CoercableComponent, StyleValue } from "features/feature";
|
import { CoercableComponent, StyleValue } from "features/feature";
|
||||||
import { FeatureNode } from "game/layers";
|
import { FeatureNode } from "game/layers";
|
||||||
import { PersistentRef } from "game/persistence";
|
import { Persistent } from "game/persistence";
|
||||||
import player from "game/player";
|
import player from "game/player";
|
||||||
import { computeComponent, processedPropType, wrapRef } from "util/vue";
|
import { computeComponent, processedPropType, wrapRef } from "util/vue";
|
||||||
import { computed, defineComponent, nextTick, PropType, Ref, ref, toRefs, unref, watch } from "vue";
|
import { computed, defineComponent, nextTick, PropType, Ref, ref, toRefs, unref, watch } from "vue";
|
||||||
|
@ -46,7 +46,7 @@ export default defineComponent({
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
minimized: {
|
minimized: {
|
||||||
type: Object as PropType<PersistentRef<boolean>>,
|
type: Object as PropType<Persistent<boolean>>,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
minWidth: {
|
minWidth: {
|
||||||
|
|
|
@ -29,6 +29,7 @@ function updateTop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTick(updateTop);
|
nextTick(updateTop);
|
||||||
|
document.fonts.ready.then(updateTop);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const el = element.value?.parentElement;
|
const el = element.value?.parentElement;
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
GenericClickable
|
GenericClickable
|
||||||
} from "features/clickables/clickable";
|
} from "features/clickables/clickable";
|
||||||
import { GenericConversion } from "features/conversion";
|
import { GenericConversion } from "features/conversion";
|
||||||
import { CoercableComponent, jsx, Replace, setDefault } from "features/feature";
|
import { CoercableComponent, OptionsFunc, jsx, Replace, setDefault } from "features/feature";
|
||||||
import { displayResource } from "features/resources/resource";
|
import { displayResource } from "features/resources/resource";
|
||||||
import {
|
import {
|
||||||
createTreeNode,
|
createTreeNode,
|
||||||
|
@ -15,7 +15,7 @@ import {
|
||||||
TreeNodeOptions
|
TreeNodeOptions
|
||||||
} from "features/trees/tree";
|
} from "features/trees/tree";
|
||||||
import player from "game/player";
|
import player from "game/player";
|
||||||
import Decimal from "util/bignum";
|
import Decimal, { DecimalSource } from "util/bignum";
|
||||||
import {
|
import {
|
||||||
Computable,
|
Computable,
|
||||||
GetComputableType,
|
GetComputableType,
|
||||||
|
@ -33,6 +33,7 @@ export interface ResetButtonOptions extends ClickableOptions {
|
||||||
showNextAt?: Computable<boolean>;
|
showNextAt?: Computable<boolean>;
|
||||||
display?: Computable<CoercableComponent>;
|
display?: Computable<CoercableComponent>;
|
||||||
canClick?: Computable<boolean>;
|
canClick?: Computable<boolean>;
|
||||||
|
minimumGain?: Computable<DecimalSource>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ResetButton<T extends ResetButtonOptions> = Replace<
|
export type ResetButton<T extends ResetButtonOptions> = Replace<
|
||||||
|
@ -42,6 +43,7 @@ export type ResetButton<T extends ResetButtonOptions> = Replace<
|
||||||
showNextAt: GetComputableTypeWithDefault<T["showNextAt"], true>;
|
showNextAt: GetComputableTypeWithDefault<T["showNextAt"], true>;
|
||||||
display: GetComputableTypeWithDefault<T["display"], Ref<JSX.Element>>;
|
display: GetComputableTypeWithDefault<T["display"], Ref<JSX.Element>>;
|
||||||
canClick: GetComputableTypeWithDefault<T["canClick"], Ref<boolean>>;
|
canClick: GetComputableTypeWithDefault<T["canClick"], Ref<boolean>>;
|
||||||
|
minimumGain: GetComputableTypeWithDefault<T["minimumGain"], 1>;
|
||||||
onClick: VoidFunction;
|
onClick: VoidFunction;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
@ -53,17 +55,19 @@ export type GenericResetButton = Replace<
|
||||||
showNextAt: ProcessedComputable<boolean>;
|
showNextAt: ProcessedComputable<boolean>;
|
||||||
display: ProcessedComputable<CoercableComponent>;
|
display: ProcessedComputable<CoercableComponent>;
|
||||||
canClick: ProcessedComputable<boolean>;
|
canClick: ProcessedComputable<boolean>;
|
||||||
|
minimumGain: ProcessedComputable<DecimalSource>;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createResetButton<T extends ClickableOptions & ResetButtonOptions>(
|
export function createResetButton<T extends ClickableOptions & ResetButtonOptions>(
|
||||||
optionsFunc: () => T
|
optionsFunc: OptionsFunc<T>
|
||||||
): ResetButton<T> {
|
): ResetButton<T> {
|
||||||
return createClickable(() => {
|
return createClickable(() => {
|
||||||
const resetButton = optionsFunc();
|
const resetButton = optionsFunc();
|
||||||
|
|
||||||
processComputable(resetButton as T, "showNextAt");
|
processComputable(resetButton as T, "showNextAt");
|
||||||
setDefault(resetButton, "showNextAt", true);
|
setDefault(resetButton, "showNextAt", true);
|
||||||
|
setDefault(resetButton, "minimumGain", 1);
|
||||||
|
|
||||||
if (resetButton.resetDescription == null) {
|
if (resetButton.resetDescription == null) {
|
||||||
resetButton.resetDescription = computed(() =>
|
resetButton.resetDescription = computed(() =>
|
||||||
|
@ -80,16 +84,22 @@ export function createResetButton<T extends ClickableOptions & ResetButtonOption
|
||||||
<b>
|
<b>
|
||||||
{displayResource(
|
{displayResource(
|
||||||
resetButton.conversion.gainResource,
|
resetButton.conversion.gainResource,
|
||||||
unref(resetButton.conversion.currentGain)
|
Decimal.max(
|
||||||
|
unref(resetButton.conversion.actualGain),
|
||||||
|
unref(resetButton.minimumGain as ProcessedComputable<DecimalSource>)
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</b>{" "}
|
</b>{" "}
|
||||||
{resetButton.conversion.gainResource.displayName}
|
{resetButton.conversion.gainResource.displayName}
|
||||||
<div v-show={unref(resetButton.showNextAt)}>
|
<div v-show={unref(resetButton.showNextAt)}>
|
||||||
<br />
|
<br />
|
||||||
Next:{" "}
|
{resetButton.conversion.buyMax ? "Next:" : "Req:"}{" "}
|
||||||
{displayResource(
|
{displayResource(
|
||||||
resetButton.conversion.baseResource,
|
resetButton.conversion.baseResource,
|
||||||
unref(resetButton.conversion.nextAt)
|
resetButton.conversion.buyMax ||
|
||||||
|
Decimal.floor(unref(resetButton.conversion.actualGain)).neq(1)
|
||||||
|
? unref(resetButton.conversion.nextAt)
|
||||||
|
: unref(resetButton.conversion.currentAt)
|
||||||
)}{" "}
|
)}{" "}
|
||||||
{resetButton.conversion.baseResource.displayName}
|
{resetButton.conversion.baseResource.displayName}
|
||||||
</div>
|
</div>
|
||||||
|
@ -99,7 +109,10 @@ export function createResetButton<T extends ClickableOptions & ResetButtonOption
|
||||||
|
|
||||||
if (resetButton.canClick == null) {
|
if (resetButton.canClick == null) {
|
||||||
resetButton.canClick = computed(() =>
|
resetButton.canClick = computed(() =>
|
||||||
Decimal.gt(unref(resetButton.conversion.currentGain), 0)
|
Decimal.gte(
|
||||||
|
unref(resetButton.conversion.actualGain),
|
||||||
|
unref(resetButton.minimumGain as ProcessedComputable<DecimalSource>)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +152,7 @@ export type GenericLayerTreeNode = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createLayerTreeNode<T extends LayerTreeNodeOptions>(
|
export function createLayerTreeNode<T extends LayerTreeNodeOptions>(
|
||||||
optionsFunc: () => T
|
optionsFunc: OptionsFunc<T>
|
||||||
): LayerTreeNode<T> {
|
): LayerTreeNode<T> {
|
||||||
return createTreeNode(() => {
|
return createTreeNode(() => {
|
||||||
const options = optionsFunc();
|
const options = optionsFunc();
|
||||||
|
|
|
@ -13,8 +13,8 @@ import { DecimalSource } from "util/bignum";
|
||||||
import { render } from "util/vue";
|
import { render } from "util/vue";
|
||||||
import { createLayerTreeNode, createResetButton } from "../common";
|
import { createLayerTreeNode, createResetButton } from "../common";
|
||||||
|
|
||||||
const layer = createLayer(() => {
|
const id = "p";
|
||||||
const id = "p";
|
const layer = createLayer(id, () => {
|
||||||
const name = "Prestige";
|
const name = "Prestige";
|
||||||
const color = "#4BDC13";
|
const color = "#4BDC13";
|
||||||
const points = createResource<DecimalSource>(0, "prestige points");
|
const points = createResource<DecimalSource>(0, "prestige points");
|
||||||
|
@ -43,7 +43,6 @@ const layer = createLayer(() => {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
|
||||||
name,
|
name,
|
||||||
color,
|
color,
|
||||||
points,
|
points,
|
||||||
|
|
|
@ -16,7 +16,7 @@ import f from "./layers/aca/f";
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
export const main = createLayer(() => {
|
export const main = createLayer("main", () => {
|
||||||
const points = createResource<DecimalSource>(10);
|
const points = createResource<DecimalSource>(10);
|
||||||
const best = trackBest(points);
|
const best = trackBest(points);
|
||||||
const total = trackTotal(points);
|
const total = trackTotal(points);
|
||||||
|
@ -62,7 +62,6 @@ export const main = createLayer(() => {
|
||||||
// but I'd recommend it over trying to remember what does and doesn't need to be included.
|
// but I'd recommend it over trying to remember what does and doesn't need to be included.
|
||||||
// Officially all you need are anything with persistency or that you want to access elsewhere
|
// Officially all you need are anything with persistency or that you want to access elsewhere
|
||||||
return {
|
return {
|
||||||
id: "main",
|
|
||||||
name: "Tree",
|
name: "Tree",
|
||||||
display: jsx(() => (
|
display: jsx(() => (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -2,6 +2,7 @@ import AchievementComponent from "features/achievements/Achievement.vue";
|
||||||
import {
|
import {
|
||||||
CoercableComponent,
|
CoercableComponent,
|
||||||
Component,
|
Component,
|
||||||
|
OptionsFunc,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
Replace,
|
Replace,
|
||||||
|
@ -10,7 +11,7 @@ import {
|
||||||
Visibility
|
Visibility
|
||||||
} from "features/feature";
|
} from "features/feature";
|
||||||
import "game/notifications";
|
import "game/notifications";
|
||||||
import { Persistent, makePersistent, PersistentState } from "game/persistence";
|
import { Persistent, PersistentState, persistent } from "game/persistence";
|
||||||
import {
|
import {
|
||||||
Computable,
|
Computable,
|
||||||
GetComputableType,
|
GetComputableType,
|
||||||
|
@ -67,11 +68,10 @@ export type GenericAchievement = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createAchievement<T extends AchievementOptions>(
|
export function createAchievement<T extends AchievementOptions>(
|
||||||
optionsFunc: () => T & ThisType<Achievement<T>>
|
optionsFunc: OptionsFunc<T, Achievement<T>, BaseAchievement>
|
||||||
): Achievement<T> {
|
): Achievement<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(persistent => {
|
||||||
const achievement: T & Partial<BaseAchievement> = optionsFunc();
|
const achievement = Object.assign(persistent, optionsFunc());
|
||||||
makePersistent<boolean>(achievement, false);
|
|
||||||
achievement.id = getUniqueID("achievement-");
|
achievement.id = getUniqueID("achievement-");
|
||||||
achievement.type = AchievementType;
|
achievement.type = AchievementType;
|
||||||
achievement[Component] = AchievementComponent;
|
achievement[Component] = AchievementComponent;
|
||||||
|
@ -122,5 +122,5 @@ export function createAchievement<T extends AchievementOptions>(
|
||||||
}
|
}
|
||||||
|
|
||||||
return achievement as unknown as Achievement<T>;
|
return achievement as unknown as Achievement<T>;
|
||||||
});
|
}, persistent<boolean>(false));
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import BarComponent from "features/bars/Bar.vue";
|
||||||
import {
|
import {
|
||||||
CoercableComponent,
|
CoercableComponent,
|
||||||
Component,
|
Component,
|
||||||
|
OptionsFunc,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
Replace,
|
Replace,
|
||||||
|
@ -79,9 +80,11 @@ export type GenericBar = Replace<
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createBar<T extends BarOptions>(optionsFunc: () => T & ThisType<Bar<T>>): Bar<T> {
|
export function createBar<T extends BarOptions>(
|
||||||
|
optionsFunc: OptionsFunc<T, Bar<T>, BaseBar>
|
||||||
|
): Bar<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const bar: T & Partial<BaseBar> = optionsFunc();
|
const bar = optionsFunc();
|
||||||
bar.id = getUniqueID("bar-");
|
bar.id = getUniqueID("bar-");
|
||||||
bar.type = BarType;
|
bar.type = BarType;
|
||||||
bar[Component] = BarComponent;
|
bar[Component] = BarComponent;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import BoardComponent from "features/boards/Board.vue";
|
import BoardComponent from "features/boards/Board.vue";
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
|
OptionsFunc,
|
||||||
findFeatures,
|
findFeatures,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
|
@ -10,7 +11,7 @@ import {
|
||||||
Visibility
|
Visibility
|
||||||
} from "features/feature";
|
} from "features/feature";
|
||||||
import { globalBus } from "game/events";
|
import { globalBus } from "game/events";
|
||||||
import { State, Persistent, makePersistent, PersistentState } from "game/persistence";
|
import { State, Persistent, PersistentState, persistent } from "game/persistence";
|
||||||
import { isFunction } from "util/common";
|
import { isFunction } from "util/common";
|
||||||
import {
|
import {
|
||||||
Computable,
|
Computable,
|
||||||
|
@ -197,137 +198,142 @@ export type GenericBoard = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createBoard<T extends BoardOptions>(
|
export function createBoard<T extends BoardOptions>(
|
||||||
optionsFunc: () => T & ThisType<Board<T>>
|
optionsFunc: OptionsFunc<T, Board<T>, BaseBoard>
|
||||||
): Board<T> {
|
): Board<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(
|
||||||
const board: T & Partial<BaseBoard> = optionsFunc();
|
persistent => {
|
||||||
makePersistent<BoardData>(board, {
|
const board = Object.assign(persistent, optionsFunc());
|
||||||
|
board.id = getUniqueID("board-");
|
||||||
|
board.type = BoardType;
|
||||||
|
board[Component] = BoardComponent;
|
||||||
|
|
||||||
|
board.nodes = computed(() => processedBoard[PersistentState].value.nodes);
|
||||||
|
board.selectedNode = computed(
|
||||||
|
() =>
|
||||||
|
processedBoard.nodes.value.find(
|
||||||
|
node => node.id === board[PersistentState].value.selectedNode
|
||||||
|
) || null
|
||||||
|
);
|
||||||
|
board.selectedAction = computed(() => {
|
||||||
|
const selectedNode = processedBoard.selectedNode.value;
|
||||||
|
if (selectedNode == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const type = processedBoard.types[selectedNode.type];
|
||||||
|
if (type.actions == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
type.actions.find(
|
||||||
|
action => action.id === processedBoard[PersistentState].value.selectedAction
|
||||||
|
) || null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
board.links = computed(() => {
|
||||||
|
if (processedBoard.selectedAction.value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
processedBoard.selectedAction.value.links &&
|
||||||
|
processedBoard.selectedNode.value
|
||||||
|
) {
|
||||||
|
return getNodeProperty(
|
||||||
|
processedBoard.selectedAction.value.links,
|
||||||
|
processedBoard.selectedNode.value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
processComputable(board as T, "visibility");
|
||||||
|
setDefault(board, "visibility", Visibility.Visible);
|
||||||
|
processComputable(board as T, "width");
|
||||||
|
setDefault(board, "width", "100%");
|
||||||
|
processComputable(board as T, "height");
|
||||||
|
setDefault(board, "height", "400px");
|
||||||
|
processComputable(board as T, "classes");
|
||||||
|
processComputable(board as T, "style");
|
||||||
|
|
||||||
|
for (const type in board.types) {
|
||||||
|
const nodeType: NodeTypeOptions & Partial<BaseNodeType> = board.types[type];
|
||||||
|
|
||||||
|
processComputable(nodeType as NodeTypeOptions, "title");
|
||||||
|
processComputable(nodeType as NodeTypeOptions, "label");
|
||||||
|
processComputable(nodeType as NodeTypeOptions, "size");
|
||||||
|
setDefault(nodeType, "size", 50);
|
||||||
|
processComputable(nodeType as NodeTypeOptions, "draggable");
|
||||||
|
setDefault(nodeType, "draggable", false);
|
||||||
|
processComputable(nodeType as NodeTypeOptions, "shape");
|
||||||
|
setDefault(nodeType, "shape", Shape.Circle);
|
||||||
|
processComputable(nodeType as NodeTypeOptions, "canAccept");
|
||||||
|
setDefault(nodeType, "canAccept", false);
|
||||||
|
processComputable(nodeType as NodeTypeOptions, "progress");
|
||||||
|
processComputable(nodeType as NodeTypeOptions, "progressDisplay");
|
||||||
|
setDefault(nodeType, "progressDisplay", ProgressDisplay.Fill);
|
||||||
|
processComputable(nodeType as NodeTypeOptions, "progressColor");
|
||||||
|
setDefault(nodeType, "progressColor", "none");
|
||||||
|
processComputable(nodeType as NodeTypeOptions, "fillColor");
|
||||||
|
processComputable(nodeType as NodeTypeOptions, "outlineColor");
|
||||||
|
processComputable(nodeType as NodeTypeOptions, "titleColor");
|
||||||
|
processComputable(nodeType as NodeTypeOptions, "actionDistance");
|
||||||
|
setDefault(nodeType, "actionDistance", Math.PI / 6);
|
||||||
|
nodeType.nodes = computed(() =>
|
||||||
|
board[PersistentState].value.nodes.filter(node => node.type === type)
|
||||||
|
);
|
||||||
|
setDefault(nodeType, "onClick", function (node: BoardNode) {
|
||||||
|
board[PersistentState].value.selectedNode = node.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (nodeType.actions) {
|
||||||
|
for (const action of nodeType.actions) {
|
||||||
|
processComputable(action, "visibility");
|
||||||
|
setDefault(action, "visibility", Visibility.Visible);
|
||||||
|
processComputable(action, "icon");
|
||||||
|
processComputable(action, "fillColor");
|
||||||
|
processComputable(action, "tooltip");
|
||||||
|
processComputable(action, "links");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
board[GatherProps] = function (this: GenericBoard) {
|
||||||
|
const {
|
||||||
|
nodes,
|
||||||
|
types,
|
||||||
|
[PersistentState]: state,
|
||||||
|
visibility,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
style,
|
||||||
|
classes,
|
||||||
|
links,
|
||||||
|
selectedAction,
|
||||||
|
selectedNode
|
||||||
|
} = this;
|
||||||
|
return {
|
||||||
|
nodes,
|
||||||
|
types,
|
||||||
|
[PersistentState]: state,
|
||||||
|
visibility,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
style: unref(style),
|
||||||
|
classes,
|
||||||
|
links,
|
||||||
|
selectedAction,
|
||||||
|
selectedNode
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is necessary because board.types is different from T and Board
|
||||||
|
const processedBoard = board as unknown as Board<T>;
|
||||||
|
return processedBoard;
|
||||||
|
},
|
||||||
|
persistent<BoardData>({
|
||||||
nodes: [],
|
nodes: [],
|
||||||
selectedNode: null,
|
selectedNode: null,
|
||||||
selectedAction: null
|
selectedAction: null
|
||||||
});
|
})
|
||||||
board.id = getUniqueID("board-");
|
);
|
||||||
board.type = BoardType;
|
|
||||||
board[Component] = BoardComponent;
|
|
||||||
|
|
||||||
board.nodes = computed(() => processedBoard[PersistentState].value.nodes);
|
|
||||||
board.selectedNode = computed(
|
|
||||||
() =>
|
|
||||||
processedBoard.nodes.value.find(
|
|
||||||
node => node.id === board[PersistentState].value.selectedNode
|
|
||||||
) || null
|
|
||||||
);
|
|
||||||
board.selectedAction = computed(() => {
|
|
||||||
const selectedNode = processedBoard.selectedNode.value;
|
|
||||||
if (selectedNode == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const type = processedBoard.types[selectedNode.type];
|
|
||||||
if (type.actions == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
type.actions.find(
|
|
||||||
action => action.id === processedBoard[PersistentState].value.selectedAction
|
|
||||||
) || null
|
|
||||||
);
|
|
||||||
});
|
|
||||||
board.links = computed(() => {
|
|
||||||
if (processedBoard.selectedAction.value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (processedBoard.selectedAction.value.links && processedBoard.selectedNode.value) {
|
|
||||||
return getNodeProperty(
|
|
||||||
processedBoard.selectedAction.value.links,
|
|
||||||
processedBoard.selectedNode.value
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
processComputable(board as T, "visibility");
|
|
||||||
setDefault(board, "visibility", Visibility.Visible);
|
|
||||||
processComputable(board as T, "width");
|
|
||||||
setDefault(board, "width", "100%");
|
|
||||||
processComputable(board as T, "height");
|
|
||||||
setDefault(board, "height", "400px");
|
|
||||||
processComputable(board as T, "classes");
|
|
||||||
processComputable(board as T, "style");
|
|
||||||
|
|
||||||
for (const type in board.types) {
|
|
||||||
const nodeType: NodeTypeOptions & Partial<BaseNodeType> = board.types[type];
|
|
||||||
|
|
||||||
processComputable(nodeType as NodeTypeOptions, "title");
|
|
||||||
processComputable(nodeType as NodeTypeOptions, "label");
|
|
||||||
processComputable(nodeType as NodeTypeOptions, "size");
|
|
||||||
setDefault(nodeType, "size", 50);
|
|
||||||
processComputable(nodeType as NodeTypeOptions, "draggable");
|
|
||||||
setDefault(nodeType, "draggable", false);
|
|
||||||
processComputable(nodeType as NodeTypeOptions, "shape");
|
|
||||||
setDefault(nodeType, "shape", Shape.Circle);
|
|
||||||
processComputable(nodeType as NodeTypeOptions, "canAccept");
|
|
||||||
setDefault(nodeType, "canAccept", false);
|
|
||||||
processComputable(nodeType as NodeTypeOptions, "progress");
|
|
||||||
processComputable(nodeType as NodeTypeOptions, "progressDisplay");
|
|
||||||
setDefault(nodeType, "progressDisplay", ProgressDisplay.Fill);
|
|
||||||
processComputable(nodeType as NodeTypeOptions, "progressColor");
|
|
||||||
setDefault(nodeType, "progressColor", "none");
|
|
||||||
processComputable(nodeType as NodeTypeOptions, "fillColor");
|
|
||||||
processComputable(nodeType as NodeTypeOptions, "outlineColor");
|
|
||||||
processComputable(nodeType as NodeTypeOptions, "titleColor");
|
|
||||||
processComputable(nodeType as NodeTypeOptions, "actionDistance");
|
|
||||||
setDefault(nodeType, "actionDistance", Math.PI / 6);
|
|
||||||
nodeType.nodes = computed(() =>
|
|
||||||
board[PersistentState].value.nodes.filter(node => node.type === type)
|
|
||||||
);
|
|
||||||
setDefault(nodeType, "onClick", function (node: BoardNode) {
|
|
||||||
board[PersistentState].value.selectedNode = node.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (nodeType.actions) {
|
|
||||||
for (const action of nodeType.actions) {
|
|
||||||
processComputable(action, "visibility");
|
|
||||||
setDefault(action, "visibility", Visibility.Visible);
|
|
||||||
processComputable(action, "icon");
|
|
||||||
processComputable(action, "fillColor");
|
|
||||||
processComputable(action, "tooltip");
|
|
||||||
processComputable(action, "links");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
board[GatherProps] = function (this: GenericBoard) {
|
|
||||||
const {
|
|
||||||
nodes,
|
|
||||||
types,
|
|
||||||
[PersistentState]: state,
|
|
||||||
visibility,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
style,
|
|
||||||
classes,
|
|
||||||
links,
|
|
||||||
selectedAction,
|
|
||||||
selectedNode
|
|
||||||
} = this;
|
|
||||||
return {
|
|
||||||
nodes,
|
|
||||||
types,
|
|
||||||
[PersistentState]: state,
|
|
||||||
visibility,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
style: unref(style),
|
|
||||||
classes,
|
|
||||||
links,
|
|
||||||
selectedAction,
|
|
||||||
selectedNode
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// This is necessary because board.types is different from T and Board
|
|
||||||
const processedBoard = board as unknown as Board<T>;
|
|
||||||
return processedBoard;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNodeProperty<T>(property: NodeComputable<T>, node: BoardNode): T {
|
export function getNodeProperty<T>(property: NodeComputable<T>, node: BoardNode): T {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import ClickableComponent from "features/clickables/Clickable.vue";
|
import ClickableComponent from "features/clickables/Clickable.vue";
|
||||||
import { Resource } from "features/resources/resource";
|
import { Resource } from "features/resources/resource";
|
||||||
import { Persistent, makePersistent, PersistentState } from "game/persistence";
|
import { Persistent, PersistentState, persistent } from "game/persistence";
|
||||||
import Decimal, { DecimalSource, format, formatWhole } from "util/bignum";
|
import Decimal, { DecimalSource, format, formatWhole } from "util/bignum";
|
||||||
import {
|
import {
|
||||||
Computable,
|
Computable,
|
||||||
|
@ -15,6 +15,7 @@ import { computed, Ref, unref } from "vue";
|
||||||
import {
|
import {
|
||||||
CoercableComponent,
|
CoercableComponent,
|
||||||
Component,
|
Component,
|
||||||
|
OptionsFunc,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
jsx,
|
jsx,
|
||||||
|
@ -87,10 +88,10 @@ export type GenericBuyable = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createBuyable<T extends BuyableOptions>(
|
export function createBuyable<T extends BuyableOptions>(
|
||||||
optionsFunc: () => T & ThisType<Buyable<T>>
|
optionsFunc: OptionsFunc<T, Buyable<T>, BaseBuyable>
|
||||||
): Buyable<T> {
|
): Buyable<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(persistent => {
|
||||||
const buyable: T & Partial<BaseBuyable> = optionsFunc();
|
const buyable = Object.assign(persistent, optionsFunc());
|
||||||
|
|
||||||
if (buyable.canPurchase == null && (buyable.resource == null || buyable.cost == null)) {
|
if (buyable.canPurchase == null && (buyable.resource == null || buyable.cost == null)) {
|
||||||
console.warn(
|
console.warn(
|
||||||
|
@ -100,7 +101,6 @@ export function createBuyable<T extends BuyableOptions>(
|
||||||
throw "Cannot create buyable without a canPurchase property or a resource and cost property";
|
throw "Cannot create buyable without a canPurchase property or a resource and cost property";
|
||||||
}
|
}
|
||||||
|
|
||||||
makePersistent<DecimalSource>(buyable, 0);
|
|
||||||
buyable.id = getUniqueID("buyable-");
|
buyable.id = getUniqueID("buyable-");
|
||||||
buyable.type = BuyableType;
|
buyable.type = BuyableType;
|
||||||
buyable[Component] = ClickableComponent;
|
buyable[Component] = ClickableComponent;
|
||||||
|
@ -239,5 +239,5 @@ export function createBuyable<T extends BuyableOptions>(
|
||||||
};
|
};
|
||||||
|
|
||||||
return buyable as unknown as Buyable<T>;
|
return buyable as unknown as Buyable<T>;
|
||||||
});
|
}, persistent<DecimalSource>(0));
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import ChallengeComponent from "features/challenges/Challenge.vue";
|
||||||
import {
|
import {
|
||||||
CoercableComponent,
|
CoercableComponent,
|
||||||
Component,
|
Component,
|
||||||
|
OptionsFunc,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
jsx,
|
jsx,
|
||||||
|
@ -15,7 +16,7 @@ import {
|
||||||
import { GenericReset } from "features/reset";
|
import { GenericReset } from "features/reset";
|
||||||
import { Resource } from "features/resources/resource";
|
import { Resource } from "features/resources/resource";
|
||||||
import { globalBus } from "game/events";
|
import { globalBus } from "game/events";
|
||||||
import { persistent, PersistentRef } from "game/persistence";
|
import { Persistent, persistent } from "game/persistence";
|
||||||
import settings, { registerSettingField } from "game/settings";
|
import settings, { registerSettingField } from "game/settings";
|
||||||
import Decimal, { DecimalSource } from "util/bignum";
|
import Decimal, { DecimalSource } from "util/bignum";
|
||||||
import {
|
import {
|
||||||
|
@ -58,10 +59,10 @@ export interface ChallengeOptions {
|
||||||
|
|
||||||
export interface BaseChallenge {
|
export interface BaseChallenge {
|
||||||
id: string;
|
id: string;
|
||||||
completions: PersistentRef<DecimalSource>;
|
completions: Persistent<DecimalSource>;
|
||||||
completed: Ref<boolean>;
|
completed: Ref<boolean>;
|
||||||
maxed: Ref<boolean>;
|
maxed: Ref<boolean>;
|
||||||
active: PersistentRef<boolean>;
|
active: Persistent<boolean>;
|
||||||
toggle: VoidFunction;
|
toggle: VoidFunction;
|
||||||
complete: (remainInChallenge?: boolean) => void;
|
complete: (remainInChallenge?: boolean) => void;
|
||||||
type: typeof ChallengeType;
|
type: typeof ChallengeType;
|
||||||
|
@ -96,10 +97,12 @@ export type GenericChallenge = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createChallenge<T extends ChallengeOptions>(
|
export function createChallenge<T extends ChallengeOptions>(
|
||||||
optionsFunc: () => T & ThisType<Challenge<T>>
|
optionsFunc: OptionsFunc<T, Challenge<T>, BaseChallenge>
|
||||||
): Challenge<T> {
|
): Challenge<T> {
|
||||||
|
const completions = persistent(0);
|
||||||
|
const active = persistent(false);
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const challenge: T & Partial<BaseChallenge> = optionsFunc();
|
const challenge = optionsFunc();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
challenge.canComplete == null &&
|
challenge.canComplete == null &&
|
||||||
|
@ -116,8 +119,8 @@ export function createChallenge<T extends ChallengeOptions>(
|
||||||
challenge.type = ChallengeType;
|
challenge.type = ChallengeType;
|
||||||
challenge[Component] = ChallengeComponent;
|
challenge[Component] = ChallengeComponent;
|
||||||
|
|
||||||
challenge.completions = persistent(0);
|
challenge.completions = completions;
|
||||||
challenge.active = persistent(false);
|
challenge.active = active;
|
||||||
challenge.completed = computed(() =>
|
challenge.completed = computed(() =>
|
||||||
Decimal.gt((challenge as GenericChallenge).completions.value, 0)
|
Decimal.gt((challenge as GenericChallenge).completions.value, 0)
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,6 +2,7 @@ import ClickableComponent from "features/clickables/Clickable.vue";
|
||||||
import {
|
import {
|
||||||
CoercableComponent,
|
CoercableComponent,
|
||||||
Component,
|
Component,
|
||||||
|
OptionsFunc,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
Replace,
|
Replace,
|
||||||
|
@ -69,10 +70,10 @@ export type GenericClickable = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createClickable<T extends ClickableOptions>(
|
export function createClickable<T extends ClickableOptions>(
|
||||||
optionsFunc: () => T & ThisType<Clickable<T>>
|
optionsFunc: OptionsFunc<T, Clickable<T>, BaseClickable>
|
||||||
): Clickable<T> {
|
): Clickable<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const clickable: T & Partial<BaseClickable> = optionsFunc();
|
const clickable = optionsFunc();
|
||||||
clickable.id = getUniqueID("clickable-");
|
clickable.id = getUniqueID("clickable-");
|
||||||
clickable.type = ClickableType;
|
clickable.type = ClickableType;
|
||||||
clickable[Component] = ClickableComponent;
|
clickable[Component] = ClickableComponent;
|
||||||
|
|
|
@ -4,19 +4,20 @@ import { isFunction } from "util/common";
|
||||||
import {
|
import {
|
||||||
Computable,
|
Computable,
|
||||||
convertComputable,
|
convertComputable,
|
||||||
DoNotCache,
|
|
||||||
GetComputableTypeWithDefault,
|
GetComputableTypeWithDefault,
|
||||||
processComputable,
|
processComputable,
|
||||||
ProcessedComputable
|
ProcessedComputable
|
||||||
} from "util/computed";
|
} from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import { computed, isRef, Ref, unref } from "vue";
|
import { computed, isRef, Ref, unref } from "vue";
|
||||||
import { Replace, setDefault } from "./feature";
|
import { OptionsFunc, Replace, setDefault } from "./feature";
|
||||||
import { Resource } from "./resources/resource";
|
import { Resource } from "./resources/resource";
|
||||||
|
|
||||||
export interface ConversionOptions {
|
export interface ConversionOptions {
|
||||||
scaling: ScalingFunction;
|
scaling: ScalingFunction;
|
||||||
currentGain?: Computable<DecimalSource>;
|
currentGain?: Computable<DecimalSource>;
|
||||||
|
actualGain?: Computable<DecimalSource>;
|
||||||
|
currentAt?: Computable<DecimalSource>;
|
||||||
nextAt?: Computable<DecimalSource>;
|
nextAt?: Computable<DecimalSource>;
|
||||||
baseResource: Resource;
|
baseResource: Resource;
|
||||||
gainResource: Resource;
|
gainResource: Resource;
|
||||||
|
@ -34,6 +35,8 @@ export type Conversion<T extends ConversionOptions> = Replace<
|
||||||
T & BaseConversion,
|
T & BaseConversion,
|
||||||
{
|
{
|
||||||
currentGain: GetComputableTypeWithDefault<T["currentGain"], Ref<DecimalSource>>;
|
currentGain: GetComputableTypeWithDefault<T["currentGain"], Ref<DecimalSource>>;
|
||||||
|
actualGain: GetComputableTypeWithDefault<T["actualGain"], Ref<DecimalSource>>;
|
||||||
|
currentAt: GetComputableTypeWithDefault<T["currentAt"], Ref<DecimalSource>>;
|
||||||
nextAt: GetComputableTypeWithDefault<T["nextAt"], Ref<DecimalSource>>;
|
nextAt: GetComputableTypeWithDefault<T["nextAt"], Ref<DecimalSource>>;
|
||||||
buyMax: GetComputableTypeWithDefault<T["buyMax"], true>;
|
buyMax: GetComputableTypeWithDefault<T["buyMax"], true>;
|
||||||
roundUpCost: GetComputableTypeWithDefault<T["roundUpCost"], true>;
|
roundUpCost: GetComputableTypeWithDefault<T["roundUpCost"], true>;
|
||||||
|
@ -44,6 +47,8 @@ export type GenericConversion = Replace<
|
||||||
Conversion<ConversionOptions>,
|
Conversion<ConversionOptions>,
|
||||||
{
|
{
|
||||||
currentGain: ProcessedComputable<DecimalSource>;
|
currentGain: ProcessedComputable<DecimalSource>;
|
||||||
|
actualGain: ProcessedComputable<DecimalSource>;
|
||||||
|
currentAt: ProcessedComputable<DecimalSource>;
|
||||||
nextAt: ProcessedComputable<DecimalSource>;
|
nextAt: ProcessedComputable<DecimalSource>;
|
||||||
buyMax: ProcessedComputable<boolean>;
|
buyMax: ProcessedComputable<boolean>;
|
||||||
roundUpCost: ProcessedComputable<boolean>;
|
roundUpCost: ProcessedComputable<boolean>;
|
||||||
|
@ -56,10 +61,10 @@ export interface GainModifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createConversion<T extends ConversionOptions>(
|
export function createConversion<T extends ConversionOptions>(
|
||||||
optionsFunc: () => T & ThisType<Conversion<T>>
|
optionsFunc: OptionsFunc<T, Conversion<T>, BaseConversion>
|
||||||
): Conversion<T> {
|
): Conversion<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const conversion: T = optionsFunc();
|
const conversion = optionsFunc();
|
||||||
|
|
||||||
if (conversion.currentGain == null) {
|
if (conversion.currentGain == null) {
|
||||||
conversion.currentGain = computed(() => {
|
conversion.currentGain = computed(() => {
|
||||||
|
@ -70,12 +75,22 @@ export function createConversion<T extends ConversionOptions>(
|
||||||
: conversion.scaling.currentGain(conversion as GenericConversion);
|
: conversion.scaling.currentGain(conversion as GenericConversion);
|
||||||
gain = Decimal.floor(gain).max(0);
|
gain = Decimal.floor(gain).max(0);
|
||||||
|
|
||||||
if (!conversion.buyMax) {
|
if (!unref(conversion.buyMax)) {
|
||||||
gain = gain.min(1);
|
gain = gain.min(1);
|
||||||
}
|
}
|
||||||
return gain;
|
return gain;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
if (conversion.nextAt == null) {
|
if (conversion.nextAt == null) {
|
||||||
conversion.nextAt = computed(() => {
|
conversion.nextAt = computed(() => {
|
||||||
let next = conversion.scaling.nextAt(conversion as GenericConversion);
|
let next = conversion.scaling.nextAt(conversion as GenericConversion);
|
||||||
|
@ -96,6 +111,8 @@ export function createConversion<T extends ConversionOptions>(
|
||||||
}
|
}
|
||||||
|
|
||||||
processComputable(conversion as T, "currentGain");
|
processComputable(conversion as T, "currentGain");
|
||||||
|
processComputable(conversion as T, "actualGain");
|
||||||
|
processComputable(conversion as T, "currentAt");
|
||||||
processComputable(conversion as T, "nextAt");
|
processComputable(conversion as T, "nextAt");
|
||||||
processComputable(conversion as T, "buyMax");
|
processComputable(conversion as T, "buyMax");
|
||||||
setDefault(conversion, "buyMax", true);
|
setDefault(conversion, "buyMax", true);
|
||||||
|
@ -108,6 +125,7 @@ export function createConversion<T extends ConversionOptions>(
|
||||||
|
|
||||||
export type ScalingFunction = {
|
export type ScalingFunction = {
|
||||||
currentGain: (conversion: GenericConversion) => DecimalSource;
|
currentGain: (conversion: GenericConversion) => DecimalSource;
|
||||||
|
currentAt: (conversion: GenericConversion) => DecimalSource;
|
||||||
nextAt: (conversion: GenericConversion) => DecimalSource;
|
nextAt: (conversion: GenericConversion) => DecimalSource;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -128,11 +146,20 @@ export function createLinearScaling(
|
||||||
.times(unref(coefficient))
|
.times(unref(coefficient))
|
||||||
.add(1);
|
.add(1);
|
||||||
},
|
},
|
||||||
|
currentAt(conversion) {
|
||||||
|
let current: DecimalSource = unref(conversion.currentGain);
|
||||||
|
if (conversion.gainModifier) {
|
||||||
|
current = conversion.gainModifier.revert(current);
|
||||||
|
}
|
||||||
|
current = Decimal.max(0, current);
|
||||||
|
return Decimal.times(current, unref(coefficient)).add(unref(base));
|
||||||
|
},
|
||||||
nextAt(conversion) {
|
nextAt(conversion) {
|
||||||
let next: DecimalSource = Decimal.add(unref(conversion.currentGain), 1);
|
let next: DecimalSource = Decimal.add(unref(conversion.currentGain), 1);
|
||||||
if (conversion.gainModifier) {
|
if (conversion.gainModifier) {
|
||||||
next = conversion.gainModifier.revert(next);
|
next = conversion.gainModifier.revert(next);
|
||||||
}
|
}
|
||||||
|
next = Decimal.max(0, next);
|
||||||
return Decimal.times(next, unref(coefficient)).add(unref(base)).max(unref(base));
|
return Decimal.times(next, unref(coefficient)).add(unref(base)).max(unref(base));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -155,24 +182,33 @@ export function createPolynomialScaling(
|
||||||
}
|
}
|
||||||
return gain;
|
return gain;
|
||||||
},
|
},
|
||||||
|
currentAt(conversion) {
|
||||||
|
let current: DecimalSource = unref(conversion.currentGain);
|
||||||
|
if (conversion.gainModifier) {
|
||||||
|
current = conversion.gainModifier.revert(current);
|
||||||
|
}
|
||||||
|
current = Decimal.max(0, current);
|
||||||
|
return Decimal.root(current, unref(exponent)).times(unref(base));
|
||||||
|
},
|
||||||
nextAt(conversion) {
|
nextAt(conversion) {
|
||||||
let next: DecimalSource = Decimal.add(unref(conversion.currentGain), 1);
|
let next: DecimalSource = Decimal.add(unref(conversion.currentGain), 1);
|
||||||
if (conversion.gainModifier) {
|
if (conversion.gainModifier) {
|
||||||
next = conversion.gainModifier.revert(next);
|
next = conversion.gainModifier.revert(next);
|
||||||
}
|
}
|
||||||
|
next = Decimal.max(0, next);
|
||||||
return Decimal.root(next, unref(exponent)).times(unref(base)).max(unref(base));
|
return Decimal.root(next, unref(exponent)).times(unref(base)).max(unref(base));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createCumulativeConversion<S extends ConversionOptions>(
|
export function createCumulativeConversion<S extends ConversionOptions>(
|
||||||
optionsFunc: () => S & ThisType<Conversion<S>>
|
optionsFunc: OptionsFunc<S, Conversion<S>>
|
||||||
): Conversion<S> {
|
): Conversion<S> {
|
||||||
return createConversion(optionsFunc);
|
return createConversion(optionsFunc);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createIndependentConversion<S extends ConversionOptions>(
|
export function createIndependentConversion<S extends ConversionOptions>(
|
||||||
optionsFunc: () => S & ThisType<Conversion<S>>
|
optionsFunc: OptionsFunc<S, Conversion<S>>
|
||||||
): Conversion<S> {
|
): Conversion<S> {
|
||||||
return createConversion(() => {
|
return createConversion(() => {
|
||||||
const conversion: S = optionsFunc();
|
const conversion: S = optionsFunc();
|
||||||
|
@ -180,14 +216,32 @@ export function createIndependentConversion<S extends ConversionOptions>(
|
||||||
setDefault(conversion, "buyMax", false);
|
setDefault(conversion, "buyMax", false);
|
||||||
|
|
||||||
if (conversion.currentGain == null) {
|
if (conversion.currentGain == null) {
|
||||||
conversion.currentGain = computed(() =>
|
conversion.currentGain = computed(() => {
|
||||||
Decimal.sub(
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (conversion.actualGain == null) {
|
||||||
|
conversion.actualGain = computed(() => {
|
||||||
|
let gain = Decimal.sub(
|
||||||
conversion.scaling.currentGain(conversion as GenericConversion),
|
conversion.scaling.currentGain(conversion as GenericConversion),
|
||||||
conversion.gainResource.value
|
conversion.gainResource.value
|
||||||
)
|
).max(0);
|
||||||
.add(1)
|
|
||||||
.max(1)
|
if (!unref(conversion.buyMax)) {
|
||||||
);
|
gain = gain.min(1);
|
||||||
|
}
|
||||||
|
return gain;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
setDefault(conversion, "convert", function () {
|
setDefault(conversion, "convert", function () {
|
||||||
conversion.gainResource.value = conversion.gainModifier
|
conversion.gainResource.value = conversion.gainModifier
|
||||||
|
|
|
@ -24,6 +24,8 @@ export type FeatureComponent<T> = Omit<
|
||||||
|
|
||||||
export type Replace<T, S> = S & Omit<T, keyof S>;
|
export type Replace<T, S> = S & Omit<T, keyof S>;
|
||||||
|
|
||||||
|
export type OptionsFunc<T, S = T, R = Record<string, unknown>> = () => T & ThisType<S> & Partial<R>;
|
||||||
|
|
||||||
let id = 0;
|
let id = 0;
|
||||||
// Get a unique ID to allow a feature to be found for creating branches
|
// Get a unique ID to allow a feature to be found for creating branches
|
||||||
// and any other uses requiring unique identifiers for each feature
|
// and any other uses requiring unique identifiers for each feature
|
||||||
|
|
|
@ -2,6 +2,7 @@ import GridComponent from "features/grids/Grid.vue";
|
||||||
import {
|
import {
|
||||||
CoercableComponent,
|
CoercableComponent,
|
||||||
Component,
|
Component,
|
||||||
|
OptionsFunc,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
Replace,
|
Replace,
|
||||||
|
@ -19,7 +20,7 @@ import {
|
||||||
} from "util/computed";
|
} from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import { computed, Ref, unref } from "vue";
|
import { computed, Ref, unref } from "vue";
|
||||||
import { State, Persistent, makePersistent, PersistentState } from "game/persistence";
|
import { State, Persistent, PersistentState, persistent } from "game/persistence";
|
||||||
|
|
||||||
export const GridType = Symbol("Grid");
|
export const GridType = Symbol("Grid");
|
||||||
|
|
||||||
|
@ -241,11 +242,10 @@ export type GenericGrid = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createGrid<T extends GridOptions>(
|
export function createGrid<T extends GridOptions>(
|
||||||
optionsFunc: () => T & ThisType<Grid<T>>
|
optionsFunc: OptionsFunc<T, Grid<T>, BaseGrid>
|
||||||
): Grid<T> {
|
): Grid<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(persistent => {
|
||||||
const grid: T & Partial<BaseGrid> = optionsFunc();
|
const grid = Object.assign(persistent, optionsFunc());
|
||||||
makePersistent(grid, {});
|
|
||||||
grid.id = getUniqueID("grid-");
|
grid.id = getUniqueID("grid-");
|
||||||
grid[Component] = GridComponent;
|
grid[Component] = GridComponent;
|
||||||
|
|
||||||
|
@ -301,5 +301,5 @@ export function createGrid<T extends GridOptions>(
|
||||||
};
|
};
|
||||||
|
|
||||||
return grid as unknown as Grid<T>;
|
return grid as unknown as Grid<T>;
|
||||||
});
|
}, persistent({}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
} from "util/computed";
|
} from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import { shallowReactive, unref } from "vue";
|
import { shallowReactive, unref } from "vue";
|
||||||
import { findFeatures, jsx, Replace, setDefault } from "./feature";
|
import { OptionsFunc, findFeatures, jsx, Replace, setDefault } from "./feature";
|
||||||
|
|
||||||
export const hotkeys: Record<string, GenericHotkey | undefined> = shallowReactive({});
|
export const hotkeys: Record<string, GenericHotkey | undefined> = shallowReactive({});
|
||||||
export const HotkeyType = Symbol("Hotkey");
|
export const HotkeyType = Symbol("Hotkey");
|
||||||
|
@ -43,10 +43,10 @@ export type GenericHotkey = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createHotkey<T extends HotkeyOptions>(
|
export function createHotkey<T extends HotkeyOptions>(
|
||||||
optionsFunc: () => T & ThisType<Hotkey<T>>
|
optionsFunc: OptionsFunc<T, Hotkey<T>, BaseHotkey>
|
||||||
): Hotkey<T> {
|
): Hotkey<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const hotkey: T & Partial<BaseHotkey> = optionsFunc();
|
const hotkey = optionsFunc();
|
||||||
hotkey.type = HotkeyType;
|
hotkey.type = HotkeyType;
|
||||||
|
|
||||||
processComputable(hotkey as T, "enabled");
|
processComputable(hotkey as T, "enabled");
|
||||||
|
|
|
@ -2,6 +2,7 @@ import InfoboxComponent from "features/infoboxes/Infobox.vue";
|
||||||
import {
|
import {
|
||||||
CoercableComponent,
|
CoercableComponent,
|
||||||
Component,
|
Component,
|
||||||
|
OptionsFunc,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
Replace,
|
Replace,
|
||||||
|
@ -18,7 +19,7 @@ import {
|
||||||
} from "util/computed";
|
} from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import { Ref, unref } from "vue";
|
import { Ref, unref } from "vue";
|
||||||
import { Persistent, makePersistent, PersistentState } from "game/persistence";
|
import { Persistent, PersistentState, persistent } from "game/persistence";
|
||||||
|
|
||||||
export const InfoboxType = Symbol("Infobox");
|
export const InfoboxType = Symbol("Infobox");
|
||||||
|
|
||||||
|
@ -63,11 +64,10 @@ export type GenericInfobox = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createInfobox<T extends InfoboxOptions>(
|
export function createInfobox<T extends InfoboxOptions>(
|
||||||
optionsFunc: () => T & ThisType<Infobox<T>>
|
optionsFunc: OptionsFunc<T, Infobox<T>, BaseInfobox>
|
||||||
): Infobox<T> {
|
): Infobox<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(persistent => {
|
||||||
const infobox: T & Partial<BaseInfobox> = optionsFunc();
|
const infobox = Object.assign(persistent, optionsFunc());
|
||||||
makePersistent<boolean>(infobox, false);
|
|
||||||
infobox.id = getUniqueID("infobox-");
|
infobox.id = getUniqueID("infobox-");
|
||||||
infobox.type = InfoboxType;
|
infobox.type = InfoboxType;
|
||||||
infobox[Component] = InfoboxComponent;
|
infobox[Component] = InfoboxComponent;
|
||||||
|
@ -112,5 +112,5 @@ export function createInfobox<T extends InfoboxOptions>(
|
||||||
};
|
};
|
||||||
|
|
||||||
return infobox as unknown as Infobox<T>;
|
return infobox as unknown as Infobox<T>;
|
||||||
});
|
}, persistent<boolean>(false));
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ function updateNodes() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
document.fonts.ready.then(updateNodes);
|
||||||
|
|
||||||
const validLinks = computed(() => {
|
const validLinks = computed(() => {
|
||||||
const n = nodes.value;
|
const n = nodes.value;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import LinksComponent from "./Links.vue";
|
import LinksComponent from "./Links.vue";
|
||||||
import { Component, GatherProps, Replace } from "features/feature";
|
import { Component, OptionsFunc, GatherProps, Replace } from "features/feature";
|
||||||
import { Position } from "game/layers";
|
import { Position } from "game/layers";
|
||||||
import {
|
import {
|
||||||
Computable,
|
Computable,
|
||||||
|
@ -44,10 +44,10 @@ export type GenericLinks = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createLinks<T extends LinksOptions>(
|
export function createLinks<T extends LinksOptions>(
|
||||||
optionsFunc: (() => T) & ThisType<Links<T>>
|
optionsFunc: OptionsFunc<T, Links<T>, BaseLinks>
|
||||||
): Links<T> {
|
): Links<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const links: T & Partial<BaseLinks> = optionsFunc();
|
const links = optionsFunc();
|
||||||
links.type = LinksType;
|
links.type = LinksType;
|
||||||
links[Component] = LinksComponent;
|
links[Component] = LinksComponent;
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import Select from "components/fields/Select.vue";
|
||||||
import {
|
import {
|
||||||
CoercableComponent,
|
CoercableComponent,
|
||||||
Component,
|
Component,
|
||||||
|
OptionsFunc,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
jsx,
|
jsx,
|
||||||
|
@ -13,7 +14,7 @@ import {
|
||||||
import MilestoneComponent from "features/milestones/Milestone.vue";
|
import MilestoneComponent from "features/milestones/Milestone.vue";
|
||||||
import { globalBus } from "game/events";
|
import { globalBus } from "game/events";
|
||||||
import "game/notifications";
|
import "game/notifications";
|
||||||
import { makePersistent, Persistent, PersistentState } from "game/persistence";
|
import { persistent, Persistent, PersistentState } from "game/persistence";
|
||||||
import settings, { registerSettingField } from "game/settings";
|
import settings, { registerSettingField } from "game/settings";
|
||||||
import { camelToTitle } from "util/common";
|
import { camelToTitle } from "util/common";
|
||||||
import {
|
import {
|
||||||
|
@ -83,11 +84,10 @@ export type GenericMilestone = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createMilestone<T extends MilestoneOptions>(
|
export function createMilestone<T extends MilestoneOptions>(
|
||||||
optionsFunc: () => T & ThisType<Milestone<T>>
|
optionsFunc: OptionsFunc<T, Milestone<T>, BaseMilestone>
|
||||||
): Milestone<T> {
|
): Milestone<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(persistent => {
|
||||||
const milestone: T & Partial<BaseMilestone> = optionsFunc();
|
const milestone = Object.assign(persistent, optionsFunc());
|
||||||
makePersistent<boolean>(milestone, false);
|
|
||||||
milestone.id = getUniqueID("milestone-");
|
milestone.id = getUniqueID("milestone-");
|
||||||
milestone.type = MilestoneType;
|
milestone.type = MilestoneType;
|
||||||
milestone[Component] = MilestoneComponent;
|
milestone[Component] = MilestoneComponent;
|
||||||
|
@ -168,7 +168,7 @@ export function createMilestone<T extends MilestoneOptions>(
|
||||||
}
|
}
|
||||||
|
|
||||||
return milestone as unknown as Milestone<T>;
|
return milestone as unknown as Milestone<T>;
|
||||||
});
|
}, persistent<boolean>(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "game/settings" {
|
declare module "game/settings" {
|
||||||
|
|
|
@ -1,89 +1,74 @@
|
||||||
onMounted,
|
onMounted,
|
||||||
<template>
|
<template>
|
||||||
<Particles
|
<div
|
||||||
:id="id"
|
ref="resizeListener"
|
||||||
:class="{
|
class="resize-listener"
|
||||||
'not-fullscreen': !fullscreen
|
:style="unref(style)"
|
||||||
}"
|
:class="unref(classes)"
|
||||||
:style="{
|
|
||||||
zIndex
|
|
||||||
}"
|
|
||||||
ref="particles"
|
|
||||||
:particlesInit="particlesInit"
|
|
||||||
:particlesLoaded="particlesLoaded"
|
|
||||||
:options="{
|
|
||||||
fpsLimit: 60,
|
|
||||||
fullScreen: { enable: fullscreen, zIndex },
|
|
||||||
particles: {
|
|
||||||
number: {
|
|
||||||
value: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
emitters: {
|
|
||||||
autoPlay: false
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
v-bind="$attrs"
|
|
||||||
/>
|
/>
|
||||||
<div ref="resizeListener" class="resize-listener" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="tsx">
|
<script lang="tsx">
|
||||||
import { loadFull } from "tsparticles";
|
import { StyleValue } from "features/feature";
|
||||||
import { Engine, Container } from "tsparticles-engine";
|
|
||||||
import { Emitters } from "tsparticles-plugin-emitters/Emitters";
|
|
||||||
import { EmitterContainer } from "tsparticles-plugin-emitters/EmitterContainer";
|
|
||||||
import { defineComponent, inject, nextTick, onMounted, PropType, ref } from "vue";
|
|
||||||
import { ParticlesComponent } from "particles.vue3";
|
|
||||||
import { FeatureNode, NodesInjectionKey } from "game/layers";
|
import { FeatureNode, NodesInjectionKey } from "game/layers";
|
||||||
|
import { Application } from "pixi.js";
|
||||||
|
import { processedPropType } from "util/vue";
|
||||||
|
import {
|
||||||
|
defineComponent,
|
||||||
|
inject,
|
||||||
|
nextTick,
|
||||||
|
onBeforeUnmount,
|
||||||
|
onMounted,
|
||||||
|
PropType,
|
||||||
|
ref,
|
||||||
|
unref
|
||||||
|
} from "vue";
|
||||||
|
|
||||||
// TODO get typing support on the Particles component
|
// TODO get typing support on the Particles component
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
zIndex: {
|
style: processedPropType<StyleValue>(String, Object, Array),
|
||||||
type: Number,
|
classes: processedPropType<Record<string, boolean>>(Object),
|
||||||
required: true
|
|
||||||
},
|
|
||||||
fullscreen: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
onInit: {
|
onInit: {
|
||||||
type: Function as PropType<(container: EmitterContainer & Container) => void>,
|
type: Function as PropType<(app: Application) => void>,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
id: {
|
id: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
onContainerResized: Function as PropType<(rect: DOMRect) => void>
|
onContainerResized: Function as PropType<(rect: DOMRect) => void>,
|
||||||
|
onHotReload: Function as PropType<VoidFunction>
|
||||||
},
|
},
|
||||||
components: { Particles: ParticlesComponent },
|
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const particles = ref<null | { particles: { container: Emitters } }>(null);
|
const app = ref<null | Application>(null);
|
||||||
|
|
||||||
async function particlesInit(engine: Engine) {
|
|
||||||
await loadFull(engine);
|
|
||||||
}
|
|
||||||
|
|
||||||
function particlesLoaded(container: EmitterContainer & Container) {
|
|
||||||
props.onInit(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
const resizeObserver = new ResizeObserver(updateBounds);
|
const resizeObserver = new ResizeObserver(updateBounds);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const nodes = inject(NodesInjectionKey)!;
|
const nodes = inject(NodesInjectionKey)!;
|
||||||
|
|
||||||
const resizeListener = ref<Element | null>(null);
|
const resizeListener = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// ResizeListener exists because ResizeObserver's don't work when told to observe an SVG element
|
// ResizeListener exists because ResizeObserver's don't work when told to observe an SVG element
|
||||||
const resListener = resizeListener.value;
|
const resListener = resizeListener.value;
|
||||||
if (resListener != null) {
|
if (resListener != null) {
|
||||||
resizeObserver.observe(resListener);
|
resizeObserver.observe(resListener);
|
||||||
|
app.value = new Application({
|
||||||
|
resizeTo: resListener,
|
||||||
|
backgroundAlpha: 0
|
||||||
|
});
|
||||||
|
resizeListener.value?.appendChild(app.value.view);
|
||||||
|
props.onInit(app.value as Application);
|
||||||
}
|
}
|
||||||
updateBounds();
|
updateBounds();
|
||||||
|
if (module.hot?.status() === "apply" && props.onHotReload) {
|
||||||
|
nextTick(props.onHotReload);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
app.value?.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
let isDirty = true;
|
let isDirty = true;
|
||||||
|
@ -93,20 +78,20 @@ export default defineComponent({
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (resizeListener.value != null && props.onContainerResized) {
|
if (resizeListener.value != null && props.onContainerResized) {
|
||||||
// TODO don't overlap with Links.vue
|
// TODO don't overlap with Links.vue
|
||||||
(Object.values(nodes.value) as FeatureNode[]).forEach(
|
(Object.values(nodes.value).filter(n => n) as FeatureNode[]).forEach(
|
||||||
node => (node.rect = node.element.getBoundingClientRect())
|
node => (node.rect = node.element.getBoundingClientRect())
|
||||||
);
|
);
|
||||||
props.onContainerResized(resizeListener.value.getBoundingClientRect());
|
props.onContainerResized(resizeListener.value.getBoundingClientRect());
|
||||||
|
app.value?.resize();
|
||||||
}
|
}
|
||||||
isDirty = true;
|
isDirty = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
document.fonts.ready.then(updateBounds);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
particles,
|
unref,
|
||||||
particlesInit,
|
|
||||||
particlesLoaded,
|
|
||||||
resizeListener
|
resizeListener
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,31 @@
|
||||||
import ParticlesComponent from "features/particles/Particles.vue";
|
import ParticlesComponent from "features/particles/Particles.vue";
|
||||||
import { Container } from "tsparticles-engine";
|
import { Ref, shallowRef, unref } from "vue";
|
||||||
import { IEmitter } from "tsparticles-plugin-emitters/Options/Interfaces/IEmitter";
|
import {
|
||||||
import { EmitterInstance } from "tsparticles-plugin-emitters/EmitterInstance";
|
Component,
|
||||||
import { EmitterContainer } from "tsparticles-plugin-emitters/EmitterContainer";
|
OptionsFunc,
|
||||||
import { Ref, shallowRef } from "vue";
|
GatherProps,
|
||||||
import { Component, GatherProps, getUniqueID, Replace, setDefault } from "features/feature";
|
getUniqueID,
|
||||||
|
Replace,
|
||||||
|
StyleValue
|
||||||
|
} from "features/feature";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
|
import { Application } from "pixi.js";
|
||||||
|
import { Emitter, EmitterConfigV3, upgradeConfig } from "@pixi/particle-emitter";
|
||||||
|
import { Computable, GetComputableType } from "util/computed";
|
||||||
|
|
||||||
export const ParticlesType = Symbol("Particles");
|
export const ParticlesType = Symbol("Particles");
|
||||||
|
|
||||||
export interface ParticlesOptions {
|
export interface ParticlesOptions {
|
||||||
fullscreen?: boolean;
|
classes?: Computable<Record<string, boolean>>;
|
||||||
zIndex?: number;
|
style?: Computable<StyleValue>;
|
||||||
onContainerResized?: (boundingRect: DOMRect) => void;
|
onContainerResized?: (boundingRect: DOMRect) => void;
|
||||||
|
onHotReload?: VoidFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseParticles {
|
export interface BaseParticles {
|
||||||
id: string;
|
id: string;
|
||||||
containerRef: Ref<null | (EmitterContainer & Container)>;
|
app: Ref<null | Application>;
|
||||||
addEmitter: (
|
addEmitter: (config: EmitterConfigV3) => Promise<Emitter>;
|
||||||
options: IEmitter & { particles: Required<IEmitter>["particles"] }
|
|
||||||
) => Promise<EmitterInstance>;
|
|
||||||
removeEmitter: (emitter: EmitterInstance) => void;
|
|
||||||
type: typeof ParticlesType;
|
type: typeof ParticlesType;
|
||||||
[Component]: typeof ParticlesComponent;
|
[Component]: typeof ParticlesComponent;
|
||||||
[GatherProps]: () => Record<string, unknown>;
|
[GatherProps]: () => Record<string, unknown>;
|
||||||
|
@ -30,68 +34,54 @@ export interface BaseParticles {
|
||||||
export type Particles<T extends ParticlesOptions> = Replace<
|
export type Particles<T extends ParticlesOptions> = Replace<
|
||||||
T & BaseParticles,
|
T & BaseParticles,
|
||||||
{
|
{
|
||||||
fullscreen: undefined extends T["fullscreen"] ? true : T["fullscreen"];
|
classes: GetComputableType<T["classes"]>;
|
||||||
zIndex: undefined extends T["zIndex"] ? 1 : T["zIndex"];
|
style: GetComputableType<T["style"]>;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type GenericParticles = Replace<
|
export type GenericParticles = Particles<ParticlesOptions>;
|
||||||
Particles<ParticlesOptions>,
|
|
||||||
{
|
|
||||||
fullscreen: boolean;
|
|
||||||
zIndex: number;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
|
|
||||||
export function createParticles<T extends ParticlesOptions>(
|
export function createParticles<T extends ParticlesOptions>(
|
||||||
optionsFunc: () => T & ThisType<Particles<T>>
|
optionsFunc: OptionsFunc<T, Particles<T>, BaseParticles>
|
||||||
): Particles<T> {
|
): Particles<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const particles: T & Partial<BaseParticles> = optionsFunc();
|
const particles = optionsFunc();
|
||||||
particles.id = getUniqueID("particles-");
|
particles.id = getUniqueID("particles-");
|
||||||
particles.type = ParticlesType;
|
particles.type = ParticlesType;
|
||||||
particles[Component] = ParticlesComponent;
|
particles[Component] = ParticlesComponent;
|
||||||
|
|
||||||
particles.containerRef = shallowRef(null);
|
particles.app = shallowRef(null);
|
||||||
particles.addEmitter = (
|
particles.addEmitter = (config: EmitterConfigV3): Promise<Emitter> => {
|
||||||
options: IEmitter & { particles: Required<IEmitter>["particles"] }
|
|
||||||
): Promise<EmitterInstance> => {
|
|
||||||
const genericParticles = particles as GenericParticles;
|
const genericParticles = particles as GenericParticles;
|
||||||
if (genericParticles.containerRef.value) {
|
if (genericParticles.app.value) {
|
||||||
// TODO why does addEmitter require a position parameter
|
return Promise.resolve(new Emitter(genericParticles.app.value.stage, config));
|
||||||
return Promise.resolve(genericParticles.containerRef.value.addEmitter(options));
|
|
||||||
}
|
}
|
||||||
return new Promise<EmitterInstance>(resolve => {
|
return new Promise<Emitter>(resolve => {
|
||||||
emittersToAdd.push({ resolve, options });
|
emittersToAdd.push({ resolve, config });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
particles.removeEmitter = (emitter: EmitterInstance) => {
|
|
||||||
// TODO I can't find a proper way to remove an emitter without accessing private functions
|
|
||||||
emitter.emitters.removeEmitter(emitter);
|
|
||||||
};
|
|
||||||
|
|
||||||
let emittersToAdd: {
|
let emittersToAdd: {
|
||||||
resolve: (value: EmitterInstance | PromiseLike<EmitterInstance>) => void;
|
resolve: (value: Emitter | PromiseLike<Emitter>) => void;
|
||||||
options: IEmitter & { particles: Required<IEmitter>["particles"] };
|
config: EmitterConfigV3;
|
||||||
}[] = [];
|
}[] = [];
|
||||||
|
|
||||||
function onInit(container: EmitterContainer & Container) {
|
function onInit(app: Application) {
|
||||||
(particles as GenericParticles).containerRef.value = container;
|
(particles as GenericParticles).app.value = app;
|
||||||
emittersToAdd.forEach(({ resolve, options }) => resolve(container.addEmitter(options)));
|
emittersToAdd.forEach(({ resolve, config }) => resolve(new Emitter(app.stage, config)));
|
||||||
emittersToAdd = [];
|
emittersToAdd = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
setDefault(particles, "fullscreen", true);
|
|
||||||
setDefault(particles, "zIndex", 1);
|
|
||||||
particles.onContainerResized = particles.onContainerResized?.bind(particles);
|
particles.onContainerResized = particles.onContainerResized?.bind(particles);
|
||||||
|
|
||||||
particles[GatherProps] = function (this: GenericParticles) {
|
particles[GatherProps] = function (this: GenericParticles) {
|
||||||
const { id, fullscreen, zIndex, onContainerResized } = this;
|
const { id, style, classes, onContainerResized, onHotReload } = this;
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
fullscreen,
|
style: unref(style),
|
||||||
zIndex,
|
classes,
|
||||||
onContainerResized,
|
onContainerResized,
|
||||||
|
onHotReload,
|
||||||
onInit
|
onInit
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -99,3 +89,10 @@ export function createParticles<T extends ParticlesOptions>(
|
||||||
return particles as unknown as Particles<T>;
|
return particles as unknown as Particles<T>;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
upgradeConfig: typeof upgradeConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.upgradeConfig = upgradeConfig;
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
import { getUniqueID, Replace } from "features/feature";
|
import { OptionsFunc, getUniqueID, Replace } from "features/feature";
|
||||||
import { globalBus } from "game/events";
|
import { globalBus } from "game/events";
|
||||||
import { GenericLayer } from "game/layers";
|
import { GenericLayer } from "game/layers";
|
||||||
import {
|
import { DefaultValue, Persistent, persistent, PersistentState } from "game/persistence";
|
||||||
DefaultValue,
|
|
||||||
Persistent,
|
|
||||||
persistent,
|
|
||||||
PersistentRef,
|
|
||||||
PersistentState
|
|
||||||
} from "game/persistence";
|
|
||||||
import Decimal from "util/bignum";
|
import Decimal from "util/bignum";
|
||||||
import { Computable, GetComputableType, processComputable } from "util/computed";
|
import { Computable, GetComputableType, processComputable } from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
|
@ -37,10 +31,10 @@ export type Reset<T extends ResetOptions> = Replace<
|
||||||
export type GenericReset = Reset<ResetOptions>;
|
export type GenericReset = Reset<ResetOptions>;
|
||||||
|
|
||||||
export function createReset<T extends ResetOptions>(
|
export function createReset<T extends ResetOptions>(
|
||||||
optionsFunc: () => T & ThisType<Reset<T>>
|
optionsFunc: OptionsFunc<T, Reset<T>, BaseReset>
|
||||||
): Reset<T> {
|
): Reset<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const reset: T & Partial<BaseReset> = optionsFunc();
|
const reset = optionsFunc();
|
||||||
reset.id = getUniqueID("reset-");
|
reset.id = getUniqueID("reset-");
|
||||||
reset.type = ResetType;
|
reset.type = ResetType;
|
||||||
|
|
||||||
|
@ -70,7 +64,7 @@ export function createReset<T extends ResetOptions>(
|
||||||
}
|
}
|
||||||
|
|
||||||
const listeners: Record<string, Unsubscribe | undefined> = {};
|
const listeners: Record<string, Unsubscribe | undefined> = {};
|
||||||
export function trackResetTime(layer: GenericLayer, reset: GenericReset): PersistentRef<Decimal> {
|
export function trackResetTime(layer: GenericLayer, reset: GenericReset): Persistent<Decimal> {
|
||||||
const resetTime = persistent<Decimal>(new Decimal(0));
|
const resetTime = persistent<Decimal>(new Decimal(0));
|
||||||
listeners[layer.id] = layer.on("preUpdate", diff => {
|
listeners[layer.id] = layer.on("preUpdate", diff => {
|
||||||
resetTime.value = Decimal.add(resetTime.value, diff);
|
resetTime.value = Decimal.add(resetTime.value, diff);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
CoercableComponent,
|
CoercableComponent,
|
||||||
Component,
|
Component,
|
||||||
|
OptionsFunc,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
Replace,
|
Replace,
|
||||||
|
@ -36,9 +37,11 @@ export type Tab<T extends TabOptions> = Replace<
|
||||||
|
|
||||||
export type GenericTab = Tab<TabOptions>;
|
export type GenericTab = Tab<TabOptions>;
|
||||||
|
|
||||||
export function createTab<T extends TabOptions>(optionsFunc: () => T & ThisType<Tab<T>>): Tab<T> {
|
export function createTab<T extends TabOptions>(
|
||||||
|
optionsFunc: OptionsFunc<T, Tab<T>, BaseTab>
|
||||||
|
): Tab<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const tab: T & Partial<BaseTab> = optionsFunc();
|
const tab = optionsFunc();
|
||||||
tab.id = getUniqueID("tab-");
|
tab.id = getUniqueID("tab-");
|
||||||
tab.type = TabType;
|
tab.type = TabType;
|
||||||
tab[Component] = TabComponent;
|
tab[Component] = TabComponent;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
CoercableComponent,
|
CoercableComponent,
|
||||||
Component,
|
Component,
|
||||||
|
OptionsFunc,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
Replace,
|
Replace,
|
||||||
|
@ -10,7 +11,7 @@ import {
|
||||||
} from "features/feature";
|
} from "features/feature";
|
||||||
import TabButtonComponent from "features/tabs/TabButton.vue";
|
import TabButtonComponent from "features/tabs/TabButton.vue";
|
||||||
import TabFamilyComponent from "features/tabs/TabFamily.vue";
|
import TabFamilyComponent from "features/tabs/TabFamily.vue";
|
||||||
import { Persistent, makePersistent, PersistentState } from "game/persistence";
|
import { Persistent, PersistentState, persistent } from "game/persistence";
|
||||||
import {
|
import {
|
||||||
Computable,
|
Computable,
|
||||||
GetComputableType,
|
GetComputableType,
|
||||||
|
@ -60,13 +61,13 @@ export type GenericTabButton = Replace<
|
||||||
|
|
||||||
export interface TabFamilyOptions {
|
export interface TabFamilyOptions {
|
||||||
visibility?: Computable<Visibility>;
|
visibility?: Computable<Visibility>;
|
||||||
tabs: Record<string, TabButtonOptions>;
|
|
||||||
classes?: Computable<Record<string, boolean>>;
|
classes?: Computable<Record<string, boolean>>;
|
||||||
style?: Computable<StyleValue>;
|
style?: Computable<StyleValue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseTabFamily extends Persistent<string> {
|
export interface BaseTabFamily extends Persistent<string> {
|
||||||
id: string;
|
id: string;
|
||||||
|
tabs: Record<string, TabButtonOptions>;
|
||||||
activeTab: Ref<GenericTab | CoercableComponent | null>;
|
activeTab: Ref<GenericTab | CoercableComponent | null>;
|
||||||
selected: Ref<string>;
|
selected: Ref<string>;
|
||||||
type: typeof TabFamilyType;
|
type: typeof TabFamilyType;
|
||||||
|
@ -90,21 +91,39 @@ export type GenericTabFamily = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createTabFamily<T extends TabFamilyOptions>(
|
export function createTabFamily<T extends TabFamilyOptions>(
|
||||||
optionsFunc: () => T & ThisType<TabFamily<T>>
|
tabs: Record<string, () => TabButtonOptions>,
|
||||||
|
optionsFunc: OptionsFunc<T, TabFamily<T>, BaseTabFamily>
|
||||||
): TabFamily<T> {
|
): TabFamily<T> {
|
||||||
return createLazyProxy(() => {
|
if (Object.keys(tabs).length === 0) {
|
||||||
const tabFamily: T & Partial<BaseTabFamily> = optionsFunc();
|
console.warn("Cannot create tab family with 0 tabs");
|
||||||
|
throw "Cannot create tab family with 0 tabs";
|
||||||
|
}
|
||||||
|
|
||||||
if (Object.keys(tabFamily.tabs).length === 0) {
|
return createLazyProxy(persistent => {
|
||||||
console.warn("Cannot create tab family with 0 tabs", tabFamily);
|
const tabFamily = Object.assign(persistent, optionsFunc());
|
||||||
throw "Cannot create tab family with 0 tabs";
|
|
||||||
}
|
|
||||||
|
|
||||||
tabFamily.id = getUniqueID("tabFamily-");
|
tabFamily.id = getUniqueID("tabFamily-");
|
||||||
tabFamily.type = TabFamilyType;
|
tabFamily.type = TabFamilyType;
|
||||||
tabFamily[Component] = TabFamilyComponent;
|
tabFamily[Component] = TabFamilyComponent;
|
||||||
|
|
||||||
makePersistent<string>(tabFamily, Object.keys(tabFamily.tabs)[0]);
|
tabFamily.tabs = Object.keys(tabs).reduce<Record<string, GenericTabButton>>(
|
||||||
|
(parsedTabs, tab) => {
|
||||||
|
const tabButton: TabButtonOptions & Partial<BaseTabButton> = tabs[tab]();
|
||||||
|
tabButton.type = TabButtonType;
|
||||||
|
tabButton[Component] = TabButtonComponent;
|
||||||
|
|
||||||
|
processComputable(tabButton as TabButtonOptions, "visibility");
|
||||||
|
setDefault(tabButton, "visibility", Visibility.Visible);
|
||||||
|
processComputable(tabButton as TabButtonOptions, "tab");
|
||||||
|
processComputable(tabButton as TabButtonOptions, "display");
|
||||||
|
processComputable(tabButton as TabButtonOptions, "classes");
|
||||||
|
processComputable(tabButton as TabButtonOptions, "style");
|
||||||
|
processComputable(tabButton as TabButtonOptions, "glowColor");
|
||||||
|
parsedTabs[tab] = tabButton as GenericTabButton;
|
||||||
|
return parsedTabs;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
tabFamily.selected = tabFamily[PersistentState];
|
tabFamily.selected = tabFamily[PersistentState];
|
||||||
tabFamily.activeTab = computed(() => {
|
tabFamily.activeTab = computed(() => {
|
||||||
const tabs = unref(processedTabFamily.tabs);
|
const tabs = unref(processedTabFamily.tabs);
|
||||||
|
@ -129,20 +148,6 @@ export function createTabFamily<T extends TabFamilyOptions>(
|
||||||
processComputable(tabFamily as T, "classes");
|
processComputable(tabFamily as T, "classes");
|
||||||
processComputable(tabFamily as T, "style");
|
processComputable(tabFamily as T, "style");
|
||||||
|
|
||||||
for (const tab in tabFamily.tabs) {
|
|
||||||
const tabButton: TabButtonOptions & Partial<BaseTabButton> = tabFamily.tabs[tab];
|
|
||||||
tabButton.type = TabButtonType;
|
|
||||||
tabButton[Component] = TabButtonComponent;
|
|
||||||
|
|
||||||
processComputable(tabButton as TabButtonOptions, "visibility");
|
|
||||||
setDefault(tabButton, "visibility", Visibility.Visible);
|
|
||||||
processComputable(tabButton as TabButtonOptions, "tab");
|
|
||||||
processComputable(tabButton as TabButtonOptions, "display");
|
|
||||||
processComputable(tabButton as TabButtonOptions, "classes");
|
|
||||||
processComputable(tabButton as TabButtonOptions, "style");
|
|
||||||
processComputable(tabButton as TabButtonOptions, "glowColor");
|
|
||||||
}
|
|
||||||
|
|
||||||
tabFamily[GatherProps] = function (this: GenericTabFamily) {
|
tabFamily[GatherProps] = function (this: GenericTabFamily) {
|
||||||
const { visibility, activeTab, selected, tabs, style, classes } = this;
|
const { visibility, activeTab, selected, tabs, style, classes } = this;
|
||||||
return { visibility, activeTab, selected, tabs, style: unref(style), classes };
|
return { visibility, activeTab, selected, tabs, style: unref(style), classes };
|
||||||
|
@ -151,5 +156,5 @@ export function createTabFamily<T extends TabFamilyOptions>(
|
||||||
// This is necessary because board.types is different from T and TabFamily
|
// This is necessary because board.types is different from T and TabFamily
|
||||||
const processedTabFamily = tabFamily as unknown as TabFamily<T>;
|
const processedTabFamily = tabFamily as unknown as TabFamily<T>;
|
||||||
return processedTabFamily;
|
return processedTabFamily;
|
||||||
});
|
}, persistent(Object.keys(tabs)[0]));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
CoercableComponent,
|
CoercableComponent,
|
||||||
Component,
|
Component,
|
||||||
|
OptionsFunc,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
Replace,
|
Replace,
|
||||||
|
@ -13,7 +14,7 @@ import { GenericReset } from "features/reset";
|
||||||
import { displayResource, Resource } from "features/resources/resource";
|
import { displayResource, Resource } from "features/resources/resource";
|
||||||
import { Tooltip } from "features/tooltip";
|
import { Tooltip } from "features/tooltip";
|
||||||
import TreeComponent from "features/trees/Tree.vue";
|
import TreeComponent from "features/trees/Tree.vue";
|
||||||
import { persistent } from "game/persistence";
|
import { deletePersistent, persistent } from "game/persistence";
|
||||||
import Decimal, { DecimalSource, format, formatWhole } from "util/bignum";
|
import Decimal, { DecimalSource, format, formatWhole } from "util/bignum";
|
||||||
import {
|
import {
|
||||||
Computable,
|
Computable,
|
||||||
|
@ -74,18 +75,20 @@ export type GenericTreeNode = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createTreeNode<T extends TreeNodeOptions>(
|
export function createTreeNode<T extends TreeNodeOptions>(
|
||||||
optionsFunc: () => T & ThisType<TreeNode<T>>
|
optionsFunc: OptionsFunc<T, TreeNode<T>, BaseTreeNode>
|
||||||
): TreeNode<T> {
|
): TreeNode<T> {
|
||||||
|
const forceTooltip = persistent(false);
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const treeNode: T & Partial<BaseTreeNode> = optionsFunc();
|
const treeNode = optionsFunc();
|
||||||
treeNode.id = getUniqueID("treeNode-");
|
treeNode.id = getUniqueID("treeNode-");
|
||||||
treeNode.type = TreeNodeType;
|
treeNode.type = TreeNodeType;
|
||||||
|
|
||||||
if (treeNode.tooltip) {
|
if (treeNode.tooltip) {
|
||||||
treeNode.forceTooltip = persistent(false);
|
treeNode.forceTooltip = forceTooltip;
|
||||||
} else {
|
} else {
|
||||||
// If we don't have a tooltip, no point in making this persistent
|
// If we don't have a tooltip, no point in making this persistent
|
||||||
treeNode.forceTooltip = ref(false);
|
treeNode.forceTooltip = ref(false);
|
||||||
|
deletePersistent(forceTooltip);
|
||||||
}
|
}
|
||||||
|
|
||||||
processComputable(treeNode as T, "visibility");
|
processComputable(treeNode as T, "visibility");
|
||||||
|
@ -166,10 +169,10 @@ export type GenericTree = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createTree<T extends TreeOptions>(
|
export function createTree<T extends TreeOptions>(
|
||||||
optionsFunc: () => T & ThisType<Tree<T>>
|
optionsFunc: OptionsFunc<T, Tree<T>, BaseTree>
|
||||||
): Tree<T> {
|
): Tree<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const tree: T & Partial<BaseTree> = optionsFunc();
|
const tree = optionsFunc();
|
||||||
tree.id = getUniqueID("tree-");
|
tree.id = getUniqueID("tree-");
|
||||||
tree.type = TreeType;
|
tree.type = TreeType;
|
||||||
tree[Component] = TreeComponent;
|
tree[Component] = TreeComponent;
|
||||||
|
|
|
@ -2,6 +2,7 @@ import UpgradeComponent from "features/upgrades/Upgrade.vue";
|
||||||
import {
|
import {
|
||||||
CoercableComponent,
|
CoercableComponent,
|
||||||
Component,
|
Component,
|
||||||
|
OptionsFunc,
|
||||||
findFeatures,
|
findFeatures,
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
|
@ -23,7 +24,7 @@ import {
|
||||||
} from "util/computed";
|
} from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import { computed, Ref, unref } from "vue";
|
import { computed, Ref, unref } from "vue";
|
||||||
import { Persistent, makePersistent, PersistentState } from "game/persistence";
|
import { persistent, Persistent, PersistentState } from "game/persistence";
|
||||||
|
|
||||||
export const UpgradeType = Symbol("Upgrade");
|
export const UpgradeType = Symbol("Upgrade");
|
||||||
|
|
||||||
|
@ -78,11 +79,10 @@ export type GenericUpgrade = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createUpgrade<T extends UpgradeOptions>(
|
export function createUpgrade<T extends UpgradeOptions>(
|
||||||
optionsFunc: () => T & ThisType<Upgrade<T>>
|
optionsFunc: OptionsFunc<T, Upgrade<T>, BaseUpgrade>
|
||||||
): Upgrade<T> {
|
): Upgrade<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(persistent => {
|
||||||
const upgrade: T & Partial<BaseUpgrade> = optionsFunc();
|
const upgrade = Object.assign(persistent, optionsFunc());
|
||||||
makePersistent<boolean>(upgrade, false);
|
|
||||||
upgrade.id = getUniqueID("upgrade-");
|
upgrade.id = getUniqueID("upgrade-");
|
||||||
upgrade.type = UpgradeType;
|
upgrade.type = UpgradeType;
|
||||||
upgrade[Component] = UpgradeComponent;
|
upgrade[Component] = UpgradeComponent;
|
||||||
|
@ -167,7 +167,7 @@ export function createUpgrade<T extends UpgradeOptions>(
|
||||||
};
|
};
|
||||||
|
|
||||||
return upgrade as unknown as Upgrade<T>;
|
return upgrade as unknown as Upgrade<T>;
|
||||||
});
|
}, persistent<boolean>(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupAutoPurchase(
|
export function setupAutoPurchase(
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import Modal from "components/Modal.vue";
|
import Modal from "components/Modal.vue";
|
||||||
import {
|
import {
|
||||||
CoercableComponent,
|
CoercableComponent,
|
||||||
|
OptionsFunc,
|
||||||
jsx,
|
jsx,
|
||||||
JSXFunction,
|
JSXFunction,
|
||||||
Replace,
|
Replace,
|
||||||
|
@ -18,7 +19,7 @@ import { createLazyProxy } from "util/proxies";
|
||||||
import { createNanoEvents, Emitter } from "nanoevents";
|
import { createNanoEvents, Emitter } from "nanoevents";
|
||||||
import { InjectionKey, Ref, ref, unref } from "vue";
|
import { InjectionKey, Ref, ref, unref } from "vue";
|
||||||
import { globalBus } from "./events";
|
import { globalBus } from "./events";
|
||||||
import { persistent, PersistentRef } from "./persistence";
|
import { Persistent, persistent } from "./persistence";
|
||||||
import player from "./player";
|
import player from "./player";
|
||||||
|
|
||||||
export interface FeatureNode {
|
export interface FeatureNode {
|
||||||
|
@ -58,7 +59,6 @@ export interface Position {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LayerOptions {
|
export interface LayerOptions {
|
||||||
id: string;
|
|
||||||
color?: Computable<string>;
|
color?: Computable<string>;
|
||||||
display: Computable<CoercableComponent>;
|
display: Computable<CoercableComponent>;
|
||||||
classes?: Computable<Record<string, boolean>>;
|
classes?: Computable<Record<string, boolean>>;
|
||||||
|
@ -70,7 +70,8 @@ export interface LayerOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseLayer {
|
export interface BaseLayer {
|
||||||
minimized: PersistentRef<boolean>;
|
id: string;
|
||||||
|
minimized: Persistent<boolean>;
|
||||||
emitter: Emitter<LayerEvents>;
|
emitter: Emitter<LayerEvents>;
|
||||||
on: OmitThisParameter<Emitter<LayerEvents>["on"]>;
|
on: OmitThisParameter<Emitter<LayerEvents>["on"]>;
|
||||||
emit: <K extends keyof LayerEvents>(event: K, ...args: Parameters<LayerEvents[K]>) => void;
|
emit: <K extends keyof LayerEvents>(event: K, ...args: Parameters<LayerEvents[K]>) => void;
|
||||||
|
@ -84,7 +85,7 @@ export type Layer<T extends LayerOptions> = Replace<
|
||||||
display: GetComputableType<T["display"]>;
|
display: GetComputableType<T["display"]>;
|
||||||
classes: GetComputableType<T["classes"]>;
|
classes: GetComputableType<T["classes"]>;
|
||||||
style: GetComputableType<T["style"]>;
|
style: GetComputableType<T["style"]>;
|
||||||
name: GetComputableTypeWithDefault<T["name"], T["id"]>;
|
name: GetComputableTypeWithDefault<T["name"], string>;
|
||||||
minWidth: GetComputableTypeWithDefault<T["minWidth"], 600>;
|
minWidth: GetComputableTypeWithDefault<T["minWidth"], 600>;
|
||||||
minimizable: GetComputableTypeWithDefault<T["minimizable"], true>;
|
minimizable: GetComputableTypeWithDefault<T["minimizable"], true>;
|
||||||
forceHideGoBack: GetComputableType<T["forceHideGoBack"]>;
|
forceHideGoBack: GetComputableType<T["forceHideGoBack"]>;
|
||||||
|
@ -100,8 +101,11 @@ export type GenericLayer = Replace<
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export const persistentRefs: Record<string, Set<Persistent>> = {};
|
||||||
|
export const addingLayers: string[] = [];
|
||||||
export function createLayer<T extends LayerOptions>(
|
export function createLayer<T extends LayerOptions>(
|
||||||
optionsFunc: (() => T) & ThisType<BaseLayer>
|
id: string,
|
||||||
|
optionsFunc: OptionsFunc<T, BaseLayer, BaseLayer>
|
||||||
): Layer<T> {
|
): Layer<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const layer = {} as T & Partial<BaseLayer>;
|
const layer = {} as T & Partial<BaseLayer>;
|
||||||
|
@ -109,10 +113,19 @@ export function createLayer<T extends LayerOptions>(
|
||||||
layer.on = emitter.on.bind(emitter);
|
layer.on = emitter.on.bind(emitter);
|
||||||
layer.emit = emitter.emit.bind(emitter);
|
layer.emit = emitter.emit.bind(emitter);
|
||||||
layer.nodes = ref({});
|
layer.nodes = ref({});
|
||||||
|
layer.id = id;
|
||||||
|
|
||||||
|
addingLayers.push(id);
|
||||||
|
persistentRefs[id] = new Set();
|
||||||
layer.minimized = persistent(false);
|
layer.minimized = persistent(false);
|
||||||
|
|
||||||
Object.assign(layer, optionsFunc.call(layer));
|
Object.assign(layer, optionsFunc.call(layer));
|
||||||
|
if (
|
||||||
|
addingLayers[addingLayers.length - 1] == null ||
|
||||||
|
addingLayers[addingLayers.length - 1] !== id
|
||||||
|
) {
|
||||||
|
throw `Adding layers stack in invalid state. This should not happen\nStack: ${addingLayers}\nTrying to pop ${layer.id}`;
|
||||||
|
}
|
||||||
|
addingLayers.pop();
|
||||||
|
|
||||||
processComputable(layer as T, "color");
|
processComputable(layer as T, "color");
|
||||||
processComputable(layer as T, "display");
|
processComputable(layer as T, "display");
|
||||||
|
|
|
@ -2,11 +2,13 @@ import { globalBus } from "game/events";
|
||||||
import Decimal, { DecimalSource } from "util/bignum";
|
import Decimal, { DecimalSource } from "util/bignum";
|
||||||
import { ProxyState } from "util/proxies";
|
import { ProxyState } from "util/proxies";
|
||||||
import { isArray } from "@vue/shared";
|
import { isArray } from "@vue/shared";
|
||||||
import { isRef, Ref, ref } from "vue";
|
import { isReactive, isRef, Ref, ref } from "vue";
|
||||||
import { GenericLayer } from "./layers";
|
import { addingLayers, GenericLayer, persistentRefs } from "./layers";
|
||||||
|
|
||||||
export const PersistentState = Symbol("PersistentState");
|
export const PersistentState = Symbol("PersistentState");
|
||||||
export const DefaultValue = Symbol("DefaultValue");
|
export const DefaultValue = Symbol("DefaultValue");
|
||||||
|
export const StackTrace = Symbol("StackTrace");
|
||||||
|
export const Deleted = Symbol("Deleted");
|
||||||
|
|
||||||
// Note: This is a union of things that should be safely stringifiable without needing
|
// Note: This is a union of things that should be safely stringifiable without needing
|
||||||
// special processes for knowing what to load them in as
|
// special processes for knowing what to load them in as
|
||||||
|
@ -20,31 +22,53 @@ export type State =
|
||||||
| { [key: string]: State }
|
| { [key: string]: State }
|
||||||
| { [key: number]: State };
|
| { [key: number]: State };
|
||||||
|
|
||||||
export type Persistent<T extends State = State> = {
|
export type Persistent<T extends State = State> = Ref<T> & {
|
||||||
[PersistentState]: Ref<T>;
|
[PersistentState]: Ref<T>;
|
||||||
[DefaultValue]: T;
|
[DefaultValue]: T;
|
||||||
|
[StackTrace]: string;
|
||||||
|
[Deleted]: boolean;
|
||||||
};
|
};
|
||||||
export type PersistentRef<T extends State = State> = Ref<T> & Persistent<T>;
|
|
||||||
|
|
||||||
export function persistent<T extends State>(defaultValue: T | Ref<T>): PersistentRef<T> {
|
function getStackTrace() {
|
||||||
|
return (
|
||||||
|
new Error().stack
|
||||||
|
?.split("\n")
|
||||||
|
.slice(3, 5)
|
||||||
|
.map(line => line.trim())
|
||||||
|
.join("\n") || ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function persistent<T extends State>(defaultValue: T | Ref<T>): Persistent<T> {
|
||||||
const persistent = (
|
const persistent = (
|
||||||
isRef(defaultValue) ? defaultValue : (ref<T>(defaultValue) as unknown)
|
isRef(defaultValue) ? defaultValue : (ref<T>(defaultValue) as unknown)
|
||||||
) as PersistentRef<T>;
|
) as Persistent<T>;
|
||||||
|
|
||||||
persistent[PersistentState] = persistent;
|
persistent[PersistentState] = persistent;
|
||||||
persistent[DefaultValue] = isRef(defaultValue) ? defaultValue.value : defaultValue;
|
persistent[DefaultValue] = isRef(defaultValue) ? defaultValue.value : defaultValue;
|
||||||
return persistent as PersistentRef<T>;
|
persistent[StackTrace] = getStackTrace();
|
||||||
|
persistent[Deleted] = false;
|
||||||
|
|
||||||
|
if (addingLayers.length === 0) {
|
||||||
|
console.warn(
|
||||||
|
"Creating a persistent ref outside of a layer. This is not officially supported",
|
||||||
|
persistent,
|
||||||
|
"\nCreated at:\n" + persistent[StackTrace]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
persistentRefs[addingLayers[addingLayers.length - 1]].add(persistent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return persistent as Persistent<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makePersistent<T extends State>(
|
export function deletePersistent(persistent: Persistent) {
|
||||||
obj: unknown,
|
if (addingLayers.length === 0) {
|
||||||
defaultValue: T
|
console.warn("Deleting a persistent ref outside of a layer. Ignoring...", persistent);
|
||||||
): asserts obj is Persistent<T> {
|
} else {
|
||||||
const persistent = obj as Partial<Persistent<T>>;
|
persistentRefs[addingLayers[addingLayers.length - 1]].delete(persistent);
|
||||||
const state = ref(defaultValue) as Ref<T>;
|
}
|
||||||
|
persistent[Deleted] = true;
|
||||||
persistent[PersistentState] = state;
|
|
||||||
persistent[DefaultValue] = isRef(defaultValue) ? (defaultValue.value as T) : defaultValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>) => {
|
globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>) => {
|
||||||
|
@ -56,6 +80,20 @@ globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>
|
||||||
if (value && typeof value === "object") {
|
if (value && typeof value === "object") {
|
||||||
if (PersistentState in value) {
|
if (PersistentState in value) {
|
||||||
foundPersistent = true;
|
foundPersistent = true;
|
||||||
|
if ((value as Persistent)[Deleted]) {
|
||||||
|
console.warn(
|
||||||
|
"Deleted persistent ref present in returned object. Ignoring...",
|
||||||
|
value,
|
||||||
|
"\nCreated at:\n" + (value as Persistent)[StackTrace]
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
persistentRefs[layer.id].delete(
|
||||||
|
ProxyState in value
|
||||||
|
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
((value as any)[ProxyState] as Persistent)
|
||||||
|
: (value as Persistent)
|
||||||
|
);
|
||||||
|
|
||||||
// Construct save path if it doesn't exist
|
// Construct save path if it doesn't exist
|
||||||
const persistentState = path.reduce<Record<string, unknown>>((acc, curr) => {
|
const persistentState = path.reduce<Record<string, unknown>>((acc, curr) => {
|
||||||
|
@ -70,12 +108,20 @@ globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>
|
||||||
// Add ref to save data
|
// Add ref to save data
|
||||||
persistentState[key] = (value as Persistent)[PersistentState];
|
persistentState[key] = (value as Persistent)[PersistentState];
|
||||||
// Load previously saved value
|
// Load previously saved value
|
||||||
if (savedValue != null) {
|
if (isReactive(persistentState)) {
|
||||||
(persistentState[key] as Ref<unknown>).value = savedValue;
|
if (savedValue != null) {
|
||||||
|
persistentState[key] = savedValue;
|
||||||
|
} else {
|
||||||
|
persistentState[key] = (value as Persistent)[DefaultValue];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
(persistentState[key] as Ref<unknown>).value = (value as Persistent)[
|
if (savedValue != null) {
|
||||||
DefaultValue
|
(persistentState[key] as Ref<unknown>).value = savedValue;
|
||||||
];
|
} else {
|
||||||
|
(persistentState[key] as Ref<unknown>).value = (value as Persistent)[
|
||||||
|
DefaultValue
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
!(value instanceof Decimal) &&
|
!(value instanceof Decimal) &&
|
||||||
|
@ -114,4 +160,12 @@ globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>
|
||||||
return foundPersistent;
|
return foundPersistent;
|
||||||
};
|
};
|
||||||
handleObject(layer);
|
handleObject(layer);
|
||||||
|
persistentRefs[layer.id].forEach(persistent => {
|
||||||
|
console.error(
|
||||||
|
`Created persistent ref in ${layer.id} without registering it to the layer! Make sure to include everything persistent in the returned object`,
|
||||||
|
persistent,
|
||||||
|
"\nCreated at:\n" + persistent[StackTrace]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
persistentRefs[layer.id].clear();
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,15 +17,18 @@ export type ProxiedWithState<T> = NonNullable<T> extends Record<PropertyKey, any
|
||||||
|
|
||||||
// Takes a function that returns an object and pretends to be that object
|
// Takes a function that returns an object and pretends to be that object
|
||||||
// Note that the object is lazily calculated
|
// Note that the object is lazily calculated
|
||||||
export function createLazyProxy<T extends object>(objectFunc: () => T): T {
|
export function createLazyProxy<T extends object, S>(
|
||||||
const obj: T | Record<string, never> = {};
|
objectFunc: (baseObject: S) => T & S,
|
||||||
|
baseObject: S = {} as S
|
||||||
|
): T {
|
||||||
|
const obj: S & Partial<T> = baseObject;
|
||||||
let calculated = false;
|
let calculated = false;
|
||||||
function calculateObj(): T {
|
function calculateObj(): T {
|
||||||
if (!calculated) {
|
if (!calculated) {
|
||||||
Object.assign(obj, objectFunc());
|
Object.assign(obj, objectFunc(obj));
|
||||||
calculated = true;
|
calculated = true;
|
||||||
}
|
}
|
||||||
return obj as T;
|
return obj as S & T;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Proxy(obj, {
|
return new Proxy(obj, {
|
||||||
|
@ -36,8 +39,8 @@ export function createLazyProxy<T extends object>(objectFunc: () => T): T {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return (calculateObj() as any)[key];
|
return (calculateObj() as any)[key];
|
||||||
},
|
},
|
||||||
set() {
|
set(target, key, value) {
|
||||||
console.error("Layers and features are shallow readonly");
|
console.error("Layers and features are shallow readonly", key, value);
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
has(target, key) {
|
has(target, key) {
|
||||||
|
@ -51,10 +54,10 @@ export function createLazyProxy<T extends object>(objectFunc: () => T): T {
|
||||||
},
|
},
|
||||||
getOwnPropertyDescriptor(target, key) {
|
getOwnPropertyDescriptor(target, key) {
|
||||||
if (!calculated) {
|
if (!calculated) {
|
||||||
Object.assign(obj, objectFunc());
|
Object.assign(obj, objectFunc(obj));
|
||||||
calculated = true;
|
calculated = true;
|
||||||
}
|
}
|
||||||
return Object.getOwnPropertyDescriptor(target, key);
|
return Object.getOwnPropertyDescriptor(target, key);
|
||||||
}
|
}
|
||||||
}) as T;
|
}) as S & T;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue