A class that can be used for cost/goal functions. It can be evaluated similar to a cost function, but also provides extra features for supported formulas. For example, a lot of math functions can be inverted. Typically, the use of these extra features is to support cost/goal functions that have multiple levels purchased/completed at once efficiently.
Evaluate the result of the indefinite integral (sans the constant of integration). Only works if there's a single variable and the formula is integrable. The formula can only have one "complex" operation (anything besides +,-,*,/).
Takes a potential result of the formula, and calculates what value the variable inside the formula would have to be for that result to occur. Only works if there's a single variable and if the formula is invertible.
Given the potential result of the formula's integral (and the constant of integration), calculate what value the variable inside the formula would have to be for that result to occur. Only works if there's a single variable and if the formula's integral is invertible.
Creates a step-wise formula. After start the formula will have an additional modifier. This function assumes the incoming value will be continuous and monotonically increasing.
How this step should modify the formula. The incoming value will be the unmodified formula value minus the start value. So for example if an incoming formula evaluates to 200 and has a step that starts at 150, the formulaModifier would be given 50 as the parameter
Tolerance is a relative tolerance, multiplied by the greater of the magnitudes of the two arguments. For example, if you put in 1e-9, then any number closer to the larger number than (larger number)*1e-9 will be considered equal.
If you're willing to spend 'resourcesAvailable' and want to buy something with additively increasing cost each purchase (start at priceStart, add by priceAdd, already own currentOwned), how much of it can you buy?
If you're willing to spend 'resourcesAvailable' and want to buy something with exponentially increasing cost each purchase (start at priceStart, multiply by priceRatio, already own currentOwned), how much of it can you buy? Adapted from Trimps source code.
Converts a DecimalSource to a Decimal, without constructing a new Decimal if the provided value is already a Decimal.
As the return value could be the provided value itself, this function returns a read-only Decimal to prevent accidental mutations of the value. Use new Decimal(value) to explicitly create a writeable copy if mutation is required.
How much resource would it cost to buy (numItems) items if you already have currentOwned, the initial price is priceStart and it adds priceAdd each purchase? Adapted from http://www.mathwords.com/a/arithmetic_series.htm
How much resource would it cost to buy (numItems) items if you already have currentOwned, the initial price is priceStart and it multiplies by priceRatio each purchase?
The maximum size for this cache. We recommend setting this to be one less than a power of 2, as most hashtables - including V8's Object hashtable (https://crsrc.org/c/v8/src/objects/ordered-hash-table.cc) - uses powers of two for hashtable sizes. It can't exactly be a power of two, as a .set() call could temporarily set the size of the map to be maxSize + 1.
Whether or not to append the layer to the tabs list. If set to false, then the tree node will instead always remove all tabs to its right and then add the layer tab. Defaults to true.
Whether or not adjacent features should merge together - removing the margin between them, and only applying the border radius to the first and last elements in the row or column.
Indicates whether assistive technologies will present all, or only parts of, the changed region based on the change notifications defined by the aria-relevant attribute.
Indicates whether inputting text could trigger display of one or more predictions of the user's intended value for an input and specifies how predictions would be presented if they are made.
Indicates an element is being modified and that assistive technologies MAY want to wait until the modifications are complete before exposing them to the user.
Identifies the next element (or elements) in an alternate reading order of content which, at the user's discretion, allows assistive technology to override the general default of reading in document source order.
Indicates that an element will be updated, and describes the types of updates the user agents, assistive technologies, and user can expect from the live region.
Identifies an element (or elements) in order to define a visual, functional, or contextual parent/child relationship between DOM elements where the DOM hierarchy cannot be used to represent the relationship.
Defines a short hint (a word or short phrase) intended to aid the user with data entry when the control has no value. A hint could be a sample value or a brief description of the expected format.
Whether or not completing this challenge should grant multiple completions if requirements met. Requires requirements to be a requirement or array of requirements with Requirement.canMaximize true.
The absolute amount the output resource will be changed by. Typically this will be set for you in a conversion constructor. This will differ from currentGain in the cases where the conversion isn't just adding the converted amount to the output resource.
The amount of the input resource currently being required in order to produce the currentGain. That is, if it went below this value then currentGain would decrease. Typically this will be set for you in a conversion constructor.
The formula used to determine how much gainResource should be earned by this converting. The passed value will be a Formula representing the baseResource variable.
A callback that happens after a conversion has been completed. Receives the amount gained via conversion. This will not be called whenever using currentGain without calling convert (e.g. passive generation)
Represents a cell within a grid. These properties will typically be accessed via a cell proxy that calls functions on the grid to get the properties for a specific cell.
Indicates whether assistive technologies will present all, or only parts of, the changed region based on the change notifications defined by the aria-relevant attribute.
Indicates whether inputting text could trigger display of one or more predictions of the user's intended value for an input and specifies how predictions would be presented if they are made.
Indicates an element is being modified and that assistive technologies MAY want to wait until the modifications are complete before exposing them to the user.
Identifies the next element (or elements) in an alternate reading order of content which, at the user's discretion, allows assistive technology to override the general default of reading in document source order.
Indicates that an element will be updated, and describes the types of updates the user agents, assistive technologies, and user can expect from the live region.
Identifies an element (or elements) in order to define a visual, functional, or contextual parent/child relationship between DOM elements where the DOM hierarchy cannot be used to represent the relationship.
Defines a short hint (a word or short phrase) intended to aid the user with data entry when the control has no value. A hint could be a sample value or a brief description of the expected format.
Whether or not clicking this repeatable should attempt to maximize amount based on the requirements met. Requires requirements to be a requirement or array of requirements with Requirement.canMaximize true.
Indicates whether assistive technologies will present all, or only parts of, the changed region based on the change notifications defined by the aria-relevant attribute.
Indicates whether inputting text could trigger display of one or more predictions of the user's intended value for an input and specifies how predictions would be presented if they are made.
Indicates an element is being modified and that assistive technologies MAY want to wait until the modifications are complete before exposing them to the user.
Identifies the next element (or elements) in an alternate reading order of content which, at the user's discretion, allows assistive technology to override the general default of reading in document source order.
Indicates that an element will be updated, and describes the types of updates the user agents, assistive technologies, and user can expect from the live region.
Identifies an element (or elements) in order to define a visual, functional, or contextual parent/child relationship between DOM elements where the DOM hierarchy cannot be used to represent the relationship.
Defines a short hint (a word or short phrase) intended to aid the user with data entry when the control has no value. A hint could be a sample value or a brief description of the expected format.
Using document.fonts.ready returns too early on firefox, so we use document.fonts.onloadingdone instead, which doesn't accept multiple listeners. This event fires when that callback is called.
An object that configures a Layer. Even moreso than features, the developer is expected to include extra properties in this object. All Persistent refs must be included somewhere within the layer object.
The layout of this layer's features. When the layer is open in game/player.PlayerData.tabs, but the tab is Layer.minimized this is the content that is displayed.
An object that can be used to apply or unapply some modification to a number. Being reversible requires the operation being invertible, but some features may rely on that. Descriptions can be optionally included for displaying them to the player. The built-in modifier creators are designed to display the modifiers using createModifierSection.
The amount of resource that must be met for this requirement. You can pass a formula, in which case maximizing will work out of the box (assuming its invertible and, for more accurate calculations, its integral is invertible). If you don't pass a formula then you can still support maximizing by passing a custom pay function.
When calculating multiple levels to be handled at once, whether it should consider resources used for each level as spent. Setting this to false causes calculations to be faster with larger numbers and supports more math functions.
Whether or not this requirement can have multiple levels of requirements that can be met at once. Requirement is assumed to not have multiple levels if this property not present.
The display for this specific requirement. This is used for displays multiple requirements condensed. Required if visibility can be Visibility.Visible.
A button that is used to control a conversion. It will show how much can be converted currently, and can show when that amount will go up, as well as handle only being clickable when a sufficient amount of currency can be gained. Assumes this button is associated with a specific node on a tree, and triggers that tree's reset propagation.
Takes an array of modifier "sections", and creates a JSXFunction that can render all those sections, and allow each section to be collapsed. Also returns a list of persistent refs that are used to control which sections are currently collapsed.
Utility function for displaying the result of a formula such that it will, when told to, preview how the formula's result will change. Requires a formula with a single variable inside.
Given a player save data object being loaded, return a list of layers that should currently be enabled. If your project does not use dynamic layers, this should just return all layers.
A type representing a computable value for a node on the board. Used for node types to return different values based on the given node and the state of the board.
Utility for taking an array of challenges where only one may be active at a time, and giving a ref to the one currently active (or null if none are active)
Lazily creates a conversion with the given options. You typically shouldn't use this function directly. Instead use one of the other conversion constructors, which will then call this.
Creates a conversion that simply adds to the gainResource amount upon converting. This is similar to the behavior of "normal" layers in The Modding Tree. This is equivalent to just calling createConversion directly.
Creates a conversion that will replace the gainResource amount with the new amount upon converting. This is similar to the behavior of "static" layers in The Modding Tree.
This will automatically increase the value of conversion.gainResource without lowering the value of the input resource. It will by default perform 100% of a conversion's currentGain per second. If you use a ref for the rate you can set it's value to 0 when passive generation should be disabled.
Utility function for a function that returns an object of a given type, with "this" bound to what the type will eventually be processed into. Intended for making lazily evaluated objects.
Any value that can be passed into an HTML element's style attribute. Note that Profectus uses its own StyleValue and CSSProperties that are extended, in order to have additional properties added to them, such as variable CSS variables.
Traverses an object and returns all features that are not any of the given types. Features are any object with a "type" property that has a symbol value.
Gets a unique ID to give to each feature, used for any sort of system that needs to identify elements in the DOM rather than references to the feature itself. (For example, branches) IDs are guaranteed unique, but NOT persistent - they likely will change between updates.
Takes a function and marks it as JSX so it won't get auto-wrapped into a ComputedRef. The function may also return empty string as empty JSX tags cause issues.
An object that represents a feature that display particle effects on the screen. The config should typically be gotten by designing the effect using the online particle effect editor and passing it into the upgradeConfig from @pixi/particle-emitter.
Returns a reference to the amount of resource being gained in terms of orders of magnitude per second, calcualted over the last tick. Useful for situations where the gain rate is increasing very rapidly.
An object that represents a feature that is a tree of nodes with branches between them. Contains support for reset mechanics that can propagate through the tree.
Utility for creating a tooltip for a tree node that displays a resource-based unlock requirement, and after unlock shows the amount of another resource. It sounds oddly specific, but comes up a lot.
Utility for calculating the cost of a formula for a given amount of purchases. If spendResources is changed to false, the calculation will be much faster with higher numbers.
Utility for calculating the maximum amount of purchases possible with a given formula and resource. If spendResources is changed to false, the calculation will be much faster with higher numbers.
A reference to all the current layers. It is shallow reactive so it will update when layers are added or removed, but not interfere with the existing refs within each layer.
When creating layers, this object a map of layer ID to a set of any created persistent refs in order to check they're all included in the final layer object.
Enables a layer object, so it will be updated every tick. Note that accessing a layer/its properties does NOT require it to be enabled. For dynamic layers you can call this function and removeLayer as necessary. Just make sure getInitialLayers will provide an accurate list of layers based on the player data object. For static layers just make getInitialLayers return all the layers.
Convenience method for removing and immediately re-adding a layer. This is useful for layers with dynamic content, to ensure persistent refs are correctly configured.
Utility function for creating a modal that display's a display. Returns the modal itself, which can be rendered anywhere you need, as well as a function to open the modal.
Utility type used to narrow down a modifier type that will have a description and/or enabled property based on optional parameters, T and S (respectively).
â–¸ createSequentialModifier<T, S>(modifiersFunc): S
Takes an array of modifiers and applies and reverses them in order. Modifiers that are not enabled will not be applied nor reversed. Also joins their descriptions together.
Create a boolean ref that will automatically be set based on the given condition, but also dismissed when hovering over a given element, typically the element where acting upon the notification would take place.
Mark a Persistent as deleted, so it won't be saved and loaded. Since persistent refs must be created during a layer's options func, features can not create persistent refs after evaluating their own options funcs. As a result, it must create any persistent refs it might need. This function can then be called after the options func is evaluated to mark the persistent ref to not be saved or loaded.
Create a persistent ref, which can be saved and loaded. All (non-deleted) persistent refs must be included somewhere within the layer object returned by that layer's options func.
Calculates the maximum number of levels that could be acquired with the current requirement states. True/false requirements will be counted as Infinity or 0.
Loads the player settings from localStorage. Calls the GlobalEvents.loadSettings event for custom properties to be included. Custom properties should be added by the file they relate to, so they won't be included if the file is tree shaken away. Custom properties should also register the field to modify said setting using registerSettingField.
This section of the docs is generated via the doc comments inside the Profectus source code. While it can be used as a reference, the comments themselves should show up in your IDE when relevant, which is probably a better way to get help. The guide has longer-form explanations of key concepts and is more likely to be of use to new developers.
-
diff --git a/assets/guide_advanced-concepts_creating-features.md.426efab2.js b/assets/guide_advanced-concepts_creating-features.md.4c37639d.js
similarity index 99%
rename from assets/guide_advanced-concepts_creating-features.md.426efab2.js
rename to assets/guide_advanced-concepts_creating-features.md.4c37639d.js
index 9d0e5c13..85baafef 100644
--- a/assets/guide_advanced-concepts_creating-features.md.426efab2.js
+++ b/assets/guide_advanced-concepts_creating-features.md.4c37639d.js
@@ -1,4 +1,4 @@
-import{_ as s,c as e,o as a,N as n}from"./chunks/framework.0799945b.js";const d=JSON.parse('{"title":"Creating Features","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/creating-features.md","lastUpdated":1681094412000}'),o={name:"guide/advanced-concepts/creating-features.md"},t=n(`
Profectus is designed to encourage the developer to eventually start designing their own features for use in specific games. Features are designed to work where they require minimal (and typically zero) modifications around the code base - you simply write a single file for the feature, and any vue components it needs, and the act of importing that feature will set everything up. This also means you can share these features with others in entire collections, and any they don't use won't be present in the build output, won't be loaded, and won't affect the project in any way.
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.
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. You cna use the getUniqueID utility to help.
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. Inside the lazy proxy the constructor should create the options object, assign onto it every property from the base type, call processComputable on every computable type, and 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.
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, which uses a utility function to mark that a function should not be wrapped.
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 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. 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 or computeOptionalComponent utilities.
To add a setting to the options menu specific to this feature, you'll need to do three things, all inside the feature's file. First, extend the settings type with the name of the new setting. For example, here's how the challenge feature adds a setting to hide completed challenges:
ts
declaremodule"game/settings"{
+import{_ as s,c as e,o as a,N as n}from"./chunks/framework.0799945b.js";const d=JSON.parse('{"title":"Creating Features","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/creating-features.md","lastUpdated":1681131771000}'),o={name:"guide/advanced-concepts/creating-features.md"},t=n(`
Profectus is designed to encourage the developer to eventually start designing their own features for use in specific games. Features are designed to work where they require minimal (and typically zero) modifications around the code base - you simply write a single file for the feature, and any vue components it needs, and the act of importing that feature will set everything up. This also means you can share these features with others in entire collections, and any they don't use won't be present in the build output, won't be loaded, and won't affect the project in any way.
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.
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. You cna use the getUniqueID utility to help.
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. Inside the lazy proxy the constructor should create the options object, assign onto it every property from the base type, call processComputable on every computable type, and 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.
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, which uses a utility function to mark that a function should not be wrapped.
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 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. 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 or computeOptionalComponent utilities.
To add a setting to the options menu specific to this feature, you'll need to do three things, all inside the feature's file. First, extend the settings type with the name of the new setting. For example, here's how the challenge feature adds a setting to hide completed challenges:
ts
declaremodule"game/settings"{interfaceSettings{ hideChallenges:boolean;}
diff --git a/assets/guide_advanced-concepts_creating-features.md.426efab2.lean.js b/assets/guide_advanced-concepts_creating-features.md.4c37639d.lean.js
similarity index 85%
rename from assets/guide_advanced-concepts_creating-features.md.426efab2.lean.js
rename to assets/guide_advanced-concepts_creating-features.md.4c37639d.lean.js
index 0f8ac931..da360e31 100644
--- a/assets/guide_advanced-concepts_creating-features.md.426efab2.lean.js
+++ b/assets/guide_advanced-concepts_creating-features.md.4c37639d.lean.js
@@ -1 +1 @@
-import{_ as s,c as e,o as a,N as n}from"./chunks/framework.0799945b.js";const d=JSON.parse('{"title":"Creating Features","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/creating-features.md","lastUpdated":1681094412000}'),o={name:"guide/advanced-concepts/creating-features.md"},t=n("",19),l=[t];function p(r,c,y,i,F,D){return a(),e("div",null,l)}const C=s(o,[["render",p]]);export{d as __pageData,C as default};
+import{_ as s,c as e,o as a,N as n}from"./chunks/framework.0799945b.js";const d=JSON.parse('{"title":"Creating Features","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/creating-features.md","lastUpdated":1681131771000}'),o={name:"guide/advanced-concepts/creating-features.md"},t=n("",19),l=[t];function p(r,c,y,i,F,D){return a(),e("div",null,l)}const C=s(o,[["render",p]]);export{d as __pageData,C as default};
diff --git a/assets/guide_advanced-concepts_dynamic-layers.md.a5b2ad1d.js b/assets/guide_advanced-concepts_dynamic-layers.md.f71d038a.js
similarity index 98%
rename from assets/guide_advanced-concepts_dynamic-layers.md.a5b2ad1d.js
rename to assets/guide_advanced-concepts_dynamic-layers.md.f71d038a.js
index 98b2a52b..dadf08bd 100644
--- a/assets/guide_advanced-concepts_dynamic-layers.md.a5b2ad1d.js
+++ b/assets/guide_advanced-concepts_dynamic-layers.md.f71d038a.js
@@ -1,4 +1,4 @@
-import{_ as a,c as s,o as e,N as n}from"./chunks/framework.0799945b.js";const D=JSON.parse('{"title":"Dynamic Layers","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/dynamic-layers.md","lastUpdated":1681094412000}'),o={name:"guide/advanced-concepts/dynamic-layers.md"},l=n(`
You can dynamically add and remove layers using the addLayer and removeLayer functions. It's important to note that removing a layer does not affect the player's save data. You can safely add and remove the same layer without losing any progress. For instances where the structure of a layer changes, such as when adding a new feature, use the reloadLayer function.
When procedurally generating layers with similar structures, consider using a utility function like the one below. This function allows you to access a correctly typed reference to a layer with a specified ID easily:
ts
functiongetDynLayer(id:string):DynamicLayer{
+import{_ as a,c as s,o as e,N as n}from"./chunks/framework.0799945b.js";const D=JSON.parse('{"title":"Dynamic Layers","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/dynamic-layers.md","lastUpdated":1681131771000}'),o={name:"guide/advanced-concepts/dynamic-layers.md"},l=n(`
You can dynamically add and remove layers using the addLayer and removeLayer functions. It's important to note that removing a layer does not affect the player's save data. You can safely add and remove the same layer without losing any progress. For instances where the structure of a layer changes, such as when adding a new feature, use the reloadLayer function.
When procedurally generating layers with similar structures, consider using a utility function like the one below. This function allows you to access a correctly typed reference to a layer with a specified ID easily:
ts
functiongetDynLayer(id:string):DynamicLayer{constlayer=layers[id];if (!layer) throw"Layer does not exist";returnlayerasDynamicLayer;// you might need an "as unknown" after layer
diff --git a/assets/guide_advanced-concepts_dynamic-layers.md.a5b2ad1d.lean.js b/assets/guide_advanced-concepts_dynamic-layers.md.f71d038a.lean.js
similarity index 85%
rename from assets/guide_advanced-concepts_dynamic-layers.md.a5b2ad1d.lean.js
rename to assets/guide_advanced-concepts_dynamic-layers.md.f71d038a.lean.js
index c94697b9..bcf8d519 100644
--- a/assets/guide_advanced-concepts_dynamic-layers.md.a5b2ad1d.lean.js
+++ b/assets/guide_advanced-concepts_dynamic-layers.md.f71d038a.lean.js
@@ -1 +1 @@
-import{_ as a,c as s,o as e,N as n}from"./chunks/framework.0799945b.js";const D=JSON.parse('{"title":"Dynamic Layers","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/dynamic-layers.md","lastUpdated":1681094412000}'),o={name:"guide/advanced-concepts/dynamic-layers.md"},l=n("",6),t=[l];function r(p,c,y,i,d,u){return e(),s("div",null,t)}const h=a(o,[["render",r]]);export{D as __pageData,h as default};
+import{_ as a,c as s,o as e,N as n}from"./chunks/framework.0799945b.js";const D=JSON.parse('{"title":"Dynamic Layers","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/dynamic-layers.md","lastUpdated":1681131771000}'),o={name:"guide/advanced-concepts/dynamic-layers.md"},l=n("",6),t=[l];function r(p,c,y,i,d,u){return e(),s("div",null,t)}const h=a(o,[["render",r]]);export{D as __pageData,h as default};
diff --git a/assets/guide_advanced-concepts_nodes.md.f29b361a.js b/assets/guide_advanced-concepts_nodes.md.7a309ab5.js
similarity index 99%
rename from assets/guide_advanced-concepts_nodes.md.f29b361a.js
rename to assets/guide_advanced-concepts_nodes.md.7a309ab5.js
index 1fae3e16..7125cec2 100644
--- a/assets/guide_advanced-concepts_nodes.md.f29b361a.js
+++ b/assets/guide_advanced-concepts_nodes.md.7a309ab5.js
@@ -1,4 +1,4 @@
-import{_ as s,c as n,o as a,N as o}from"./chunks/framework.0799945b.js";const C=JSON.parse('{"title":"Nodes","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/nodes.md","lastUpdated":1681094412000}'),l={name:"guide/advanced-concepts/nodes.md"},e=o(`
Features rendered in the DOM should include a Node component, which registers itself to the nearest Context component (usually within the Layer's component) and tracks the bounding rect (both size and position) of the DOM element. Access the DOM element for a feature via its unique id property within layer.nodes, provided it currently exists.
This is useful for features with complex displays, such as particle effects positioned relative to another feature or drawing links between different nodes. To illustrate this, let's look at a complete example of using layer.nodes to get a node's bounding rect and then placing a particle effect using it. Here's an example from Kronos:
ts
const particlesEmitter =ref(particles.addEmitter(element.particlesConfig));
+import{_ as s,c as n,o as a,N as o}from"./chunks/framework.0799945b.js";const C=JSON.parse('{"title":"Nodes","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/nodes.md","lastUpdated":1681131771000}'),l={name:"guide/advanced-concepts/nodes.md"},e=o(`
Features rendered in the DOM should include a Node component, which registers itself to the nearest Context component (usually within the Layer's component) and tracks the bounding rect (both size and position) of the DOM element. Access the DOM element for a feature via its unique id property within layer.nodes, provided it currently exists.
This is useful for features with complex displays, such as particle effects positioned relative to another feature or drawing links between different nodes. To illustrate this, let's look at a complete example of using layer.nodes to get a node's bounding rect and then placing a particle effect using it. Here's an example from Kronos:
ts
const particlesEmitter =ref(particles.addEmitter(element.particlesConfig));const updateParticleEffect =async ([shouldEmit, rect, boundingRect]: [boolean,DOMRect|undefined,
diff --git a/assets/guide_advanced-concepts_nodes.md.f29b361a.lean.js b/assets/guide_advanced-concepts_nodes.md.7a309ab5.lean.js
similarity index 84%
rename from assets/guide_advanced-concepts_nodes.md.f29b361a.lean.js
rename to assets/guide_advanced-concepts_nodes.md.7a309ab5.lean.js
index c89f48cb..f6091a41 100644
--- a/assets/guide_advanced-concepts_nodes.md.f29b361a.lean.js
+++ b/assets/guide_advanced-concepts_nodes.md.7a309ab5.lean.js
@@ -1 +1 @@
-import{_ as s,c as n,o as a,N as o}from"./chunks/framework.0799945b.js";const C=JSON.parse('{"title":"Nodes","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/nodes.md","lastUpdated":1681094412000}'),l={name:"guide/advanced-concepts/nodes.md"},e=o("",6),p=[e];function t(c,r,y,D,F,i){return a(),n("div",null,p)}const d=s(l,[["render",t]]);export{C as __pageData,d as default};
+import{_ as s,c as n,o as a,N as o}from"./chunks/framework.0799945b.js";const C=JSON.parse('{"title":"Nodes","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/nodes.md","lastUpdated":1681131771000}'),l={name:"guide/advanced-concepts/nodes.md"},e=o("",6),p=[e];function t(c,r,y,D,F,i){return a(),n("div",null,p)}const d=s(l,[["render",t]]);export{C as __pageData,d as default};
diff --git a/assets/guide_creating-your-project_changelog.md.2ba9cb27.js b/assets/guide_creating-your-project_changelog.md.d1bc7baa.js
similarity index 98%
rename from assets/guide_creating-your-project_changelog.md.2ba9cb27.js
rename to assets/guide_creating-your-project_changelog.md.d1bc7baa.js
index b6680daf..d25347d5 100644
--- a/assets/guide_creating-your-project_changelog.md.2ba9cb27.js
+++ b/assets/guide_creating-your-project_changelog.md.d1bc7baa.js
@@ -1,4 +1,4 @@
-import{_ as s,c as a,o as n,N as e}from"./chunks/framework.0799945b.js";const g=JSON.parse('{"title":"Changelog","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/changelog.md","lastUpdated":1681094412000}'),l={name:"guide/creating-your-project/changelog.md"},o=e(`
This is a Vue component stored at /src/data/Changelog.vue used to display all the changes version to version. You can use any features you'd like within here, but it's recommended to simply add new <details> elements for each new major release, and mark the most recent one as open by default. It is strongly advised to not change any of the code relating to making the changelog appear in a modal.
There is a single version included by default that can serve as a reference of how it is recommended to add a version to the changelog:
html
<detailsopen>
+import{_ as s,c as a,o as n,N as e}from"./chunks/framework.0799945b.js";const g=JSON.parse('{"title":"Changelog","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/changelog.md","lastUpdated":1681131771000}'),l={name:"guide/creating-your-project/changelog.md"},o=e(`
This is a Vue component stored at /src/data/Changelog.vue used to display all the changes version to version. You can use any features you'd like within here, but it's recommended to simply add new <details> elements for each new major release, and mark the most recent one as open by default. It is strongly advised to not change any of the code relating to making the changelog appear in a modal.
There is a single version included by default that can serve as a reference of how it is recommended to add a version to the changelog:
html
<detailsopen><summary>v0.0 Initial Commit - <time>2021-09-04</time></summary> This is the first release :D<ul>
diff --git a/assets/guide_creating-your-project_changelog.md.2ba9cb27.lean.js b/assets/guide_creating-your-project_changelog.md.d1bc7baa.lean.js
similarity index 85%
rename from assets/guide_creating-your-project_changelog.md.2ba9cb27.lean.js
rename to assets/guide_creating-your-project_changelog.md.d1bc7baa.lean.js
index fd03c889..111b3e8a 100644
--- a/assets/guide_creating-your-project_changelog.md.2ba9cb27.lean.js
+++ b/assets/guide_creating-your-project_changelog.md.d1bc7baa.lean.js
@@ -1 +1 @@
-import{_ as s,c as a,o as n,N as e}from"./chunks/framework.0799945b.js";const g=JSON.parse('{"title":"Changelog","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/changelog.md","lastUpdated":1681094412000}'),l={name:"guide/creating-your-project/changelog.md"},o=e("",6),t=[o];function p(c,r,D,i,F,y){return n(),a("div",null,t)}const h=s(l,[["render",p]]);export{g as __pageData,h as default};
+import{_ as s,c as a,o as n,N as e}from"./chunks/framework.0799945b.js";const g=JSON.parse('{"title":"Changelog","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/changelog.md","lastUpdated":1681131771000}'),l={name:"guide/creating-your-project/changelog.md"},o=e("",6),t=[o];function p(c,r,D,i,F,y){return n(),a("div",null,t)}const h=s(l,[["render",p]]);export{g as __pageData,h as default};
diff --git a/assets/guide_creating-your-project_project-entry.md.56be55a7.js b/assets/guide_creating-your-project_project-entry.md.99e6dee7.js
similarity index 98%
rename from assets/guide_creating-your-project_project-entry.md.56be55a7.js
rename to assets/guide_creating-your-project_project-entry.md.99e6dee7.js
index 0f30ffcc..d5b5f6c7 100644
--- a/assets/guide_creating-your-project_project-entry.md.56be55a7.js
+++ b/assets/guide_creating-your-project_project-entry.md.99e6dee7.js
@@ -1 +1 @@
-import{_ as e,c as a,o as t,N as o}from"./chunks/framework.0799945b.js";const f=JSON.parse('{"title":"Project Entry","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/project-entry.md","lastUpdated":1681094412000}'),s={name:"guide/creating-your-project/project-entry.md"},r=o('
This is a TypeScript file containing the non-static parts of your project, and acts as the entry point for it.
It is stored at /src/data/projEntry.jsx.
This file has 3 things it must export, but beyond that can export anything the creator wants it to. Typically in addition to the required 3, the initial/"main" layer will be exported. Typically utilites belong in common.tsx, which exists next to projEntry.tsx.
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.
This function will be run whenever a save is loaded that has a different version than the one in project info. It will be given the old version number, and the player save data object currently being loaded.
The purpose of this function is to perform any necessary migrations, such as capping a resource that accidentally inflated in a previous version of the project. By default it will do nothing.
',17),n=[r];function l(i,p,c,d,h,y){return t(),a("div",null,n)}const g=e(s,[["render",l]]);export{f as __pageData,g as default};
+import{_ as e,c as a,o as t,N as o}from"./chunks/framework.0799945b.js";const f=JSON.parse('{"title":"Project Entry","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/project-entry.md","lastUpdated":1681131771000}'),s={name:"guide/creating-your-project/project-entry.md"},r=o('
This is a TypeScript file containing the non-static parts of your project, and acts as the entry point for it.
It is stored at /src/data/projEntry.jsx.
This file has 3 things it must export, but beyond that can export anything the creator wants it to. Typically in addition to the required 3, the initial/"main" layer will be exported. Typically utilites belong in common.tsx, which exists next to projEntry.tsx.
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.
This function will be run whenever a save is loaded that has a different version than the one in project info. It will be given the old version number, and the player save data object currently being loaded.
The purpose of this function is to perform any necessary migrations, such as capping a resource that accidentally inflated in a previous version of the project. By default it will do nothing.
',17),n=[r];function l(i,p,c,d,h,y){return t(),a("div",null,n)}const g=e(s,[["render",l]]);export{f as __pageData,g as default};
diff --git a/assets/guide_creating-your-project_project-entry.md.56be55a7.lean.js b/assets/guide_creating-your-project_project-entry.md.99e6dee7.lean.js
similarity index 85%
rename from assets/guide_creating-your-project_project-entry.md.56be55a7.lean.js
rename to assets/guide_creating-your-project_project-entry.md.99e6dee7.lean.js
index 28124616..50ee1c57 100644
--- a/assets/guide_creating-your-project_project-entry.md.56be55a7.lean.js
+++ b/assets/guide_creating-your-project_project-entry.md.99e6dee7.lean.js
@@ -1 +1 @@
-import{_ as e,c as a,o as t,N as o}from"./chunks/framework.0799945b.js";const f=JSON.parse('{"title":"Project Entry","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/project-entry.md","lastUpdated":1681094412000}'),s={name:"guide/creating-your-project/project-entry.md"},r=o("",17),n=[r];function l(i,p,c,d,h,y){return t(),a("div",null,n)}const g=e(s,[["render",l]]);export{f as __pageData,g as default};
+import{_ as e,c as a,o as t,N as o}from"./chunks/framework.0799945b.js";const f=JSON.parse('{"title":"Project Entry","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/project-entry.md","lastUpdated":1681131771000}'),s={name:"guide/creating-your-project/project-entry.md"},r=o("",17),n=[r];function l(i,p,c,d,h,y){return t(),a("div",null,n)}const g=e(s,[["render",l]]);export{f as __pageData,g as default};
diff --git a/assets/guide_creating-your-project_project-info.md.9bccc4b8.js b/assets/guide_creating-your-project_project-info.md.4c371e5e.js
similarity index 99%
rename from assets/guide_creating-your-project_project-info.md.9bccc4b8.js
rename to assets/guide_creating-your-project_project-info.md.4c371e5e.js
index e73ba280..cb45a420 100644
--- a/assets/guide_creating-your-project_project-info.md.9bccc4b8.js
+++ b/assets/guide_creating-your-project_project-info.md.4c371e5e.js
@@ -1 +1 @@
-import{_ as e,c as a,o,N as t}from"./chunks/framework.0799945b.js";const b=JSON.parse('{"title":"Project Info","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/project-info.md","lastUpdated":1681094412000}'),i={name:"guide/creating-your-project/project-info.md"},l=t('
This is a unique ID used when saving player data. Changing this will effectively erase all save data for all players.
WARNING
This ID MUST be unique to your project, and should not be left as the default value. Otherwise, your project may use the save data from another project and cause issues for both projects.
The text to display for the discord server to point users to. This will appear when hovering over the discord icon, inside the info tab, the game over screen, as well as the NaN detected screen.
By default, this is The Paper Pilot Community, which can act as a catch-all for any Profectus projects without their own servers. If you change the discord server with your own, The Paper Pilot Community will still display underneath the custom server when hovering over the discord icon and within the info tab. Those places will also contain a link to the Modding Tree discord server.
The current version of the project loaded. If the player data was last saved in a different version of the project, fixOldSave will be run, so you can perform any save migrations necessary. This will also appear in the nav, the info tab, and the game over screen.
The display name for the current version of the project loaded. This will also appear in the nav, the info tab, and the game over screen unless set to an empty string.
Whether or not to allow resources to display small values (<.001). If false they'll just display as 0. Individual resources can also be configured to override this value.
Default precision to display numbers at when passed into format. Individual format calls can override this value, and resources can be configured with a custom precision as well.
Whether or not to display the nav as a header at the top of the screen. If disabled, the nav will appear on the left side of the screen laid over the first tab.
A path to an image file to display as the logo of the app. If null, the title will be shown instead. This will appear in the nav when useHeader is true.
The list of initial tabs to display on new saves. This value must have at least one element. Each element should be the ID of the layer to display in that tab.
The longest duration a single tick can be, in seconds. When calculating things like offline time, a single tick will be forced to be this amount or lower. This will make calculating offline time spread out across many ticks as necessary. The default value is 1 hour.
Whether or not to allow the player to pause the game. Turning this off disables the toggle from the options menu as well as the NaN screen. Developers can still manually pause by just running player.devSpeed = 0 in console (or = 1 to resume).
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.
',66),r=[l];function n(s,d,c,h,u,p){return o(),a("div",null,r)}const m=e(i,[["render",n]]);export{b as __pageData,m as default};
+import{_ as e,c as a,o,N as t}from"./chunks/framework.0799945b.js";const b=JSON.parse('{"title":"Project Info","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/project-info.md","lastUpdated":1681131771000}'),i={name:"guide/creating-your-project/project-info.md"},l=t('
This is a unique ID used when saving player data. Changing this will effectively erase all save data for all players.
WARNING
This ID MUST be unique to your project, and should not be left as the default value. Otherwise, your project may use the save data from another project and cause issues for both projects.
The text to display for the discord server to point users to. This will appear when hovering over the discord icon, inside the info tab, the game over screen, as well as the NaN detected screen.
By default, this is The Paper Pilot Community, which can act as a catch-all for any Profectus projects without their own servers. If you change the discord server with your own, The Paper Pilot Community will still display underneath the custom server when hovering over the discord icon and within the info tab. Those places will also contain a link to the Modding Tree discord server.
The current version of the project loaded. If the player data was last saved in a different version of the project, fixOldSave will be run, so you can perform any save migrations necessary. This will also appear in the nav, the info tab, and the game over screen.
The display name for the current version of the project loaded. This will also appear in the nav, the info tab, and the game over screen unless set to an empty string.
Whether or not to allow resources to display small values (<.001). If false they'll just display as 0. Individual resources can also be configured to override this value.
Default precision to display numbers at when passed into format. Individual format calls can override this value, and resources can be configured with a custom precision as well.
Whether or not to display the nav as a header at the top of the screen. If disabled, the nav will appear on the left side of the screen laid over the first tab.
A path to an image file to display as the logo of the app. If null, the title will be shown instead. This will appear in the nav when useHeader is true.
The list of initial tabs to display on new saves. This value must have at least one element. Each element should be the ID of the layer to display in that tab.
The longest duration a single tick can be, in seconds. When calculating things like offline time, a single tick will be forced to be this amount or lower. This will make calculating offline time spread out across many ticks as necessary. The default value is 1 hour.
Whether or not to allow the player to pause the game. Turning this off disables the toggle from the options menu as well as the NaN screen. Developers can still manually pause by just running player.devSpeed = 0 in console (or = 1 to resume).
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.
',66),r=[l];function n(s,d,c,h,u,p){return o(),a("div",null,r)}const m=e(i,[["render",n]]);export{b as __pageData,m as default};
diff --git a/assets/guide_creating-your-project_project-info.md.9bccc4b8.lean.js b/assets/guide_creating-your-project_project-info.md.4c371e5e.lean.js
similarity index 85%
rename from assets/guide_creating-your-project_project-info.md.9bccc4b8.lean.js
rename to assets/guide_creating-your-project_project-info.md.4c371e5e.lean.js
index 3b522ff6..7fc68eec 100644
--- a/assets/guide_creating-your-project_project-info.md.9bccc4b8.lean.js
+++ b/assets/guide_creating-your-project_project-info.md.4c371e5e.lean.js
@@ -1 +1 @@
-import{_ as e,c as a,o,N as t}from"./chunks/framework.0799945b.js";const b=JSON.parse('{"title":"Project Info","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/project-info.md","lastUpdated":1681094412000}'),i={name:"guide/creating-your-project/project-info.md"},l=t("",66),r=[l];function n(s,d,c,h,u,p){return o(),a("div",null,r)}const m=e(i,[["render",n]]);export{b as __pageData,m as default};
+import{_ as e,c as a,o,N as t}from"./chunks/framework.0799945b.js";const b=JSON.parse('{"title":"Project Info","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/project-info.md","lastUpdated":1681131771000}'),i={name:"guide/creating-your-project/project-info.md"},l=t("",66),r=[l];function n(s,d,c,h,u,p){return o(),a("div",null,r)}const m=e(i,[["render",n]]);export{b as __pageData,m as default};
diff --git a/assets/guide_creating-your-project_themes.md.5c750649.js b/assets/guide_creating-your-project_themes.md.ad0d91c8.js
similarity index 97%
rename from assets/guide_creating-your-project_themes.md.5c750649.js
rename to assets/guide_creating-your-project_themes.md.ad0d91c8.js
index 786e0924..10d4eb1c 100644
--- a/assets/guide_creating-your-project_themes.md.5c750649.js
+++ b/assets/guide_creating-your-project_themes.md.ad0d91c8.js
@@ -1 +1 @@
-import{_ as e,c as t,o as a,N as o}from"./chunks/framework.0799945b.js";const g=JSON.parse('{"title":"Themes","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/themes.md","lastUpdated":1681094412000}'),i={name:"guide/creating-your-project/themes.md"},s=o('
Themes are objects that change how the project's interface should look. This is done mostly by changing the values of various CSS variables. You can look at the existing themes as a reference for the kind of values these CSS variables expect. They can also set various theme options that change how parts of the screen are laid out, which are described below.
You can add a theme by adding a property to the Themes enum and then including the theme in the exported object. It's recommended to use the spread operator if you'd like to have a theme look like another, but override specific options / CSS variables.
Themes added in this way will be automatically included in the Themes dropdown in the Options tab. Removing themes from the enum and exported object will similarly hide them from the dropdown.
If you'd like to change which theme is the default, you may modify the initial player settings object in the /src/game/settings.ts file. Keep in mind you'll also want to change it in the hardResetSettings function in the same file.
Toggles whether to display tab buttons in a tab list, similar to how a browser displays tabs; or to display them as floating buttons, similar to how TMT displays buttons.
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.
',15),n=[s];function r(h,l,d,c,m,p){return a(),t("div",null,n)}const b=e(i,[["render",r]]);export{g as __pageData,b as default};
+import{_ as e,c as t,o as a,N as o}from"./chunks/framework.0799945b.js";const g=JSON.parse('{"title":"Themes","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/themes.md","lastUpdated":1681131771000}'),i={name:"guide/creating-your-project/themes.md"},s=o('
Themes are objects that change how the project's interface should look. This is done mostly by changing the values of various CSS variables. You can look at the existing themes as a reference for the kind of values these CSS variables expect. They can also set various theme options that change how parts of the screen are laid out, which are described below.
You can add a theme by adding a property to the Themes enum and then including the theme in the exported object. It's recommended to use the spread operator if you'd like to have a theme look like another, but override specific options / CSS variables.
Themes added in this way will be automatically included in the Themes dropdown in the Options tab. Removing themes from the enum and exported object will similarly hide them from the dropdown.
If you'd like to change which theme is the default, you may modify the initial player settings object in the /src/game/settings.ts file. Keep in mind you'll also want to change it in the hardResetSettings function in the same file.
Toggles whether to display tab buttons in a tab list, similar to how a browser displays tabs; or to display them as floating buttons, similar to how TMT displays buttons.
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.
',15),n=[s];function r(h,l,d,c,m,p){return a(),t("div",null,n)}const b=e(i,[["render",r]]);export{g as __pageData,b as default};
diff --git a/assets/guide_creating-your-project_themes.md.5c750649.lean.js b/assets/guide_creating-your-project_themes.md.ad0d91c8.lean.js
similarity index 84%
rename from assets/guide_creating-your-project_themes.md.5c750649.lean.js
rename to assets/guide_creating-your-project_themes.md.ad0d91c8.lean.js
index 33aec2f6..a751d152 100644
--- a/assets/guide_creating-your-project_themes.md.5c750649.lean.js
+++ b/assets/guide_creating-your-project_themes.md.ad0d91c8.lean.js
@@ -1 +1 @@
-import{_ as e,c as t,o as a,N as o}from"./chunks/framework.0799945b.js";const g=JSON.parse('{"title":"Themes","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/themes.md","lastUpdated":1681094412000}'),i={name:"guide/creating-your-project/themes.md"},s=o("",15),n=[s];function r(h,l,d,c,m,p){return a(),t("div",null,n)}const b=e(i,[["render",r]]);export{g as __pageData,b as default};
+import{_ as e,c as t,o as a,N as o}from"./chunks/framework.0799945b.js";const g=JSON.parse('{"title":"Themes","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/themes.md","lastUpdated":1681131771000}'),i={name:"guide/creating-your-project/themes.md"},s=o("",15),n=[s];function r(h,l,d,c,m,p){return a(),t("div",null,n)}const b=e(i,[["render",r]]);export{g as __pageData,b as default};
diff --git a/assets/guide_creating-your-project_utils.md.264c40cd.js b/assets/guide_creating-your-project_utils.md.b7441a18.js
similarity index 95%
rename from assets/guide_creating-your-project_utils.md.264c40cd.js
rename to assets/guide_creating-your-project_utils.md.b7441a18.js
index 4f141e64..3b355393 100644
--- a/assets/guide_creating-your-project_utils.md.264c40cd.js
+++ b/assets/guide_creating-your-project_utils.md.b7441a18.js
@@ -1 +1 @@
-import{_ as a,c as o,o as s,x as e,a as t}from"./chunks/framework.0799945b.js";const b=JSON.parse('{"title":"Utilities","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/utils.md","lastUpdated":1681094412000}'),i={name:"guide/creating-your-project/utils.md"},n=e("h1",{id:"utilities",tabindex:"-1"},[t("Utilities "),e("a",{class:"header-anchor",href:"#utilities","aria-label":'Permalink to "Utilities"'},"​")],-1),r=e("p",null,"There are often concepts that aren't inherent to a single feature, but rather work with joining different features together. For example, a reset clickable that activates a conversion and resets a tree, which happens to be a common use case but isn't inherent to clickables, conversions, or trees.",-1),c=e("p",null,[t("These are perfect situations for utilities, and so to encourage creators to learn to identify and take advantage of these situations, a file called "),e("code",null,"src/data/common.tsx"),t(" has been created to demo some of the more common utility functions a project might use. Adding new utilities to this file is encouraged, as is creating utils in general. It also works as a good stepping stone to creating your own features.")],-1),l=[n,r,c];function d(u,h,p,f,m,_){return s(),o("div",null,l)}const k=a(i,[["render",d]]);export{b as __pageData,k as default};
+import{_ as a,c as o,o as s,x as e,a as t}from"./chunks/framework.0799945b.js";const b=JSON.parse('{"title":"Utilities","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/utils.md","lastUpdated":1681131771000}'),i={name:"guide/creating-your-project/utils.md"},n=e("h1",{id:"utilities",tabindex:"-1"},[t("Utilities "),e("a",{class:"header-anchor",href:"#utilities","aria-label":'Permalink to "Utilities"'},"​")],-1),r=e("p",null,"There are often concepts that aren't inherent to a single feature, but rather work with joining different features together. For example, a reset clickable that activates a conversion and resets a tree, which happens to be a common use case but isn't inherent to clickables, conversions, or trees.",-1),c=e("p",null,[t("These are perfect situations for utilities, and so to encourage creators to learn to identify and take advantage of these situations, a file called "),e("code",null,"src/data/common.tsx"),t(" has been created to demo some of the more common utility functions a project might use. Adding new utilities to this file is encouraged, as is creating utils in general. It also works as a good stepping stone to creating your own features.")],-1),l=[n,r,c];function d(u,h,p,f,m,_){return s(),o("div",null,l)}const k=a(i,[["render",d]]);export{b as __pageData,k as default};
diff --git a/assets/guide_creating-your-project_utils.md.264c40cd.lean.js b/assets/guide_creating-your-project_utils.md.b7441a18.lean.js
similarity index 95%
rename from assets/guide_creating-your-project_utils.md.264c40cd.lean.js
rename to assets/guide_creating-your-project_utils.md.b7441a18.lean.js
index 4f141e64..3b355393 100644
--- a/assets/guide_creating-your-project_utils.md.264c40cd.lean.js
+++ b/assets/guide_creating-your-project_utils.md.b7441a18.lean.js
@@ -1 +1 @@
-import{_ as a,c as o,o as s,x as e,a as t}from"./chunks/framework.0799945b.js";const b=JSON.parse('{"title":"Utilities","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/utils.md","lastUpdated":1681094412000}'),i={name:"guide/creating-your-project/utils.md"},n=e("h1",{id:"utilities",tabindex:"-1"},[t("Utilities "),e("a",{class:"header-anchor",href:"#utilities","aria-label":'Permalink to "Utilities"'},"​")],-1),r=e("p",null,"There are often concepts that aren't inherent to a single feature, but rather work with joining different features together. For example, a reset clickable that activates a conversion and resets a tree, which happens to be a common use case but isn't inherent to clickables, conversions, or trees.",-1),c=e("p",null,[t("These are perfect situations for utilities, and so to encourage creators to learn to identify and take advantage of these situations, a file called "),e("code",null,"src/data/common.tsx"),t(" has been created to demo some of the more common utility functions a project might use. Adding new utilities to this file is encouraged, as is creating utils in general. It also works as a good stepping stone to creating your own features.")],-1),l=[n,r,c];function d(u,h,p,f,m,_){return s(),o("div",null,l)}const k=a(i,[["render",d]]);export{b as __pageData,k as default};
+import{_ as a,c as o,o as s,x as e,a as t}from"./chunks/framework.0799945b.js";const b=JSON.parse('{"title":"Utilities","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/utils.md","lastUpdated":1681131771000}'),i={name:"guide/creating-your-project/utils.md"},n=e("h1",{id:"utilities",tabindex:"-1"},[t("Utilities "),e("a",{class:"header-anchor",href:"#utilities","aria-label":'Permalink to "Utilities"'},"​")],-1),r=e("p",null,"There are often concepts that aren't inherent to a single feature, but rather work with joining different features together. For example, a reset clickable that activates a conversion and resets a tree, which happens to be a common use case but isn't inherent to clickables, conversions, or trees.",-1),c=e("p",null,[t("These are perfect situations for utilities, and so to encourage creators to learn to identify and take advantage of these situations, a file called "),e("code",null,"src/data/common.tsx"),t(" has been created to demo some of the more common utility functions a project might use. Adding new utilities to this file is encouraged, as is creating utils in general. It also works as a good stepping stone to creating your own features.")],-1),l=[n,r,c];function d(u,h,p,f,m,_){return s(),o("div",null,l)}const k=a(i,[["render",d]]);export{b as __pageData,k as default};
diff --git a/assets/guide_getting-started_examples.md.305530d8.js b/assets/guide_getting-started_examples.md.9aa0f345.js
similarity index 97%
rename from assets/guide_getting-started_examples.md.305530d8.js
rename to assets/guide_getting-started_examples.md.9aa0f345.js
index 1c1f082c..20d159da 100644
--- a/assets/guide_getting-started_examples.md.305530d8.js
+++ b/assets/guide_getting-started_examples.md.9aa0f345.js
@@ -1 +1 @@
-import{_ as o,c as n,x as e,a as t,C as a,o as s,D as i}from"./chunks/framework.0799945b.js";const D=JSON.parse('{"title":"Example Projects","description":"","frontmatter":{},"headers":[],"relativePath":"guide/getting-started/examples.md","lastUpdated":1681094412000}'),l={name:"guide/getting-started/examples.md"},c=e("h1",{id:"example-projects",tabindex:"-1"},[t("Example Projects "),e("a",{class:"header-anchor",href:"#example-projects","aria-label":'Permalink to "Example Projects"'},"​")],-1),d={id:"advent-incremental",tabindex:"-1"},p=e("a",{class:"header-anchor",href:"#advent-incremental","aria-label":'Permalink to "Advent Incremental "'},"​",-1),h=e("p",null,[e("a",{href:"https://github.com/thepaperpilot/advent-Incremental/",target:"_blank",rel:"noreferrer"},"View Source"),t(" | "),e("a",{href:"https://www.thepaperpilot.org/advent/",target:"_blank",rel:"noreferrer"},"View Project")],-1),m=e("p",null,[t("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 "),e("a",{href:"https://github.com/thepaperpilot/advent-Incremental/tree/next",target:"_blank",rel:"noreferrer"},"here"),t(".")],-1),f={id:"primordia",tabindex:"-1"},_=e("a",{class:"header-anchor",href:"#primordia","aria-label":'Permalink to "Primordia "'},"​",-1),g=e("p",null,[e("a",{href:"https://github.com/Jacorb90/Primordial-Tree",target:"_blank",rel:"noreferrer"},"View Source"),t(" | "),e("a",{href:"https://jacorb90.me/Primordial-Tree/",target:"_blank",rel:"noreferrer"},"View Project")],-1),u=e("p",null,'A "The Prestige Tree" style incremental game, developed by the original creator of TPT.',-1),b={id:"tmt-demo",tabindex:"-1"},x=e("a",{class:"header-anchor",href:"#tmt-demo","aria-label":'Permalink to "TMT-Demo "'},"​",-1),P=e("p",null,[e("a",{href:"https://github.com/profectus-engine/tmt-demo",target:"_blank",rel:"noreferrer"},"View Source"),t(" | "),e("a",{href:"https://profectus-engine.github.io/TMT-Demo/",target:"_blank",rel:"noreferrer"},"View Project")],-1),T=e("p",null,"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.",-1);function k(w,v,j,y,V,B){const r=i("Badge");return s(),n("div",null,[c,e("h2",d,[t("Advent Incremental "),a(r,{type:"warning",text:"Profectus 0.5"}),t(),p]),h,m,e("h2",f,[t("Primordia "),a(r,{type:"warning",text:"Profectus 0.5"}),t(),_]),g,u,e("h2",b,[t("TMT-Demo "),a(r,{type:"tip",text:"Profectus 0.6"}),t(),x]),P,T])}const E=o(l,[["render",k]]);export{D as __pageData,E as default};
+import{_ as o,c as n,x as e,a as t,C as a,o as s,D as i}from"./chunks/framework.0799945b.js";const D=JSON.parse('{"title":"Example Projects","description":"","frontmatter":{},"headers":[],"relativePath":"guide/getting-started/examples.md","lastUpdated":1681131771000}'),l={name:"guide/getting-started/examples.md"},c=e("h1",{id:"example-projects",tabindex:"-1"},[t("Example Projects "),e("a",{class:"header-anchor",href:"#example-projects","aria-label":'Permalink to "Example Projects"'},"​")],-1),d={id:"advent-incremental",tabindex:"-1"},p=e("a",{class:"header-anchor",href:"#advent-incremental","aria-label":'Permalink to "Advent Incremental "'},"​",-1),h=e("p",null,[e("a",{href:"https://github.com/thepaperpilot/advent-Incremental/",target:"_blank",rel:"noreferrer"},"View Source"),t(" | "),e("a",{href:"https://www.thepaperpilot.org/advent/",target:"_blank",rel:"noreferrer"},"View Project")],-1),m=e("p",null,[t("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 "),e("a",{href:"https://github.com/thepaperpilot/advent-Incremental/tree/next",target:"_blank",rel:"noreferrer"},"here"),t(".")],-1),f={id:"primordia",tabindex:"-1"},_=e("a",{class:"header-anchor",href:"#primordia","aria-label":'Permalink to "Primordia "'},"​",-1),g=e("p",null,[e("a",{href:"https://github.com/Jacorb90/Primordial-Tree",target:"_blank",rel:"noreferrer"},"View Source"),t(" | "),e("a",{href:"https://jacorb90.me/Primordial-Tree/",target:"_blank",rel:"noreferrer"},"View Project")],-1),u=e("p",null,'A "The Prestige Tree" style incremental game, developed by the original creator of TPT.',-1),b={id:"tmt-demo",tabindex:"-1"},x=e("a",{class:"header-anchor",href:"#tmt-demo","aria-label":'Permalink to "TMT-Demo "'},"​",-1),P=e("p",null,[e("a",{href:"https://github.com/profectus-engine/tmt-demo",target:"_blank",rel:"noreferrer"},"View Source"),t(" | "),e("a",{href:"https://profectus-engine.github.io/TMT-Demo/",target:"_blank",rel:"noreferrer"},"View Project")],-1),T=e("p",null,"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.",-1);function k(w,v,j,y,V,B){const r=i("Badge");return s(),n("div",null,[c,e("h2",d,[t("Advent Incremental "),a(r,{type:"warning",text:"Profectus 0.5"}),t(),p]),h,m,e("h2",f,[t("Primordia "),a(r,{type:"warning",text:"Profectus 0.5"}),t(),_]),g,u,e("h2",b,[t("TMT-Demo "),a(r,{type:"tip",text:"Profectus 0.6"}),t(),x]),P,T])}const E=o(l,[["render",k]]);export{D as __pageData,E as default};
diff --git a/assets/guide_getting-started_examples.md.305530d8.lean.js b/assets/guide_getting-started_examples.md.9aa0f345.lean.js
similarity index 97%
rename from assets/guide_getting-started_examples.md.305530d8.lean.js
rename to assets/guide_getting-started_examples.md.9aa0f345.lean.js
index 1c1f082c..20d159da 100644
--- a/assets/guide_getting-started_examples.md.305530d8.lean.js
+++ b/assets/guide_getting-started_examples.md.9aa0f345.lean.js
@@ -1 +1 @@
-import{_ as o,c as n,x as e,a as t,C as a,o as s,D as i}from"./chunks/framework.0799945b.js";const D=JSON.parse('{"title":"Example Projects","description":"","frontmatter":{},"headers":[],"relativePath":"guide/getting-started/examples.md","lastUpdated":1681094412000}'),l={name:"guide/getting-started/examples.md"},c=e("h1",{id:"example-projects",tabindex:"-1"},[t("Example Projects "),e("a",{class:"header-anchor",href:"#example-projects","aria-label":'Permalink to "Example Projects"'},"​")],-1),d={id:"advent-incremental",tabindex:"-1"},p=e("a",{class:"header-anchor",href:"#advent-incremental","aria-label":'Permalink to "Advent Incremental "'},"​",-1),h=e("p",null,[e("a",{href:"https://github.com/thepaperpilot/advent-Incremental/",target:"_blank",rel:"noreferrer"},"View Source"),t(" | "),e("a",{href:"https://www.thepaperpilot.org/advent/",target:"_blank",rel:"noreferrer"},"View Project")],-1),m=e("p",null,[t("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 "),e("a",{href:"https://github.com/thepaperpilot/advent-Incremental/tree/next",target:"_blank",rel:"noreferrer"},"here"),t(".")],-1),f={id:"primordia",tabindex:"-1"},_=e("a",{class:"header-anchor",href:"#primordia","aria-label":'Permalink to "Primordia "'},"​",-1),g=e("p",null,[e("a",{href:"https://github.com/Jacorb90/Primordial-Tree",target:"_blank",rel:"noreferrer"},"View Source"),t(" | "),e("a",{href:"https://jacorb90.me/Primordial-Tree/",target:"_blank",rel:"noreferrer"},"View Project")],-1),u=e("p",null,'A "The Prestige Tree" style incremental game, developed by the original creator of TPT.',-1),b={id:"tmt-demo",tabindex:"-1"},x=e("a",{class:"header-anchor",href:"#tmt-demo","aria-label":'Permalink to "TMT-Demo "'},"​",-1),P=e("p",null,[e("a",{href:"https://github.com/profectus-engine/tmt-demo",target:"_blank",rel:"noreferrer"},"View Source"),t(" | "),e("a",{href:"https://profectus-engine.github.io/TMT-Demo/",target:"_blank",rel:"noreferrer"},"View Project")],-1),T=e("p",null,"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.",-1);function k(w,v,j,y,V,B){const r=i("Badge");return s(),n("div",null,[c,e("h2",d,[t("Advent Incremental "),a(r,{type:"warning",text:"Profectus 0.5"}),t(),p]),h,m,e("h2",f,[t("Primordia "),a(r,{type:"warning",text:"Profectus 0.5"}),t(),_]),g,u,e("h2",b,[t("TMT-Demo "),a(r,{type:"tip",text:"Profectus 0.6"}),t(),x]),P,T])}const E=o(l,[["render",k]]);export{D as __pageData,E as default};
+import{_ as o,c as n,x as e,a as t,C as a,o as s,D as i}from"./chunks/framework.0799945b.js";const D=JSON.parse('{"title":"Example Projects","description":"","frontmatter":{},"headers":[],"relativePath":"guide/getting-started/examples.md","lastUpdated":1681131771000}'),l={name:"guide/getting-started/examples.md"},c=e("h1",{id:"example-projects",tabindex:"-1"},[t("Example Projects "),e("a",{class:"header-anchor",href:"#example-projects","aria-label":'Permalink to "Example Projects"'},"​")],-1),d={id:"advent-incremental",tabindex:"-1"},p=e("a",{class:"header-anchor",href:"#advent-incremental","aria-label":'Permalink to "Advent Incremental "'},"​",-1),h=e("p",null,[e("a",{href:"https://github.com/thepaperpilot/advent-Incremental/",target:"_blank",rel:"noreferrer"},"View Source"),t(" | "),e("a",{href:"https://www.thepaperpilot.org/advent/",target:"_blank",rel:"noreferrer"},"View Project")],-1),m=e("p",null,[t("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 "),e("a",{href:"https://github.com/thepaperpilot/advent-Incremental/tree/next",target:"_blank",rel:"noreferrer"},"here"),t(".")],-1),f={id:"primordia",tabindex:"-1"},_=e("a",{class:"header-anchor",href:"#primordia","aria-label":'Permalink to "Primordia "'},"​",-1),g=e("p",null,[e("a",{href:"https://github.com/Jacorb90/Primordial-Tree",target:"_blank",rel:"noreferrer"},"View Source"),t(" | "),e("a",{href:"https://jacorb90.me/Primordial-Tree/",target:"_blank",rel:"noreferrer"},"View Project")],-1),u=e("p",null,'A "The Prestige Tree" style incremental game, developed by the original creator of TPT.',-1),b={id:"tmt-demo",tabindex:"-1"},x=e("a",{class:"header-anchor",href:"#tmt-demo","aria-label":'Permalink to "TMT-Demo "'},"​",-1),P=e("p",null,[e("a",{href:"https://github.com/profectus-engine/tmt-demo",target:"_blank",rel:"noreferrer"},"View Source"),t(" | "),e("a",{href:"https://profectus-engine.github.io/TMT-Demo/",target:"_blank",rel:"noreferrer"},"View Project")],-1),T=e("p",null,"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.",-1);function k(w,v,j,y,V,B){const r=i("Badge");return s(),n("div",null,[c,e("h2",d,[t("Advent Incremental "),a(r,{type:"warning",text:"Profectus 0.5"}),t(),p]),h,m,e("h2",f,[t("Primordia "),a(r,{type:"warning",text:"Profectus 0.5"}),t(),_]),g,u,e("h2",b,[t("TMT-Demo "),a(r,{type:"tip",text:"Profectus 0.6"}),t(),x]),P,T])}const E=o(l,[["render",k]]);export{D as __pageData,E as default};
diff --git a/assets/guide_getting-started_setup.md.036f5e9e.js b/assets/guide_getting-started_setup.md.b8a9be2d.js
similarity index 99%
rename from assets/guide_getting-started_setup.md.036f5e9e.js
rename to assets/guide_getting-started_setup.md.b8a9be2d.js
index 4962e926..5bb24b87 100644
--- a/assets/guide_getting-started_setup.md.036f5e9e.js
+++ b/assets/guide_getting-started_setup.md.b8a9be2d.js
@@ -1 +1 @@
-import{_ as e,c as t,o,N as a}from"./chunks/framework.0799945b.js";const r="/assets/workflow-perms.afbb596c.png",i="/assets/actionsbutton.f1ba9d8e.png",n="/assets/gh-pages.a24cefcf.png",y=JSON.parse('{"title":"Setting Up","description":"","frontmatter":{},"headers":[],"relativePath":"guide/getting-started/setup.md","lastUpdated":1681094412000}'),s={name:"guide/getting-started/setup.md"},l=a('
Profectus requires a Node.js development environment for working on a project. If you are comfortable with the command line, a local development environment is recommended.
Create a new project from the Profectus repository by clicking the "Use this template" button. Then, clone the repository locally using the provided link.
INFO
The template repository allows easy creation of multiple projects from one repository. However, updating an existing project to a newer version of Profectus can be challenging. Consider updating Profectusbefore starting development to avoid issues with unrelated histories.
It's recommended to create a new Git branch for development, allowing you to push changes without affecting the live build. The GitHub workflow will automatically rebuild the page when you push to the main branch.
Next, install Profectus' dependencies by running npm install. Run npm run serve to start a local server hosting your project. The site will automatically reload as you modify files.
Also, follow the steps to update Profectus before starting to make future updates easier without worrying about unrelated histories.
Using Git, the repository's workflow action automates deployment. However, you need to grant write permissions for the action in the repository settings. Go to Actions, General, Workflow permissions, and select "Read and write permissions".
To deploy, push changes to the main branch. The site will be updated automatically in a few minutes. Check progress or errors from the Actions tab on your repository.
Enable GitHub Pages in the repo settings to host the generated site. Select the gh-pages branch. Perform this step once. This will automatically start another GitHub action to deploy the website.
As an alternative to local development, you may use Replit. Replit sets up your development and hosts your project.
On the free plan, you'll face limitations, and the program may need occasional startups.
To create a Profectus project on Replit, all you have to do is click this button:
Click the "Run" button at the top of the screen to start development. This will also make the project publicly accessible, essentially auto-deploying it. However, this means you cannot separate your development and production environments.
Glitch is a site similar to Replit, with many of the same pros and cons. To create a Profectus project on Glitch, select "New Project", "Import from GitHub", and enter profectus-engine/Profectus. The new project will be automatically configured and ready to go.
',30),p=[l];function c(u,h,d,m,f,g){return o(),t("div",null,p)}const _=e(s,[["render",c]]);export{y as __pageData,_ as default};
+import{_ as e,c as t,o,N as a}from"./chunks/framework.0799945b.js";const r="/assets/workflow-perms.afbb596c.png",i="/assets/actionsbutton.f1ba9d8e.png",n="/assets/gh-pages.a24cefcf.png",y=JSON.parse('{"title":"Setting Up","description":"","frontmatter":{},"headers":[],"relativePath":"guide/getting-started/setup.md","lastUpdated":1681131771000}'),s={name:"guide/getting-started/setup.md"},l=a('
Profectus requires a Node.js development environment for working on a project. If you are comfortable with the command line, a local development environment is recommended.
Create a new project from the Profectus repository by clicking the "Use this template" button. Then, clone the repository locally using the provided link.
INFO
The template repository allows easy creation of multiple projects from one repository. However, updating an existing project to a newer version of Profectus can be challenging. Consider updating Profectusbefore starting development to avoid issues with unrelated histories.
It's recommended to create a new Git branch for development, allowing you to push changes without affecting the live build. The GitHub workflow will automatically rebuild the page when you push to the main branch.
Next, install Profectus' dependencies by running npm install. Run npm run serve to start a local server hosting your project. The site will automatically reload as you modify files.
Also, follow the steps to update Profectus before starting to make future updates easier without worrying about unrelated histories.
Using Git, the repository's workflow action automates deployment. However, you need to grant write permissions for the action in the repository settings. Go to Actions, General, Workflow permissions, and select "Read and write permissions".
To deploy, push changes to the main branch. The site will be updated automatically in a few minutes. Check progress or errors from the Actions tab on your repository.
Enable GitHub Pages in the repo settings to host the generated site. Select the gh-pages branch. Perform this step once. This will automatically start another GitHub action to deploy the website.
As an alternative to local development, you may use Replit. Replit sets up your development and hosts your project.
On the free plan, you'll face limitations, and the program may need occasional startups.
To create a Profectus project on Replit, all you have to do is click this button:
Click the "Run" button at the top of the screen to start development. This will also make the project publicly accessible, essentially auto-deploying it. However, this means you cannot separate your development and production environments.
Glitch is a site similar to Replit, with many of the same pros and cons. To create a Profectus project on Glitch, select "New Project", "Import from GitHub", and enter profectus-engine/Profectus. The new project will be automatically configured and ready to go.
',30),p=[l];function c(u,h,d,m,f,g){return o(),t("div",null,p)}const _=e(s,[["render",c]]);export{y as __pageData,_ as default};
diff --git a/assets/guide_getting-started_setup.md.036f5e9e.lean.js b/assets/guide_getting-started_setup.md.b8a9be2d.lean.js
similarity index 87%
rename from assets/guide_getting-started_setup.md.036f5e9e.lean.js
rename to assets/guide_getting-started_setup.md.b8a9be2d.lean.js
index 813403c4..7afb995e 100644
--- a/assets/guide_getting-started_setup.md.036f5e9e.lean.js
+++ b/assets/guide_getting-started_setup.md.b8a9be2d.lean.js
@@ -1 +1 @@
-import{_ as e,c as t,o,N as a}from"./chunks/framework.0799945b.js";const r="/assets/workflow-perms.afbb596c.png",i="/assets/actionsbutton.f1ba9d8e.png",n="/assets/gh-pages.a24cefcf.png",y=JSON.parse('{"title":"Setting Up","description":"","frontmatter":{},"headers":[],"relativePath":"guide/getting-started/setup.md","lastUpdated":1681094412000}'),s={name:"guide/getting-started/setup.md"},l=a("",30),p=[l];function c(u,h,d,m,f,g){return o(),t("div",null,p)}const _=e(s,[["render",c]]);export{y as __pageData,_ as default};
+import{_ as e,c as t,o,N as a}from"./chunks/framework.0799945b.js";const r="/assets/workflow-perms.afbb596c.png",i="/assets/actionsbutton.f1ba9d8e.png",n="/assets/gh-pages.a24cefcf.png",y=JSON.parse('{"title":"Setting Up","description":"","frontmatter":{},"headers":[],"relativePath":"guide/getting-started/setup.md","lastUpdated":1681131771000}'),s={name:"guide/getting-started/setup.md"},l=a("",30),p=[l];function c(u,h,d,m,f,g){return o(),t("div",null,p)}const _=e(s,[["render",c]]);export{y as __pageData,_ as default};
diff --git a/assets/guide_getting-started_updating.md.6193ab7a.js b/assets/guide_getting-started_updating.md.0e2b10bd.js
similarity index 98%
rename from assets/guide_getting-started_updating.md.6193ab7a.js
rename to assets/guide_getting-started_updating.md.0e2b10bd.js
index 82a9374a..014acb4d 100644
--- a/assets/guide_getting-started_updating.md.6193ab7a.js
+++ b/assets/guide_getting-started_updating.md.0e2b10bd.js
@@ -1,3 +1,3 @@
-import{_ as e,c as t,o as a,N as o}from"./chunks/framework.0799945b.js";const g=JSON.parse('{"title":"Updating Profectus","description":"","frontmatter":{},"headers":[],"relativePath":"guide/getting-started/updating.md","lastUpdated":1681094412000}'),s={name:"guide/getting-started/updating.md"},n=o(`
Due to Profectus being a template repository, your projects do not share a git history with Profectus. In order to update changes, you will need to run the following:
bash
gitremoteaddtemplatehttps://github.com/profectus-engine/Profectus
+import{_ as e,c as t,o as a,N as o}from"./chunks/framework.0799945b.js";const g=JSON.parse('{"title":"Updating Profectus","description":"","frontmatter":{},"headers":[],"relativePath":"guide/getting-started/updating.md","lastUpdated":1681131771000}'),s={name:"guide/getting-started/updating.md"},n=o(`
Due to Profectus being a template repository, your projects do not share a git history with Profectus. In order to update changes, you will need to run the following:
The first command only has to be performed once. The third command may require you to merge conflicts between code both you and Profectus have changed - however, due to the modularity of Profectus, this should be fairly rare. Unfortunately, due to the unrelated histories the first time you do this every change will be marked as a conflict, and you'll need to accept each one.
The sidebar has a tab labelled "Version Control", which you can use to merge all changes made to Profectus into your project. Unfortunately, replit does not have a merge tool so this process may irrecoverably erase changes you've made - I'd recommend making a backup first.
Unfortunately glitch does not provide any method by which to update a project from a github repository. If you've only changed things in the data folder you may consider creating a new project, importing the current version of Profectus, and then placing your data folder in the new project.
`,9),r=[n];function l(i,c,p,d,h,u){return a(),t("div",null,r)}const m=e(s,[["render",l]]);export{g as __pageData,m as default};
diff --git a/assets/guide_getting-started_updating.md.6193ab7a.lean.js b/assets/guide_getting-started_updating.md.0e2b10bd.lean.js
similarity index 85%
rename from assets/guide_getting-started_updating.md.6193ab7a.lean.js
rename to assets/guide_getting-started_updating.md.0e2b10bd.lean.js
index 6ac5b16c..2457adf8 100644
--- a/assets/guide_getting-started_updating.md.6193ab7a.lean.js
+++ b/assets/guide_getting-started_updating.md.0e2b10bd.lean.js
@@ -1 +1 @@
-import{_ as e,c as t,o as a,N as o}from"./chunks/framework.0799945b.js";const g=JSON.parse('{"title":"Updating Profectus","description":"","frontmatter":{},"headers":[],"relativePath":"guide/getting-started/updating.md","lastUpdated":1681094412000}'),s={name:"guide/getting-started/updating.md"},n=o("",9),r=[n];function l(i,c,p,d,h,u){return a(),t("div",null,r)}const m=e(s,[["render",l]]);export{g as __pageData,m as default};
+import{_ as e,c as t,o as a,N as o}from"./chunks/framework.0799945b.js";const g=JSON.parse('{"title":"Updating Profectus","description":"","frontmatter":{},"headers":[],"relativePath":"guide/getting-started/updating.md","lastUpdated":1681131771000}'),s={name:"guide/getting-started/updating.md"},n=o("",9),r=[n];function l(i,c,p,d,h,u){return a(),t("div",null,r)}const m=e(s,[["render",l]]);export{g as __pageData,m as default};
diff --git a/assets/guide_important-concepts_coercable.md.025d8a15.js b/assets/guide_important-concepts_coercable.md.d6377a4d.js
similarity index 99%
rename from assets/guide_important-concepts_coercable.md.025d8a15.js
rename to assets/guide_important-concepts_coercable.md.d6377a4d.js
index 7353989d..32227dbd 100644
--- a/assets/guide_important-concepts_coercable.md.025d8a15.js
+++ b/assets/guide_important-concepts_coercable.md.d6377a4d.js
@@ -1,4 +1,4 @@
-import{_ as e,c as a,o as s,N as n}from"./chunks/framework.0799945b.js";const h=JSON.parse('{"title":"Coercable Components","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/coercable.md","lastUpdated":1681094412000}'),o={name:"guide/important-concepts/coercable.md"},t=n(`
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
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.
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. Note that JSX must be returned in a function - it does not work "standalone". The CoercableComponent type will enforce this for you.
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 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.
{
+import{_ as e,c as a,o as s,N as n}from"./chunks/framework.0799945b.js";const h=JSON.parse('{"title":"Coercable Components","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/coercable.md","lastUpdated":1681131771000}'),o={name:"guide/important-concepts/coercable.md"},t=n(`
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
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.
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. Note that JSX must be returned in a function - it does not work "standalone". The CoercableComponent type will enforce this for you.
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 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.
{display:jsx(()=> (<><MainDisplayresource={points}color={color}/>
diff --git a/assets/guide_important-concepts_coercable.md.025d8a15.lean.js b/assets/guide_important-concepts_coercable.md.d6377a4d.lean.js
similarity index 85%
rename from assets/guide_important-concepts_coercable.md.025d8a15.lean.js
rename to assets/guide_important-concepts_coercable.md.d6377a4d.lean.js
index 8feec1d7..fed1c818 100644
--- a/assets/guide_important-concepts_coercable.md.025d8a15.lean.js
+++ b/assets/guide_important-concepts_coercable.md.d6377a4d.lean.js
@@ -1 +1 @@
-import{_ as e,c as a,o as s,N as n}from"./chunks/framework.0799945b.js";const h=JSON.parse('{"title":"Coercable Components","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/coercable.md","lastUpdated":1681094412000}'),o={name:"guide/important-concepts/coercable.md"},t=n("",13),l=[t];function p(r,c,i,d,u,y){return s(),a("div",null,l)}const F=e(o,[["render",p]]);export{h as __pageData,F as default};
+import{_ as e,c as a,o as s,N as n}from"./chunks/framework.0799945b.js";const h=JSON.parse('{"title":"Coercable Components","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/coercable.md","lastUpdated":1681131771000}'),o={name:"guide/important-concepts/coercable.md"},t=n("",13),l=[t];function p(r,c,i,d,u,y){return s(),a("div",null,l)}const F=e(o,[["render",p]]);export{h as __pageData,F as default};
diff --git a/assets/guide_important-concepts_features.md.1957772b.js b/assets/guide_important-concepts_features.md.09bfe05c.js
similarity index 99%
rename from assets/guide_important-concepts_features.md.1957772b.js
rename to assets/guide_important-concepts_features.md.09bfe05c.js
index cc1ec226..7cfbf651 100644
--- a/assets/guide_important-concepts_features.md.1957772b.js
+++ b/assets/guide_important-concepts_features.md.09bfe05c.js
@@ -1,4 +1,4 @@
-import{_ as e,c as s,o as a,N as n}from"./chunks/framework.0799945b.js";const D=JSON.parse('{"title":"Features","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/features.md","lastUpdated":1681094412000}'),t={name:"guide/important-concepts/features.md"},o=n(`
A layer is made up of features. There are many types of features included in Profectus, and more can be created once you become familiar with the engine.
To create a feature, the feature type will have one or more functions to help you. They'll typically look something like this:
ts
const addGainUpgrade =createUpgrade(()=> ({
+import{_ as e,c as s,o as a,N as n}from"./chunks/framework.0799945b.js";const D=JSON.parse('{"title":"Features","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/features.md","lastUpdated":1681131771000}'),t={name:"guide/important-concepts/features.md"},o=n(`
A layer is made up of features. There are many types of features included in Profectus, and more can be created once you become familiar with the engine.
To create a feature, the feature type will have one or more functions to help you. They'll typically look something like this:
ts
const addGainUpgrade =createUpgrade(()=> ({display:{title:"Generator of Genericness",description:"Gain 1 point every second"
diff --git a/assets/guide_important-concepts_features.md.1957772b.lean.js b/assets/guide_important-concepts_features.md.09bfe05c.lean.js
similarity index 84%
rename from assets/guide_important-concepts_features.md.1957772b.lean.js
rename to assets/guide_important-concepts_features.md.09bfe05c.lean.js
index a6d47772..7fd49de1 100644
--- a/assets/guide_important-concepts_features.md.1957772b.lean.js
+++ b/assets/guide_important-concepts_features.md.09bfe05c.lean.js
@@ -1 +1 @@
-import{_ as e,c as s,o as a,N as n}from"./chunks/framework.0799945b.js";const D=JSON.parse('{"title":"Features","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/features.md","lastUpdated":1681094412000}'),t={name:"guide/important-concepts/features.md"},o=n("",12),l=[o];function p(r,c,i,y,d,u){return a(),s("div",null,l)}const C=e(t,[["render",p]]);export{D as __pageData,C as default};
+import{_ as e,c as s,o as a,N as n}from"./chunks/framework.0799945b.js";const D=JSON.parse('{"title":"Features","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/features.md","lastUpdated":1681131771000}'),t={name:"guide/important-concepts/features.md"},o=n("",12),l=[o];function p(r,c,i,y,d,u){return a(),s("div",null,l)}const C=e(t,[["render",p]]);export{D as __pageData,C as default};
diff --git a/assets/guide_important-concepts_formulas.md.592f0311.js b/assets/guide_important-concepts_formulas.md.92a97105.js
similarity index 99%
rename from assets/guide_important-concepts_formulas.md.592f0311.js
rename to assets/guide_important-concepts_formulas.md.92a97105.js
index d95aaa20..496dea69 100644
--- a/assets/guide_important-concepts_formulas.md.592f0311.js
+++ b/assets/guide_important-concepts_formulas.md.92a97105.js
@@ -1,4 +1,4 @@
-import{_ as e,c as a,o as s,N as o}from"./chunks/framework.0799945b.js";const y=JSON.parse('{"title":"Formulas","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/formulas.md","lastUpdated":1681094412000}'),n={name:"guide/important-concepts/formulas.md"},t=o(`
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).
ts
const myRepeatable =createRepeatable(function(this:GenericRepeatable){
+import{_ as e,c as a,o as s,N as o}from"./chunks/framework.0799945b.js";const y=JSON.parse('{"title":"Formulas","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/formulas.md","lastUpdated":1681131771000}'),n={name:"guide/important-concepts/formulas.md"},t=o(`
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).
ts
const myRepeatable =createRepeatable(function(this:GenericRepeatable){return{ requirements:createCostRequirement(()=> ({ resource:points,
diff --git a/assets/guide_important-concepts_formulas.md.592f0311.lean.js b/assets/guide_important-concepts_formulas.md.92a97105.lean.js
similarity index 84%
rename from assets/guide_important-concepts_formulas.md.592f0311.lean.js
rename to assets/guide_important-concepts_formulas.md.92a97105.lean.js
index 1e4fd02a..83797c5e 100644
--- a/assets/guide_important-concepts_formulas.md.592f0311.lean.js
+++ b/assets/guide_important-concepts_formulas.md.92a97105.lean.js
@@ -1 +1 @@
-import{_ as e,c as a,o as s,N as o}from"./chunks/framework.0799945b.js";const y=JSON.parse('{"title":"Formulas","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/formulas.md","lastUpdated":1681094412000}'),n={name:"guide/important-concepts/formulas.md"},t=o("",18),r=[t];function l(i,p,c,u,h,m){return s(),a("div",null,r)}const F=e(n,[["render",l]]);export{y as __pageData,F as default};
+import{_ as e,c as a,o as s,N as o}from"./chunks/framework.0799945b.js";const y=JSON.parse('{"title":"Formulas","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/formulas.md","lastUpdated":1681131771000}'),n={name:"guide/important-concepts/formulas.md"},t=o("",18),r=[t];function l(i,p,c,u,h,m){return s(),a("div",null,r)}const F=e(n,[["render",l]]);export{y as __pageData,F as default};
diff --git a/assets/guide_important-concepts_layers.md.12789b41.js b/assets/guide_important-concepts_layers.md.5ce0f121.js
similarity index 96%
rename from assets/guide_important-concepts_layers.md.12789b41.js
rename to assets/guide_important-concepts_layers.md.5ce0f121.js
index bffc00a5..bb0d6c9f 100644
--- a/assets/guide_important-concepts_layers.md.12789b41.js
+++ b/assets/guide_important-concepts_layers.md.5ce0f121.js
@@ -1 +1 @@
-import{_ as e,c as a,o as t,N as r}from"./chunks/framework.0799945b.js";const f=JSON.parse('{"title":"Layers","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/layers.md","lastUpdated":1681094412000}'),o={name:"guide/important-concepts/layers.md"},s=r('
Profectus content is organized into units called "Layers". When displaying content to the user, the screen will be divided into several tabs that each display the content of a layer. These layers are stored in /src/data/layers.
Each layer is ultimately a collection of different features, and a display function. While there are a couple reserved properties for layers, most of its structure is fully up to the creator.
Layers can be dynamically added or removed at any time, which also allows for effectively disabling or enabling content based on arbitrary conditions. Just make sure getInitialLayers can process the player save data object and determine which layers should be currently active.
Layers (and features) are not actually created immediately. Instead, their options are gotten through a function which is then run the first time something inside the layer is accessed. This is a concept called lazy evaluation, which is also used for things like computed, and allows for features to reference each other without worrying about cyclical dependencies.
',6),i=[s];function n(c,l,d,h,y,p){return t(),a("div",null,i)}const _=e(o,[["render",n]]);export{f as __pageData,_ as default};
+import{_ as e,c as a,o as t,N as r}from"./chunks/framework.0799945b.js";const f=JSON.parse('{"title":"Layers","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/layers.md","lastUpdated":1681131771000}'),o={name:"guide/important-concepts/layers.md"},s=r('
Profectus content is organized into units called "Layers". When displaying content to the user, the screen will be divided into several tabs that each display the content of a layer. These layers are stored in /src/data/layers.
Each layer is ultimately a collection of different features, and a display function. While there are a couple reserved properties for layers, most of its structure is fully up to the creator.
Layers can be dynamically added or removed at any time, which also allows for effectively disabling or enabling content based on arbitrary conditions. Just make sure getInitialLayers can process the player save data object and determine which layers should be currently active.
Layers (and features) are not actually created immediately. Instead, their options are gotten through a function which is then run the first time something inside the layer is accessed. This is a concept called lazy evaluation, which is also used for things like computed, and allows for features to reference each other without worrying about cyclical dependencies.
',6),i=[s];function n(c,l,d,h,y,p){return t(),a("div",null,i)}const _=e(o,[["render",n]]);export{f as __pageData,_ as default};
diff --git a/assets/guide_important-concepts_layers.md.12789b41.lean.js b/assets/guide_important-concepts_layers.md.5ce0f121.lean.js
similarity index 84%
rename from assets/guide_important-concepts_layers.md.12789b41.lean.js
rename to assets/guide_important-concepts_layers.md.5ce0f121.lean.js
index 8316db11..baca370a 100644
--- a/assets/guide_important-concepts_layers.md.12789b41.lean.js
+++ b/assets/guide_important-concepts_layers.md.5ce0f121.lean.js
@@ -1 +1 @@
-import{_ as e,c as a,o as t,N as r}from"./chunks/framework.0799945b.js";const f=JSON.parse('{"title":"Layers","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/layers.md","lastUpdated":1681094412000}'),o={name:"guide/important-concepts/layers.md"},s=r("",6),i=[s];function n(c,l,d,h,y,p){return t(),a("div",null,i)}const _=e(o,[["render",n]]);export{f as __pageData,_ as default};
+import{_ as e,c as a,o as t,N as r}from"./chunks/framework.0799945b.js";const f=JSON.parse('{"title":"Layers","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/layers.md","lastUpdated":1681131771000}'),o={name:"guide/important-concepts/layers.md"},s=r("",6),i=[s];function n(c,l,d,h,y,p){return t(),a("div",null,i)}const _=e(o,[["render",n]]);export{f as __pageData,_ as default};
diff --git a/assets/guide_important-concepts_persistence.md.e64f98b0.js b/assets/guide_important-concepts_persistence.md.5574dcf4.js
similarity index 97%
rename from assets/guide_important-concepts_persistence.md.e64f98b0.js
rename to assets/guide_important-concepts_persistence.md.5574dcf4.js
index 7ed44288..6e89619b 100644
--- a/assets/guide_important-concepts_persistence.md.e64f98b0.js
+++ b/assets/guide_important-concepts_persistence.md.5574dcf4.js
@@ -1 +1 @@
-import{_ as e,c as t,o as a,N as s}from"./chunks/framework.0799945b.js";const m=JSON.parse('{"title":"Persistence","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/persistence.md","lastUpdated":1681094412000}'),r={name:"guide/important-concepts/persistence.md"},n=s('
Persistence refers to data that is saved, so that it persists when the user closes the tab and opens it again in the future.
In Profectus, this is handled by creating "persistent refs", which act like refs but whose value is stored in an object that gets saved to localStorage. Other than that you can treat them like any other ref - when adding the layer, any persistent refs will automatically have their values updated to the ones saved in localStorage. If there isn't a saved value, it'll use the default value passed to the persistent ref constructor.
Many features in Profectus, such as upgrades, milestones, and challenges, internally have persistent refs to save things like whether the upgrade has been purchased, the milestone achieved, or the challenge completed. Creators can also create their own custom persistent refs to store any arbitrary (but serializable) data they need - that means Numbers (including big numbers), strings, booleans, or objects containing only serializable values. Another notable function is the resource constructor. If you pass a default value into its contructor, it will automatically create a persistent ref for that resource. If you pass in a ref, it will NOT make the ref persistent.
It's important for saving and loading these properties for these refs to be in a well known location. This is implemented based on the location of the persistent ref within a layer. That means its important that all persistent refs are located within the object returned by the createLayer options function. If a persistent ref is not within that object, it will NOT be saved and loaded - regardless of whether its a persistent ref within a feature, one you manually created, or otherwise.
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 function can be used to migrate the old player save data to the new structure expected by the current version of the project.
',6),i=[n];function o(c,l,h,d,u,p){return a(),t("div",null,i)}const y=e(r,[["render",o]]);export{m as __pageData,y as default};
+import{_ as e,c as t,o as a,N as s}from"./chunks/framework.0799945b.js";const m=JSON.parse('{"title":"Persistence","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/persistence.md","lastUpdated":1681131771000}'),r={name:"guide/important-concepts/persistence.md"},n=s('
Persistence refers to data that is saved, so that it persists when the user closes the tab and opens it again in the future.
In Profectus, this is handled by creating "persistent refs", which act like refs but whose value is stored in an object that gets saved to localStorage. Other than that you can treat them like any other ref - when adding the layer, any persistent refs will automatically have their values updated to the ones saved in localStorage. If there isn't a saved value, it'll use the default value passed to the persistent ref constructor.
Many features in Profectus, such as upgrades, milestones, and challenges, internally have persistent refs to save things like whether the upgrade has been purchased, the milestone achieved, or the challenge completed. Creators can also create their own custom persistent refs to store any arbitrary (but serializable) data they need - that means Numbers (including big numbers), strings, booleans, or objects containing only serializable values. Another notable function is the resource constructor. If you pass a default value into its contructor, it will automatically create a persistent ref for that resource. If you pass in a ref, it will NOT make the ref persistent.
It's important for saving and loading these properties for these refs to be in a well known location. This is implemented based on the location of the persistent ref within a layer. That means its important that all persistent refs are located within the object returned by the createLayer options function. If a persistent ref is not within that object, it will NOT be saved and loaded - regardless of whether its a persistent ref within a feature, one you manually created, or otherwise.
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 function can be used to migrate the old player save data to the new structure expected by the current version of the project.
',6),i=[n];function o(c,l,h,d,u,p){return a(),t("div",null,i)}const y=e(r,[["render",o]]);export{m as __pageData,y as default};
diff --git a/assets/guide_important-concepts_persistence.md.e64f98b0.lean.js b/assets/guide_important-concepts_persistence.md.5574dcf4.lean.js
similarity index 85%
rename from assets/guide_important-concepts_persistence.md.e64f98b0.lean.js
rename to assets/guide_important-concepts_persistence.md.5574dcf4.lean.js
index d7eb0102..addf4137 100644
--- a/assets/guide_important-concepts_persistence.md.e64f98b0.lean.js
+++ b/assets/guide_important-concepts_persistence.md.5574dcf4.lean.js
@@ -1 +1 @@
-import{_ as e,c as t,o as a,N as s}from"./chunks/framework.0799945b.js";const m=JSON.parse('{"title":"Persistence","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/persistence.md","lastUpdated":1681094412000}'),r={name:"guide/important-concepts/persistence.md"},n=s("",6),i=[n];function o(c,l,h,d,u,p){return a(),t("div",null,i)}const y=e(r,[["render",o]]);export{m as __pageData,y as default};
+import{_ as e,c as t,o as a,N as s}from"./chunks/framework.0799945b.js";const m=JSON.parse('{"title":"Persistence","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/persistence.md","lastUpdated":1681131771000}'),r={name:"guide/important-concepts/persistence.md"},n=s("",6),i=[n];function o(c,l,h,d,u,p){return a(),t("div",null,i)}const y=e(r,[["render",o]]);export{m as __pageData,y as default};
diff --git a/assets/guide_important-concepts_reactivity.md.bcec6c75.js b/assets/guide_important-concepts_reactivity.md.ef52d055.js
similarity index 97%
rename from assets/guide_important-concepts_reactivity.md.bcec6c75.js
rename to assets/guide_important-concepts_reactivity.md.ef52d055.js
index 873b331f..ba366cc7 100644
--- a/assets/guide_important-concepts_reactivity.md.bcec6c75.js
+++ b/assets/guide_important-concepts_reactivity.md.ef52d055.js
@@ -1 +1 @@
-import{_ as e,c as t,o as a,N as o}from"./chunks/framework.0799945b.js";const f=JSON.parse('{"title":"Reactivity","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/reactivity.md","lastUpdated":1681094412000}'),r={name:"guide/important-concepts/reactivity.md"},s=o('
Profectus takes large advantage of Vue's reactivity system. It's recommended to read up on how refs and computed refs work. Ultimately this means that sometimes you'll need to type .value to get the actual value of something, but also you are able to pass things around by reference instead of by value. Indeed, it is recommended to only unwrap the actual value when you actually need it. .value is guaranteed to be correct and up to date only on the exact moment it is accessed.
With a proper IDE, such as Visual Studio Code, you should be able to see whether or not something is a ref or not from type hints. If in doubt, you can always wrap the property in an unref call.
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 of common misconceptions around Vue reactivity.
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.
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.
',7),i=[s];function n(c,u,d,l,p,h){return a(),t("div",null,i)}const y=e(r,[["render",n]]);export{f as __pageData,y as default};
+import{_ as e,c as t,o as a,N as o}from"./chunks/framework.0799945b.js";const f=JSON.parse('{"title":"Reactivity","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/reactivity.md","lastUpdated":1681131771000}'),r={name:"guide/important-concepts/reactivity.md"},s=o('
Profectus takes large advantage of Vue's reactivity system. It's recommended to read up on how refs and computed refs work. Ultimately this means that sometimes you'll need to type .value to get the actual value of something, but also you are able to pass things around by reference instead of by value. Indeed, it is recommended to only unwrap the actual value when you actually need it. .value is guaranteed to be correct and up to date only on the exact moment it is accessed.
With a proper IDE, such as Visual Studio Code, you should be able to see whether or not something is a ref or not from type hints. If in doubt, you can always wrap the property in an unref call.
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 of common misconceptions around Vue reactivity.
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.
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.
',7),i=[s];function n(c,u,d,l,p,h){return a(),t("div",null,i)}const y=e(r,[["render",n]]);export{f as __pageData,y as default};
diff --git a/assets/guide_important-concepts_reactivity.md.bcec6c75.lean.js b/assets/guide_important-concepts_reactivity.md.ef52d055.lean.js
similarity index 85%
rename from assets/guide_important-concepts_reactivity.md.bcec6c75.lean.js
rename to assets/guide_important-concepts_reactivity.md.ef52d055.lean.js
index 953b8238..aac8d077 100644
--- a/assets/guide_important-concepts_reactivity.md.bcec6c75.lean.js
+++ b/assets/guide_important-concepts_reactivity.md.ef52d055.lean.js
@@ -1 +1 @@
-import{_ as e,c as t,o as a,N as o}from"./chunks/framework.0799945b.js";const f=JSON.parse('{"title":"Reactivity","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/reactivity.md","lastUpdated":1681094412000}'),r={name:"guide/important-concepts/reactivity.md"},s=o("",7),i=[s];function n(c,u,d,l,p,h){return a(),t("div",null,i)}const y=e(r,[["render",n]]);export{f as __pageData,y as default};
+import{_ as e,c as t,o as a,N as o}from"./chunks/framework.0799945b.js";const f=JSON.parse('{"title":"Reactivity","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/reactivity.md","lastUpdated":1681131771000}'),r={name:"guide/important-concepts/reactivity.md"},s=o("",7),i=[s];function n(c,u,d,l,p,h){return a(),t("div",null,i)}const y=e(r,[["render",n]]);export{f as __pageData,y as default};
diff --git a/assets/guide_important-concepts_requirements.md.856b4ffd.js b/assets/guide_important-concepts_requirements.md.fb6512c2.js
similarity index 98%
rename from assets/guide_important-concepts_requirements.md.856b4ffd.js
rename to assets/guide_important-concepts_requirements.md.fb6512c2.js
index e377a4d2..77f53d31 100644
--- a/assets/guide_important-concepts_requirements.md.856b4ffd.js
+++ b/assets/guide_important-concepts_requirements.md.fb6512c2.js
@@ -1 +1 @@
-import{_ as e,c as t,o as i,N as r}from"./chunks/framework.0799945b.js";const q=JSON.parse('{"title":"Requirements","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/requirements.md","lastUpdated":1681094412000}'),a={name:"guide/important-concepts/requirements.md"},n=r('
The requirements system in Profectus is designed to handle various conditions that must be met before certain actions or features can be accessed by the player. These conditions can include resource amounts, completed challenges, or other milestones within the game.
Several features will have a requirements property that takes one or more requirements that must be fulfilled for a certain action to be performed with that feature.
When implementing requirements, you can use the displayRequirements utility. This utility is designed to give a human-readable string generated by the requirement(s) given, making it easier for players to understand the conditions needed to progress in the game. Typically features that support requirements will already use this utility internally.
You may need to "pay" requirements upon meeting their conditions and performing the associated action. This action typically involves spending resources or making other adjustments to the game state. The payRequirements function simplifies this process by handling the payment for one or more requirements, considering the number of levels to pay for. Additionally, custom pay functions like payByDivision and payByReset can be passed into createCostRequirement for more specialized cases.
Requirements can have multiple "levels", which are typically used for things like multi-level challenges with scaling requirements. When checking if requirements are met, the requirementsMet function can be used. It accepts a single requirement or an array of requirements.
Requirements that are just on/off, such as boolean or visibility requirements, will count as infinite levels when in the same array. This allows you to combine different types of requirements in the same array and use the maxRequirementsMet function to calculate the maximum number of levels that could be acquired with the current requirement states.
',11),s=[n];function o(u,m,l,c,h,d){return i(),t("div",null,s)}const f=e(a,[["render",o]]);export{q as __pageData,f as default};
+import{_ as e,c as t,o as i,N as r}from"./chunks/framework.0799945b.js";const q=JSON.parse('{"title":"Requirements","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/requirements.md","lastUpdated":1681131771000}'),a={name:"guide/important-concepts/requirements.md"},n=r('
The requirements system in Profectus is designed to handle various conditions that must be met before certain actions or features can be accessed by the player. These conditions can include resource amounts, completed challenges, or other milestones within the game.
Several features will have a requirements property that takes one or more requirements that must be fulfilled for a certain action to be performed with that feature.
When implementing requirements, you can use the displayRequirements utility. This utility is designed to give a human-readable string generated by the requirement(s) given, making it easier for players to understand the conditions needed to progress in the game. Typically features that support requirements will already use this utility internally.
You may need to "pay" requirements upon meeting their conditions and performing the associated action. This action typically involves spending resources or making other adjustments to the game state. The payRequirements function simplifies this process by handling the payment for one or more requirements, considering the number of levels to pay for. Additionally, custom pay functions like payByDivision and payByReset can be passed into createCostRequirement for more specialized cases.
Requirements can have multiple "levels", which are typically used for things like multi-level challenges with scaling requirements. When checking if requirements are met, the requirementsMet function can be used. It accepts a single requirement or an array of requirements.
Requirements that are just on/off, such as boolean or visibility requirements, will count as infinite levels when in the same array. This allows you to combine different types of requirements in the same array and use the maxRequirementsMet function to calculate the maximum number of levels that could be acquired with the current requirement states.
',11),s=[n];function o(u,m,l,c,h,d){return i(),t("div",null,s)}const f=e(a,[["render",o]]);export{q as __pageData,f as default};
diff --git a/assets/guide_important-concepts_requirements.md.856b4ffd.lean.js b/assets/guide_important-concepts_requirements.md.fb6512c2.lean.js
similarity index 85%
rename from assets/guide_important-concepts_requirements.md.856b4ffd.lean.js
rename to assets/guide_important-concepts_requirements.md.fb6512c2.lean.js
index 4b2bd1e9..a8e8d4b7 100644
--- a/assets/guide_important-concepts_requirements.md.856b4ffd.lean.js
+++ b/assets/guide_important-concepts_requirements.md.fb6512c2.lean.js
@@ -1 +1 @@
-import{_ as e,c as t,o as i,N as r}from"./chunks/framework.0799945b.js";const q=JSON.parse('{"title":"Requirements","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/requirements.md","lastUpdated":1681094412000}'),a={name:"guide/important-concepts/requirements.md"},n=r("",11),s=[n];function o(u,m,l,c,h,d){return i(),t("div",null,s)}const f=e(a,[["render",o]]);export{q as __pageData,f as default};
+import{_ as e,c as t,o as i,N as r}from"./chunks/framework.0799945b.js";const q=JSON.parse('{"title":"Requirements","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/requirements.md","lastUpdated":1681131771000}'),a={name:"guide/important-concepts/requirements.md"},n=r("",11),s=[n];function o(u,m,l,c,h,d){return i(),t("div",null,s)}const f=e(a,[["render",o]]);export{q as __pageData,f as default};
diff --git a/assets/guide_index.md.02afca62.js b/assets/guide_index.md.463f69d1.js
similarity index 98%
rename from assets/guide_index.md.02afca62.js
rename to assets/guide_index.md.463f69d1.js
index 3743385c..36f9fd41 100644
--- a/assets/guide_index.md.02afca62.js
+++ b/assets/guide_index.md.463f69d1.js
@@ -1 +1 @@
-import{_ as e,c as t,o as a,N as o}from"./chunks/framework.0799945b.js";const m=JSON.parse('{"title":"Introduction","description":"","frontmatter":{"title":"Introduction"},"headers":[],"relativePath":"guide/index.md","lastUpdated":1681094412000}'),n={name:"guide/index.md"},r=o('
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.
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.
While this engine is intended to make game development (and web app development) easier, it still requires you to be comfortable with programming in general and javascript in particular. Fortunately, that is a solvable problem.
If you've never used Javascript before, learn-js.org is a good resource for learning the important concepts. If you'd like a more thorough lesson on all the ins and outs of web development, javascript.info and MDN's tutorials should have you covered. MDN is also a great resource to use as a reference - for example, if you want to know more about something and you google it, there'll typically be a MDN link that'll explain it thoroughly.
Beyond the basics, Profectus uses a modern web development pipeline, using tools like node, typescript, and JSX. While most of it should be relatively easy to pick up through context, if this becomes too complicated you may be interested in The Modding Tree, a predecessor of Profectus that uses plain old javascript.
There are many popular game engines out there, such as GameMaker Studio 2, Unity3D, Unreal Engine 4, and Godot, that are all general purpose and also more useful if you're planning on going into a career in game development. These are all more mature and robust game engines compared to Profectus, and are used by actual game development studios.
Profectus, on the other hand, is a very opinionated engine that is better at specific types of projects. Where the above engines will require you to design and create your own interfaces, menus, save management systems, etc. Profectus will include those out of the box. Profectus will also output games that run natively in the browser, and will typically be easier for players to play than traditional engines.
If you are not interested in programming but still want to get into game development, the above engines also all support "visual programming", which may be more amenable to you. Each engine will have varying levels of support, and of course with varying complexities, but ultimately any of these will help you learn the concepts of programming and game development, and all of them also offer traditional programming for when you think you're ready.
',14),i=[r];function s(l,u,h,d,p,c){return a(),t("div",null,i)}const f=e(n,[["render",s]]);export{m as __pageData,f as default};
+import{_ as e,c as t,o as a,N as o}from"./chunks/framework.0799945b.js";const m=JSON.parse('{"title":"Introduction","description":"","frontmatter":{"title":"Introduction"},"headers":[],"relativePath":"guide/index.md","lastUpdated":1681131771000}'),n={name:"guide/index.md"},r=o('
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.
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.
While this engine is intended to make game development (and web app development) easier, it still requires you to be comfortable with programming in general and javascript in particular. Fortunately, that is a solvable problem.
If you've never used Javascript before, learn-js.org is a good resource for learning the important concepts. If you'd like a more thorough lesson on all the ins and outs of web development, javascript.info and MDN's tutorials should have you covered. MDN is also a great resource to use as a reference - for example, if you want to know more about something and you google it, there'll typically be a MDN link that'll explain it thoroughly.
Beyond the basics, Profectus uses a modern web development pipeline, using tools like node, typescript, and JSX. While most of it should be relatively easy to pick up through context, if this becomes too complicated you may be interested in The Modding Tree, a predecessor of Profectus that uses plain old javascript.
There are many popular game engines out there, such as GameMaker Studio 2, Unity3D, Unreal Engine 4, and Godot, that are all general purpose and also more useful if you're planning on going into a career in game development. These are all more mature and robust game engines compared to Profectus, and are used by actual game development studios.
Profectus, on the other hand, is a very opinionated engine that is better at specific types of projects. Where the above engines will require you to design and create your own interfaces, menus, save management systems, etc. Profectus will include those out of the box. Profectus will also output games that run natively in the browser, and will typically be easier for players to play than traditional engines.
If you are not interested in programming but still want to get into game development, the above engines also all support "visual programming", which may be more amenable to you. Each engine will have varying levels of support, and of course with varying complexities, but ultimately any of these will help you learn the concepts of programming and game development, and all of them also offer traditional programming for when you think you're ready.
',14),i=[r];function s(l,u,h,d,p,c){return a(),t("div",null,i)}const f=e(n,[["render",s]]);export{m as __pageData,f as default};
diff --git a/assets/guide_index.md.02afca62.lean.js b/assets/guide_index.md.463f69d1.lean.js
similarity index 84%
rename from assets/guide_index.md.02afca62.lean.js
rename to assets/guide_index.md.463f69d1.lean.js
index be98a128..cd1a38f2 100644
--- a/assets/guide_index.md.02afca62.lean.js
+++ b/assets/guide_index.md.463f69d1.lean.js
@@ -1 +1 @@
-import{_ as e,c as t,o as a,N as o}from"./chunks/framework.0799945b.js";const m=JSON.parse('{"title":"Introduction","description":"","frontmatter":{"title":"Introduction"},"headers":[],"relativePath":"guide/index.md","lastUpdated":1681094412000}'),n={name:"guide/index.md"},r=o("",14),i=[r];function s(l,u,h,d,p,c){return a(),t("div",null,i)}const f=e(n,[["render",s]]);export{m as __pageData,f as default};
+import{_ as e,c as t,o as a,N as o}from"./chunks/framework.0799945b.js";const m=JSON.parse('{"title":"Introduction","description":"","frontmatter":{"title":"Introduction"},"headers":[],"relativePath":"guide/index.md","lastUpdated":1681131771000}'),n={name:"guide/index.md"},r=o("",14),i=[r];function s(l,u,h,d,p,c){return a(),t("div",null,i)}const f=e(n,[["render",s]]);export{m as __pageData,f as default};
diff --git a/assets/guide_recipes_particles.md.30664cc1.js b/assets/guide_recipes_particles.md.d4df0645.js
similarity index 79%
rename from assets/guide_recipes_particles.md.30664cc1.js
rename to assets/guide_recipes_particles.md.d4df0645.js
index 2568239e..b4386114 100644
--- a/assets/guide_recipes_particles.md.30664cc1.js
+++ b/assets/guide_recipes_particles.md.d4df0645.js
@@ -1,14 +1,14 @@
-import{_ as s,c as a,o as n,N as l}from"./chunks/framework.0799945b.js";const i=JSON.parse('{"title":"Particles","description":"","frontmatter":{},"headers":[],"relativePath":"guide/recipes/particles.md","lastUpdated":1681094412000}'),p={name:"guide/recipes/particles.md"},o=l(`
This is a more complete example from the Kronos example used in the nodes docs. You will design a particle effect, make it appear on another feature, and make it adapt to the game state.
Let's start with designing the particle effect. Profectus uses pixi-particles, and there's an online particle effect editor for it here. However, it will return an older format of the particle effect emitter options, so you'll need to convert it like so:
ts
import myParticleEffect from"myParticleEffect.json";
+import{_ as s,c as a,o as n,N as l}from"./chunks/framework.0799945b.js";const i=JSON.parse('{"title":"Particles","description":"","frontmatter":{},"headers":[],"relativePath":"guide/recipes/particles.md","lastUpdated":1681131771000}'),p={name:"guide/recipes/particles.md"},o=l(`
This is a more comprehensive example based on the Kronos example used in the nodes docs. You will design a particle effect, make it appear on another feature, and ensure it adapts to the game state.
First, design the particle effect. Profectus uses pixi-particles, and you can use the online particle effect editor here. However, the editor returns an older format of the particle effect emitter config, so you'll need to convert it like this:
Next we need to create the particles feature and render it. We'll also want to track the bounding rect of the particle effects. We'll consider the next step:
You'll note this adds a bounding rect and updates it's based on the boundingRect set to non-null. The next step will be creating the emitter. Now we can pull in the Kronos example, which make a particle effect that appears when actualGain > 0.
This code adds a bounding rect for the particles and updates it when the container is created or resizes. Now, create the emitter. Pull in the Kronos example, which displays a particle effect when actualGain > 0.
ts
const particlesEmitter =ref(particles.addEmitter(element.particlesConfig));const updateParticleEffect =async ([shouldEmit, rect, boundingRect]: [boolean,DOMRect|undefined,
@@ -32,7 +32,7 @@ import{_ as s,c as a,o as n,N as l}from"./chunks/framework.0799945b.js";const i=
particles.boundingRect ], updateParticleEffect
-)
You'll note this checks regularly whether the boundingRect exists. If you ensure all the references to things are being watched, youy can make complex situations for various emitters.
If you're hot reloading, it may be required to re-load the particle effect. Here's example code from Kronos.
ts
const refreshParticleEffect =()=>{
+)
This code watches for whether the node exists and the boundingRect exists, which are required to display the effect. You can additionally watch any other values that would have an impact on the particle effect, like whether or not it should be emitting.
If you're using hot reloading, you might need to reload the particle effect. Here's an example from Kronos:
ts
const refreshParticleEffect =()=>{particlesEmitter.value.then(e=>e.destroy()).then(
diff --git a/assets/guide_recipes_particles.md.30664cc1.lean.js b/assets/guide_recipes_particles.md.d4df0645.lean.js
similarity index 84%
rename from assets/guide_recipes_particles.md.30664cc1.lean.js
rename to assets/guide_recipes_particles.md.d4df0645.lean.js
index 452a2b8b..f26fba35 100644
--- a/assets/guide_recipes_particles.md.30664cc1.lean.js
+++ b/assets/guide_recipes_particles.md.d4df0645.lean.js
@@ -1 +1 @@
-import{_ as s,c as a,o as n,N as l}from"./chunks/framework.0799945b.js";const i=JSON.parse('{"title":"Particles","description":"","frontmatter":{},"headers":[],"relativePath":"guide/recipes/particles.md","lastUpdated":1681094412000}'),p={name:"guide/recipes/particles.md"},o=l("",11),e=[o];function t(c,r,y,F,D,A){return n(),a("div",null,e)}const d=s(p,[["render",t]]);export{i as __pageData,d as default};
+import{_ as s,c as a,o as n,N as l}from"./chunks/framework.0799945b.js";const i=JSON.parse('{"title":"Particles","description":"","frontmatter":{},"headers":[],"relativePath":"guide/recipes/particles.md","lastUpdated":1681131771000}'),p={name:"guide/recipes/particles.md"},o=l("",11),e=[o];function t(c,r,y,F,D,A){return n(),a("div",null,e)}const d=s(p,[["render",t]]);export{i as __pageData,d as default};
diff --git a/assets/guide_recipes_save-progress.md.300c07f4.js b/assets/guide_recipes_save-progress.md.77ef05d3.js
similarity index 99%
rename from assets/guide_recipes_save-progress.md.300c07f4.js
rename to assets/guide_recipes_save-progress.md.77ef05d3.js
index 8ec9e3a1..c5408498 100644
--- a/assets/guide_recipes_save-progress.md.300c07f4.js
+++ b/assets/guide_recipes_save-progress.md.77ef05d3.js
@@ -1,4 +1,4 @@
-import{_ as s,c as a,o as e,N as n}from"./chunks/framework.0799945b.js";const o="/assets/save-progress.2c9d1bae.png",u=JSON.parse('{"title":"Display Save Progress","description":"","frontmatter":{},"headers":[],"relativePath":"guide/recipes/save-progress.md","lastUpdated":1681094412000}'),t={name:"guide/recipes/save-progress.md"},l=n('
This is a recipe to add a section to each save in the Saves Manager that will describe the amount of progress within that save. This can allow the player to more easily compare the saves to determine which is which. This would be in addition to the game version, last time played, and the name of the save itself, which can already be used for comparing saves without any configuration.
This recipe will involve modifying the Save.vue file within your project to include an extra component in the saves details. It will go over creating the new component, how to work with the save data object, and then displaying the component.
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(
+import{_ as s,c as a,o as e,N as n}from"./chunks/framework.0799945b.js";const o="/assets/save-progress.2c9d1bae.png",u=JSON.parse('{"title":"Display Save Progress","description":"","frontmatter":{},"headers":[],"relativePath":"guide/recipes/save-progress.md","lastUpdated":1681131771000}'),t={name:"guide/recipes/save-progress.md"},l=n('
This is a recipe to add a section to each save in the Saves Manager that will describe the amount of progress within that save. This can allow the player to more easily compare the saves to determine which is which. This would be in addition to the game version, last time played, and the name of the save itself, which can already be used for comparing saves without any configuration.
This recipe will involve modifying the Save.vue file within your project to include an extra component in the saves details. It will go over creating the new component, how to work with the save data object, and then displaying the component.
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";
diff --git a/assets/guide_recipes_save-progress.md.300c07f4.lean.js b/assets/guide_recipes_save-progress.md.77ef05d3.lean.js
similarity index 86%
rename from assets/guide_recipes_save-progress.md.300c07f4.lean.js
rename to assets/guide_recipes_save-progress.md.77ef05d3.lean.js
index 366918d8..cb3ce688 100644
--- a/assets/guide_recipes_save-progress.md.300c07f4.lean.js
+++ b/assets/guide_recipes_save-progress.md.77ef05d3.lean.js
@@ -1 +1 @@
-import{_ as s,c as a,o as e,N as n}from"./chunks/framework.0799945b.js";const o="/assets/save-progress.2c9d1bae.png",u=JSON.parse('{"title":"Display Save Progress","description":"","frontmatter":{},"headers":[],"relativePath":"guide/recipes/save-progress.md","lastUpdated":1681094412000}'),t={name:"guide/recipes/save-progress.md"},l=n("",16),p=[l];function r(c,i,y,D,F,d){return e(),a("div",null,p)}const m=s(t,[["render",r]]);export{u as __pageData,m as default};
+import{_ as s,c as a,o as e,N as n}from"./chunks/framework.0799945b.js";const o="/assets/save-progress.2c9d1bae.png",u=JSON.parse('{"title":"Display Save Progress","description":"","frontmatter":{},"headers":[],"relativePath":"guide/recipes/save-progress.md","lastUpdated":1681131771000}'),t={name:"guide/recipes/save-progress.md"},l=n("",16),p=[l];function r(c,i,y,D,F,d){return e(),a("div",null,p)}const m=s(t,[["render",r]]);export{u as __pageData,m as default};
diff --git a/assets/index.md.6970eb14.js b/assets/index.md.a72140fb.js
similarity index 94%
rename from assets/index.md.6970eb14.js
rename to assets/index.md.a72140fb.js
index 871b0052..222cd4a0 100644
--- a/assets/index.md.6970eb14.js
+++ b/assets/index.md.a72140fb.js
@@ -1 +1 @@
-import{_ as e,c as t,o}from"./chunks/framework.0799945b.js";const m=JSON.parse('{"title":"Home","description":"","frontmatter":{"layout":"home","title":"Home","hero":{"name":"Profectus","text":"A game engine that grows with you","tagline":"Starts at your skill level and encourages your ambition to make your projects bigger and better.","actions":[{"theme":"brand","text":"Get Started","link":"/guide/getting-started/setup"},{"theme":"alt","text":"Learn More","link":"/guide/"}]},"features":[{"title":"Easy to Use","details":"Everything is written to be as intuitive to use as possible, through consistent design."},{"title":"Helpful","details":"Built with TypeScript to guide you as you write. Seamlessly deploy your project with pre-configured github workflows, and more."},{"title":"Incremental","details":"Designed to actively encourage you to become better at programming. The engine will never limit you."}]},"headers":[],"relativePath":"index.md","lastUpdated":1681094412000}'),i={name:"index.md"};function a(r,n,s,l,d,u){return o(),t("div")}const g=e(i,[["render",a]]);export{m as __pageData,g as default};
+import{_ as e,c as t,o}from"./chunks/framework.0799945b.js";const m=JSON.parse('{"title":"Home","description":"","frontmatter":{"layout":"home","title":"Home","hero":{"name":"Profectus","text":"A game engine that grows with you","tagline":"Starts at your skill level and encourages your ambition to make your projects bigger and better.","actions":[{"theme":"brand","text":"Get Started","link":"/guide/getting-started/setup"},{"theme":"alt","text":"Learn More","link":"/guide/"}]},"features":[{"title":"Easy to Use","details":"Everything is written to be as intuitive to use as possible, through consistent design."},{"title":"Helpful","details":"Built with TypeScript to guide you as you write. Seamlessly deploy your project with pre-configured github workflows, and more."},{"title":"Incremental","details":"Designed to actively encourage you to become better at programming. The engine will never limit you."}]},"headers":[],"relativePath":"index.md","lastUpdated":1681131771000}'),i={name:"index.md"};function a(r,n,s,l,d,u){return o(),t("div")}const g=e(i,[["render",a]]);export{m as __pageData,g as default};
diff --git a/assets/index.md.6970eb14.lean.js b/assets/index.md.a72140fb.lean.js
similarity index 94%
rename from assets/index.md.6970eb14.lean.js
rename to assets/index.md.a72140fb.lean.js
index 871b0052..222cd4a0 100644
--- a/assets/index.md.6970eb14.lean.js
+++ b/assets/index.md.a72140fb.lean.js
@@ -1 +1 @@
-import{_ as e,c as t,o}from"./chunks/framework.0799945b.js";const m=JSON.parse('{"title":"Home","description":"","frontmatter":{"layout":"home","title":"Home","hero":{"name":"Profectus","text":"A game engine that grows with you","tagline":"Starts at your skill level and encourages your ambition to make your projects bigger and better.","actions":[{"theme":"brand","text":"Get Started","link":"/guide/getting-started/setup"},{"theme":"alt","text":"Learn More","link":"/guide/"}]},"features":[{"title":"Easy to Use","details":"Everything is written to be as intuitive to use as possible, through consistent design."},{"title":"Helpful","details":"Built with TypeScript to guide you as you write. Seamlessly deploy your project with pre-configured github workflows, and more."},{"title":"Incremental","details":"Designed to actively encourage you to become better at programming. The engine will never limit you."}]},"headers":[],"relativePath":"index.md","lastUpdated":1681094412000}'),i={name:"index.md"};function a(r,n,s,l,d,u){return o(),t("div")}const g=e(i,[["render",a]]);export{m as __pageData,g as default};
+import{_ as e,c as t,o}from"./chunks/framework.0799945b.js";const m=JSON.parse('{"title":"Home","description":"","frontmatter":{"layout":"home","title":"Home","hero":{"name":"Profectus","text":"A game engine that grows with you","tagline":"Starts at your skill level and encourages your ambition to make your projects bigger and better.","actions":[{"theme":"brand","text":"Get Started","link":"/guide/getting-started/setup"},{"theme":"alt","text":"Learn More","link":"/guide/"}]},"features":[{"title":"Easy to Use","details":"Everything is written to be as intuitive to use as possible, through consistent design."},{"title":"Helpful","details":"Built with TypeScript to guide you as you write. Seamlessly deploy your project with pre-configured github workflows, and more."},{"title":"Incremental","details":"Designed to actively encourage you to become better at programming. The engine will never limit you."}]},"headers":[],"relativePath":"index.md","lastUpdated":1681131771000}'),i={name:"index.md"};function a(r,n,s,l,d,u){return o(),t("div")}const g=e(i,[["render",a]]);export{m as __pageData,g as default};
diff --git a/guide/advanced-concepts/creating-features.html b/guide/advanced-concepts/creating-features.html
index 67aae7c2..4ff40b16 100644
--- a/guide/advanced-concepts/creating-features.html
+++ b/guide/advanced-concepts/creating-features.html
@@ -10,7 +10,7 @@
-
+
@@ -51,8 +51,8 @@
// unsubscribe from postUpdatelisteners[layer.id]?.();listeners[layer.id] =undefined;
-});
This is a TypeScript file containing the non-static parts of your project, and acts as the entry point for it.
It is stored at /src/data/projEntry.jsx.
This file has 3 things it must export, but beyond that can export anything the creator wants it to. Typically in addition to the required 3, the initial/"main" layer will be exported. Typically utilites belong in common.tsx, which exists next to projEntry.tsx.
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.
This function will be run whenever a save is loaded that has a different version than the one in project info. It will be given the old version number, and the player save data object currently being loaded.
The purpose of this function is to perform any necessary migrations, such as capping a resource that accidentally inflated in a previous version of the project. By default it will do nothing.
This is a unique ID used when saving player data. Changing this will effectively erase all save data for all players.
WARNING
This ID MUST be unique to your project, and should not be left as the default value. Otherwise, your project may use the save data from another project and cause issues for both projects.
The text to display for the discord server to point users to. This will appear when hovering over the discord icon, inside the info tab, the game over screen, as well as the NaN detected screen.
By default, this is The Paper Pilot Community, which can act as a catch-all for any Profectus projects without their own servers. If you change the discord server with your own, The Paper Pilot Community will still display underneath the custom server when hovering over the discord icon and within the info tab. Those places will also contain a link to the Modding Tree discord server.
The current version of the project loaded. If the player data was last saved in a different version of the project, fixOldSave will be run, so you can perform any save migrations necessary. This will also appear in the nav, the info tab, and the game over screen.
The display name for the current version of the project loaded. This will also appear in the nav, the info tab, and the game over screen unless set to an empty string.
Whether or not to allow resources to display small values (<.001). If false they'll just display as 0. Individual resources can also be configured to override this value.
Default precision to display numbers at when passed into format. Individual format calls can override this value, and resources can be configured with a custom precision as well.
Whether or not to display the nav as a header at the top of the screen. If disabled, the nav will appear on the left side of the screen laid over the first tab.
A path to an image file to display as the logo of the app. If null, the title will be shown instead. This will appear in the nav when useHeader is true.
The list of initial tabs to display on new saves. This value must have at least one element. Each element should be the ID of the layer to display in that tab.
The longest duration a single tick can be, in seconds. When calculating things like offline time, a single tick will be forced to be this amount or lower. This will make calculating offline time spread out across many ticks as necessary. The default value is 1 hour.
Whether or not to allow the player to pause the game. Turning this off disables the toggle from the options menu as well as the NaN screen. Developers can still manually pause by just running player.devSpeed = 0 in console (or = 1 to resume).
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.
Themes are objects that change how the project's interface should look. This is done mostly by changing the values of various CSS variables. You can look at the existing themes as a reference for the kind of values these CSS variables expect. They can also set various theme options that change how parts of the screen are laid out, which are described below.
You can add a theme by adding a property to the Themes enum and then including the theme in the exported object. It's recommended to use the spread operator if you'd like to have a theme look like another, but override specific options / CSS variables.
Themes added in this way will be automatically included in the Themes dropdown in the Options tab. Removing themes from the enum and exported object will similarly hide them from the dropdown.
If you'd like to change which theme is the default, you may modify the initial player settings object in the /src/game/settings.ts file. Keep in mind you'll also want to change it in the hardResetSettings function in the same file.
Toggles whether to display tab buttons in a tab list, similar to how a browser displays tabs; or to display them as floating buttons, similar to how TMT displays buttons.
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.
There are often concepts that aren't inherent to a single feature, but rather work with joining different features together. For example, a reset clickable that activates a conversion and resets a tree, which happens to be a common use case but isn't inherent to clickables, conversions, or trees.
These are perfect situations for utilities, and so to encourage creators to learn to identify and take advantage of these situations, a file called src/data/common.tsx has been created to demo some of the more common utility functions a project might use. Adding new utilities to this file is encouraged, as is creating utils in general. It also works as a good stepping stone to creating your own features.
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.
Profectus requires a Node.js development environment for working on a project. If you are comfortable with the command line, a local development environment is recommended.
Create a new project from the Profectus repository by clicking the "Use this template" button. Then, clone the repository locally using the provided link.
INFO
The template repository allows easy creation of multiple projects from one repository. However, updating an existing project to a newer version of Profectus can be challenging. Consider updating Profectusbefore starting development to avoid issues with unrelated histories.
It's recommended to create a new Git branch for development, allowing you to push changes without affecting the live build. The GitHub workflow will automatically rebuild the page when you push to the main branch.
Next, install Profectus' dependencies by running npm install. Run npm run serve to start a local server hosting your project. The site will automatically reload as you modify files.
Also, follow the steps to update Profectus before starting to make future updates easier without worrying about unrelated histories.
Using Git, the repository's workflow action automates deployment. However, you need to grant write permissions for the action in the repository settings. Go to Actions, General, Workflow permissions, and select "Read and write permissions".
To deploy, push changes to the main branch. The site will be updated automatically in a few minutes. Check progress or errors from the Actions tab on your repository.
Enable GitHub Pages in the repo settings to host the generated site. Select the gh-pages branch. Perform this step once. This will automatically start another GitHub action to deploy the website.
As an alternative to local development, you may use Replit. Replit sets up your development and hosts your project.
On the free plan, you'll face limitations, and the program may need occasional startups.
To create a Profectus project on Replit, all you have to do is click this button:
Click the "Run" button at the top of the screen to start development. This will also make the project publicly accessible, essentially auto-deploying it. However, this means you cannot separate your development and production environments.
Glitch is a site similar to Replit, with many of the same pros and cons. To create a Profectus project on Glitch, select "New Project", "Import from GitHub", and enter profectus-engine/Profectus. The new project will be automatically configured and ready to go.
Due to Profectus being a template repository, your projects do not share a git history with Profectus. In order to update changes, you will need to run the following:
The first command only has to be performed once. The third command may require you to merge conflicts between code both you and Profectus have changed - however, due to the modularity of Profectus, this should be fairly rare. Unfortunately, due to the unrelated histories the first time you do this every change will be marked as a conflict, and you'll need to accept each one.
The sidebar has a tab labelled "Version Control", which you can use to merge all changes made to Profectus into your project. Unfortunately, replit does not have a merge tool so this process may irrecoverably erase changes you've made - I'd recommend making a backup first.
Unfortunately glitch does not provide any method by which to update a project from a github repository. If you've only changed things in the data folder you may consider creating a new project, importing the current version of Profectus, and then placing your data folder in the new project.
Profectus content is organized into units called "Layers". When displaying content to the user, the screen will be divided into several tabs that each display the content of a layer. These layers are stored in /src/data/layers.
Each layer is ultimately a collection of different features, and a display function. While there are a couple reserved properties for layers, most of its structure is fully up to the creator.
Layers can be dynamically added or removed at any time, which also allows for effectively disabling or enabling content based on arbitrary conditions. Just make sure getInitialLayers can process the player save data object and determine which layers should be currently active.
Layers (and features) are not actually created immediately. Instead, their options are gotten through a function which is then run the first time something inside the layer is accessed. This is a concept called lazy evaluation, which is also used for things like computed, and allows for features to reference each other without worrying about cyclical dependencies.
Persistence refers to data that is saved, so that it persists when the user closes the tab and opens it again in the future.
In Profectus, this is handled by creating "persistent refs", which act like refs but whose value is stored in an object that gets saved to localStorage. Other than that you can treat them like any other ref - when adding the layer, any persistent refs will automatically have their values updated to the ones saved in localStorage. If there isn't a saved value, it'll use the default value passed to the persistent ref constructor.
Many features in Profectus, such as upgrades, milestones, and challenges, internally have persistent refs to save things like whether the upgrade has been purchased, the milestone achieved, or the challenge completed. Creators can also create their own custom persistent refs to store any arbitrary (but serializable) data they need - that means Numbers (including big numbers), strings, booleans, or objects containing only serializable values. Another notable function is the resource constructor. If you pass a default value into its contructor, it will automatically create a persistent ref for that resource. If you pass in a ref, it will NOT make the ref persistent.
It's important for saving and loading these properties for these refs to be in a well known location. This is implemented based on the location of the persistent ref within a layer. That means its important that all persistent refs are located within the object returned by the createLayer options function. If a persistent ref is not within that object, it will NOT be saved and loaded - regardless of whether its a persistent ref within a feature, one you manually created, or otherwise.
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 function can be used to migrate the old player save data to the new structure expected by the current version of the project.
Profectus takes large advantage of Vue's reactivity system. It's recommended to read up on how refs and computed refs work. Ultimately this means that sometimes you'll need to type .value to get the actual value of something, but also you are able to pass things around by reference instead of by value. Indeed, it is recommended to only unwrap the actual value when you actually need it. .value is guaranteed to be correct and up to date only on the exact moment it is accessed.
With a proper IDE, such as Visual Studio Code, you should be able to see whether or not something is a ref or not from type hints. If in doubt, you can always wrap the property in an unref call.
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 of common misconceptions around Vue reactivity.
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.
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.
The requirements system in Profectus is designed to handle various conditions that must be met before certain actions or features can be accessed by the player. These conditions can include resource amounts, completed challenges, or other milestones within the game.
Several features will have a requirements property that takes one or more requirements that must be fulfilled for a certain action to be performed with that feature.
When implementing requirements, you can use the displayRequirements utility. This utility is designed to give a human-readable string generated by the requirement(s) given, making it easier for players to understand the conditions needed to progress in the game. Typically features that support requirements will already use this utility internally.
You may need to "pay" requirements upon meeting their conditions and performing the associated action. This action typically involves spending resources or making other adjustments to the game state. The payRequirements function simplifies this process by handling the payment for one or more requirements, considering the number of levels to pay for. Additionally, custom pay functions like payByDivision and payByReset can be passed into createCostRequirement for more specialized cases.
Requirements can have multiple "levels", which are typically used for things like multi-level challenges with scaling requirements. When checking if requirements are met, the requirementsMet function can be used. It accepts a single requirement or an array of requirements.
Requirements that are just on/off, such as boolean or visibility requirements, will count as infinite levels when in the same array. This allows you to combine different types of requirements in the same array and use the maxRequirementsMet function to calculate the maximum number of levels that could be acquired with the current requirement states.
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.
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.
While this engine is intended to make game development (and web app development) easier, it still requires you to be comfortable with programming in general and javascript in particular. Fortunately, that is a solvable problem.
If you've never used Javascript before, learn-js.org is a good resource for learning the important concepts. If you'd like a more thorough lesson on all the ins and outs of web development, javascript.info and MDN's tutorials should have you covered. MDN is also a great resource to use as a reference - for example, if you want to know more about something and you google it, there'll typically be a MDN link that'll explain it thoroughly.
Beyond the basics, Profectus uses a modern web development pipeline, using tools like node, typescript, and JSX. While most of it should be relatively easy to pick up through context, if this becomes too complicated you may be interested in The Modding Tree, a predecessor of Profectus that uses plain old javascript.
There are many popular game engines out there, such as GameMaker Studio 2, Unity3D, Unreal Engine 4, and Godot, that are all general purpose and also more useful if you're planning on going into a career in game development. These are all more mature and robust game engines compared to Profectus, and are used by actual game development studios.
Profectus, on the other hand, is a very opinionated engine that is better at specific types of projects. Where the above engines will require you to design and create your own interfaces, menus, save management systems, etc. Profectus will include those out of the box. Profectus will also output games that run natively in the browser, and will typically be easier for players to play than traditional engines.
If you are not interested in programming but still want to get into game development, the above engines also all support "visual programming", which may be more amenable to you. Each engine will have varying levels of support, and of course with varying complexities, but ultimately any of these will help you learn the concepts of programming and game development, and all of them also offer traditional programming for when you think you're ready.
This is a more complete example from the Kronos example used in the nodes docs. You will design a particle effect, make it appear on another feature, and make it adapt to the game state.
Let's start with designing the particle effect. Profectus uses pixi-particles, and there's an online particle effect editor for it here. However, it will return an older format of the particle effect emitter options, so you'll need to convert it like so:
This is a more comprehensive example based on the Kronos example used in the nodes docs. You will design a particle effect, make it appear on another feature, and ensure it adapts to the game state.
First, design the particle effect. Profectus uses pixi-particles, and you can use the online particle effect editor here. However, the editor returns an older format of the particle effect emitter config, so you'll need to convert it like this:
Next we need to create the particles feature and render it. We'll also want to track the bounding rect of the particle effects. We'll consider the next step:
You'll note this adds a bounding rect and updates it's based on the boundingRect set to non-null. The next step will be creating the emitter. Now we can pull in the Kronos example, which make a particle effect that appears when actualGain > 0.
This code adds a bounding rect for the particles and updates it when the container is created or resizes. Now, create the emitter. Pull in the Kronos example, which displays a particle effect when actualGain > 0.
You'll note this checks regularly whether the boundingRect exists. If you ensure all the references to things are being watched, youy can make complex situations for various emitters.
If you're hot reloading, it may be required to re-load the particle effect. Here's example code from Kronos.
ts
const refreshParticleEffect =()=>{
+)
This code watches for whether the node exists and the boundingRect exists, which are required to display the effect. You can additionally watch any other values that would have an impact on the particle effect, like whether or not it should be emitting.
If you're using hot reloading, you might need to reload the particle effect. Here's an example from Kronos: