Merge remote-tracking branch 'template/main'

This commit is contained in:
thepaperpilot 2022-03-11 19:17:28 -06:00
commit a2e490c291
39 changed files with 342 additions and 212 deletions

View file

@ -6,6 +6,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.1.3] - 2022-03-11
### Added
- Milestone.complete
- Challenge.complete
- setupAutoClick function to run a clickable's onClick every tick
- setupAutoComplete function to attempt to complete a challenge every tick
- isAnyChallengeActive function to query if any challenge from a given list is active
- Hotkeys now appear in info modal, if any exist
- projInfo.json now includes a "enablePausing" option that can be used to prevent the player from pausing the game
- Added a "gameWon" global event
### Changed
- **BREAKING** Buyables now default to an infinite purchase limit
- **BREAKING** devSpeed, playedTime, offlineTime, and diff now use numbers instead of Decimals
- **BREAKING** Achievements and milestones now use watchEffect to check for completion, instead of polling each tick. shouldEarn properties now only accept functions
- Cached more decimal values for optimization
### Fixed
- Many types not being exported
- setupHoldToClick wouldn't stop clicking after a component is unmounted
- Header's banner would not have correct width
### Removed
- **BREAKING** Removed setupAutoReset
### Documentation
- Support for documentation generation using typedoc
- Hide main layer from docs
- Hide prestige layer from docs
- Use stub declaration files for libs that don't provide types (vue-panzoom and vue-textarea-autosize)
## [0.1.2] - 2022-03-05
### Changed
- **BREAKING** Removed "@" path alias, and used baseUrl instead
- **BREAKING** Renamed createExponentialScaling to createPolynomialScaling and removed coefficient parameter
- Changed options passed into createLayerTreeNode; now allows overriding display
- App component is no longer cloned before being passed to `createApp`
- Changed TS version from ^4.5.4 to ~4.5.5
### Fixed
- Document title is set as soon as possible now
## [0.1.1] - 2022-03-02 ## [0.1.1] - 2022-03-02
### Added ### Added
- Configuration for Glitch projects - Configuration for Glitch projects

View file

@ -1,6 +1,6 @@
{ {
"name": "profectus", "name": "profectus",
"version": "0.1.1", "version": "0.1.3",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "vue-cli-service serve", "start": "vue-cli-service serve",

View file

@ -44,17 +44,21 @@
</div> </div>
<br /> <br />
<div>Time Played: {{ timePlayed }}</div> <div>Time Played: {{ timePlayed }}</div>
<component :is="infoComponent" />
</div> </div>
</template> </template>
</Modal> </Modal>
</template> </template>
<script setup lang="ts"> <script setup lang="tsx">
import Modal from "components/Modal.vue"; import Modal from "components/Modal.vue";
import type Changelog from "data/Changelog.vue"; import type Changelog from "data/Changelog.vue";
import projInfo from "data/projInfo.json"; import projInfo from "data/projInfo.json";
import { jsx } from "features/feature";
import player from "game/player"; import player from "game/player";
import { infoComponents } from "game/settings";
import { formatTime } from "util/bignum"; import { formatTime } from "util/bignum";
import { coerceComponent, render } from "util/vue";
import { computed, ref, toRefs, unref } from "vue"; import { computed, ref, toRefs, unref } from "vue";
const { title, logo, author, discordName, discordLink, versionNumber, versionTitle } = projInfo; const { title, logo, author, discordName, discordLink, versionNumber, versionTitle } = projInfo;
@ -66,6 +70,10 @@ const isOpen = ref(false);
const timePlayed = computed(() => formatTime(player.timePlayed)); const timePlayed = computed(() => formatTime(player.timePlayed));
const infoComponent = computed(() => {
return coerceComponent(jsx(() => <>{infoComponents.map(render)}</>));
});
defineExpose({ defineExpose({
open() { open() {
isOpen.value = true; isOpen.value = true;

View file

@ -21,7 +21,7 @@
</div> </div>
<br /> <br />
<Toggle title="Autosave" v-model="autosave" /> <Toggle title="Autosave" v-model="autosave" />
<Toggle title="Pause game" v-model="isPaused" /> <Toggle v-if="projInfo.enablePausing" title="Pause game" v-model="isPaused" />
</template> </template>
<template v-slot:footer> <template v-slot:footer>
<div class="nan-footer"> <div class="nan-footer">

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="nav" v-if="useHeader" v-bind="$attrs"> <div class="nav" v-if="useHeader" v-bind="$attrs">
<img v-if="banner" :src="banner" height="100%" :alt="title" /> <img v-if="banner" :src="banner" class="banner" :alt="title" />
<div v-else class="title">{{ title }}</div> <div v-else class="title">{{ title }}</div>
<div @click="changelog?.open()" class="version-container"> <div @click="changelog?.open()" class="version-container">
<Tooltip display="Changelog" bottom class="version" <Tooltip display="Changelog" bottom class="version"
@ -141,6 +141,11 @@ function openDiscord() {
flex-shrink: 0; flex-shrink: 0;
} }
.nav > .banner {
height: 100%;
width: unset;
}
.overlay-nav { .overlay-nav {
position: absolute; position: absolute;
top: 10px; top: 10px;

View file

@ -13,23 +13,24 @@
<Toggle title="Unthrottled" v-model="unthrottled" /> <Toggle title="Unthrottled" v-model="unthrottled" />
<Toggle :title="offlineProdTitle" v-model="offlineProd" /> <Toggle :title="offlineProdTitle" v-model="offlineProd" />
<Toggle :title="autosaveTitle" v-model="autosave" /> <Toggle :title="autosaveTitle" v-model="autosave" />
<Toggle :title="isPausedTitle" v-model="isPaused" /> <Toggle v-if="projInfo.enablePausing" :title="isPausedTitle" v-model="isPaused" />
</template> </template>
</Modal> </Modal>
</template> </template>
<script setup lang="tsx"> <script setup lang="tsx">
import Modal from "components/Modal.vue"; import Modal from "components/Modal.vue";
import projInfo from "data/projInfo.json";
import rawThemes from "data/themes"; import rawThemes from "data/themes";
import { jsx } from "features/feature";
import player from "game/player"; import player from "game/player";
import settings, { settingFields } from "game/settings"; import settings, { settingFields } from "game/settings";
import { camelToTitle } from "util/common"; import { camelToTitle } from "util/common";
import { computed, ref, toRefs } from "vue";
import Toggle from "./fields/Toggle.vue";
import Select from "./fields/Select.vue";
import Tooltip from "./Tooltip.vue";
import { jsx } from "features/feature";
import { coerceComponent, render } from "util/vue"; import { coerceComponent, render } from "util/vue";
import { computed, ref, toRefs } from "vue";
import Select from "./fields/Select.vue";
import Toggle from "./fields/Toggle.vue";
import Tooltip from "./Tooltip.vue";
const isOpen = ref(false); const isOpen = ref(false);

View file

@ -35,7 +35,7 @@ export interface ResetButtonOptions extends ClickableOptions {
canClick?: Computable<boolean>; canClick?: Computable<boolean>;
} }
type ResetButton<T extends ResetButtonOptions> = Replace< export type ResetButton<T extends ResetButtonOptions> = Replace<
Clickable<T>, Clickable<T>,
{ {
resetDescription: GetComputableTypeWithDefault<T["resetDescription"], Ref<string>>; resetDescription: GetComputableTypeWithDefault<T["resetDescription"], Ref<string>>;

View file

@ -1,3 +1,7 @@
/**
* @module
* @hidden
*/
import { main } from "data/projEntry"; import { main } from "data/projEntry";
import { createCumulativeConversion, createPolynomialScaling } from "features/conversion"; import { createCumulativeConversion, createPolynomialScaling } from "features/conversion";
import { jsx } from "features/feature"; import { jsx } from "features/feature";
@ -5,7 +9,7 @@ import { createReset } from "features/reset";
import MainDisplay from "features/resources/MainDisplay.vue"; import MainDisplay from "features/resources/MainDisplay.vue";
import { createResource } from "features/resources/resource"; import { createResource } from "features/resources/resource";
import { createLayer } from "game/layers"; import { createLayer } from "game/layers";
import { DecimalSource } from "lib/break_eternity"; 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";

View file

@ -6,14 +6,16 @@ import { branchedResetPropagation, createTree, GenericTree } from "features/tree
import { globalBus } from "game/events"; import { globalBus } from "game/events";
import { createLayer, GenericLayer, setupLayerModal } from "game/layers"; import { createLayer, GenericLayer, setupLayerModal } from "game/layers";
import player, { PlayerData } from "game/player"; import player, { PlayerData } from "game/player";
import { DecimalSource } from "lib/break_eternity"; import Decimal, { DecimalSource, format, formatTime } from "util/bignum";
import Decimal, { format, formatTime } from "util/bignum";
import { render } from "util/vue"; import { render } from "util/vue";
import { computed, toRaw } from "vue"; import { computed, toRaw } from "vue";
import a from "./layers/aca/a"; import a from "./layers/aca/a";
import c from "./layers/aca/c"; import c from "./layers/aca/c";
import f from "./layers/aca/f"; import f from "./layers/aca/f";
/**
* @hidden
*/
export const main = createLayer(() => { export const main = createLayer(() => {
const points = createResource<DecimalSource>(10); const points = createResource<DecimalSource>(10);
const best = trackBest(points); const best = trackBest(points);

View file

@ -17,5 +17,6 @@
"initialTabs": [ "main", "c" ], "initialTabs": [ "main", "c" ],
"maxTickLength": 3600, "maxTickLength": 3600,
"offlineLimit": 1 "offlineLimit": 1,
"enablePausing": true
} }

View file

@ -1,4 +1,4 @@
interface ThemeVars { export interface ThemeVars {
"--foreground": string; "--foreground": string;
"--background": string; "--background": string;
"--feature-foreground": string; "--feature-foreground": string;

View file

@ -2,7 +2,6 @@ import AchievementComponent from "features/achievements/Achievement.vue";
import { import {
CoercableComponent, CoercableComponent,
Component, Component,
findFeatures,
GatherProps, GatherProps,
getUniqueID, getUniqueID,
Replace, Replace,
@ -10,7 +9,6 @@ import {
StyleValue, StyleValue,
Visibility Visibility
} from "features/feature"; } from "features/feature";
import { globalBus } from "game/events";
import "game/notifications"; import "game/notifications";
import { Persistent, makePersistent, PersistentState } from "game/persistence"; import { Persistent, makePersistent, PersistentState } from "game/persistence";
import { import {
@ -22,15 +20,16 @@ import {
} from "util/computed"; } from "util/computed";
import { createLazyProxy } from "util/proxies"; import { createLazyProxy } from "util/proxies";
import { coerceComponent } from "util/vue"; import { coerceComponent } from "util/vue";
import { Unsubscribe } from "nanoevents"; import { Ref, unref, watchEffect } from "vue";
import { Ref, unref } from "vue";
import { useToast } from "vue-toastification"; import { useToast } from "vue-toastification";
const toast = useToast();
export const AchievementType = Symbol("Achievement"); export const AchievementType = Symbol("Achievement");
export interface AchievementOptions { export interface AchievementOptions {
visibility?: Computable<Visibility>; visibility?: Computable<Visibility>;
shouldEarn?: Computable<boolean>; shouldEarn?: () => boolean;
display?: Computable<CoercableComponent>; display?: Computable<CoercableComponent>;
mark?: Computable<boolean | string>; mark?: Computable<boolean | string>;
image?: Computable<string>; image?: Computable<string>;
@ -39,7 +38,7 @@ export interface AchievementOptions {
onComplete?: VoidFunction; onComplete?: VoidFunction;
} }
interface BaseAchievement extends Persistent<boolean> { export interface BaseAchievement extends Persistent<boolean> {
id: string; id: string;
earned: Ref<boolean>; earned: Ref<boolean>;
complete: VoidFunction; complete: VoidFunction;
@ -52,7 +51,6 @@ export type Achievement<T extends AchievementOptions> = Replace<
T & BaseAchievement, T & BaseAchievement,
{ {
visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>; visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>;
shouldEarn: GetComputableType<T["shouldEarn"]>;
display: GetComputableType<T["display"]>; display: GetComputableType<T["display"]>;
mark: GetComputableType<T["mark"]>; mark: GetComputableType<T["mark"]>;
image: GetComputableType<T["image"]>; image: GetComputableType<T["image"]>;
@ -85,7 +83,6 @@ export function createAchievement<T extends AchievementOptions>(
processComputable(achievement as T, "visibility"); processComputable(achievement as T, "visibility");
setDefault(achievement, "visibility", Visibility.Visible); setDefault(achievement, "visibility", Visibility.Visible);
processComputable(achievement as T, "shouldEarn");
processComputable(achievement as T, "display"); processComputable(achievement as T, "display");
processComputable(achievement as T, "mark"); processComputable(achievement as T, "mark");
processComputable(achievement as T, "image"); processComputable(achievement as T, "image");
@ -97,29 +94,18 @@ export function createAchievement<T extends AchievementOptions>(
return { visibility, display, earned, image, style, classes, mark, id }; return { visibility, display, earned, image, style, classes, mark, id };
}; };
return achievement as unknown as Achievement<T>; if (achievement.shouldEarn) {
}); const genericAchievement = achievement as GenericAchievement;
} watchEffect(() => {
const toast = useToast();
const listeners: Record<string, Unsubscribe | undefined> = {};
globalBus.on("addLayer", layer => {
const achievements: GenericAchievement[] = (
findFeatures(layer, AchievementType) as GenericAchievement[]
).filter(ach => ach.shouldEarn != null);
if (achievements.length) {
listeners[layer.id] = layer.on("postUpdate", () => {
achievements.forEach(achievement => {
if ( if (
unref(achievement.visibility) === Visibility.Visible && !genericAchievement.earned.value &&
!unref(achievement.earned) && unref(genericAchievement.visibility) === Visibility.Visible &&
unref(achievement.shouldEarn) genericAchievement.shouldEarn?.()
) { ) {
achievement[PersistentState].value = true; genericAchievement.earned.value = true;
achievement.onComplete?.(); genericAchievement.onComplete?.();
if (achievement.display) { if (genericAchievement.display) {
const Display = coerceComponent(unref(achievement.display)); const Display = coerceComponent(unref(genericAchievement.display));
toast.info( toast.info(
<div> <div>
<h3>Achievement earned!</h3> <h3>Achievement earned!</h3>
@ -133,11 +119,8 @@ globalBus.on("addLayer", layer => {
} }
} }
}); });
});
} }
});
globalBus.on("removeLayer", layer => { return achievement as unknown as Achievement<T>;
// unsubscribe from postUpdate });
listeners[layer.id]?.(); }
listeners[layer.id] = undefined;
});

View file

@ -9,7 +9,7 @@ import {
StyleValue, StyleValue,
Visibility Visibility
} from "features/feature"; } from "features/feature";
import { DecimalSource } from "lib/break_eternity"; import { DecimalSource } from "util/bignum";
import { import {
Computable, Computable,
GetComputableType, GetComputableType,
@ -45,7 +45,7 @@ export interface BarOptions {
mark?: Computable<boolean | string>; mark?: Computable<boolean | string>;
} }
interface BaseBar { export interface BaseBar {
id: string; id: string;
type: typeof BarType; type: typeof BarType;
[Component]: typeof BarComponent; [Component]: typeof BarComponent;

View file

@ -11,7 +11,6 @@ import {
} 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, makePersistent, PersistentState } from "game/persistence";
import Decimal, { DecimalSource } from "lib/break_eternity";
import { isFunction } from "util/common"; import { isFunction } from "util/common";
import { import {
Computable, Computable,
@ -85,7 +84,7 @@ export interface NodeTypeOptions {
actionDistance?: NodeComputable<number>; actionDistance?: NodeComputable<number>;
onClick?: (node: BoardNode) => void; onClick?: (node: BoardNode) => void;
onDrop?: (node: BoardNode, otherNode: BoardNode) => void; onDrop?: (node: BoardNode, otherNode: BoardNode) => void;
update?: (node: BoardNode, diff: DecimalSource) => void; update?: (node: BoardNode, diff: number) => void;
} }
export interface BaseNodeType { export interface BaseNodeType {
@ -167,7 +166,7 @@ export interface BoardOptions {
types: Record<string, NodeTypeOptions>; types: Record<string, NodeTypeOptions>;
} }
interface BaseBoard extends Persistent<BoardData> { export interface BaseBoard extends Persistent<BoardData> {
id: string; id: string;
links: Ref<BoardNodeLink[] | null>; links: Ref<BoardNodeLink[] | null>;
nodes: Ref<BoardNode[]>; nodes: Ref<BoardNode[]>;
@ -348,7 +347,7 @@ export function getUniqueNodeID(board: GenericBoard): number {
const listeners: Record<string, Unsubscribe | undefined> = {}; const listeners: Record<string, Unsubscribe | undefined> = {};
globalBus.on("addLayer", layer => { globalBus.on("addLayer", layer => {
const boards: GenericBoard[] = findFeatures(layer, BoardType) as GenericBoard[]; const boards: GenericBoard[] = findFeatures(layer, BoardType) as GenericBoard[];
listeners[layer.id] = layer.on("postUpdate", (diff: Decimal) => { listeners[layer.id] = layer.on("postUpdate", diff => {
boards.forEach(board => { boards.forEach(board => {
Object.values(board.types).forEach(type => Object.values(board.types).forEach(type =>
type.nodes.value.forEach(node => type.update?.(node, diff)) type.nodes.value.forEach(node => type.update?.(node, diff))

View file

@ -26,7 +26,7 @@ import {
export const BuyableType = Symbol("Buyable"); export const BuyableType = Symbol("Buyable");
type BuyableDisplay = export type BuyableDisplay =
| CoercableComponent | CoercableComponent
| { | {
title?: CoercableComponent; title?: CoercableComponent;
@ -48,7 +48,7 @@ export interface BuyableOptions {
onPurchase?: (cost: DecimalSource) => void; onPurchase?: (cost: DecimalSource) => void;
} }
interface BaseBuyable extends Persistent<DecimalSource> { export interface BaseBuyable extends Persistent<DecimalSource> {
id: string; id: string;
amount: Ref<DecimalSource>; amount: Ref<DecimalSource>;
maxed: Ref<boolean>; maxed: Ref<boolean>;
@ -68,7 +68,7 @@ export type Buyable<T extends BuyableOptions> = Replace<
cost: GetComputableType<T["cost"]>; cost: GetComputableType<T["cost"]>;
resource: GetComputableType<T["resource"]>; resource: GetComputableType<T["resource"]>;
canPurchase: GetComputableTypeWithDefault<T["canPurchase"], Ref<boolean>>; canPurchase: GetComputableTypeWithDefault<T["canPurchase"], Ref<boolean>>;
purchaseLimit: GetComputableTypeWithDefault<T["purchaseLimit"], 1>; purchaseLimit: GetComputableTypeWithDefault<T["purchaseLimit"], Decimal>;
classes: GetComputableType<T["classes"]>; classes: GetComputableType<T["classes"]>;
style: GetComputableType<T["style"]>; style: GetComputableType<T["style"]>;
mark: GetComputableType<T["mark"]>; mark: GetComputableType<T["mark"]>;
@ -172,6 +172,16 @@ export function createBuyable<T extends BuyableOptions>(
const Title = coerceComponent(currDisplay.title || "", "h3"); const Title = coerceComponent(currDisplay.title || "", "h3");
const Description = coerceComponent(currDisplay.description); const Description = coerceComponent(currDisplay.description);
const EffectDisplay = coerceComponent(currDisplay.effectDisplay || ""); const EffectDisplay = coerceComponent(currDisplay.effectDisplay || "");
const amountDisplay =
unref(genericBuyable.purchaseLimit) === Decimal.dInf ? (
<>Amount: {formatWhole(genericBuyable.amount.value)}</>
) : (
<>
Amount: {formatWhole(genericBuyable.amount.value)} /{" "}
{formatWhole(unref(genericBuyable.purchaseLimit))}
</>
);
return ( return (
<span> <span>
{currDisplay.title ? ( {currDisplay.title ? (
@ -182,8 +192,7 @@ export function createBuyable<T extends BuyableOptions>(
<Description /> <Description />
<div> <div>
<br /> <br />
Amount: {formatWhole(genericBuyable.amount.value)} /{" "} {amountDisplay}
{formatWhole(unref(genericBuyable.purchaseLimit))}
</div> </div>
{currDisplay.effectDisplay ? ( {currDisplay.effectDisplay ? (
<div> <div>
@ -209,7 +218,7 @@ export function createBuyable<T extends BuyableOptions>(
processComputable(buyable as T, "cost"); processComputable(buyable as T, "cost");
processComputable(buyable as T, "resource"); processComputable(buyable as T, "resource");
processComputable(buyable as T, "purchaseLimit"); processComputable(buyable as T, "purchaseLimit");
setDefault(buyable, "purchaseLimit", 1); setDefault(buyable, "purchaseLimit", Decimal.dInf);
processComputable(buyable as T, "style"); processComputable(buyable as T, "style");
processComputable(buyable as T, "mark"); processComputable(buyable as T, "mark");
processComputable(buyable as T, "small"); processComputable(buyable as T, "small");

View file

@ -1,3 +1,4 @@
import { isArray } from "@vue/shared";
import Toggle from "components/fields/Toggle.vue"; import Toggle from "components/fields/Toggle.vue";
import ChallengeComponent from "features/challenges/Challenge.vue"; import ChallengeComponent from "features/challenges/Challenge.vue";
import { import {
@ -25,7 +26,7 @@ import {
ProcessedComputable ProcessedComputable
} 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, watch, WatchStopHandle } from "vue";
export const ChallengeType = Symbol("ChallengeType"); export const ChallengeType = Symbol("ChallengeType");
@ -55,13 +56,14 @@ export interface ChallengeOptions {
onEnter?: VoidFunction; onEnter?: VoidFunction;
} }
interface BaseChallenge { export interface BaseChallenge {
id: string; id: string;
completions: PersistentRef<DecimalSource>; completions: PersistentRef<DecimalSource>;
completed: Ref<boolean>; completed: Ref<boolean>;
maxed: Ref<boolean>; maxed: Ref<boolean>;
active: PersistentRef<boolean>; active: PersistentRef<boolean>;
toggle: VoidFunction; toggle: VoidFunction;
complete: (remainInChallenge?: boolean) => void;
type: typeof ChallengeType; type: typeof ChallengeType;
[Component]: typeof ChallengeComponent; [Component]: typeof ChallengeComponent;
[GatherProps]: () => Record<string, unknown>; [GatherProps]: () => Record<string, unknown>;
@ -87,18 +89,12 @@ export type GenericChallenge = Replace<
{ {
visibility: ProcessedComputable<Visibility>; visibility: ProcessedComputable<Visibility>;
canStart: ProcessedComputable<boolean>; canStart: ProcessedComputable<boolean>;
canComplete: ProcessedComputable<boolean>; canComplete: ProcessedComputable<boolean | DecimalSource>;
completionLimit: ProcessedComputable<DecimalSource>; completionLimit: ProcessedComputable<DecimalSource>;
mark: ProcessedComputable<boolean>; mark: ProcessedComputable<boolean>;
} }
>; >;
export function createActiveChallenge(
challenges: GenericChallenge[]
): Ref<GenericChallenge | undefined> {
return computed(() => challenges.find(challenge => challenge.active.value));
}
export function createChallenge<T extends ChallengeOptions>( export function createChallenge<T extends ChallengeOptions>(
optionsFunc: () => T & ThisType<Challenge<T>> optionsFunc: () => T & ThisType<Challenge<T>>
): Challenge<T> { ): Challenge<T> {
@ -134,11 +130,7 @@ export function createChallenge<T extends ChallengeOptions>(
challenge.toggle = function () { challenge.toggle = function () {
const genericChallenge = challenge as GenericChallenge; const genericChallenge = challenge as GenericChallenge;
if (genericChallenge.active.value) { if (genericChallenge.active.value) {
if ( if (unref(genericChallenge.canComplete) && !genericChallenge.maxed.value) {
genericChallenge.canComplete &&
unref(genericChallenge.canComplete) &&
!genericChallenge.maxed.value
) {
let completions: boolean | DecimalSource = unref(genericChallenge.canComplete); let completions: boolean | DecimalSource = unref(genericChallenge.canComplete);
if (typeof completions === "boolean") { if (typeof completions === "boolean") {
completions = 1; completions = 1;
@ -152,12 +144,40 @@ export function createChallenge<T extends ChallengeOptions>(
genericChallenge.active.value = false; genericChallenge.active.value = false;
genericChallenge.onExit?.(); genericChallenge.onExit?.();
genericChallenge.reset?.reset(); genericChallenge.reset?.reset();
} else if (unref(genericChallenge.canStart)) { } else if (
unref(genericChallenge.canStart) &&
unref(genericChallenge.visibility) === Visibility.Visible &&
!genericChallenge.maxed.value
) {
genericChallenge.reset?.reset(); genericChallenge.reset?.reset();
genericChallenge.active.value = true; genericChallenge.active.value = true;
genericChallenge.onEnter?.(); genericChallenge.onEnter?.();
} }
}; };
challenge.complete = function (remainInChallenge?: boolean) {
const genericChallenge = challenge as GenericChallenge;
let completions: boolean | DecimalSource = unref(genericChallenge.canComplete);
if (
genericChallenge.active.value &&
completions !== false &&
(completions === true || Decimal.neq(0, completions)) &&
!genericChallenge.maxed.value
) {
if (typeof completions === "boolean") {
completions = 1;
}
genericChallenge.completions.value = Decimal.min(
Decimal.add(genericChallenge.completions.value, completions),
unref(genericChallenge.completionLimit)
);
genericChallenge.onComplete?.();
if (remainInChallenge !== true) {
genericChallenge.active.value = false;
genericChallenge.onExit?.();
genericChallenge.reset?.reset();
}
}
};
processComputable(challenge as T, "visibility"); processComputable(challenge as T, "visibility");
setDefault(challenge, "visibility", Visibility.Visible); setDefault(challenge, "visibility", Visibility.Visible);
const visibility = challenge.visibility as ProcessedComputable<Visibility>; const visibility = challenge.visibility as ProcessedComputable<Visibility>;
@ -167,16 +187,6 @@ export function createChallenge<T extends ChallengeOptions>(
} }
return unref(visibility); return unref(visibility);
}); });
if (challenge.canStart == null) {
challenge.canStart = computed(
() =>
unref((challenge as GenericChallenge).visibility) === Visibility.Visible &&
Decimal.lt(
(challenge as GenericChallenge).completions.value,
unref((challenge as GenericChallenge).completionLimit)
)
);
}
if (challenge.canComplete == null) { if (challenge.canComplete == null) {
challenge.canComplete = computed(() => { challenge.canComplete = computed(() => {
const genericChallenge = challenge as GenericChallenge; const genericChallenge = challenge as GenericChallenge;
@ -251,6 +261,34 @@ export function createChallenge<T extends ChallengeOptions>(
}); });
} }
export function setupAutoComplete(
challenge: GenericChallenge,
autoActive: Computable<boolean> = true,
exitOnComplete = true
): WatchStopHandle {
const isActive = typeof autoActive === "function" ? computed(autoActive) : autoActive;
return watch([challenge.canComplete, isActive], ([canComplete, isActive]) => {
if (canComplete && isActive) {
challenge.complete(!exitOnComplete);
}
});
}
export function createActiveChallenge(
challenges: GenericChallenge[]
): Ref<GenericChallenge | undefined> {
return computed(() => challenges.find(challenge => challenge.active.value));
}
export function isAnyChallengeActive(
challenges: GenericChallenge[] | Ref<GenericChallenge | undefined>
): Ref<boolean> {
if (isArray(challenges)) {
challenges = createActiveChallenge(challenges);
}
return computed(() => (challenges as Ref<GenericChallenge | undefined>).value != null);
}
declare module "game/settings" { declare module "game/settings" {
interface Settings { interface Settings {
hideChallenges: boolean; hideChallenges: boolean;

View file

@ -9,6 +9,8 @@ import {
StyleValue, StyleValue,
Visibility Visibility
} from "features/feature"; } from "features/feature";
import { GenericLayer } from "game/layers";
import { Unsubscribe } from "nanoevents";
import { import {
Computable, Computable,
GetComputableType, GetComputableType,
@ -17,7 +19,7 @@ import {
ProcessedComputable ProcessedComputable
} from "util/computed"; } from "util/computed";
import { createLazyProxy } from "util/proxies"; import { createLazyProxy } from "util/proxies";
import { unref } from "vue"; import { computed, unref } from "vue";
export const ClickableType = Symbol("Clickable"); export const ClickableType = Symbol("Clickable");
@ -39,7 +41,7 @@ export interface ClickableOptions {
onHold?: VoidFunction; onHold?: VoidFunction;
} }
interface BaseClickable { export interface BaseClickable {
id: string; id: string;
type: typeof ClickableType; type: typeof ClickableType;
[Component]: typeof ClickableComponent; [Component]: typeof ClickableComponent;
@ -131,3 +133,16 @@ export function createClickable<T extends ClickableOptions>(
return clickable as unknown as Clickable<T>; return clickable as unknown as Clickable<T>;
}); });
} }
export function setupAutoClick(
layer: GenericLayer,
clickable: GenericClickable,
autoActive: Computable<boolean> = true
): Unsubscribe {
const isActive = typeof autoActive === "function" ? computed(autoActive) : autoActive;
return layer.on("update", () => {
if (unref(isActive) && unref(clickable.canClick)) {
clickable.onClick?.();
}
});
}

View file

@ -23,7 +23,7 @@ export interface ConversionOptions {
modifyGainAmount?: (gain: DecimalSource) => DecimalSource; modifyGainAmount?: (gain: DecimalSource) => DecimalSource;
} }
interface BaseConversion { export interface BaseConversion {
convert: VoidFunction; convert: VoidFunction;
} }
@ -206,7 +206,7 @@ export function setupPassiveGeneration(
conversion: GenericConversion, conversion: GenericConversion,
rate: ProcessedComputable<DecimalSource> = 1 rate: ProcessedComputable<DecimalSource> = 1
): void { ): void {
layer.on("preUpdate", (diff: Decimal) => { layer.on("preUpdate", diff => {
const currRate = isRef(rate) ? rate.value : rate; const currRate = isRef(rate) ? rate.value : rate;
if (Decimal.neq(currRate, 0)) { if (Decimal.neq(currRate, 0)) {
conversion.gainResource.value = Decimal.add( conversion.gainResource.value = Decimal.add(

View file

@ -1,6 +1,7 @@
import { hasWon } from "data/projEntry"; import { hasWon } from "data/projEntry";
import { globalBus } from "game/events"; import { globalBus } from "game/events";
import player from "game/player"; import player from "game/player";
import { registerInfoComponent } from "game/settings";
import { import {
Computable, Computable,
GetComputableTypeWithDefault, GetComputableTypeWithDefault,
@ -9,10 +10,10 @@ import {
processComputable processComputable
} from "util/computed"; } from "util/computed";
import { createLazyProxy } from "util/proxies"; import { createLazyProxy } from "util/proxies";
import { unref } from "vue"; import { shallowReactive, unref } from "vue";
import { findFeatures, Replace, setDefault } from "./feature"; import { findFeatures, jsx, Replace, setDefault } from "./feature";
export const hotkeys: Record<string, GenericHotkey | undefined> = {}; export const hotkeys: Record<string, GenericHotkey | undefined> = shallowReactive({});
export const HotkeyType = Symbol("Hotkey"); export const HotkeyType = Symbol("Hotkey");
export interface HotkeyOptions { export interface HotkeyOptions {
@ -22,7 +23,7 @@ export interface HotkeyOptions {
onPress: VoidFunction; onPress: VoidFunction;
} }
interface BaseHotkey { export interface BaseHotkey {
type: typeof HotkeyType; type: typeof HotkeyType;
} }
@ -88,3 +89,23 @@ document.onkeydown = function (e) {
hotkey.onPress(); hotkey.onPress();
} }
}; };
registerInfoComponent(
jsx(() => {
const keys = Object.values(hotkeys).filter(hotkey => unref(hotkey?.enabled));
if (keys.length === 0) {
return "";
}
return (
<div>
<br />
<h4>Hotkeys</h4>
{keys.map(hotkey => (
<div>
{hotkey?.key}: {hotkey?.description}
</div>
))}
</div>
);
})
);

View file

@ -33,7 +33,7 @@ export interface InfoboxOptions {
display: Computable<CoercableComponent>; display: Computable<CoercableComponent>;
} }
interface BaseInfobox extends Persistent<boolean> { export interface BaseInfobox extends Persistent<boolean> {
id: string; id: string;
collapsed: Ref<boolean>; collapsed: Ref<boolean>;
type: typeof InfoboxType; type: typeof InfoboxType;

View file

@ -2,7 +2,6 @@ import Select from "components/fields/Select.vue";
import { import {
CoercableComponent, CoercableComponent,
Component, Component,
findFeatures,
GatherProps, GatherProps,
getUniqueID, getUniqueID,
jsx, jsx,
@ -26,10 +25,11 @@ import {
} from "util/computed"; } from "util/computed";
import { createLazyProxy } from "util/proxies"; import { createLazyProxy } from "util/proxies";
import { coerceComponent, isCoercableComponent } from "util/vue"; import { coerceComponent, isCoercableComponent } from "util/vue";
import { Unsubscribe } from "nanoevents"; import { computed, Ref, unref, watchEffect } from "vue";
import { computed, Ref, unref } from "vue";
import { useToast } from "vue-toastification"; import { useToast } from "vue-toastification";
const toast = useToast();
export const MilestoneType = Symbol("Milestone"); export const MilestoneType = Symbol("Milestone");
export enum MilestoneDisplay { export enum MilestoneDisplay {
@ -42,7 +42,7 @@ export enum MilestoneDisplay {
export interface MilestoneOptions { export interface MilestoneOptions {
visibility?: Computable<Visibility>; visibility?: Computable<Visibility>;
shouldEarn: Computable<boolean>; shouldEarn?: () => boolean;
style?: Computable<StyleValue>; style?: Computable<StyleValue>;
classes?: Computable<Record<string, boolean>>; classes?: Computable<Record<string, boolean>>;
display?: Computable< display?: Computable<
@ -56,9 +56,10 @@ export interface MilestoneOptions {
onComplete?: VoidFunction; onComplete?: VoidFunction;
} }
interface BaseMilestone extends Persistent<boolean> { export interface BaseMilestone extends Persistent<boolean> {
id: string; id: string;
earned: Ref<boolean>; earned: Ref<boolean>;
complete: VoidFunction;
type: typeof MilestoneType; type: typeof MilestoneType;
[Component]: typeof MilestoneComponent; [Component]: typeof MilestoneComponent;
[GatherProps]: () => Record<string, unknown>; [GatherProps]: () => Record<string, unknown>;
@ -68,7 +69,6 @@ export type Milestone<T extends MilestoneOptions> = Replace<
T & BaseMilestone, T & BaseMilestone,
{ {
visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>; visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>;
shouldEarn: GetComputableType<T["shouldEarn"]>;
style: GetComputableType<T["style"]>; style: GetComputableType<T["style"]>;
classes: GetComputableType<T["classes"]>; classes: GetComputableType<T["classes"]>;
display: GetComputableType<T["display"]>; display: GetComputableType<T["display"]>;
@ -93,6 +93,10 @@ export function createMilestone<T extends MilestoneOptions>(
milestone[Component] = MilestoneComponent; milestone[Component] = MilestoneComponent;
milestone.earned = milestone[PersistentState]; milestone.earned = milestone[PersistentState];
milestone.complete = function () {
milestone[PersistentState].value = true;
};
processComputable(milestone as T, "visibility"); processComputable(milestone as T, "visibility");
setDefault(milestone, "visibility", Visibility.Visible); setDefault(milestone, "visibility", Visibility.Visible);
const visibility = milestone.visibility as ProcessedComputable<Visibility>; const visibility = milestone.visibility as ProcessedComputable<Visibility>;
@ -124,7 +128,6 @@ export function createMilestone<T extends MilestoneOptions>(
} }
}); });
processComputable(milestone as T, "shouldEarn");
processComputable(milestone as T, "style"); processComputable(milestone as T, "style");
processComputable(milestone as T, "classes"); processComputable(milestone as T, "classes");
processComputable(milestone as T, "display"); processComputable(milestone as T, "display");
@ -134,28 +137,18 @@ export function createMilestone<T extends MilestoneOptions>(
return { visibility, display, style, classes, earned, id }; return { visibility, display, style, classes, earned, id };
}; };
return milestone as unknown as Milestone<T>; if (milestone.shouldEarn) {
}); const genericMilestone = milestone as GenericMilestone;
} watchEffect(() => {
const toast = useToast();
const listeners: Record<string, Unsubscribe | undefined> = {};
globalBus.on("addLayer", layer => {
const milestones: GenericMilestone[] = (
findFeatures(layer, MilestoneType) as GenericMilestone[]
).filter(milestone => milestone.shouldEarn != null);
listeners[layer.id] = layer.on("postUpdate", () => {
milestones.forEach(milestone => {
if ( if (
unref(milestone.visibility) === Visibility.Visible && !genericMilestone.earned.value &&
!milestone.earned.value && unref(genericMilestone.visibility) === Visibility.Visible &&
unref(milestone.shouldEarn) genericMilestone.shouldEarn?.()
) { ) {
milestone[PersistentState].value = true; genericMilestone.earned.value = true;
milestone.onComplete?.(); genericMilestone.onComplete?.();
if (milestone.display) { if (genericMilestone.display) {
const display = unref(milestone.display); const display = unref(genericMilestone.display);
const Display = coerceComponent( const Display = coerceComponent(
isCoercableComponent(display) ? display : display.requirement isCoercableComponent(display) ? display : display.requirement
); );
@ -172,13 +165,11 @@ globalBus.on("addLayer", layer => {
} }
} }
}); });
}
return milestone as unknown as Milestone<T>;
}); });
}); }
globalBus.on("removeLayer", layer => {
// unsubscribe from postUpdate
listeners[layer.id]?.();
listeners[layer.id] = undefined;
});
declare module "game/settings" { declare module "game/settings" {
interface Settings { interface Settings {

View file

@ -8,11 +8,11 @@ import {
PersistentRef, PersistentRef,
PersistentState PersistentState
} from "game/persistence"; } from "game/persistence";
import Decimal from "lib/break_eternity"; 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";
import { Unsubscribe } from "nanoevents"; import { Unsubscribe } from "nanoevents";
import { computed, isRef, unref } from "vue"; import { isRef, unref } from "vue";
export const ResetType = Symbol("Reset"); export const ResetType = Symbol("Reset");
@ -21,7 +21,7 @@ export interface ResetOptions {
onReset?: VoidFunction; onReset?: VoidFunction;
} }
interface BaseReset { export interface BaseReset {
id: string; id: string;
reset: VoidFunction; reset: VoidFunction;
type: typeof ResetType; type: typeof ResetType;
@ -69,23 +69,10 @@ export function createReset<T extends ResetOptions>(
}); });
} }
export function setupAutoReset(
layer: GenericLayer,
reset: GenericReset,
autoActive: Computable<boolean> = true
): Unsubscribe {
const isActive = typeof autoActive === "function" ? computed(autoActive) : autoActive;
return layer.on("update", () => {
if (unref(isActive)) {
reset.reset();
}
});
}
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): PersistentRef<Decimal> {
const resetTime = persistent<Decimal>(new Decimal(0)); const resetTime = persistent<Decimal>(new Decimal(0));
listeners[layer.id] = layer.on("preUpdate", (diff: Decimal) => { listeners[layer.id] = layer.on("preUpdate", diff => {
resetTime.value = Decimal.add(resetTime.value, diff); resetTime.value = Decimal.add(resetTime.value, diff);
}); });
globalBus.on("reset", currentReset => { globalBus.on("reset", currentReset => {

View file

@ -42,6 +42,8 @@ export function trackTotal(resource: Resource): Ref<DecimalSource> {
return total; return total;
} }
const tetra8 = new Decimal("10^^8");
const e100 = new Decimal("1e100");
export function trackOOMPS( export function trackOOMPS(
resource: Resource, resource: Resource,
pointGain?: ComputedRef<DecimalSource> pointGain?: ComputedRef<DecimalSource>
@ -52,7 +54,7 @@ export function trackOOMPS(
globalBus.on("update", diff => { globalBus.on("update", diff => {
oompsMag.value = 0; oompsMag.value = 0;
if (Decimal.lte(resource.value, 1e100)) { if (Decimal.lte(resource.value, e100)) {
lastPoints.value = resource.value; lastPoints.value = resource.value;
return; return;
} }
@ -61,7 +63,7 @@ export function trackOOMPS(
let prev = lastPoints.value; let prev = lastPoints.value;
lastPoints.value = curr; lastPoints.value = curr;
if (Decimal.gt(curr, prev)) { if (Decimal.gt(curr, prev)) {
if (Decimal.gte(curr, "10^^8")) { if (Decimal.gte(curr, tetra8)) {
curr = Decimal.slog(curr, 1e10); curr = Decimal.slog(curr, 1e10);
prev = Decimal.slog(prev, 1e10); prev = Decimal.slog(prev, 1e10);
oomps.value = curr.sub(prev).div(diff); oomps.value = curr.sub(prev).div(diff);

View file

@ -18,7 +18,7 @@ export interface TabOptions {
display: Computable<CoercableComponent>; display: Computable<CoercableComponent>;
} }
interface BaseTab { export interface BaseTab {
id: string; id: string;
type: typeof TabType; type: typeof TabType;
[Component]: typeof TabComponent; [Component]: typeof TabComponent;

View file

@ -34,7 +34,7 @@ export interface TabButtonOptions {
glowColor?: Computable<string>; glowColor?: Computable<string>;
} }
interface BaseTabButton { export interface BaseTabButton {
type: typeof TabButtonType; type: typeof TabButtonType;
[Component]: typeof TabButtonComponent; [Component]: typeof TabButtonComponent;
} }
@ -65,7 +65,7 @@ export interface TabFamilyOptions {
style?: Computable<StyleValue>; style?: Computable<StyleValue>;
} }
interface BaseTabFamily extends Persistent<string> { export interface BaseTabFamily extends Persistent<string> {
id: string; id: string;
activeTab: Ref<GenericTab | CoercableComponent | null>; activeTab: Ref<GenericTab | CoercableComponent | null>;
selected: Ref<string>; selected: Ref<string>;

View file

@ -14,8 +14,7 @@ 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 { persistent } from "game/persistence";
import { DecimalSource, format } from "util/bignum"; import Decimal, { DecimalSource, format, formatWhole } from "util/bignum";
import Decimal, { formatWhole } from "util/break_eternity";
import { import {
Computable, Computable,
convertComputable, convertComputable,
@ -137,7 +136,7 @@ export interface TreeOptions {
onReset?: (node: GenericTreeNode) => void; onReset?: (node: GenericTreeNode) => void;
} }
interface BaseTree { export interface BaseTree {
id: string; id: string;
links: Ref<Link[]>; links: Ref<Link[]>;
reset: (node: GenericTreeNode) => void; reset: (node: GenericTreeNode) => void;

View file

@ -31,7 +31,7 @@ import MarkNode from "components/MarkNode.vue";
import { jsx, StyleValue, Visibility } from "features/feature"; import { jsx, StyleValue, Visibility } from "features/feature";
import { displayResource, Resource } from "features/resources/resource"; import { displayResource, Resource } from "features/resources/resource";
import { GenericUpgrade } from "features/upgrades/upgrade"; import { GenericUpgrade } from "features/upgrades/upgrade";
import { DecimalSource } from "lib/break_eternity"; import { DecimalSource } from "util/bignum";
import { coerceComponent, isCoercableComponent, processedPropType, unwrapRef } from "util/vue"; import { coerceComponent, isCoercableComponent, processedPropType, unwrapRef } from "util/vue";
import { import {
Component, Component,

View file

@ -46,7 +46,7 @@ export interface UpgradeOptions {
onPurchase?: VoidFunction; onPurchase?: VoidFunction;
} }
interface BaseUpgrade extends Persistent<boolean> { export interface BaseUpgrade extends Persistent<boolean> {
id: string; id: string;
bought: Ref<boolean>; bought: Ref<boolean>;
canPurchase: Ref<boolean>; canPurchase: Ref<boolean>;

View file

@ -1,7 +1,7 @@
import projInfo from "data/projInfo.json"; import projInfo from "data/projInfo.json";
import Decimal, { DecimalSource } from "util/bignum"; import Decimal from "util/bignum";
import { createNanoEvents } from "nanoevents"; import { createNanoEvents } from "nanoevents";
import { App, Ref } from "vue"; import { App, Ref, watch } from "vue";
import { GenericLayer } from "./layers"; import { GenericLayer } from "./layers";
import player from "./player"; import player from "./player";
import settings, { Settings } from "./settings"; import settings, { Settings } from "./settings";
@ -10,8 +10,9 @@ import state from "./state";
export interface GlobalEvents { export interface GlobalEvents {
addLayer: (layer: GenericLayer, saveData: Record<string, unknown>) => void; addLayer: (layer: GenericLayer, saveData: Record<string, unknown>) => void;
removeLayer: (layer: GenericLayer) => void; removeLayer: (layer: GenericLayer) => void;
update: (diff: Decimal, trueDiff: number) => void; update: (diff: number, trueDiff: number) => void;
loadSettings: (settings: Partial<Settings>) => void; loadSettings: (settings: Partial<Settings>) => void;
gameWon: VoidFunction;
setupVue: (vue: App) => void; setupVue: (vue: App) => void;
} }
@ -25,7 +26,7 @@ let hasWon: null | Ref<boolean> = null;
function update() { function update() {
const now = Date.now(); const now = Date.now();
let diff: DecimalSource = (now - player.time) / 1e3; let diff = (now - player.time) / 1e3;
player.time = now; player.time = now;
const trueDiff = diff; const trueDiff = diff;
@ -43,7 +44,7 @@ function update() {
return; return;
} }
diff = new Decimal(diff).max(0); diff = Math.max(diff, 0);
if (player.devSpeed === 0) { if (player.devSpeed === 0) {
return; return;
@ -52,14 +53,14 @@ function update() {
// Add offline time if any // Add offline time if any
if (player.offlineTime != undefined) { if (player.offlineTime != undefined) {
if (Decimal.gt(player.offlineTime, projInfo.offlineLimit * 3600)) { if (Decimal.gt(player.offlineTime, projInfo.offlineLimit * 3600)) {
player.offlineTime = new Decimal(projInfo.offlineLimit * 3600); player.offlineTime = projInfo.offlineLimit * 3600;
} }
if (Decimal.gt(player.offlineTime, 0) && player.devSpeed !== 0) { if (Decimal.gt(player.offlineTime, 0) && player.devSpeed !== 0) {
const offlineDiff = Decimal.div(player.offlineTime, 10).max(diff); const offlineDiff = Math.max(player.offlineTime / 10, diff);
player.offlineTime = Decimal.sub(player.offlineTime, offlineDiff); player.offlineTime = player.offlineTime - offlineDiff;
diff = diff.add(offlineDiff); diff += offlineDiff;
} else if (player.devSpeed === 0) { } else if (player.devSpeed === 0) {
player.offlineTime = Decimal.add(player.offlineTime, diff); player.offlineTime += diff;
} }
if (!player.offlineProd || Decimal.lt(player.offlineTime, 0)) { if (!player.offlineProd || Decimal.lt(player.offlineTime, 0)) {
player.offlineTime = null; player.offlineTime = null;
@ -67,18 +68,26 @@ function update() {
} }
// Cap at max tick length // Cap at max tick length
diff = Decimal.min(diff, projInfo.maxTickLength); diff = Math.min(diff, projInfo.maxTickLength);
// Apply dev speed // Apply dev speed
if (player.devSpeed != undefined) { if (player.devSpeed != undefined) {
diff = diff.times(player.devSpeed); diff *= player.devSpeed;
}
if (!Number.isFinite(diff)) {
diff = 1e308;
} }
// Update // Update
if (diff.eq(0)) { if (Decimal.eq(diff, 0)) {
return; return;
} }
player.timePlayed = Decimal.add(player.timePlayed, diff);
player.timePlayed += diff;
if (!Number.isFinite(player.timePlayed)) {
player.timePlayed = 1e308;
}
globalBus.emit("update", diff, trueDiff); globalBus.emit("update", diff, trueDiff);
if (settings.unthrottled) { if (settings.unthrottled) {
@ -94,6 +103,11 @@ function update() {
export async function startGameLoop() { export async function startGameLoop() {
hasWon = (await import("data/projEntry")).hasWon; hasWon = (await import("data/projEntry")).hasWon;
watch(hasWon, hasWon => {
if (hasWon) {
globalBus.emit("gameWon");
}
});
if (settings.unthrottled) { if (settings.unthrottled) {
requestAnimationFrame(update); requestAnimationFrame(update);
} else { } else {

View file

@ -8,7 +8,6 @@ import {
StyleValue StyleValue
} from "features/feature"; } from "features/feature";
import { Link } from "features/links"; import { Link } from "features/links";
import Decimal from "util/bignum";
import { import {
Computable, Computable,
GetComputableType, GetComputableType,
@ -25,11 +24,11 @@ import player from "./player";
export interface LayerEvents { export interface LayerEvents {
// Generation // Generation
preUpdate: (diff: Decimal) => void; preUpdate: (diff: number) => void;
// Actions (e.g. automation) // Actions (e.g. automation)
update: (diff: Decimal) => void; update: (diff: number) => void;
// Effects (e.g. milestones) // Effects (e.g. milestones)
postUpdate: (diff: Decimal) => void; postUpdate: (diff: number) => void;
} }
export const layers: Record<string, Readonly<GenericLayer> | undefined> = {}; export const layers: Record<string, Readonly<GenericLayer> | undefined> = {};

View file

@ -1,4 +1,4 @@
import Decimal, { DecimalSource } from "util/bignum"; import Decimal from "util/bignum";
import { isPlainObject } from "util/common"; import { isPlainObject } from "util/common";
import { ProxiedWithState, ProxyPath, ProxyState } from "util/proxies"; import { ProxiedWithState, ProxyPath, ProxyState } from "util/proxies";
import { reactive, unref } from "vue"; import { reactive, unref } from "vue";
@ -6,14 +6,14 @@ import transientState from "./state";
export interface PlayerData { export interface PlayerData {
id: string; id: string;
devSpeed: DecimalSource | null; devSpeed: number | null;
name: string; name: string;
tabs: Array<string>; tabs: Array<string>;
time: number; time: number;
autosave: boolean; autosave: boolean;
offlineProd: boolean; offlineProd: boolean;
offlineTime: DecimalSource | null; offlineTime: number | null;
timePlayed: DecimalSource; timePlayed: number;
keepGoing: boolean; keepGoing: boolean;
modID: string; modID: string;
modVersion: string; modVersion: string;
@ -31,7 +31,7 @@ const state = reactive<PlayerData>({
autosave: true, autosave: true,
offlineProd: true, offlineProd: true,
offlineTime: null, offlineTime: null,
timePlayed: new Decimal(0), timePlayed: 0,
keepGoing: false, keepGoing: false,
modID: "", modID: "",
modVersion: "", modVersion: "",

View file

@ -59,7 +59,11 @@ export const hardResetSettings = (window.hardResetSettings = () => {
}); });
export const settingFields: CoercableComponent[] = reactive([]); export const settingFields: CoercableComponent[] = reactive([]);
export function registerSettingField(component: CoercableComponent) { export function registerSettingField(component: CoercableComponent) {
settingFields.push(component); settingFields.push(component);
} }
export const infoComponents: CoercableComponent[] = reactive([]);
export function registerInfoComponent(component: CoercableComponent) {
infoComponents.push(component);
}

1
src/lib/vue-panzoom.d.ts vendored Normal file
View file

@ -0,0 +1 @@
declare module 'vue-panzoom';

1
src/lib/vue-textarea-autosize.d.ts vendored Normal file
View file

@ -0,0 +1 @@
declare module 'vue-textarea-autosize';

View file

@ -5,7 +5,7 @@ import { GenericLayer } from "./game/layers";
import { PlayerData } from "./game/player"; import { PlayerData } from "./game/player";
import { Settings } from "./game/settings"; import { Settings } from "./game/settings";
import { Transient } from "./game/state"; import { Transient } from "./game/state";
import Decimal, { DecimalSource } from "./lib/break_eternity"; import Decimal, { DecimalSource } from "./util/bignum";
import { load } from "./util/save"; import { load } from "./util/save";
document.title = projInfo.title; document.title = projInfo.title;

View file

@ -15,9 +15,9 @@ export type GetComputableType<T> = T extends { [DoNotCache]: true }
export type GetComputableTypeWithDefault<T, S> = undefined extends T export type GetComputableTypeWithDefault<T, S> = undefined extends T
? S ? S
: GetComputableType<NonNullable<T>>; : GetComputableType<NonNullable<T>>;
type UnwrapComputableType<T> = T extends Ref<infer S> ? S : T extends () => infer S ? S : T; export type UnwrapComputableType<T> = T extends Ref<infer S> ? S : T extends () => infer S ? S : T;
type ComputableKeysOf<T> = Pick< export type ComputableKeysOf<T> = Pick<
T, T,
{ {
[K in keyof T]: T[K] extends Computable<unknown> ? K : never; [K in keyof T]: T[K] extends Computable<unknown> ? K : never;

View file

@ -1,7 +1,6 @@
import projInfo from "data/projInfo.json"; import projInfo from "data/projInfo.json";
import player, { Player, PlayerData, stringifySave } from "game/player"; import player, { Player, PlayerData, stringifySave } from "game/player";
import settings, { loadSettings } from "game/settings"; import settings, { loadSettings } from "game/settings";
import Decimal from "./bignum";
import { ProxyState } from "./proxies"; import { ProxyState } from "./proxies";
export function setupInitialStore(player: Partial<PlayerData> = {}): Player { export function setupInitialStore(player: Partial<PlayerData> = {}): Player {
@ -13,8 +12,8 @@ export function setupInitialStore(player: Partial<PlayerData> = {}): Player {
time: Date.now(), time: Date.now(),
autosave: true, autosave: true,
offlineProd: true, offlineProd: true,
offlineTime: new Decimal(0), offlineTime: 0,
timePlayed: new Decimal(0), timePlayed: 0,
keepGoing: false, keepGoing: false,
modID: projInfo.id, modID: projInfo.id,
modVersion: projInfo.versionNumber, modVersion: projInfo.versionNumber,
@ -85,11 +84,8 @@ export async function loadSave(playerObj: Partial<PlayerData>): Promise<void> {
playerObj = setupInitialStore(playerObj); playerObj = setupInitialStore(playerObj);
if (playerObj.offlineProd && playerObj.time) { if (playerObj.offlineProd && playerObj.time) {
if (playerObj.offlineTime == undefined) playerObj.offlineTime = new Decimal(0); if (playerObj.offlineTime == undefined) playerObj.offlineTime = 0;
playerObj.offlineTime = Decimal.add( playerObj.offlineTime += (Date.now() - playerObj.time) / 1000;
playerObj.offlineTime,
(Date.now() - playerObj.time) / 1000
);
} }
playerObj.time = Date.now(); playerObj.time = Date.now();
if (playerObj.modVersion !== projInfo.versionNumber) { if (playerObj.modVersion !== projInfo.versionNumber) {

View file

@ -14,6 +14,7 @@ import {
DefineComponent, DefineComponent,
defineComponent, defineComponent,
isRef, isRef,
onUnmounted,
PropType, PropType,
ref, ref,
Ref, Ref,
@ -113,6 +114,8 @@ export function setupHoldToClick(
} }
} }
onUnmounted(stop);
return { start, stop, handleHolding }; return { start, stop, handleHolding };
} }
@ -154,7 +157,7 @@ export function setRefValue<T>(ref: Ref<T | Ref<T>>, value: T) {
} }
} }
type PropTypes = export type PropTypes =
| typeof Boolean | typeof Boolean
| typeof String | typeof String
| typeof Number | typeof Number

View file

@ -35,5 +35,15 @@
], ],
"exclude": [ "exclude": [
"node_modules" "node_modules"
] ],
"typedocOptions": {
"entryPoints": ["src"],
"entryPointStrategy": "expand",
"cleanOutputDir": true,
"name": "Profectus",
"includeVersion": true,
"categorizeByGroup": false,
"readme": "none",
"out": "../docs/api"
}
} }