Merge remote-tracking branch 'template/main'
This commit is contained in:
commit
a2e490c291
39 changed files with 342 additions and 212 deletions
37
CHANGELOG.md
37
CHANGELOG.md
|
@ -6,6 +6,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [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
|
||||
### Added
|
||||
- Configuration for Glitch projects
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "profectus",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "vue-cli-service serve",
|
||||
|
|
|
@ -44,17 +44,21 @@
|
|||
</div>
|
||||
<br />
|
||||
<div>Time Played: {{ timePlayed }}</div>
|
||||
<component :is="infoComponent" />
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script setup lang="tsx">
|
||||
import Modal from "components/Modal.vue";
|
||||
import type Changelog from "data/Changelog.vue";
|
||||
import projInfo from "data/projInfo.json";
|
||||
import { jsx } from "features/feature";
|
||||
import player from "game/player";
|
||||
import { infoComponents } from "game/settings";
|
||||
import { formatTime } from "util/bignum";
|
||||
import { coerceComponent, render } from "util/vue";
|
||||
import { computed, ref, toRefs, unref } from "vue";
|
||||
|
||||
const { title, logo, author, discordName, discordLink, versionNumber, versionTitle } = projInfo;
|
||||
|
@ -66,6 +70,10 @@ const isOpen = ref(false);
|
|||
|
||||
const timePlayed = computed(() => formatTime(player.timePlayed));
|
||||
|
||||
const infoComponent = computed(() => {
|
||||
return coerceComponent(jsx(() => <>{infoComponents.map(render)}</>));
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
open() {
|
||||
isOpen.value = true;
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
</div>
|
||||
<br />
|
||||
<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 v-slot:footer>
|
||||
<div class="nan-footer">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<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 @click="changelog?.open()" class="version-container">
|
||||
<Tooltip display="Changelog" bottom class="version"
|
||||
|
@ -141,6 +141,11 @@ function openDiscord() {
|
|||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav > .banner {
|
||||
height: 100%;
|
||||
width: unset;
|
||||
}
|
||||
|
||||
.overlay-nav {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
|
|
|
@ -13,23 +13,24 @@
|
|||
<Toggle title="Unthrottled" v-model="unthrottled" />
|
||||
<Toggle :title="offlineProdTitle" v-model="offlineProd" />
|
||||
<Toggle :title="autosaveTitle" v-model="autosave" />
|
||||
<Toggle :title="isPausedTitle" v-model="isPaused" />
|
||||
<Toggle v-if="projInfo.enablePausing" :title="isPausedTitle" v-model="isPaused" />
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import Modal from "components/Modal.vue";
|
||||
import projInfo from "data/projInfo.json";
|
||||
import rawThemes from "data/themes";
|
||||
import { jsx } from "features/feature";
|
||||
import player from "game/player";
|
||||
import settings, { settingFields } from "game/settings";
|
||||
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 { 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);
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ export interface ResetButtonOptions extends ClickableOptions {
|
|||
canClick?: Computable<boolean>;
|
||||
}
|
||||
|
||||
type ResetButton<T extends ResetButtonOptions> = Replace<
|
||||
export type ResetButton<T extends ResetButtonOptions> = Replace<
|
||||
Clickable<T>,
|
||||
{
|
||||
resetDescription: GetComputableTypeWithDefault<T["resetDescription"], Ref<string>>;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* @module
|
||||
* @hidden
|
||||
*/
|
||||
import { main } from "data/projEntry";
|
||||
import { createCumulativeConversion, createPolynomialScaling } from "features/conversion";
|
||||
import { jsx } from "features/feature";
|
||||
|
@ -5,7 +9,7 @@ import { createReset } from "features/reset";
|
|||
import MainDisplay from "features/resources/MainDisplay.vue";
|
||||
import { createResource } from "features/resources/resource";
|
||||
import { createLayer } from "game/layers";
|
||||
import { DecimalSource } from "lib/break_eternity";
|
||||
import { DecimalSource } from "util/bignum";
|
||||
import { render } from "util/vue";
|
||||
import { createLayerTreeNode, createResetButton } from "../common";
|
||||
|
||||
|
|
|
@ -6,14 +6,16 @@ import { branchedResetPropagation, createTree, GenericTree } from "features/tree
|
|||
import { globalBus } from "game/events";
|
||||
import { createLayer, GenericLayer, setupLayerModal } from "game/layers";
|
||||
import player, { PlayerData } from "game/player";
|
||||
import { DecimalSource } from "lib/break_eternity";
|
||||
import Decimal, { format, formatTime } from "util/bignum";
|
||||
import Decimal, { DecimalSource, format, formatTime } from "util/bignum";
|
||||
import { render } from "util/vue";
|
||||
import { computed, toRaw } from "vue";
|
||||
import a from "./layers/aca/a";
|
||||
import c from "./layers/aca/c";
|
||||
import f from "./layers/aca/f";
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
export const main = createLayer(() => {
|
||||
const points = createResource<DecimalSource>(10);
|
||||
const best = trackBest(points);
|
||||
|
|
|
@ -17,5 +17,6 @@
|
|||
"initialTabs": [ "main", "c" ],
|
||||
|
||||
"maxTickLength": 3600,
|
||||
"offlineLimit": 1
|
||||
"offlineLimit": 1,
|
||||
"enablePausing": true
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
interface ThemeVars {
|
||||
export interface ThemeVars {
|
||||
"--foreground": string;
|
||||
"--background": string;
|
||||
"--feature-foreground": string;
|
||||
|
|
|
@ -2,7 +2,6 @@ import AchievementComponent from "features/achievements/Achievement.vue";
|
|||
import {
|
||||
CoercableComponent,
|
||||
Component,
|
||||
findFeatures,
|
||||
GatherProps,
|
||||
getUniqueID,
|
||||
Replace,
|
||||
|
@ -10,7 +9,6 @@ import {
|
|||
StyleValue,
|
||||
Visibility
|
||||
} from "features/feature";
|
||||
import { globalBus } from "game/events";
|
||||
import "game/notifications";
|
||||
import { Persistent, makePersistent, PersistentState } from "game/persistence";
|
||||
import {
|
||||
|
@ -22,15 +20,16 @@ import {
|
|||
} from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { coerceComponent } from "util/vue";
|
||||
import { Unsubscribe } from "nanoevents";
|
||||
import { Ref, unref } from "vue";
|
||||
import { Ref, unref, watchEffect } from "vue";
|
||||
import { useToast } from "vue-toastification";
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
export const AchievementType = Symbol("Achievement");
|
||||
|
||||
export interface AchievementOptions {
|
||||
visibility?: Computable<Visibility>;
|
||||
shouldEarn?: Computable<boolean>;
|
||||
shouldEarn?: () => boolean;
|
||||
display?: Computable<CoercableComponent>;
|
||||
mark?: Computable<boolean | string>;
|
||||
image?: Computable<string>;
|
||||
|
@ -39,7 +38,7 @@ export interface AchievementOptions {
|
|||
onComplete?: VoidFunction;
|
||||
}
|
||||
|
||||
interface BaseAchievement extends Persistent<boolean> {
|
||||
export interface BaseAchievement extends Persistent<boolean> {
|
||||
id: string;
|
||||
earned: Ref<boolean>;
|
||||
complete: VoidFunction;
|
||||
|
@ -52,7 +51,6 @@ export type Achievement<T extends AchievementOptions> = Replace<
|
|||
T & BaseAchievement,
|
||||
{
|
||||
visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>;
|
||||
shouldEarn: GetComputableType<T["shouldEarn"]>;
|
||||
display: GetComputableType<T["display"]>;
|
||||
mark: GetComputableType<T["mark"]>;
|
||||
image: GetComputableType<T["image"]>;
|
||||
|
@ -85,7 +83,6 @@ export function createAchievement<T extends AchievementOptions>(
|
|||
|
||||
processComputable(achievement as T, "visibility");
|
||||
setDefault(achievement, "visibility", Visibility.Visible);
|
||||
processComputable(achievement as T, "shouldEarn");
|
||||
processComputable(achievement as T, "display");
|
||||
processComputable(achievement as T, "mark");
|
||||
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 achievement as unknown as Achievement<T>;
|
||||
});
|
||||
}
|
||||
|
||||
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 (achievement.shouldEarn) {
|
||||
const genericAchievement = achievement as GenericAchievement;
|
||||
watchEffect(() => {
|
||||
if (
|
||||
unref(achievement.visibility) === Visibility.Visible &&
|
||||
!unref(achievement.earned) &&
|
||||
unref(achievement.shouldEarn)
|
||||
!genericAchievement.earned.value &&
|
||||
unref(genericAchievement.visibility) === Visibility.Visible &&
|
||||
genericAchievement.shouldEarn?.()
|
||||
) {
|
||||
achievement[PersistentState].value = true;
|
||||
achievement.onComplete?.();
|
||||
if (achievement.display) {
|
||||
const Display = coerceComponent(unref(achievement.display));
|
||||
genericAchievement.earned.value = true;
|
||||
genericAchievement.onComplete?.();
|
||||
if (genericAchievement.display) {
|
||||
const Display = coerceComponent(unref(genericAchievement.display));
|
||||
toast.info(
|
||||
<div>
|
||||
<h3>Achievement earned!</h3>
|
||||
|
@ -133,11 +119,8 @@ globalBus.on("addLayer", layer => {
|
|||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
globalBus.on("removeLayer", layer => {
|
||||
// unsubscribe from postUpdate
|
||||
listeners[layer.id]?.();
|
||||
listeners[layer.id] = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
return achievement as unknown as Achievement<T>;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
StyleValue,
|
||||
Visibility
|
||||
} from "features/feature";
|
||||
import { DecimalSource } from "lib/break_eternity";
|
||||
import { DecimalSource } from "util/bignum";
|
||||
import {
|
||||
Computable,
|
||||
GetComputableType,
|
||||
|
@ -45,7 +45,7 @@ export interface BarOptions {
|
|||
mark?: Computable<boolean | string>;
|
||||
}
|
||||
|
||||
interface BaseBar {
|
||||
export interface BaseBar {
|
||||
id: string;
|
||||
type: typeof BarType;
|
||||
[Component]: typeof BarComponent;
|
||||
|
|
|
@ -11,7 +11,6 @@ import {
|
|||
} from "features/feature";
|
||||
import { globalBus } from "game/events";
|
||||
import { State, Persistent, makePersistent, PersistentState } from "game/persistence";
|
||||
import Decimal, { DecimalSource } from "lib/break_eternity";
|
||||
import { isFunction } from "util/common";
|
||||
import {
|
||||
Computable,
|
||||
|
@ -85,7 +84,7 @@ export interface NodeTypeOptions {
|
|||
actionDistance?: NodeComputable<number>;
|
||||
onClick?: (node: BoardNode) => void;
|
||||
onDrop?: (node: BoardNode, otherNode: BoardNode) => void;
|
||||
update?: (node: BoardNode, diff: DecimalSource) => void;
|
||||
update?: (node: BoardNode, diff: number) => void;
|
||||
}
|
||||
|
||||
export interface BaseNodeType {
|
||||
|
@ -167,7 +166,7 @@ export interface BoardOptions {
|
|||
types: Record<string, NodeTypeOptions>;
|
||||
}
|
||||
|
||||
interface BaseBoard extends Persistent<BoardData> {
|
||||
export interface BaseBoard extends Persistent<BoardData> {
|
||||
id: string;
|
||||
links: Ref<BoardNodeLink[] | null>;
|
||||
nodes: Ref<BoardNode[]>;
|
||||
|
@ -348,7 +347,7 @@ export function getUniqueNodeID(board: GenericBoard): number {
|
|||
const listeners: Record<string, Unsubscribe | undefined> = {};
|
||||
globalBus.on("addLayer", layer => {
|
||||
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 => {
|
||||
Object.values(board.types).forEach(type =>
|
||||
type.nodes.value.forEach(node => type.update?.(node, diff))
|
||||
|
|
|
@ -26,7 +26,7 @@ import {
|
|||
|
||||
export const BuyableType = Symbol("Buyable");
|
||||
|
||||
type BuyableDisplay =
|
||||
export type BuyableDisplay =
|
||||
| CoercableComponent
|
||||
| {
|
||||
title?: CoercableComponent;
|
||||
|
@ -48,7 +48,7 @@ export interface BuyableOptions {
|
|||
onPurchase?: (cost: DecimalSource) => void;
|
||||
}
|
||||
|
||||
interface BaseBuyable extends Persistent<DecimalSource> {
|
||||
export interface BaseBuyable extends Persistent<DecimalSource> {
|
||||
id: string;
|
||||
amount: Ref<DecimalSource>;
|
||||
maxed: Ref<boolean>;
|
||||
|
@ -68,7 +68,7 @@ export type Buyable<T extends BuyableOptions> = Replace<
|
|||
cost: GetComputableType<T["cost"]>;
|
||||
resource: GetComputableType<T["resource"]>;
|
||||
canPurchase: GetComputableTypeWithDefault<T["canPurchase"], Ref<boolean>>;
|
||||
purchaseLimit: GetComputableTypeWithDefault<T["purchaseLimit"], 1>;
|
||||
purchaseLimit: GetComputableTypeWithDefault<T["purchaseLimit"], Decimal>;
|
||||
classes: GetComputableType<T["classes"]>;
|
||||
style: GetComputableType<T["style"]>;
|
||||
mark: GetComputableType<T["mark"]>;
|
||||
|
@ -172,6 +172,16 @@ export function createBuyable<T extends BuyableOptions>(
|
|||
const Title = coerceComponent(currDisplay.title || "", "h3");
|
||||
const Description = coerceComponent(currDisplay.description);
|
||||
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 (
|
||||
<span>
|
||||
{currDisplay.title ? (
|
||||
|
@ -182,8 +192,7 @@ export function createBuyable<T extends BuyableOptions>(
|
|||
<Description />
|
||||
<div>
|
||||
<br />
|
||||
Amount: {formatWhole(genericBuyable.amount.value)} /{" "}
|
||||
{formatWhole(unref(genericBuyable.purchaseLimit))}
|
||||
{amountDisplay}
|
||||
</div>
|
||||
{currDisplay.effectDisplay ? (
|
||||
<div>
|
||||
|
@ -209,7 +218,7 @@ export function createBuyable<T extends BuyableOptions>(
|
|||
processComputable(buyable as T, "cost");
|
||||
processComputable(buyable as T, "resource");
|
||||
processComputable(buyable as T, "purchaseLimit");
|
||||
setDefault(buyable, "purchaseLimit", 1);
|
||||
setDefault(buyable, "purchaseLimit", Decimal.dInf);
|
||||
processComputable(buyable as T, "style");
|
||||
processComputable(buyable as T, "mark");
|
||||
processComputable(buyable as T, "small");
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { isArray } from "@vue/shared";
|
||||
import Toggle from "components/fields/Toggle.vue";
|
||||
import ChallengeComponent from "features/challenges/Challenge.vue";
|
||||
import {
|
||||
|
@ -25,7 +26,7 @@ import {
|
|||
ProcessedComputable
|
||||
} from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { computed, Ref, unref } from "vue";
|
||||
import { computed, Ref, unref, watch, WatchStopHandle } from "vue";
|
||||
|
||||
export const ChallengeType = Symbol("ChallengeType");
|
||||
|
||||
|
@ -55,13 +56,14 @@ export interface ChallengeOptions {
|
|||
onEnter?: VoidFunction;
|
||||
}
|
||||
|
||||
interface BaseChallenge {
|
||||
export interface BaseChallenge {
|
||||
id: string;
|
||||
completions: PersistentRef<DecimalSource>;
|
||||
completed: Ref<boolean>;
|
||||
maxed: Ref<boolean>;
|
||||
active: PersistentRef<boolean>;
|
||||
toggle: VoidFunction;
|
||||
complete: (remainInChallenge?: boolean) => void;
|
||||
type: typeof ChallengeType;
|
||||
[Component]: typeof ChallengeComponent;
|
||||
[GatherProps]: () => Record<string, unknown>;
|
||||
|
@ -87,18 +89,12 @@ export type GenericChallenge = Replace<
|
|||
{
|
||||
visibility: ProcessedComputable<Visibility>;
|
||||
canStart: ProcessedComputable<boolean>;
|
||||
canComplete: ProcessedComputable<boolean>;
|
||||
canComplete: ProcessedComputable<boolean | DecimalSource>;
|
||||
completionLimit: ProcessedComputable<DecimalSource>;
|
||||
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>(
|
||||
optionsFunc: () => T & ThisType<Challenge<T>>
|
||||
): Challenge<T> {
|
||||
|
@ -134,11 +130,7 @@ export function createChallenge<T extends ChallengeOptions>(
|
|||
challenge.toggle = function () {
|
||||
const genericChallenge = challenge as GenericChallenge;
|
||||
if (genericChallenge.active.value) {
|
||||
if (
|
||||
genericChallenge.canComplete &&
|
||||
unref(genericChallenge.canComplete) &&
|
||||
!genericChallenge.maxed.value
|
||||
) {
|
||||
if (unref(genericChallenge.canComplete) && !genericChallenge.maxed.value) {
|
||||
let completions: boolean | DecimalSource = unref(genericChallenge.canComplete);
|
||||
if (typeof completions === "boolean") {
|
||||
completions = 1;
|
||||
|
@ -152,12 +144,40 @@ export function createChallenge<T extends ChallengeOptions>(
|
|||
genericChallenge.active.value = false;
|
||||
genericChallenge.onExit?.();
|
||||
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.active.value = true;
|
||||
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");
|
||||
setDefault(challenge, "visibility", Visibility.Visible);
|
||||
const visibility = challenge.visibility as ProcessedComputable<Visibility>;
|
||||
|
@ -167,16 +187,6 @@ export function createChallenge<T extends ChallengeOptions>(
|
|||
}
|
||||
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) {
|
||||
challenge.canComplete = computed(() => {
|
||||
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" {
|
||||
interface Settings {
|
||||
hideChallenges: boolean;
|
||||
|
|
|
@ -9,6 +9,8 @@ import {
|
|||
StyleValue,
|
||||
Visibility
|
||||
} from "features/feature";
|
||||
import { GenericLayer } from "game/layers";
|
||||
import { Unsubscribe } from "nanoevents";
|
||||
import {
|
||||
Computable,
|
||||
GetComputableType,
|
||||
|
@ -17,7 +19,7 @@ import {
|
|||
ProcessedComputable
|
||||
} from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { unref } from "vue";
|
||||
import { computed, unref } from "vue";
|
||||
|
||||
export const ClickableType = Symbol("Clickable");
|
||||
|
||||
|
@ -39,7 +41,7 @@ export interface ClickableOptions {
|
|||
onHold?: VoidFunction;
|
||||
}
|
||||
|
||||
interface BaseClickable {
|
||||
export interface BaseClickable {
|
||||
id: string;
|
||||
type: typeof ClickableType;
|
||||
[Component]: typeof ClickableComponent;
|
||||
|
@ -131,3 +133,16 @@ export function createClickable<T extends ClickableOptions>(
|
|||
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?.();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ export interface ConversionOptions {
|
|||
modifyGainAmount?: (gain: DecimalSource) => DecimalSource;
|
||||
}
|
||||
|
||||
interface BaseConversion {
|
||||
export interface BaseConversion {
|
||||
convert: VoidFunction;
|
||||
}
|
||||
|
||||
|
@ -206,7 +206,7 @@ export function setupPassiveGeneration(
|
|||
conversion: GenericConversion,
|
||||
rate: ProcessedComputable<DecimalSource> = 1
|
||||
): void {
|
||||
layer.on("preUpdate", (diff: Decimal) => {
|
||||
layer.on("preUpdate", diff => {
|
||||
const currRate = isRef(rate) ? rate.value : rate;
|
||||
if (Decimal.neq(currRate, 0)) {
|
||||
conversion.gainResource.value = Decimal.add(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { hasWon } from "data/projEntry";
|
||||
import { globalBus } from "game/events";
|
||||
import player from "game/player";
|
||||
import { registerInfoComponent } from "game/settings";
|
||||
import {
|
||||
Computable,
|
||||
GetComputableTypeWithDefault,
|
||||
|
@ -9,10 +10,10 @@ import {
|
|||
processComputable
|
||||
} from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { unref } from "vue";
|
||||
import { findFeatures, Replace, setDefault } from "./feature";
|
||||
import { shallowReactive, unref } from "vue";
|
||||
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 interface HotkeyOptions {
|
||||
|
@ -22,7 +23,7 @@ export interface HotkeyOptions {
|
|||
onPress: VoidFunction;
|
||||
}
|
||||
|
||||
interface BaseHotkey {
|
||||
export interface BaseHotkey {
|
||||
type: typeof HotkeyType;
|
||||
}
|
||||
|
||||
|
@ -88,3 +89,23 @@ document.onkeydown = function (e) {
|
|||
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>
|
||||
);
|
||||
})
|
||||
);
|
|
@ -33,7 +33,7 @@ export interface InfoboxOptions {
|
|||
display: Computable<CoercableComponent>;
|
||||
}
|
||||
|
||||
interface BaseInfobox extends Persistent<boolean> {
|
||||
export interface BaseInfobox extends Persistent<boolean> {
|
||||
id: string;
|
||||
collapsed: Ref<boolean>;
|
||||
type: typeof InfoboxType;
|
||||
|
|
|
@ -2,7 +2,6 @@ import Select from "components/fields/Select.vue";
|
|||
import {
|
||||
CoercableComponent,
|
||||
Component,
|
||||
findFeatures,
|
||||
GatherProps,
|
||||
getUniqueID,
|
||||
jsx,
|
||||
|
@ -26,10 +25,11 @@ import {
|
|||
} from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { coerceComponent, isCoercableComponent } from "util/vue";
|
||||
import { Unsubscribe } from "nanoevents";
|
||||
import { computed, Ref, unref } from "vue";
|
||||
import { computed, Ref, unref, watchEffect } from "vue";
|
||||
import { useToast } from "vue-toastification";
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
export const MilestoneType = Symbol("Milestone");
|
||||
|
||||
export enum MilestoneDisplay {
|
||||
|
@ -42,7 +42,7 @@ export enum MilestoneDisplay {
|
|||
|
||||
export interface MilestoneOptions {
|
||||
visibility?: Computable<Visibility>;
|
||||
shouldEarn: Computable<boolean>;
|
||||
shouldEarn?: () => boolean;
|
||||
style?: Computable<StyleValue>;
|
||||
classes?: Computable<Record<string, boolean>>;
|
||||
display?: Computable<
|
||||
|
@ -56,9 +56,10 @@ export interface MilestoneOptions {
|
|||
onComplete?: VoidFunction;
|
||||
}
|
||||
|
||||
interface BaseMilestone extends Persistent<boolean> {
|
||||
export interface BaseMilestone extends Persistent<boolean> {
|
||||
id: string;
|
||||
earned: Ref<boolean>;
|
||||
complete: VoidFunction;
|
||||
type: typeof MilestoneType;
|
||||
[Component]: typeof MilestoneComponent;
|
||||
[GatherProps]: () => Record<string, unknown>;
|
||||
|
@ -68,7 +69,6 @@ export type Milestone<T extends MilestoneOptions> = Replace<
|
|||
T & BaseMilestone,
|
||||
{
|
||||
visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>;
|
||||
shouldEarn: GetComputableType<T["shouldEarn"]>;
|
||||
style: GetComputableType<T["style"]>;
|
||||
classes: GetComputableType<T["classes"]>;
|
||||
display: GetComputableType<T["display"]>;
|
||||
|
@ -93,6 +93,10 @@ export function createMilestone<T extends MilestoneOptions>(
|
|||
milestone[Component] = MilestoneComponent;
|
||||
|
||||
milestone.earned = milestone[PersistentState];
|
||||
milestone.complete = function () {
|
||||
milestone[PersistentState].value = true;
|
||||
};
|
||||
|
||||
processComputable(milestone as T, "visibility");
|
||||
setDefault(milestone, "visibility", Visibility.Visible);
|
||||
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, "classes");
|
||||
processComputable(milestone as T, "display");
|
||||
|
@ -134,52 +137,40 @@ export function createMilestone<T extends MilestoneOptions>(
|
|||
return { visibility, display, style, classes, earned, id };
|
||||
};
|
||||
|
||||
if (milestone.shouldEarn) {
|
||||
const genericMilestone = milestone as GenericMilestone;
|
||||
watchEffect(() => {
|
||||
if (
|
||||
!genericMilestone.earned.value &&
|
||||
unref(genericMilestone.visibility) === Visibility.Visible &&
|
||||
genericMilestone.shouldEarn?.()
|
||||
) {
|
||||
genericMilestone.earned.value = true;
|
||||
genericMilestone.onComplete?.();
|
||||
if (genericMilestone.display) {
|
||||
const display = unref(genericMilestone.display);
|
||||
const Display = coerceComponent(
|
||||
isCoercableComponent(display) ? display : display.requirement
|
||||
);
|
||||
toast(
|
||||
<>
|
||||
<h3>Milestone earned!</h3>
|
||||
<div>
|
||||
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
|
||||
{/* @ts-ignore */}
|
||||
<Display />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return milestone as unknown as Milestone<T>;
|
||||
});
|
||||
}
|
||||
|
||||
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 (
|
||||
unref(milestone.visibility) === Visibility.Visible &&
|
||||
!milestone.earned.value &&
|
||||
unref(milestone.shouldEarn)
|
||||
) {
|
||||
milestone[PersistentState].value = true;
|
||||
milestone.onComplete?.();
|
||||
if (milestone.display) {
|
||||
const display = unref(milestone.display);
|
||||
const Display = coerceComponent(
|
||||
isCoercableComponent(display) ? display : display.requirement
|
||||
);
|
||||
toast(
|
||||
<>
|
||||
<h3>Milestone earned!</h3>
|
||||
<div>
|
||||
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
|
||||
{/* @ts-ignore */}
|
||||
<Display />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
globalBus.on("removeLayer", layer => {
|
||||
// unsubscribe from postUpdate
|
||||
listeners[layer.id]?.();
|
||||
listeners[layer.id] = undefined;
|
||||
});
|
||||
|
||||
declare module "game/settings" {
|
||||
interface Settings {
|
||||
msDisplay: MilestoneDisplay;
|
||||
|
|
|
@ -8,11 +8,11 @@ import {
|
|||
PersistentRef,
|
||||
PersistentState
|
||||
} from "game/persistence";
|
||||
import Decimal from "lib/break_eternity";
|
||||
import Decimal from "util/bignum";
|
||||
import { Computable, GetComputableType, processComputable } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { Unsubscribe } from "nanoevents";
|
||||
import { computed, isRef, unref } from "vue";
|
||||
import { isRef, unref } from "vue";
|
||||
|
||||
export const ResetType = Symbol("Reset");
|
||||
|
||||
|
@ -21,7 +21,7 @@ export interface ResetOptions {
|
|||
onReset?: VoidFunction;
|
||||
}
|
||||
|
||||
interface BaseReset {
|
||||
export interface BaseReset {
|
||||
id: string;
|
||||
reset: VoidFunction;
|
||||
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> = {};
|
||||
export function trackResetTime(layer: GenericLayer, reset: GenericReset): PersistentRef<Decimal> {
|
||||
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);
|
||||
});
|
||||
globalBus.on("reset", currentReset => {
|
||||
|
|
|
@ -42,6 +42,8 @@ export function trackTotal(resource: Resource): Ref<DecimalSource> {
|
|||
return total;
|
||||
}
|
||||
|
||||
const tetra8 = new Decimal("10^^8");
|
||||
const e100 = new Decimal("1e100");
|
||||
export function trackOOMPS(
|
||||
resource: Resource,
|
||||
pointGain?: ComputedRef<DecimalSource>
|
||||
|
@ -52,7 +54,7 @@ export function trackOOMPS(
|
|||
|
||||
globalBus.on("update", diff => {
|
||||
oompsMag.value = 0;
|
||||
if (Decimal.lte(resource.value, 1e100)) {
|
||||
if (Decimal.lte(resource.value, e100)) {
|
||||
lastPoints.value = resource.value;
|
||||
return;
|
||||
}
|
||||
|
@ -61,7 +63,7 @@ export function trackOOMPS(
|
|||
let prev = lastPoints.value;
|
||||
lastPoints.value = curr;
|
||||
if (Decimal.gt(curr, prev)) {
|
||||
if (Decimal.gte(curr, "10^^8")) {
|
||||
if (Decimal.gte(curr, tetra8)) {
|
||||
curr = Decimal.slog(curr, 1e10);
|
||||
prev = Decimal.slog(prev, 1e10);
|
||||
oomps.value = curr.sub(prev).div(diff);
|
||||
|
|
|
@ -18,7 +18,7 @@ export interface TabOptions {
|
|||
display: Computable<CoercableComponent>;
|
||||
}
|
||||
|
||||
interface BaseTab {
|
||||
export interface BaseTab {
|
||||
id: string;
|
||||
type: typeof TabType;
|
||||
[Component]: typeof TabComponent;
|
||||
|
|
|
@ -34,7 +34,7 @@ export interface TabButtonOptions {
|
|||
glowColor?: Computable<string>;
|
||||
}
|
||||
|
||||
interface BaseTabButton {
|
||||
export interface BaseTabButton {
|
||||
type: typeof TabButtonType;
|
||||
[Component]: typeof TabButtonComponent;
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ export interface TabFamilyOptions {
|
|||
style?: Computable<StyleValue>;
|
||||
}
|
||||
|
||||
interface BaseTabFamily extends Persistent<string> {
|
||||
export interface BaseTabFamily extends Persistent<string> {
|
||||
id: string;
|
||||
activeTab: Ref<GenericTab | CoercableComponent | null>;
|
||||
selected: Ref<string>;
|
||||
|
|
|
@ -14,8 +14,7 @@ import { displayResource, Resource } from "features/resources/resource";
|
|||
import { Tooltip } from "features/tooltip";
|
||||
import TreeComponent from "features/trees/Tree.vue";
|
||||
import { persistent } from "game/persistence";
|
||||
import { DecimalSource, format } from "util/bignum";
|
||||
import Decimal, { formatWhole } from "util/break_eternity";
|
||||
import Decimal, { DecimalSource, format, formatWhole } from "util/bignum";
|
||||
import {
|
||||
Computable,
|
||||
convertComputable,
|
||||
|
@ -137,7 +136,7 @@ export interface TreeOptions {
|
|||
onReset?: (node: GenericTreeNode) => void;
|
||||
}
|
||||
|
||||
interface BaseTree {
|
||||
export interface BaseTree {
|
||||
id: string;
|
||||
links: Ref<Link[]>;
|
||||
reset: (node: GenericTreeNode) => void;
|
||||
|
|
|
@ -31,7 +31,7 @@ import MarkNode from "components/MarkNode.vue";
|
|||
import { jsx, StyleValue, Visibility } from "features/feature";
|
||||
import { displayResource, Resource } from "features/resources/resource";
|
||||
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 {
|
||||
Component,
|
||||
|
|
|
@ -46,7 +46,7 @@ export interface UpgradeOptions {
|
|||
onPurchase?: VoidFunction;
|
||||
}
|
||||
|
||||
interface BaseUpgrade extends Persistent<boolean> {
|
||||
export interface BaseUpgrade extends Persistent<boolean> {
|
||||
id: string;
|
||||
bought: Ref<boolean>;
|
||||
canPurchase: Ref<boolean>;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import projInfo from "data/projInfo.json";
|
||||
import Decimal, { DecimalSource } from "util/bignum";
|
||||
import Decimal from "util/bignum";
|
||||
import { createNanoEvents } from "nanoevents";
|
||||
import { App, Ref } from "vue";
|
||||
import { App, Ref, watch } from "vue";
|
||||
import { GenericLayer } from "./layers";
|
||||
import player from "./player";
|
||||
import settings, { Settings } from "./settings";
|
||||
|
@ -10,8 +10,9 @@ import state from "./state";
|
|||
export interface GlobalEvents {
|
||||
addLayer: (layer: GenericLayer, saveData: Record<string, unknown>) => void;
|
||||
removeLayer: (layer: GenericLayer) => void;
|
||||
update: (diff: Decimal, trueDiff: number) => void;
|
||||
update: (diff: number, trueDiff: number) => void;
|
||||
loadSettings: (settings: Partial<Settings>) => void;
|
||||
gameWon: VoidFunction;
|
||||
setupVue: (vue: App) => void;
|
||||
}
|
||||
|
||||
|
@ -25,7 +26,7 @@ let hasWon: null | Ref<boolean> = null;
|
|||
|
||||
function update() {
|
||||
const now = Date.now();
|
||||
let diff: DecimalSource = (now - player.time) / 1e3;
|
||||
let diff = (now - player.time) / 1e3;
|
||||
player.time = now;
|
||||
const trueDiff = diff;
|
||||
|
||||
|
@ -43,7 +44,7 @@ function update() {
|
|||
return;
|
||||
}
|
||||
|
||||
diff = new Decimal(diff).max(0);
|
||||
diff = Math.max(diff, 0);
|
||||
|
||||
if (player.devSpeed === 0) {
|
||||
return;
|
||||
|
@ -52,14 +53,14 @@ function update() {
|
|||
// Add offline time if any
|
||||
if (player.offlineTime != undefined) {
|
||||
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) {
|
||||
const offlineDiff = Decimal.div(player.offlineTime, 10).max(diff);
|
||||
player.offlineTime = Decimal.sub(player.offlineTime, offlineDiff);
|
||||
diff = diff.add(offlineDiff);
|
||||
const offlineDiff = Math.max(player.offlineTime / 10, diff);
|
||||
player.offlineTime = player.offlineTime - offlineDiff;
|
||||
diff += offlineDiff;
|
||||
} else if (player.devSpeed === 0) {
|
||||
player.offlineTime = Decimal.add(player.offlineTime, diff);
|
||||
player.offlineTime += diff;
|
||||
}
|
||||
if (!player.offlineProd || Decimal.lt(player.offlineTime, 0)) {
|
||||
player.offlineTime = null;
|
||||
|
@ -67,18 +68,26 @@ function update() {
|
|||
}
|
||||
|
||||
// Cap at max tick length
|
||||
diff = Decimal.min(diff, projInfo.maxTickLength);
|
||||
diff = Math.min(diff, projInfo.maxTickLength);
|
||||
|
||||
// Apply dev speed
|
||||
if (player.devSpeed != undefined) {
|
||||
diff = diff.times(player.devSpeed);
|
||||
diff *= player.devSpeed;
|
||||
}
|
||||
|
||||
if (!Number.isFinite(diff)) {
|
||||
diff = 1e308;
|
||||
}
|
||||
|
||||
// Update
|
||||
if (diff.eq(0)) {
|
||||
if (Decimal.eq(diff, 0)) {
|
||||
return;
|
||||
}
|
||||
player.timePlayed = Decimal.add(player.timePlayed, diff);
|
||||
|
||||
player.timePlayed += diff;
|
||||
if (!Number.isFinite(player.timePlayed)) {
|
||||
player.timePlayed = 1e308;
|
||||
}
|
||||
globalBus.emit("update", diff, trueDiff);
|
||||
|
||||
if (settings.unthrottled) {
|
||||
|
@ -94,6 +103,11 @@ function update() {
|
|||
|
||||
export async function startGameLoop() {
|
||||
hasWon = (await import("data/projEntry")).hasWon;
|
||||
watch(hasWon, hasWon => {
|
||||
if (hasWon) {
|
||||
globalBus.emit("gameWon");
|
||||
}
|
||||
});
|
||||
if (settings.unthrottled) {
|
||||
requestAnimationFrame(update);
|
||||
} else {
|
||||
|
|
|
@ -8,7 +8,6 @@ import {
|
|||
StyleValue
|
||||
} from "features/feature";
|
||||
import { Link } from "features/links";
|
||||
import Decimal from "util/bignum";
|
||||
import {
|
||||
Computable,
|
||||
GetComputableType,
|
||||
|
@ -25,11 +24,11 @@ import player from "./player";
|
|||
|
||||
export interface LayerEvents {
|
||||
// Generation
|
||||
preUpdate: (diff: Decimal) => void;
|
||||
preUpdate: (diff: number) => void;
|
||||
// Actions (e.g. automation)
|
||||
update: (diff: Decimal) => void;
|
||||
update: (diff: number) => void;
|
||||
// Effects (e.g. milestones)
|
||||
postUpdate: (diff: Decimal) => void;
|
||||
postUpdate: (diff: number) => void;
|
||||
}
|
||||
|
||||
export const layers: Record<string, Readonly<GenericLayer> | undefined> = {};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Decimal, { DecimalSource } from "util/bignum";
|
||||
import Decimal from "util/bignum";
|
||||
import { isPlainObject } from "util/common";
|
||||
import { ProxiedWithState, ProxyPath, ProxyState } from "util/proxies";
|
||||
import { reactive, unref } from "vue";
|
||||
|
@ -6,14 +6,14 @@ import transientState from "./state";
|
|||
|
||||
export interface PlayerData {
|
||||
id: string;
|
||||
devSpeed: DecimalSource | null;
|
||||
devSpeed: number | null;
|
||||
name: string;
|
||||
tabs: Array<string>;
|
||||
time: number;
|
||||
autosave: boolean;
|
||||
offlineProd: boolean;
|
||||
offlineTime: DecimalSource | null;
|
||||
timePlayed: DecimalSource;
|
||||
offlineTime: number | null;
|
||||
timePlayed: number;
|
||||
keepGoing: boolean;
|
||||
modID: string;
|
||||
modVersion: string;
|
||||
|
@ -31,7 +31,7 @@ const state = reactive<PlayerData>({
|
|||
autosave: true,
|
||||
offlineProd: true,
|
||||
offlineTime: null,
|
||||
timePlayed: new Decimal(0),
|
||||
timePlayed: 0,
|
||||
keepGoing: false,
|
||||
modID: "",
|
||||
modVersion: "",
|
||||
|
|
|
@ -59,7 +59,11 @@ export const hardResetSettings = (window.hardResetSettings = () => {
|
|||
});
|
||||
|
||||
export const settingFields: CoercableComponent[] = reactive([]);
|
||||
|
||||
export function registerSettingField(component: CoercableComponent) {
|
||||
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
1
src/lib/vue-panzoom.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
declare module 'vue-panzoom';
|
1
src/lib/vue-textarea-autosize.d.ts
vendored
Normal file
1
src/lib/vue-textarea-autosize.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
declare module 'vue-textarea-autosize';
|
|
@ -5,7 +5,7 @@ import { GenericLayer } from "./game/layers";
|
|||
import { PlayerData } from "./game/player";
|
||||
import { Settings } from "./game/settings";
|
||||
import { Transient } from "./game/state";
|
||||
import Decimal, { DecimalSource } from "./lib/break_eternity";
|
||||
import Decimal, { DecimalSource } from "./util/bignum";
|
||||
import { load } from "./util/save";
|
||||
|
||||
document.title = projInfo.title;
|
||||
|
|
|
@ -15,9 +15,9 @@ export type GetComputableType<T> = T extends { [DoNotCache]: true }
|
|||
export type GetComputableTypeWithDefault<T, S> = undefined extends T
|
||||
? S
|
||||
: 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,
|
||||
{
|
||||
[K in keyof T]: T[K] extends Computable<unknown> ? K : never;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import projInfo from "data/projInfo.json";
|
||||
import player, { Player, PlayerData, stringifySave } from "game/player";
|
||||
import settings, { loadSettings } from "game/settings";
|
||||
import Decimal from "./bignum";
|
||||
import { ProxyState } from "./proxies";
|
||||
|
||||
export function setupInitialStore(player: Partial<PlayerData> = {}): Player {
|
||||
|
@ -13,8 +12,8 @@ export function setupInitialStore(player: Partial<PlayerData> = {}): Player {
|
|||
time: Date.now(),
|
||||
autosave: true,
|
||||
offlineProd: true,
|
||||
offlineTime: new Decimal(0),
|
||||
timePlayed: new Decimal(0),
|
||||
offlineTime: 0,
|
||||
timePlayed: 0,
|
||||
keepGoing: false,
|
||||
modID: projInfo.id,
|
||||
modVersion: projInfo.versionNumber,
|
||||
|
@ -85,11 +84,8 @@ export async function loadSave(playerObj: Partial<PlayerData>): Promise<void> {
|
|||
|
||||
playerObj = setupInitialStore(playerObj);
|
||||
if (playerObj.offlineProd && playerObj.time) {
|
||||
if (playerObj.offlineTime == undefined) playerObj.offlineTime = new Decimal(0);
|
||||
playerObj.offlineTime = Decimal.add(
|
||||
playerObj.offlineTime,
|
||||
(Date.now() - playerObj.time) / 1000
|
||||
);
|
||||
if (playerObj.offlineTime == undefined) playerObj.offlineTime = 0;
|
||||
playerObj.offlineTime += (Date.now() - playerObj.time) / 1000;
|
||||
}
|
||||
playerObj.time = Date.now();
|
||||
if (playerObj.modVersion !== projInfo.versionNumber) {
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
DefineComponent,
|
||||
defineComponent,
|
||||
isRef,
|
||||
onUnmounted,
|
||||
PropType,
|
||||
ref,
|
||||
Ref,
|
||||
|
@ -113,6 +114,8 @@ export function setupHoldToClick(
|
|||
}
|
||||
}
|
||||
|
||||
onUnmounted(stop);
|
||||
|
||||
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 String
|
||||
| typeof Number
|
||||
|
|
|
@ -35,5 +35,15 @@
|
|||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
],
|
||||
"typedocOptions": {
|
||||
"entryPoints": ["src"],
|
||||
"entryPointStrategy": "expand",
|
||||
"cleanOutputDir": true,
|
||||
"name": "Profectus",
|
||||
"includeVersion": true,
|
||||
"categorizeByGroup": false,
|
||||
"readme": "none",
|
||||
"out": "../docs/api"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue