Update other pages for 0.7

This commit is contained in:
thepaperpilot 2024-12-25 11:21:25 -06:00
parent 45989b4aa6
commit 5b499c58e2
19 changed files with 108 additions and 147 deletions

View file

@ -64,7 +64,6 @@ module.exports = {
items: [
{ text: "Layers", link: "/guide/important-concepts/layers" },
{ text: "Features", link: "/guide/important-concepts/features" },
{ text: "Coercable Components", link: "/guide/important-concepts/coercable" },
{ text: "Reactivity", link: "/guide/important-concepts/reactivity" },
{ text: "Persistence", link: "/guide/important-concepts/persistence" },
{ text: "Requirements", link: "/guide/important-concepts/requirements" },
@ -87,6 +86,7 @@ module.exports = {
{ text: "Boards", link: "/guide/advanced-concepts/boards" },
{ text: "Creating Features", link: "/guide/advanced-concepts/creating-features" },
{ text: "Dynamic Layers", link: "/guide/advanced-concepts/dynamic-layers" },
{ text: "Mixins and Wrappers", link: "/guide/advanced-concepts/mixins" },
{ text: "Nodes", link: "/guide/advanced-concepts/nodes" }
]
},
@ -94,7 +94,8 @@ module.exports = {
text: "Migrations",
collapsed: true,
items: [
{ text: "0.5.X to 0.6.0", link: "/guide/migrations/0-6" }
{ text: "0.5.X to 0.6.0", link: "/guide/migrations/0-6" },
{ text: "0.6.X to 0.7.0", link: "/guide/migrations/0-7" }
]
}
],

View file

@ -7,14 +7,14 @@ To get started with a board, with a node that's just an upgrade locked to a spec
```ts
const upgrade = createUpgrade({
class: "board-node",
style: "transform: translate(100px, 100px)",
style: { transform: "translate(100px, 100px)" },
/** snip **/
});
/** snip **/
// Render function
jsx(() => <Board>
() => (<Board>
{render(upgrade)}
</Board>);
```
@ -113,11 +113,7 @@ makeDraggable(upgrade, {
hasDragged,
nodeBeingDragged,
dragDelta,
onMouseUp() {
if (!hasDragged.value) {
upgrade.purchase();
}
}
onMouseUp: upgrade.purchase
});
```
@ -145,7 +141,7 @@ The board system is intended to be very generic, allowing you to make whatever s
Keep in mind, for performance reasons, it may be beneficial to put many elements in one `SVGNode`, particularly if they don't need to use the event handlers. For example, examples like this where you're rendering lines connecting various nodes:
```ts
const links = jsx(() => (
const links = () => (
<>
{nodes.value
.reduce(
@ -184,7 +180,7 @@ const links = jsx(() => (
/>
))}
</>
));
);
// And then in the render function:
<SVGNode>{links()}</SVGNode>

View file

@ -4,17 +4,21 @@ Profectus is designed to encourage the developer to eventually start designing t
## Creating the Feature
Every feature has a couple of types. They have the feature themselves, a generic version for convenience, and any constructor typically has an options type and a type that gets "added" to it to create the feature itself. These typically involve replacing some types to denote how various properties change from, for example, `Computable<X>` to `ProcessedComputable<X>`. You should be able to use any of the existing features as a reference for how these types look and work.
Every feature has an interface for the feature itself as well as an options object for every constructor the feature has (typically just one). The main difference is that the options object will have computable properties typed as `MaybeRefOrGetter` but the feature itself will have replaced those with just `MaybeRef`. You should be able to use any of the existing features as a reference for how these types look and work.
Most significantly, the base type should typically always have a `type` property pointing to a symbol unique to this feature, so they can be easily differentiated at runtime. If it's a feature that should be renderable, then it'll also need `[Component]` and `[GatherProps]` properties, which describe the vue component to use and how to get the props for it from this feature, as well as a unique ID for the feature's [node](./nodes). You can use the [getUniqueID](/api/modules/features/feature#getuniqueid) utility to help.
The feature should typically have a `type` property pointing to a symbol unique to this feature, so they can be easily differentiated at runtime. If it's a feature that should be renderable, then it'll also need to make sure the options extends `VueFeatureOptions` and the feature itself `VueFeature`. These will handle allowing it to be rendered using the `render` utility function and handle things like style, classes, visibility, a unique ID, and adding its [node](./nodes) to the layer or modal's context.
The constructor itself should do several things. They should take their options within a function, so that they're not resolved when the object is constructed. It should return a lazy proxy of the feature, which allows features to reference each other and only resolve themselves once every feature is defined. The constructor should create any persistent refs it may require outside of the lazy proxy - it won't have access to the options at this point, so it should make any it _potentially_ may require. Any that turn out not being needed can be [deleted](/api/modules/game/persistence#deletepersistent). Inside the lazy proxy the constructor should create the options object, assign onto it every property from the base type, call [processComputable](/api/modules/util/computed#processcomputable) on every computable type, and [setDefault](/api/modules/features/feature#setdefault) on any property with a default value. Then you should be able to simply return the options object, likely with a type cast, and the constructor will be complete.
The constructor itself should do several things. They should take their options within a function, so that they're not resolved when the object is constructed. It should return a lazy proxy of the feature, which allows features to reference each other and only resolve themselves once every feature is defined. The constructor should create any persistent refs it may require outside of the lazy proxy - it won't have access to the options at this point, so it should make any it _potentially_ may require. Any that turn out not being needed can be [deleted](/api/modules/game/persistence#deletepersistent). Inside the lazy proxy the constructor should create the feature object, including the extra properties in the options object, vue feature mixin, and use [processGetter](/api/modules/util/computed#processgetter) on every computable type. You should ensure the feature object `satisfies` the feature interface, and then return it.
Because typescript does not emit JS, if a property is supposed to be a function it is impossible to differentiate between a function that is itself the intended value or a function that returns the actual value. For this reason, it is not recommended for any feature types to include properties that are `Computable<Function>`s, and all functions _will_ be wrapped in `computed`. The notable exception to this is [JSX](../important-concepts/coercable#render-functions-jsx), which uses a utility function to mark that a function should not be wrapped.
The vue feature mixin will require a string unique to the feature as well as a function to get a `JSX.Element` for this feature. Typically that just means returning the vue component made for this feature, passing in props from the feature object.
Because typescript does not emit JS, if a property is supposed to be a function it is impossible to differentiate between a function that is itself the intended value or a function that returns the actual value. For this reason, it is not recommended for any feature types to include properties that are `MaybeRefOrGetter<Function>`s, and all functions _will_ be wrapped in `computed`.
## Vue Components
Any vue components you write need to do a couple things. Typically they'll need to type any props that come from computed properties appropriately, for which you can use the [processedPropType](/api/modules/util/vue#processedproptype) utility - using it will look something like `style: processedPropType<StyleValue>(String, Object, Array)`. You'll also want to make sure to `unref` any of these props you use in the template. `style` is an exception though, where it should actually be unreffed inside the `GatherProps` function or else it won't work with computed refs. The template should make sure to include a `Node` component with the feature's ID. Also, if there are custom displays this feature may have, you'll want to convert the `CoercableComponent` into a Vue component inside the setup function, typically using the [computeComponent](/api/modules/util/vue#computecomponent) or [computeOptionalComponent](/api/modules/util/vue#computeoptionalcomponent) utilities.
Any vue components you write need to do a couple things. Typically they'll need to type each prop directly, but you can just type it as the property on the feature itself. That is, you can't do `defineProps<MyFeature>()` but you can do `defineProps<{ display: MyFeature["display"]; }>()`. If there are custom displays this feature may have, you'll want to create a PascalCase variable that is just a function passing the prop into the [render](/api/modules/util/vue#render) utility, like this:
`const Title = () => render(props.title);`
## Custom Settings
@ -32,22 +36,22 @@ Next you must set the default value of this setting when the settings is loaded,
```ts
globalBus.on("loadSettings", settings => {
setDefault(settings, "hideChallenges", false);
settings.hideChallenges ??= false;
});
```
Finally, you'll need to register a Vue component to the settings menu so the player can actually modify this setting. Here's the example for the `hideChallenges` setting:
```ts
registerSettingField(
jsx(() => (
globalBus.on("setupVue", () =>
registerSettingField(() => (
<Toggle
title={jsx(() => (
title={
<span class="option-title">
Hide maxed challenges
<desc>Hide challenges that have been fully completed.</desc>
</span>
))}
}
onUpdate:modelValue={value => (settings.hideChallenges = value)}
modelValue={settings.hideChallenges}
/>
@ -62,7 +66,7 @@ If your custom feature requires running some sort of update method every tick, y
```ts
const listeners: Record<string, Unsubscribe | undefined> = {};
globalBus.on("addLayer", layer => {
const actions: GenericAction[] = findFeatures(layer, ActionType) as GenericAction[];
const actions: Action[] = findFeatures(layer, ActionType) as Action[];
listeners[layer.id] = layer.on("postUpdate", diff => {
actions.forEach(action => action.update(diff));
});

View file

@ -8,7 +8,7 @@ When procedurally generating layers with similar structures, consider using a ut
function getDynLayer(id: string): DynamicLayer {
const layer = layers[id];
if (!layer) throw "Layer does not exist";
return layer as DynamicLayer; // you might need an "as unknown" after layer
return layer as DynamicLayer;
}
```

View file

@ -10,7 +10,7 @@ This file has 3 things it must export, but beyond that can export anything the c
### getInitialLayers
- Type: `(player: Partial<PlayerData>) => GenericLayer[]`
- Type: `(player: Partial<PlayerData>) => Layer[]`
A function that is given a player save data object currently being loaded, and returns a list of layers that should be active for that player. If a project does not have dynamic layers, this should always return a list of all layers.

View file

@ -150,3 +150,10 @@ Whether or not to allow the player to pause the game. Turning this off disables
- Default: `base64`
The encoding to use when exporting to the clipboard. Plain-text is fast to generate but is easiest for the player to manipulate and cheat with. Base 64 is slightly slower and the string will be longer but will offer a small barrier to people trying to cheat. LZ-String is the slowest method, but produces the smallest strings and still offers a small barrier to those trying to cheat. Some sharing platforms like pastebin may automatically delete base64 encoded text, and some sites might not support all the characters used in lz-string exports.
### disableHealthWarning
- Type: `boolean`
- Default: `false`
Whether or not to disable the health warning that appears to the player after excessive playtime (activity during 6 of the last 8 hours). If left enabled, the player will still be able to individually turn off the health warning in settings or by clicking "Do not show again" in the warning itself.

View file

@ -25,5 +25,3 @@ Toggles whether to display tab buttons in a tab list, similar to how a browser d
- Type: `boolean`
If true, elements in a row or column will have their margins removed and border radiuses set to 0 between elements. This will cause the elements to appear as segments in a single object.
Currently, this can only merge in a single dimension. Rows of columns or columns of rows will not merge into a single rectangular object.

View file

@ -1,36 +1,36 @@
# Example Projects
## Planar Pioneers <Badge type="tip" text="Profectus 0.6" />
[View Source](https://github.com/thepaperpilot/planar-pioneers/) | [View Project](https://galaxy.click/play/64)
An incremental game with procedurally generated content.
## this crazy idea <Badge type="tip" text="Profectus 0.6" />
[View Source](https://gitlab.com/yhvr/to-be-named) | [View Project](https://galaxy.click/play/94)
A "hopeless startup simulator", made for the [Profectus Creation Jam](https://itch.io/jam/profectus-creation-jam)
## Kronos <Badge type="tip" text="Profectus 0.6" />
[View Source](https://github.com/thepaperpilot/kronos/)
This is a project that's still under development but is a good resource for things like implementing custom features.
## TMT-Demo <Badge type="tip" text="Profectus 0.6" />
## Demo <Badge type="tip" text="Profectus 0.7" />
[View Source](https://code.incremental.social/profectus/TMT-Demo) | [View Project](https://profectus.pages.incremental.social//TMT-Demo/)
A project loosely based off the Demo project for TMT. Uses most of the different features of Profectus, but doesn't have any real gameplay.
## Advent Incremental <Badge type="warning" text="Profectus 0.5" />
## Planar Pioneers <Badge type="warning" text="Profectus 0.6" />
[View Source](https://github.com/thepaperpilot/planar-pioneers/) | [View Project](https://galaxy.click/play/64)
An incremental game with procedurally generated content.
## this crazy idea <Badge type="warning" text="Profectus 0.6" />
[View Source](https://gitlab.com/yhvr/to-be-named) | [View Project](https://galaxy.click/play/94)
A "hopeless startup simulator", made for the [Profectus Creation Jam](https://itch.io/jam/profectus-creation-jam)
## Kronos <Badge type="warning" text="Profectus 0.6" />
[View Source](https://github.com/thepaperpilot/kronos/)
This is a project that's still under development but is a good resource for things like implementing custom features.
## Advent Incremental <Badge type="danger" text="Profectus 0.5" />
[View Source](https://github.com/thepaperpilot/advent-Incremental/) | [View Project](https://www.thepaperpilot.org/advent/)
An incremental game with 25 different layers of content. A good example of what a large project looks like. There's also a partial port to 0.6 available [here](https://github.com/thepaperpilot/advent-Incremental/tree/next).
## Primordia <Badge type="warning" text="Profectus 0.5" />
## Primordia <Badge type="danger" text="Profectus 0.5" />
[View Source](https://github.com/Jacorb90/Primordial-Tree) | [View Project](https://jacorb90.me/Primordial-Tree/)

View file

@ -14,9 +14,9 @@ The `createLayer` function will need a unique ID for your layer and a function t
```ts
const id = "p";
const layer = createLayer(id, function (this: BaseLayer) {
const layer = createLayer(id, layer => {
return {
display: jsx(() => <>My layer</>)
display: () => <>My layer</>
};
});
```
@ -37,16 +37,16 @@ In your IDE you'll be able to see the documentation for each parameter - in this
```ts
const id = "p";
const layer = createLayer(id, function (this: BaseLayer) {
const layer = createLayer(id, layer => {
const points = createResource<DecimalSource>(0, "prestige points");
return {
points,
display: jsx(() => (
display: () => (
<>
<MainDisplay resource={points} />
</>
))
)
};
});
```
@ -56,12 +56,12 @@ const layer = createLayer(id, function (this: BaseLayer) {
Some things happen every tick, such as passive resource generation. You can hook into the update loop using an event bus. There's a global one and one for each layer. For example, within the layer function, you can add this code to our example to increase our points at a rate of 1 per second:
```ts
this.on("update", diff => {
layer.on("update", diff => {
points.value = Decimal.add(points.value, diff);
});
```
Note that within the `createLayer`'s function, `this` refers to the layer you're actively creating, and the `diff` parameter represents the seconds that have passed since the last update - which will typically be around 0.05 unless throttling is disabled in the settings. If we wanted to generate an amount other than 1/s, we could multiply diff by the per-second rate.
Note that the `createLayer`'s function receives a parameter for the base layer, which you may have to add. The `diff` parameter insidde the event callback represents the seconds that have passed since the last update - which will typically be around 0.05 unless throttling is disabled in the settings. If we wanted to generate an amount other than 1/s, we could multiply diff by the per-second rate.
## Adding an upgrade
@ -85,12 +85,12 @@ We'll add this upgrade to our returned object and our display. Upgrades are a re
return {
points,
myUpgrade,
display: jsx(() => (
display: () => (
<>
<MainDisplay resource={points} />
{render(myUpgrade)}
</>
))
)
}
```

View file

@ -1,58 +0,0 @@
# Coercable Components
Most times a feature has some sort of dynamic display, it'll allow you to pass a "Coercable Component", or rather, something that can be coerced into a Vue component. This page goes over the different types of values you can use
## Template Strings
If you provide a string, it will be wrapped in a component using it as the template. This is the simplest method, although not suitable for complex displays, and realistically cannot use Vue components as none are registered globally (by default). Recommended for static or simple dynamic displays, such as displays on features.
Template strings need to be wrapped in some HTML element. By default, they'll be wrapped in a `<span>` element, although certain features may wrap things in div or header elements instead, as appropriate.
## Render Functions (JSX)
You can provide a render function and it will be wrapped in a component as well. The intended use for this is to write JSX inside a function, which will get automatically converted into a render function. You can read more about that process on the Vue docs on [Render Functions & JSX](https://vuejs.org/guide/extras/render-function.html#render-functions-jsx). Note that JSX must be returned in a function - it does not work "standalone". The CoercableComponent type will enforce this for you. Also of note is that you can use `<>` and `</>` as wrappers to render multiple elements without a containing element, however keep in mind an empty JSX element such as `jsx(() => <></>)` is invalid and will fail to render.
JSX can use imported components, making this suited for writing the display properties on things like Tabs or Layers. There are also built-in functions to `render` features (either as their own or in a layout via `renderRow` and `renderCol`), so you don't need to import the Vue component for every feature you plan on using.
Typically a feature will accept a `Computable<CoercableComponent>`, which means functions would (normally) be wrapped in a computed (see [Computable](./reactivity#computable) for more details). This would break render functions, so when passing a render function as a CoercableComponent it must be specially marked that it shouldn't be cached. You can use the built-in `jsx` function to mark a function for you.
#### Example
```tsx
{
display: jsx(() => (
<>
<MainDisplay resource={points} color={color} />
{render(resetButton)}
{renderRow(upgrade1, upgrade2, upgrade3)}
</>
)),
}
```
### Slots and Models
Modals and other features that utilize slots are a bit trickier in JSX, as each slot must _also_ be JSX. Here's an example utility for creating modals that correctly uses slots:
```tsx
function createModal(title: string, body: JSXFunction, otherData = {}) {
const showModal = persistent<boolean>(false);
const modal = jsx(() => (
<Modal
modelValue={showModal.value}
onUpdate:modelValue={(value: boolean) => (showModal.value = value)}
v-slots={{
header: () => <h2>{title}</h2>,
body
}}
/>
));
return { modal, showModal, ...otherData };
}
```
That example also shows how to use models in JSX, which are a concept in vue for allowing a component to read and write a value. It requires specifying both the model value as well as a function to update it's value.
## Components
This one might be the most obvious, but you can also just give it a Vue component to display outright. Keep in mind it will not be passed any props, so it should not depend on any. You can read more about creating Vue components on [Components Basics](https://vuejs.org/guide/essentials/component-basics.html).

View file

@ -10,8 +10,10 @@ const addGainUpgrade = createUpgrade(() => ({
title: "Generator of Genericness",
description: "Gain 1 point every second"
},
cost: 1,
resource: points
requirements: createCostRequirement(() => ({
resource: noPersist(points),
cost: 1
}))
}));
```
@ -28,6 +30,18 @@ const upgrades = { addGainUpgrade, gainMultUpgrade, upgMultUpgrade };
const numUpgrades = computed(() => Object.values(upgrades).length);
```
## Displays
Most times a feature has some sort of dynamic display, it'll allow you to pass a string or `JSX.Element`, the latter within a function or the former either dynamically or statically.
### Template Strings
Providing a string is the simplest method, although not suitable for complex displays and cannot use Vue components. Recommended for static or simple dynamic displays, such as titles or descriptions on features. These strings will be rendered as text and not be parsed as HTML. Some features may wrap the text in header elements or others as appropriate, but this can be overidden by using JSX instead.
### Render Functions (JSX)
You can provide a render function to have more control over what is displayed. Here you can include vue components and even other features, and is how the layer's display itself should be defined. You can use `<>` and `</>` as wrappers to render multiple elements without a containing element. You can read other details about how JSX works in the Vue docs on [Render Functions & JSX](https://vuejs.org/guide/extras/render-function.html#render-functions-jsx), particularly how advanced featuers like slots and models work.
## Tree Shaking
Since Profectus takes advantage of [tree shaking](https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking), any type of feature that is not used will not be included in the output of the project. That means users have less code to download, a slight performance boost, and you don't need to worry about feature type-specific settings appearing (such as whether to show maxed challenges).

View file

@ -2,7 +2,7 @@
Profectus utilizes formulas for various features, such as increasing requirements for repeatables and challenges or determining resource gains in conversions. These formulas often need to be inverted or integrated to enable features like buying multiple levels of a repeatable at once or determining when a conversion will increase resource gains. The Formula class can handle these operations, supporting every function Decimal does, while tracking the operations internally.
For example, a cost function like `Decimal.pow(this.amount, 1.05).times(100)` can be represented using a Formula: `Formula.variable(this.amount).pow(1.05).times(100)`.
For example, a cost function like `Decimal.pow(amount, 1.05).times(100)` can be represented using a Formula: `Formula.variable(amount).pow(1.05).times(100)`.
```ts
const myRepeatable = createRepeatable(() => ({

View file

@ -11,3 +11,5 @@ It's important for saving and loading these properties for these refs to be in a
Additionally, this structure should typically remain consistent between project versions. If a value is in a new location, it will not load the value from localStorage correctly. This is exacerbated if two values swap places, such as when an array is re-ordered. In the event a creator changes this structure anyways, the [fixOldSave](../creating-your-project/project-entry.md#fixoldsave) function can be used to migrate the old player save data to the new structure expected by the current version of the project.
As of Profectus 0.6, save data will now report warnings whenever there is redundancy - two locations for the same persistent data, which creates larger saves that can cause issues when loading after updates. To fix redundancies, wrap all but one location for the data in [noPersist](../../api/modules/game/persistence#nopersist).
One place to look out for specifically is tree nodes, which typically have a persisten `pinned` value and can appear in both the nodes array as well as the links array.

View file

@ -6,8 +6,8 @@ With a proper IDE, such as [Visual Studio Code](../getting-started/setup#visual-
Vue's reactivity is probably the "quirkiest" part of Profectus, and not even the documentation makes all of those quirks clear. It is recommend to read [this thread](https://github.com/vuejs/docs/issues/849) of common misconceptions around Vue reactivity.
## Computable
## Optionally computable values
Most properties on features will accept `Computable` values. Computable values can either be a raw value, a ref to the value, or a function that returns the value. In the lattermost case it will be wrapped in `computed`, turning it into a ref. The feature type will handle it being a ref or a raw value by using `unref` when accessing those values. With type hints, your IDE should correctly identify these values as refs or raw values so you can treat them as the types they actually are.
Most properties on features will accept `MaybeRefOrGetter` values. These properties can receive either a raw value, a ref to the value, or a function that returns the value. In the lattermost case it will be wrapped in `computed`, turning it into a ref. The feature type will handle it being a ref or a raw value by using `unref` when accessing those values. With type hints, your IDE should correctly identify these values as refs or raw values so you can treat them as the types they actually are.
Because functions are automatically wrapped in `computed` for many properties, it might be expected to happen to custom properties you add to a feature that isn't defined by the feature type. These functions will _not_ be wrapped, and if you want it cached you should wrap it in a `computed` yourself. This does, however, allow you to include custom methods on a feature without worry.

View file

@ -9,14 +9,13 @@ To create a requirement, you can use one of the provided utility functions like
Cost requirements are probably the most common requirement you'll be using. For something with multiple levels, like repeatables, you'll typically want to use a formula for the cost instead of a function, and the input to the formula will be the repeatable's `amount` property. Typically that means the code will look like this:
```ts
createRepeatable(repeatable => ({
requirements: createCostRequirement(() => ({
resource: points,
cost: Formula.variable(repeatable.amount).add(1).times(100)
}))
const repeatable = createRepeatable(() => ({
requirements: createCostRequirement(() => ({
resource: noPersist(points),
cost: Formula.variable(repeatable.amount).add(1).times(100)
}))
}));
```
Important to note here is the parameter added to the `createRepeatable`'s options function. That is a reference to the repeatable being created, so you can access it's `amount` property in the formula.
## Using Requirements

View file

@ -3,7 +3,7 @@ title: Introduction
---
# Introduction
Profectus is a web-based game engine. You can write your content using many built in features, write your own features, and build up complex gameplay quickly and easily.
Profectus is a template for web-based games and projects. You can write your content using many built in features, write your own features, and build up complex gameplay quickly and easily.
The purpose of creating profectus was to create an easy to use engine that does not create a ceiling for a programmer's personal growth. This engine will grow in complexity with you, empowering you to create increasingly complex designs and mechanics.

View file

@ -19,11 +19,10 @@ Next, create the particles feature and render it. You'll also want to track the
```ts
const particles = createParticles(() => ({
fullscreen: false,
zIndex: -1,
style: { zIndex: "-1" },
boundingRect: ref<null | DOMRect>(null),
onContainerResized(boundingRect) {
this.boundingRect.value = boundingRect;
particles.boundingRect.value = boundingRect;
}
}));
```
@ -70,11 +69,10 @@ If you're using hot reloading, you might need to reload the particle effect. Her
```ts
const particles = createParticles(() => ({
fullscreen: false,
zIndex: -1,
style: { zIndex: "-1" },
boundingRect: ref<null | DOMRect>(null),
onContainerResized(boundingRect) {
this.boundingRect.value = boundingRect;
particles.boundingRect.value = boundingRect;
},
onHotReload() {
Object.values(elements).forEach(element => element.refreshParticleEffect());

View file

@ -11,18 +11,16 @@ This recipe will involve modifying the `Save.vue` file within your project to in
Let's start with creating the coerced component. For this recipe we're going to make a couple assumptions about what this display should be. We'll assume the text will be more complex than displaying a single value. That is, at different stages of the game progress will be indicated by different metrics. We'll also assume it will be a single line of descriptive text - no images or anything else that would justify making a new .vue component. Breaking these assumptions is left as an exercise for the reader. But for now, with those assumptions in mind, we'll write our component (in the `<script>` tag in `Save.vue`) similar to this example:
```ts
const progressDisplay = computeComponent(
computed(() => {
if (someCondition) {
return "Just started";
}
if (someOtherCondition) {
return `Early game; ${formatWhole(someResourceValue)} resource name`;
}
...
return "Unknown progress";
})
);
const progressDisplay = computed(() => {
if (someCondition) {
return "Just started";
}
if (someOtherCondition) {
return `Early game; ${formatWhole(someResourceValue)} resource name`;
}
...
return "Unknown progress";
});
```
This code will create a component that will simply render the returned text, and update as required. However, there's one significant complication in writing the code for these conditions and resource values: you're working with a potentially incomplete save data object.
@ -50,8 +48,10 @@ The `Save.vue` template contains, amongst other things, an element that displays
</button>
<span class="save-version">v{{ save.modVersion }}</span
><br />
<div v-if="currentTime">Last played {{ dateFormat.format(currentTime) }}</div>
++ <div v-if="progressDisplay"><component :is="progressDisplay" /></div>
<div v-if="currentTime" class="time">
Last played {{ dateFormat.format(currentTime) }}
</div>
<div v-if="progressDisplay">{{ progressDisplay }}</div>
</div>
```

@ -1 +1 @@
Subproject commit d0fffd3b89aa6dbecf689bbb30cb0a6621afa24d
Subproject commit 0a5f63ff048227e74ad95e9e23ab41a6646c7d62