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.
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?
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.
The stack trace of where the persistent ref was created. This is used for debugging purposes when a persistent ref is created but not placed in its layer object.
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.
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.
A modifier that will be applied to the cost amounts. That is to say, this modifier will be applied to the amount of baseResource before going into the scaling function. A cost modifier of x0.5 would give gain amounts equal to the player having half the baseResource they actually have. Must be reversible in order to correctly calculate nextAt.
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.
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)
A collection of functions that allow a conversion to scale the amount of resources gained based on the input resource. This typically shouldn't be created directly. Instead use one of the scaling function constructors.
Calculates the amount of the input resource that is required for the current value of conversion.currentGain. Note that conversion.currentGain has been modified by conversion.gainModifier, so you will need to revert that as appropriate. The conversion is responsible for rounding up the amount as appropriate. The returned value should not be below 0.
Calculates the amount of the output resource a conversion should be able to currently produce. This should be based off of conversion.baseResource.value. The conversion is responsible for applying the gainModifier, so this function should be un-modified. It does not need to be clamped or rounded.
Calculates the amount of the input resource that would be required for the current value of conversion.currentGain to increase. Note that conversion.currentGain has been modified by conversion.gainModifier, so you will need to revert that as appropriate. The conversion is responsible for rounding up the amount as appropriate. The returned value should not be below 0.
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.
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.
The delta time since last tick, in ms. Unaffected by time modifiers like game/player.Player.devSpeed or game/player.Player.offlineTime. Intended for things like updating animations.
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.
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.
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.
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.
Creates a scaling function off an existing function, with a hardcap applied to it. The harcap will ensure that the currentGain will stop at a given cap.
Creates a scaling function based off an existing scaling function, with a softcap applied to it. The softcap will take any value above a certain value and raise it to a power. If the power is <1, this will effectively make the value scale slower after the cap.
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.
Creates a scaling function based off the formula (baseResource - base) * coefficient. If the baseResource value is less than base then the currentGain will be 0.
Example
A scaling function created via createLinearScaling(10, 0.5) would produce the following values:
Creates a scaling function based off the formula (baseResource / base) ^ exponent. If the baseResource value is less than base then the currentGain will be 0.
Example
A scaling function created via createPolynomialScaling(10, 0.5) would produce the following values:
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.
Given a value, this function finds the amount above a certain value and raises it to a power. If the power is <1, this will effectively make the value scale slower after the cap.
Example
A softcap added via addSoftcap(scaling, 100, 0.5) would produce the following values:
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.
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>(...modifiers): 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.
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.
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. It can be used as a reference while developing, but should also appear in your preferred IDE as appropriate.
-
+
diff --git a/assets/guide_advanced-concepts_creating-features.md.0ca7d072.js b/assets/guide_advanced-concepts_creating-features.md.0d34bbd9.js
similarity index 94%
rename from assets/guide_advanced-concepts_creating-features.md.0ca7d072.js
rename to assets/guide_advanced-concepts_creating-features.md.0d34bbd9.js
index 1a938c83..4ed23cb5 100644
--- a/assets/guide_advanced-concepts_creating-features.md.0ca7d072.js
+++ b/assets/guide_advanced-concepts_creating-features.md.0d34bbd9.js
@@ -1 +1 @@
-import{_ as e,c as t,o as a,a as n}from"./app.ab34650d.js";const h=JSON.parse('{"title":"Creating Features","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/creating-features.md","lastUpdated":1658112303000}'),o={name:"guide/advanced-concepts/creating-features.md"},i=n('
Because typescript does not emit JS, if a value is supposed to be a function it is impossible to determine if a given function is 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.
',3),r=[i];function s(c,d,u,p,l,_){return a(),t("div",null,r)}var m=e(o,[["render",s]]);export{h as __pageData,m as default};
+import{_ as e,c as t,o as a,a as n}from"./app.ab34650d.js";const h=JSON.parse('{"title":"Creating Features","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/creating-features.md","lastUpdated":1658112388000}'),o={name:"guide/advanced-concepts/creating-features.md"},i=n('
Because typescript does not emit JS, if a value is supposed to be a function it is impossible to determine if a given function is 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.
',3),r=[i];function s(c,d,u,p,l,_){return a(),t("div",null,r)}var m=e(o,[["render",s]]);export{h as __pageData,m as default};
diff --git a/assets/guide_advanced-concepts_creating-features.md.0ca7d072.lean.js b/assets/guide_advanced-concepts_creating-features.md.0d34bbd9.lean.js
similarity index 85%
rename from assets/guide_advanced-concepts_creating-features.md.0ca7d072.lean.js
rename to assets/guide_advanced-concepts_creating-features.md.0d34bbd9.lean.js
index 0e79ae10..bf18e006 100644
--- a/assets/guide_advanced-concepts_creating-features.md.0ca7d072.lean.js
+++ b/assets/guide_advanced-concepts_creating-features.md.0d34bbd9.lean.js
@@ -1 +1 @@
-import{_ as e,c as t,o as a,a as n}from"./app.ab34650d.js";const h=JSON.parse('{"title":"Creating Features","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/creating-features.md","lastUpdated":1658112303000}'),o={name:"guide/advanced-concepts/creating-features.md"},i=n("",3),r=[i];function s(c,d,u,p,l,_){return a(),t("div",null,r)}var m=e(o,[["render",s]]);export{h as __pageData,m as default};
+import{_ as e,c as t,o as a,a as n}from"./app.ab34650d.js";const h=JSON.parse('{"title":"Creating Features","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/creating-features.md","lastUpdated":1658112388000}'),o={name:"guide/advanced-concepts/creating-features.md"},i=n("",3),r=[i];function s(c,d,u,p,l,_){return a(),t("div",null,r)}var m=e(o,[["render",s]]);export{h as __pageData,m as default};
diff --git a/assets/guide_advanced-concepts_dynamic-layers.md.d3364479.js b/assets/guide_advanced-concepts_dynamic-layers.md.b1fdfd3f.js
similarity index 98%
rename from assets/guide_advanced-concepts_dynamic-layers.md.d3364479.js
rename to assets/guide_advanced-concepts_dynamic-layers.md.b1fdfd3f.js
index fe2a5258..80a60f21 100644
--- a/assets/guide_advanced-concepts_dynamic-layers.md.d3364479.js
+++ b/assets/guide_advanced-concepts_dynamic-layers.md.b1fdfd3f.js
@@ -1,4 +1,4 @@
-import{_ as a,c as s,o as n,a as e}from"./app.ab34650d.js";const u=JSON.parse('{"title":"Dynamic Layers","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/dynamic-layers.md","lastUpdated":1658112303000}'),o={name:"guide/advanced-concepts/dynamic-layers.md"},l=e(`
You can dynamically add and remove layers using the addLayer and removeLayer functions. Note that removing a layer does not change the player save data in any way, so you can safely add and remove the same layer. In fact, there is a reloadLayer to do just that, which is used for when the structure of a layer changes - e.g., adding a new feature.
If you're going to be procedurally generating layers, all with a similar structure, it might make sense to use a utility function like the following in order to easily access a correctly typed reference to a layer with a given ID:
functiongetDynLayer(id:string):DynamicLayer{
+import{_ as a,c as s,o as n,a as e}from"./app.ab34650d.js";const u=JSON.parse('{"title":"Dynamic Layers","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/dynamic-layers.md","lastUpdated":1658112388000}'),o={name:"guide/advanced-concepts/dynamic-layers.md"},l=e(`
You can dynamically add and remove layers using the addLayer and removeLayer functions. Note that removing a layer does not change the player save data in any way, so you can safely add and remove the same layer. In fact, there is a reloadLayer to do just that, which is used for when the structure of a layer changes - e.g., adding a new feature.
If you're going to be procedurally generating layers, all with a similar structure, it might make sense to use a utility function like the following in order to easily access a correctly typed reference to a layer with a given ID:
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.d3364479.lean.js b/assets/guide_advanced-concepts_dynamic-layers.md.b1fdfd3f.lean.js
similarity index 84%
rename from assets/guide_advanced-concepts_dynamic-layers.md.d3364479.lean.js
rename to assets/guide_advanced-concepts_dynamic-layers.md.b1fdfd3f.lean.js
index 466e7165..4efdeb10 100644
--- a/assets/guide_advanced-concepts_dynamic-layers.md.d3364479.lean.js
+++ b/assets/guide_advanced-concepts_dynamic-layers.md.b1fdfd3f.lean.js
@@ -1 +1 @@
-import{_ as a,c as s,o as n,a as e}from"./app.ab34650d.js";const u=JSON.parse('{"title":"Dynamic Layers","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/dynamic-layers.md","lastUpdated":1658112303000}'),o={name:"guide/advanced-concepts/dynamic-layers.md"},l=e("",4),t=[l];function p(r,c,y,i,d,F){return n(),s("div",null,t)}var h=a(o,[["render",p]]);export{u as __pageData,h as default};
+import{_ as a,c as s,o as n,a as e}from"./app.ab34650d.js";const u=JSON.parse('{"title":"Dynamic Layers","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/dynamic-layers.md","lastUpdated":1658112388000}'),o={name:"guide/advanced-concepts/dynamic-layers.md"},l=e("",4),t=[l];function p(r,c,y,i,d,F){return n(),s("div",null,t)}var h=a(o,[["render",p]]);export{u as __pageData,h as default};
diff --git a/assets/guide_creating-your-project_changelog.md.5749200d.js b/assets/guide_creating-your-project_changelog.md.c0fb8c8f.js
similarity index 98%
rename from assets/guide_creating-your-project_changelog.md.5749200d.js
rename to assets/guide_creating-your-project_changelog.md.c0fb8c8f.js
index 6c4c1d74..828b3a42 100644
--- a/assets/guide_creating-your-project_changelog.md.5749200d.js
+++ b/assets/guide_creating-your-project_changelog.md.c0fb8c8f.js
@@ -1,4 +1,4 @@
-import{_ as s,c as a,o as n,a as e}from"./app.ab34650d.js";const g=JSON.parse('{"title":"Changelog","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/changelog.md","lastUpdated":1658112303000}'),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:
<detailsopen>
+import{_ as s,c as a,o as n,a as e}from"./app.ab34650d.js";const g=JSON.parse('{"title":"Changelog","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/changelog.md","lastUpdated":1658112388000}'),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:
<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.5749200d.lean.js b/assets/guide_creating-your-project_changelog.md.c0fb8c8f.lean.js
similarity index 84%
rename from assets/guide_creating-your-project_changelog.md.5749200d.lean.js
rename to assets/guide_creating-your-project_changelog.md.c0fb8c8f.lean.js
index 99d78a51..a38b2352 100644
--- a/assets/guide_creating-your-project_changelog.md.5749200d.lean.js
+++ b/assets/guide_creating-your-project_changelog.md.c0fb8c8f.lean.js
@@ -1 +1 @@
-import{_ as s,c as a,o as n,a as e}from"./app.ab34650d.js";const g=JSON.parse('{"title":"Changelog","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/changelog.md","lastUpdated":1658112303000}'),l={name:"guide/creating-your-project/changelog.md"},o=e("",6),t=[o];function p(c,r,D,F,y,i){return n(),a("div",null,t)}var h=s(l,[["render",p]]);export{g as __pageData,h as default};
+import{_ as s,c as a,o as n,a as e}from"./app.ab34650d.js";const g=JSON.parse('{"title":"Changelog","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/changelog.md","lastUpdated":1658112388000}'),l={name:"guide/creating-your-project/changelog.md"},o=e("",6),t=[o];function p(c,r,D,F,y,i){return n(),a("div",null,t)}var h=s(l,[["render",p]]);export{g as __pageData,h as default};
diff --git a/assets/guide_creating-your-project_project-entry.md.3bed1026.js b/assets/guide_creating-your-project_project-entry.md.06b07710.js
similarity index 98%
rename from assets/guide_creating-your-project_project-entry.md.3bed1026.js
rename to assets/guide_creating-your-project_project-entry.md.06b07710.js
index efccac4c..9a166748 100644
--- a/assets/guide_creating-your-project_project-entry.md.3bed1026.js
+++ b/assets/guide_creating-your-project_project-entry.md.06b07710.js
@@ -1,2 +1,2 @@
-import{_ as e,c as a,o as t,a as s}from"./app.ab34650d.js";const f=JSON.parse('{"title":"Project Entry","description":"","frontmatter":{},"headers":[{"level":2,"title":"Required Exports","slug":"required-exports"},{"level":3,"title":"getInitialLayers","slug":"getinitiallayers"},{"level":3,"title":"hasWon","slug":"haswon"},{"level":3,"title":"fixOldSave","slug":"fixoldsave"}],"relativePath":"guide/creating-your-project/project-entry.md","lastUpdated":1658112303000}'),o={name:"guide/creating-your-project/project-entry.md"},r=s(`
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.
A computed ref whose value is true whenever the game is over.
For example, in a game where the goal is to have a resource reach 10:
exportconst hasWon =computed(()=> Decimal.gte(resource.value,10));
+import{_ as e,c as a,o as t,a as s}from"./app.ab34650d.js";const f=JSON.parse('{"title":"Project Entry","description":"","frontmatter":{},"headers":[{"level":2,"title":"Required Exports","slug":"required-exports"},{"level":3,"title":"getInitialLayers","slug":"getinitiallayers"},{"level":3,"title":"hasWon","slug":"haswon"},{"level":3,"title":"fixOldSave","slug":"fixoldsave"}],"relativePath":"guide/creating-your-project/project-entry.md","lastUpdated":1658112388000}'),o={name:"guide/creating-your-project/project-entry.md"},r=s(`
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)}var g=e(o,[["render",l]]);export{f as __pageData,g as default};
diff --git a/assets/guide_creating-your-project_project-entry.md.3bed1026.lean.js b/assets/guide_creating-your-project_project-entry.md.06b07710.lean.js
similarity index 90%
rename from assets/guide_creating-your-project_project-entry.md.3bed1026.lean.js
rename to assets/guide_creating-your-project_project-entry.md.06b07710.lean.js
index 113172ba..6ea74ad1 100644
--- a/assets/guide_creating-your-project_project-entry.md.3bed1026.lean.js
+++ b/assets/guide_creating-your-project_project-entry.md.06b07710.lean.js
@@ -1 +1 @@
-import{_ as e,c as a,o as t,a as s}from"./app.ab34650d.js";const f=JSON.parse('{"title":"Project Entry","description":"","frontmatter":{},"headers":[{"level":2,"title":"Required Exports","slug":"required-exports"},{"level":3,"title":"getInitialLayers","slug":"getinitiallayers"},{"level":3,"title":"hasWon","slug":"haswon"},{"level":3,"title":"fixOldSave","slug":"fixoldsave"}],"relativePath":"guide/creating-your-project/project-entry.md","lastUpdated":1658112303000}'),o={name:"guide/creating-your-project/project-entry.md"},r=s("",17),n=[r];function l(i,p,c,d,h,y){return t(),a("div",null,n)}var g=e(o,[["render",l]]);export{f as __pageData,g as default};
+import{_ as e,c as a,o as t,a as s}from"./app.ab34650d.js";const f=JSON.parse('{"title":"Project Entry","description":"","frontmatter":{},"headers":[{"level":2,"title":"Required Exports","slug":"required-exports"},{"level":3,"title":"getInitialLayers","slug":"getinitiallayers"},{"level":3,"title":"hasWon","slug":"haswon"},{"level":3,"title":"fixOldSave","slug":"fixoldsave"}],"relativePath":"guide/creating-your-project/project-entry.md","lastUpdated":1658112388000}'),o={name:"guide/creating-your-project/project-entry.md"},r=s("",17),n=[r];function l(i,p,c,d,h,y){return t(),a("div",null,n)}var g=e(o,[["render",l]]);export{f as __pageData,g as default};
diff --git a/assets/guide_creating-your-project_project-info.md.7c9b4461.js b/assets/guide_creating-your-project_project-info.md.2c1855e8.js
similarity index 99%
rename from assets/guide_creating-your-project_project-info.md.7c9b4461.js
rename to assets/guide_creating-your-project_project-info.md.2c1855e8.js
index d8d2799b..00fd7ca4 100644
--- a/assets/guide_creating-your-project_project-info.md.7c9b4461.js
+++ b/assets/guide_creating-your-project_project-info.md.2c1855e8.js
@@ -1 +1 @@
-import{_ as e,c as a,o as i,a as l}from"./app.ab34650d.js";const g=JSON.parse('{"title":"Project Info","description":"","frontmatter":{},"headers":[{"level":2,"title":"Basic Config","slug":"basic-config"},{"level":3,"title":"title","slug":"title"},{"level":3,"title":"id","slug":"id"},{"level":3,"title":"author","slug":"author"},{"level":3,"title":"discordName","slug":"discordname"},{"level":3,"title":"discordLink","slug":"discordlink"},{"level":2,"title":"Version Config","slug":"version-config"},{"level":3,"title":"versionNumber","slug":"versionnumber"},{"level":3,"title":"versionTitle","slug":"versiontitle"},{"level":2,"title":"Display Config","slug":"display-config"},{"level":3,"title":"allowGoBack","slug":"allowgoback"},{"level":3,"title":"defaultShowSmall","slug":"defaultshowsmall"},{"level":3,"title":"defaultDecimalsShown","slug":"defaultdecimalsshown"},{"level":3,"title":"useHeader","slug":"useheader"},{"level":3,"title":"banner","slug":"banner"},{"level":3,"title":"logo","slug":"logo"},{"level":3,"title":"initialTabs","slug":"initialtabs"},{"level":2,"title":"Advanced Config","slug":"advanced-config"},{"level":3,"title":"maxTickLength","slug":"maxticklength"},{"level":3,"title":"offlineLimit","slug":"offlinelimit"},{"level":3,"title":"enablePausing","slug":"enablepausing"}],"relativePath":"guide/creating-your-project/project-info.md","lastUpdated":1658112303000}'),t={name:"guide/creating-your-project/project-info.md"},o=l('
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 user's 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).
',60),n=[o];function r(d,s,h,c,u,f){return i(),a("div",null,n)}var m=e(t,[["render",r]]);export{g as __pageData,m as default};
+import{_ as e,c as a,o as i,a as l}from"./app.ab34650d.js";const g=JSON.parse('{"title":"Project Info","description":"","frontmatter":{},"headers":[{"level":2,"title":"Basic Config","slug":"basic-config"},{"level":3,"title":"title","slug":"title"},{"level":3,"title":"id","slug":"id"},{"level":3,"title":"author","slug":"author"},{"level":3,"title":"discordName","slug":"discordname"},{"level":3,"title":"discordLink","slug":"discordlink"},{"level":2,"title":"Version Config","slug":"version-config"},{"level":3,"title":"versionNumber","slug":"versionnumber"},{"level":3,"title":"versionTitle","slug":"versiontitle"},{"level":2,"title":"Display Config","slug":"display-config"},{"level":3,"title":"allowGoBack","slug":"allowgoback"},{"level":3,"title":"defaultShowSmall","slug":"defaultshowsmall"},{"level":3,"title":"defaultDecimalsShown","slug":"defaultdecimalsshown"},{"level":3,"title":"useHeader","slug":"useheader"},{"level":3,"title":"banner","slug":"banner"},{"level":3,"title":"logo","slug":"logo"},{"level":3,"title":"initialTabs","slug":"initialtabs"},{"level":2,"title":"Advanced Config","slug":"advanced-config"},{"level":3,"title":"maxTickLength","slug":"maxticklength"},{"level":3,"title":"offlineLimit","slug":"offlinelimit"},{"level":3,"title":"enablePausing","slug":"enablepausing"}],"relativePath":"guide/creating-your-project/project-info.md","lastUpdated":1658112388000}'),t={name:"guide/creating-your-project/project-info.md"},o=l('
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 user's 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).
',60),n=[o];function r(d,s,h,c,u,f){return i(),a("div",null,n)}var m=e(t,[["render",r]]);export{g as __pageData,m as default};
diff --git a/assets/guide_creating-your-project_project-info.md.7c9b4461.lean.js b/assets/guide_creating-your-project_project-info.md.2c1855e8.lean.js
similarity index 95%
rename from assets/guide_creating-your-project_project-info.md.7c9b4461.lean.js
rename to assets/guide_creating-your-project_project-info.md.2c1855e8.lean.js
index 3387ce1c..37a4542d 100644
--- a/assets/guide_creating-your-project_project-info.md.7c9b4461.lean.js
+++ b/assets/guide_creating-your-project_project-info.md.2c1855e8.lean.js
@@ -1 +1 @@
-import{_ as e,c as a,o as i,a as l}from"./app.ab34650d.js";const g=JSON.parse('{"title":"Project Info","description":"","frontmatter":{},"headers":[{"level":2,"title":"Basic Config","slug":"basic-config"},{"level":3,"title":"title","slug":"title"},{"level":3,"title":"id","slug":"id"},{"level":3,"title":"author","slug":"author"},{"level":3,"title":"discordName","slug":"discordname"},{"level":3,"title":"discordLink","slug":"discordlink"},{"level":2,"title":"Version Config","slug":"version-config"},{"level":3,"title":"versionNumber","slug":"versionnumber"},{"level":3,"title":"versionTitle","slug":"versiontitle"},{"level":2,"title":"Display Config","slug":"display-config"},{"level":3,"title":"allowGoBack","slug":"allowgoback"},{"level":3,"title":"defaultShowSmall","slug":"defaultshowsmall"},{"level":3,"title":"defaultDecimalsShown","slug":"defaultdecimalsshown"},{"level":3,"title":"useHeader","slug":"useheader"},{"level":3,"title":"banner","slug":"banner"},{"level":3,"title":"logo","slug":"logo"},{"level":3,"title":"initialTabs","slug":"initialtabs"},{"level":2,"title":"Advanced Config","slug":"advanced-config"},{"level":3,"title":"maxTickLength","slug":"maxticklength"},{"level":3,"title":"offlineLimit","slug":"offlinelimit"},{"level":3,"title":"enablePausing","slug":"enablepausing"}],"relativePath":"guide/creating-your-project/project-info.md","lastUpdated":1658112303000}'),t={name:"guide/creating-your-project/project-info.md"},o=l("",60),n=[o];function r(d,s,h,c,u,f){return i(),a("div",null,n)}var m=e(t,[["render",r]]);export{g as __pageData,m as default};
+import{_ as e,c as a,o as i,a as l}from"./app.ab34650d.js";const g=JSON.parse('{"title":"Project Info","description":"","frontmatter":{},"headers":[{"level":2,"title":"Basic Config","slug":"basic-config"},{"level":3,"title":"title","slug":"title"},{"level":3,"title":"id","slug":"id"},{"level":3,"title":"author","slug":"author"},{"level":3,"title":"discordName","slug":"discordname"},{"level":3,"title":"discordLink","slug":"discordlink"},{"level":2,"title":"Version Config","slug":"version-config"},{"level":3,"title":"versionNumber","slug":"versionnumber"},{"level":3,"title":"versionTitle","slug":"versiontitle"},{"level":2,"title":"Display Config","slug":"display-config"},{"level":3,"title":"allowGoBack","slug":"allowgoback"},{"level":3,"title":"defaultShowSmall","slug":"defaultshowsmall"},{"level":3,"title":"defaultDecimalsShown","slug":"defaultdecimalsshown"},{"level":3,"title":"useHeader","slug":"useheader"},{"level":3,"title":"banner","slug":"banner"},{"level":3,"title":"logo","slug":"logo"},{"level":3,"title":"initialTabs","slug":"initialtabs"},{"level":2,"title":"Advanced Config","slug":"advanced-config"},{"level":3,"title":"maxTickLength","slug":"maxticklength"},{"level":3,"title":"offlineLimit","slug":"offlinelimit"},{"level":3,"title":"enablePausing","slug":"enablepausing"}],"relativePath":"guide/creating-your-project/project-info.md","lastUpdated":1658112388000}'),t={name:"guide/creating-your-project/project-info.md"},o=l("",60),n=[o];function r(d,s,h,c,u,f){return i(),a("div",null,n)}var m=e(t,[["render",r]]);export{g as __pageData,m as default};
diff --git a/assets/guide_creating-your-project_themes.md.43025d93.js b/assets/guide_creating-your-project_themes.md.ae164b17.js
similarity index 97%
rename from assets/guide_creating-your-project_themes.md.43025d93.js
rename to assets/guide_creating-your-project_themes.md.ae164b17.js
index 06b562d0..aacc4494 100644
--- a/assets/guide_creating-your-project_themes.md.43025d93.js
+++ b/assets/guide_creating-your-project_themes.md.ae164b17.js
@@ -1 +1 @@
-import{_ as e,c as t,o as a,a as o}from"./app.ab34650d.js";const g=JSON.parse('{"title":"Themes","description":"","frontmatter":{},"headers":[{"level":2,"title":"Modifying Themes","slug":"modifying-themes"},{"level":2,"title":"Theme Options","slug":"theme-options"},{"level":3,"title":"floatingTabs","slug":"floatingtabs"},{"level":3,"title":"mergeAdjacent","slug":"mergeadjacent"}],"relativePath":"guide/creating-your-project/themes.md","lastUpdated":1658112303000}'),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 h(r,d,l,c,m,p){return a(),t("div",null,n)}var f=e(i,[["render",h]]);export{g as __pageData,f as default};
+import{_ as e,c as t,o as a,a as o}from"./app.ab34650d.js";const g=JSON.parse('{"title":"Themes","description":"","frontmatter":{},"headers":[{"level":2,"title":"Modifying Themes","slug":"modifying-themes"},{"level":2,"title":"Theme Options","slug":"theme-options"},{"level":3,"title":"floatingTabs","slug":"floatingtabs"},{"level":3,"title":"mergeAdjacent","slug":"mergeadjacent"}],"relativePath":"guide/creating-your-project/themes.md","lastUpdated":1658112388000}'),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 h(r,d,l,c,m,p){return a(),t("div",null,n)}var f=e(i,[["render",h]]);export{g as __pageData,f as default};
diff --git a/assets/guide_creating-your-project_themes.md.43025d93.lean.js b/assets/guide_creating-your-project_themes.md.ae164b17.lean.js
similarity index 90%
rename from assets/guide_creating-your-project_themes.md.43025d93.lean.js
rename to assets/guide_creating-your-project_themes.md.ae164b17.lean.js
index 85e97178..803266e8 100644
--- a/assets/guide_creating-your-project_themes.md.43025d93.lean.js
+++ b/assets/guide_creating-your-project_themes.md.ae164b17.lean.js
@@ -1 +1 @@
-import{_ as e,c as t,o as a,a as o}from"./app.ab34650d.js";const g=JSON.parse('{"title":"Themes","description":"","frontmatter":{},"headers":[{"level":2,"title":"Modifying Themes","slug":"modifying-themes"},{"level":2,"title":"Theme Options","slug":"theme-options"},{"level":3,"title":"floatingTabs","slug":"floatingtabs"},{"level":3,"title":"mergeAdjacent","slug":"mergeadjacent"}],"relativePath":"guide/creating-your-project/themes.md","lastUpdated":1658112303000}'),i={name:"guide/creating-your-project/themes.md"},s=o("",15),n=[s];function h(r,d,l,c,m,p){return a(),t("div",null,n)}var f=e(i,[["render",h]]);export{g as __pageData,f as default};
+import{_ as e,c as t,o as a,a as o}from"./app.ab34650d.js";const g=JSON.parse('{"title":"Themes","description":"","frontmatter":{},"headers":[{"level":2,"title":"Modifying Themes","slug":"modifying-themes"},{"level":2,"title":"Theme Options","slug":"theme-options"},{"level":3,"title":"floatingTabs","slug":"floatingtabs"},{"level":3,"title":"mergeAdjacent","slug":"mergeadjacent"}],"relativePath":"guide/creating-your-project/themes.md","lastUpdated":1658112388000}'),i={name:"guide/creating-your-project/themes.md"},s=o("",15),n=[s];function h(r,d,l,c,m,p){return a(),t("div",null,n)}var f=e(i,[["render",h]]);export{g as __pageData,f as default};
diff --git a/assets/guide_creating-your-project_utils.md.2595396c.js b/assets/guide_creating-your-project_utils.md.cbb851b0.js
similarity index 95%
rename from assets/guide_creating-your-project_utils.md.2595396c.js
rename to assets/guide_creating-your-project_utils.md.cbb851b0.js
index 703e3a14..4ec9269e 100644
--- a/assets/guide_creating-your-project_utils.md.2595396c.js
+++ b/assets/guide_creating-your-project_utils.md.cbb851b0.js
@@ -1 +1 @@
-import{_ as a,c as o,o as s,b as e,d as t}from"./app.ab34650d.js";const b=JSON.parse('{"title":"Utilities","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/utils.md","lastUpdated":1658112303000}'),i={name:"guide/creating-your-project/utils.md"},r=e("h1",{id:"utilities",tabindex:"-1"},[t("Utilities "),e("a",{class:"header-anchor",href:"#utilities","aria-hidden":"true"},"#")],-1),n=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),d=[r,n,c];function l(u,h,p,f,m,_){return s(),o("div",null,d)}var k=a(i,[["render",l]]);export{b as __pageData,k as default};
+import{_ as a,c as o,o as s,b as e,d as t}from"./app.ab34650d.js";const b=JSON.parse('{"title":"Utilities","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/utils.md","lastUpdated":1658112388000}'),i={name:"guide/creating-your-project/utils.md"},r=e("h1",{id:"utilities",tabindex:"-1"},[t("Utilities "),e("a",{class:"header-anchor",href:"#utilities","aria-hidden":"true"},"#")],-1),n=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),d=[r,n,c];function l(u,h,p,f,m,_){return s(),o("div",null,d)}var k=a(i,[["render",l]]);export{b as __pageData,k as default};
diff --git a/assets/guide_creating-your-project_utils.md.2595396c.lean.js b/assets/guide_creating-your-project_utils.md.cbb851b0.lean.js
similarity index 95%
rename from assets/guide_creating-your-project_utils.md.2595396c.lean.js
rename to assets/guide_creating-your-project_utils.md.cbb851b0.lean.js
index 703e3a14..4ec9269e 100644
--- a/assets/guide_creating-your-project_utils.md.2595396c.lean.js
+++ b/assets/guide_creating-your-project_utils.md.cbb851b0.lean.js
@@ -1 +1 @@
-import{_ as a,c as o,o as s,b as e,d as t}from"./app.ab34650d.js";const b=JSON.parse('{"title":"Utilities","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/utils.md","lastUpdated":1658112303000}'),i={name:"guide/creating-your-project/utils.md"},r=e("h1",{id:"utilities",tabindex:"-1"},[t("Utilities "),e("a",{class:"header-anchor",href:"#utilities","aria-hidden":"true"},"#")],-1),n=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),d=[r,n,c];function l(u,h,p,f,m,_){return s(),o("div",null,d)}var k=a(i,[["render",l]]);export{b as __pageData,k as default};
+import{_ as a,c as o,o as s,b as e,d as t}from"./app.ab34650d.js";const b=JSON.parse('{"title":"Utilities","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/utils.md","lastUpdated":1658112388000}'),i={name:"guide/creating-your-project/utils.md"},r=e("h1",{id:"utilities",tabindex:"-1"},[t("Utilities "),e("a",{class:"header-anchor",href:"#utilities","aria-hidden":"true"},"#")],-1),n=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),d=[r,n,c];function l(u,h,p,f,m,_){return s(),o("div",null,d)}var k=a(i,[["render",l]]);export{b as __pageData,k as default};
diff --git a/assets/guide_getting-started_examples.md.18cf1379.js b/assets/guide_getting-started_examples.md.4df3b8a9.js
similarity index 94%
rename from assets/guide_getting-started_examples.md.18cf1379.js
rename to assets/guide_getting-started_examples.md.4df3b8a9.js
index 4a938b27..4eeebe74 100644
--- a/assets/guide_getting-started_examples.md.18cf1379.js
+++ b/assets/guide_getting-started_examples.md.4df3b8a9.js
@@ -1 +1 @@
-import{_ as e,c as t,o as a,a as r}from"./app.ab34650d.js";const f=JSON.parse('{"title":"Example Projects","description":"","frontmatter":{},"headers":[{"level":2,"title":"TMT-Demo","slug":"tmt-demo"}],"relativePath":"guide/getting-started/examples.md","lastUpdated":1658112303000}'),o={name:"guide/getting-started/examples.md"},s=r('
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.
',4),n=[s];function d(c,p,i,l,m,_){return a(),t("div",null,n)}var u=e(o,[["render",d]]);export{f as __pageData,u as default};
+import{_ as e,c as t,o as a,a as r}from"./app.ab34650d.js";const f=JSON.parse('{"title":"Example Projects","description":"","frontmatter":{},"headers":[{"level":2,"title":"TMT-Demo","slug":"tmt-demo"}],"relativePath":"guide/getting-started/examples.md","lastUpdated":1658112388000}'),o={name:"guide/getting-started/examples.md"},s=r('
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.
',4),n=[s];function d(c,p,i,l,m,_){return a(),t("div",null,n)}var u=e(o,[["render",d]]);export{f as __pageData,u as default};
diff --git a/assets/guide_getting-started_examples.md.18cf1379.lean.js b/assets/guide_getting-started_examples.md.4df3b8a9.lean.js
similarity index 86%
rename from assets/guide_getting-started_examples.md.18cf1379.lean.js
rename to assets/guide_getting-started_examples.md.4df3b8a9.lean.js
index c816ad0c..44fc17d9 100644
--- a/assets/guide_getting-started_examples.md.18cf1379.lean.js
+++ b/assets/guide_getting-started_examples.md.4df3b8a9.lean.js
@@ -1 +1 @@
-import{_ as e,c as t,o as a,a as r}from"./app.ab34650d.js";const f=JSON.parse('{"title":"Example Projects","description":"","frontmatter":{},"headers":[{"level":2,"title":"TMT-Demo","slug":"tmt-demo"}],"relativePath":"guide/getting-started/examples.md","lastUpdated":1658112303000}'),o={name:"guide/getting-started/examples.md"},s=r("",4),n=[s];function d(c,p,i,l,m,_){return a(),t("div",null,n)}var u=e(o,[["render",d]]);export{f as __pageData,u as default};
+import{_ as e,c as t,o as a,a as r}from"./app.ab34650d.js";const f=JSON.parse('{"title":"Example Projects","description":"","frontmatter":{},"headers":[{"level":2,"title":"TMT-Demo","slug":"tmt-demo"}],"relativePath":"guide/getting-started/examples.md","lastUpdated":1658112388000}'),o={name:"guide/getting-started/examples.md"},s=r("",4),n=[s];function d(c,p,i,l,m,_){return a(),t("div",null,n)}var u=e(o,[["render",d]]);export{f as __pageData,u as default};
diff --git a/assets/guide_getting-started_setup.md.18784fa3.js b/assets/guide_getting-started_setup.md.18784fa3.js
deleted file mode 100644
index e94a8d64..00000000
--- a/assets/guide_getting-started_setup.md.18784fa3.js
+++ /dev/null
@@ -1 +0,0 @@
-import{_ as e,c as t,o,a}from"./app.ab34650d.js";var r="/assets/actionsbutton.f1ba9d8e.png",n="/assets/gh-pages.a24cefcf.png";const y=JSON.parse('{"title":"Setting Up","description":"","frontmatter":{},"headers":[{"level":2,"title":"Local Development","slug":"local-development"},{"level":3,"title":"Deploying","slug":"deploying"},{"level":3,"title":"Visual Studio Code Setup","slug":"visual-studio-code-setup"},{"level":2,"title":"Replit","slug":"replit"},{"level":2,"title":"Glitch","slug":"glitch"}],"relativePath":"guide/getting-started/setup.md","lastUpdated":1658112303000}'),i={name:"guide/getting-started/setup.md"},l=a('
Profectus requires a node development environment in order to work on a project. If you are comfortable with the command line, it is recommended to use a local development environment.
Create a new project from the Profectus repository via the "Use this template" button. You can then copy the link for the repository to clone it locally.
INFO
Since the repository is a template repository, you can easily create multiple projects from one repository. However, it does make updating an existing project to a newer version of Profectus more difficult.
It's recommended to create a new git branch for development, so you can push your changes without it affecting the live build. This allows you to continue working with smaller commits, and only release new versions when you're actually ready to. The github workflow will automatically rebuild the page whenever you push to the main branch.
The next step is to install Profectus' dependencies. This is as simple as running npm install.
You can now run npm run serve to start a local server that will host your project so you can work on it. As you change files the site will automatically reload them.
Also, you might consider following the steps to update Profectus before you start working, as it'll make updating in the future easier without needing to worry about unrelated histories.
If you're using git, deploying is as easy as pushing your changes to the main branch. In a couple minutes the site will be updated fully automatically. If you'd like to see progress on it, or look at any errors that happened, you can do so from the actions tab on your repository.
Before github knows to actually host the generated site, you'll have to enable github pages in the repo settings. This just means selecting the branch to use - gh-pages. You will only need to perform this step once. This will automatically start another github action to deploy the website.
If you don't already have a preferred IDE, Profectus is currently developed in Visual Studio Code and is known to work well with that IDE in particular.
As an alternative to local development, you may use replit, which will automatically set up your development environment for you and also host your project itself.
However, on the free plan you will be limited by the amount of resources you can use, and the program will need to start up occasionally.
To create a Profectus project on replit, all you have to do is click this button:
You can then start the developing by clicking the "Run" button at the top of the screen. This will also make the project run publicly, so you could consider it as automatically deployed. This does mean you cannot have a separate development environment from your production environment, however.
Glitch is a site similar to replit, with much the same pros and cons. To create a projectus project on glitch, select "New Project", "Import from GitHub", and then type in profectus-engine/Profectus. The new project will be created and should be automatically configured and ready to go.
',29),s=[l];function p(c,u,h,d,g,m){return o(),t("div",null,s)}var b=e(i,[["render",p]]);export{y as __pageData,b as default};
diff --git a/assets/guide_getting-started_setup.md.b46876d2.js b/assets/guide_getting-started_setup.md.b46876d2.js
new file mode 100644
index 00000000..563ac422
--- /dev/null
+++ b/assets/guide_getting-started_setup.md.b46876d2.js
@@ -0,0 +1 @@
+import{_ as e,c as t,o,a}from"./app.ab34650d.js";var r="/assets/actionsbutton.f1ba9d8e.png",n="/assets/gh-pages.a24cefcf.png";const y=JSON.parse('{"title":"Setting Up","description":"","frontmatter":{},"headers":[{"level":2,"title":"Local Development","slug":"local-development"},{"level":3,"title":"Deploying","slug":"deploying"},{"level":3,"title":"Visual Studio Code Setup","slug":"visual-studio-code-setup"},{"level":2,"title":"Replit","slug":"replit"},{"level":2,"title":"Glitch","slug":"glitch"}],"relativePath":"guide/getting-started/setup.md","lastUpdated":1658112388000}'),i={name:"guide/getting-started/setup.md"},l=a('
Profectus requires a node development environment in order to work on a project. If you are comfortable with the command line, it is recommended to use a local development environment.
Create a new project from the Profectus repository via the "Use this template" button. You can then copy the link for the repository to clone it locally.
INFO
Since the repository is a template repository, you can easily create multiple projects from one repository. However, it does make updating an existing project to a newer version of Profectus more difficult. Consider updating Profectusbefore you start developing, to avoid the issue with unrelated histories.
It's recommended to create a new git branch for development, so you can push your changes without it affecting the live build. This allows you to continue working with smaller commits, and only release new versions when you're actually ready to. The github workflow will automatically rebuild the page whenever you push to the main branch.
The next step is to install Profectus' dependencies. This is as simple as running npm install.
You can now run npm run serve to start a local server that will host your project so you can work on it. As you change files the site will automatically reload them.
Also, you might consider following the steps to update Profectus before you start working, as it'll make updating in the future easier without needing to worry about unrelated histories.
If you're using git, deploying is as easy as pushing your changes to the main branch. In a couple minutes the site will be updated fully automatically. If you'd like to see progress on it, or look at any errors that happened, you can do so from the actions tab on your repository.
Before github knows to actually host the generated site, you'll have to enable github pages in the repo settings. This just means selecting the branch to use - gh-pages. You will only need to perform this step once. This will automatically start another github action to deploy the website.
If you don't already have a preferred IDE, Profectus is currently developed in Visual Studio Code and is known to work well with that IDE in particular.
As an alternative to local development, you may use replit, which will automatically set up your development environment for you and also host your project itself.
However, on the free plan you will be limited by the amount of resources you can use, and the program will need to start up occasionally.
To create a Profectus project on replit, all you have to do is click this button:
You can then start the developing by clicking the "Run" button at the top of the screen. This will also make the project run publicly, so you could consider it as automatically deployed. This does mean you cannot have a separate development environment from your production environment, however.
Glitch is a site similar to replit, with much the same pros and cons. To create a projectus project on glitch, select "New Project", "Import from GitHub", and then type in profectus-engine/Profectus. The new project will be created and should be automatically configured and ready to go.
',29),s=[l];function p(c,u,h,d,g,m){return o(),t("div",null,s)}var b=e(i,[["render",p]]);export{y as __pageData,b as default};
diff --git a/assets/guide_getting-started_setup.md.18784fa3.lean.js b/assets/guide_getting-started_setup.md.b46876d2.lean.js
similarity index 91%
rename from assets/guide_getting-started_setup.md.18784fa3.lean.js
rename to assets/guide_getting-started_setup.md.b46876d2.lean.js
index ea3d65f6..c15fceb1 100644
--- a/assets/guide_getting-started_setup.md.18784fa3.lean.js
+++ b/assets/guide_getting-started_setup.md.b46876d2.lean.js
@@ -1 +1 @@
-import{_ as e,c as t,o,a}from"./app.ab34650d.js";var r="/assets/actionsbutton.f1ba9d8e.png",n="/assets/gh-pages.a24cefcf.png";const y=JSON.parse('{"title":"Setting Up","description":"","frontmatter":{},"headers":[{"level":2,"title":"Local Development","slug":"local-development"},{"level":3,"title":"Deploying","slug":"deploying"},{"level":3,"title":"Visual Studio Code Setup","slug":"visual-studio-code-setup"},{"level":2,"title":"Replit","slug":"replit"},{"level":2,"title":"Glitch","slug":"glitch"}],"relativePath":"guide/getting-started/setup.md","lastUpdated":1658112303000}'),i={name:"guide/getting-started/setup.md"},l=a("",29),s=[l];function p(c,u,h,d,g,m){return o(),t("div",null,s)}var b=e(i,[["render",p]]);export{y as __pageData,b as default};
+import{_ as e,c as t,o,a}from"./app.ab34650d.js";var r="/assets/actionsbutton.f1ba9d8e.png",n="/assets/gh-pages.a24cefcf.png";const y=JSON.parse('{"title":"Setting Up","description":"","frontmatter":{},"headers":[{"level":2,"title":"Local Development","slug":"local-development"},{"level":3,"title":"Deploying","slug":"deploying"},{"level":3,"title":"Visual Studio Code Setup","slug":"visual-studio-code-setup"},{"level":2,"title":"Replit","slug":"replit"},{"level":2,"title":"Glitch","slug":"glitch"}],"relativePath":"guide/getting-started/setup.md","lastUpdated":1658112388000}'),i={name:"guide/getting-started/setup.md"},l=a("",29),s=[l];function p(c,u,h,d,g,m){return o(),t("div",null,s)}var b=e(i,[["render",p]]);export{y as __pageData,b as default};
diff --git a/assets/guide_getting-started_updating.md.1b35755d.js b/assets/guide_getting-started_updating.md.954bab42.js
similarity index 97%
rename from assets/guide_getting-started_updating.md.1b35755d.js
rename to assets/guide_getting-started_updating.md.954bab42.js
index 6c1ed6d5..2d470ea9 100644
--- a/assets/guide_getting-started_updating.md.1b35755d.js
+++ b/assets/guide_getting-started_updating.md.954bab42.js
@@ -1,4 +1,4 @@
-import{_ as e,c as t,o as a,a as o}from"./app.ab34650d.js";const f=JSON.parse('{"title":"Updating Profectus","description":"","frontmatter":{},"headers":[{"level":2,"title":"Github","slug":"github"},{"level":2,"title":"Replit","slug":"replit"},{"level":2,"title":"Glitch","slug":"glitch"}],"relativePath":"guide/getting-started/updating.md","lastUpdated":1658112303000}'),r={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:
git remote add template https://github.com/profectus-engine/Profectus
+import{_ as e,c as t,o as a,a as o}from"./app.ab34650d.js";const f=JSON.parse('{"title":"Updating Profectus","description":"","frontmatter":{},"headers":[{"level":2,"title":"Github","slug":"github"},{"level":2,"title":"Replit","slug":"replit"},{"level":2,"title":"Glitch","slug":"glitch"}],"relativePath":"guide/getting-started/updating.md","lastUpdated":1658112388000}'),r={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),i=[n];function s(l,c,d,h,p,u){return a(),t("div",null,i)}var m=e(r,[["render",s]]);export{f as __pageData,m as default};
diff --git a/assets/guide_getting-started_updating.md.1b35755d.lean.js b/assets/guide_getting-started_updating.md.954bab42.lean.js
similarity index 88%
rename from assets/guide_getting-started_updating.md.1b35755d.lean.js
rename to assets/guide_getting-started_updating.md.954bab42.lean.js
index 3c66cd51..2169393d 100644
--- a/assets/guide_getting-started_updating.md.1b35755d.lean.js
+++ b/assets/guide_getting-started_updating.md.954bab42.lean.js
@@ -1 +1 @@
-import{_ as e,c as t,o as a,a as o}from"./app.ab34650d.js";const f=JSON.parse('{"title":"Updating Profectus","description":"","frontmatter":{},"headers":[{"level":2,"title":"Github","slug":"github"},{"level":2,"title":"Replit","slug":"replit"},{"level":2,"title":"Glitch","slug":"glitch"}],"relativePath":"guide/getting-started/updating.md","lastUpdated":1658112303000}'),r={name:"guide/getting-started/updating.md"},n=o("",9),i=[n];function s(l,c,d,h,p,u){return a(),t("div",null,i)}var m=e(r,[["render",s]]);export{f as __pageData,m as default};
+import{_ as e,c as t,o as a,a as o}from"./app.ab34650d.js";const f=JSON.parse('{"title":"Updating Profectus","description":"","frontmatter":{},"headers":[{"level":2,"title":"Github","slug":"github"},{"level":2,"title":"Replit","slug":"replit"},{"level":2,"title":"Glitch","slug":"glitch"}],"relativePath":"guide/getting-started/updating.md","lastUpdated":1658112388000}'),r={name:"guide/getting-started/updating.md"},n=o("",9),i=[n];function s(l,c,d,h,p,u){return a(),t("div",null,i)}var m=e(r,[["render",s]]);export{f as __pageData,m as default};
diff --git a/assets/guide_important-concepts_coercable.md.b3e4b201.js b/assets/guide_important-concepts_coercable.md.2f60c9ea.js
similarity index 98%
rename from assets/guide_important-concepts_coercable.md.b3e4b201.js
rename to assets/guide_important-concepts_coercable.md.2f60c9ea.js
index 72fc7bf9..480347a6 100644
--- a/assets/guide_important-concepts_coercable.md.b3e4b201.js
+++ b/assets/guide_important-concepts_coercable.md.2f60c9ea.js
@@ -1,4 +1,4 @@
-import{_ as e,c as s,o as n,a}from"./app.ab34650d.js";const m=JSON.parse('{"title":"Coercable Components","description":"","frontmatter":{},"headers":[{"level":2,"title":"Template Strings","slug":"template-strings"},{"level":2,"title":"Render Functions (JSX)","slug":"render-functions-jsx"},{"level":2,"title":"Components","slug":"components"}],"relativePath":"guide/important-concepts/coercable.md","lastUpdated":1658112303000}'),o={name:"guide/important-concepts/coercable.md"},t=a(`
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 s,o as n,a}from"./app.ab34650d.js";const m=JSON.parse('{"title":"Coercable Components","description":"","frontmatter":{},"headers":[{"level":2,"title":"Template Strings","slug":"template-strings"},{"level":2,"title":"Render Functions (JSX)","slug":"render-functions-jsx"},{"level":2,"title":"Components","slug":"components"}],"relativePath":"guide/important-concepts/coercable.md","lastUpdated":1658112388000}'),o={name:"guide/important-concepts/coercable.md"},t=a(`
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.b3e4b201.lean.js b/assets/guide_important-concepts_coercable.md.2f60c9ea.lean.js
similarity index 89%
rename from assets/guide_important-concepts_coercable.md.b3e4b201.lean.js
rename to assets/guide_important-concepts_coercable.md.2f60c9ea.lean.js
index f83837d5..c4562b22 100644
--- a/assets/guide_important-concepts_coercable.md.b3e4b201.lean.js
+++ b/assets/guide_important-concepts_coercable.md.2f60c9ea.lean.js
@@ -1 +1 @@
-import{_ as e,c as s,o as n,a}from"./app.ab34650d.js";const m=JSON.parse('{"title":"Coercable Components","description":"","frontmatter":{},"headers":[{"level":2,"title":"Template Strings","slug":"template-strings"},{"level":2,"title":"Render Functions (JSX)","slug":"render-functions-jsx"},{"level":2,"title":"Components","slug":"components"}],"relativePath":"guide/important-concepts/coercable.md","lastUpdated":1658112303000}'),o={name:"guide/important-concepts/coercable.md"},t=a("",13),l=[t];function p(r,c,i,d,u,y){return n(),s("div",null,l)}var F=e(o,[["render",p]]);export{m as __pageData,F as default};
+import{_ as e,c as s,o as n,a}from"./app.ab34650d.js";const m=JSON.parse('{"title":"Coercable Components","description":"","frontmatter":{},"headers":[{"level":2,"title":"Template Strings","slug":"template-strings"},{"level":2,"title":"Render Functions (JSX)","slug":"render-functions-jsx"},{"level":2,"title":"Components","slug":"components"}],"relativePath":"guide/important-concepts/coercable.md","lastUpdated":1658112388000}'),o={name:"guide/important-concepts/coercable.md"},t=a("",13),l=[t];function p(r,c,i,d,u,y){return n(),s("div",null,l)}var F=e(o,[["render",p]]);export{m as __pageData,F as default};
diff --git a/assets/guide_important-concepts_features.md.97d5dfdd.js b/assets/guide_important-concepts_features.md.484070c2.js
similarity index 99%
rename from assets/guide_important-concepts_features.md.97d5dfdd.js
rename to assets/guide_important-concepts_features.md.484070c2.js
index bbd62fa4..88b0b2f2 100644
--- a/assets/guide_important-concepts_features.md.97d5dfdd.js
+++ b/assets/guide_important-concepts_features.md.484070c2.js
@@ -1,4 +1,4 @@
-import{_ as e,c as s,o as a,a as n}from"./app.ab34650d.js";const D=JSON.parse('{"title":"Features","description":"","frontmatter":{},"headers":[{"level":2,"title":"Tree Shaking","slug":"tree-shaking"}],"relativePath":"guide/important-concepts/features.md","lastUpdated":1658112303000}'),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:
const addGainUpgrade =createUpgrade(()=> ({
+import{_ as e,c as s,o as a,a as n}from"./app.ab34650d.js";const D=JSON.parse('{"title":"Features","description":"","frontmatter":{},"headers":[{"level":2,"title":"Tree Shaking","slug":"tree-shaking"}],"relativePath":"guide/important-concepts/features.md","lastUpdated":1658112388000}'),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:
const addGainUpgrade =createUpgrade(()=> ({display:{title:"Generator of Genericness",description:"Gain 1 point every second"
diff --git a/assets/guide_important-concepts_features.md.97d5dfdd.lean.js b/assets/guide_important-concepts_features.md.484070c2.lean.js
similarity index 86%
rename from assets/guide_important-concepts_features.md.97d5dfdd.lean.js
rename to assets/guide_important-concepts_features.md.484070c2.lean.js
index 61a63cc4..0bdd5d4d 100644
--- a/assets/guide_important-concepts_features.md.97d5dfdd.lean.js
+++ b/assets/guide_important-concepts_features.md.484070c2.lean.js
@@ -1 +1 @@
-import{_ as e,c as s,o as a,a as n}from"./app.ab34650d.js";const D=JSON.parse('{"title":"Features","description":"","frontmatter":{},"headers":[{"level":2,"title":"Tree Shaking","slug":"tree-shaking"}],"relativePath":"guide/important-concepts/features.md","lastUpdated":1658112303000}'),t={name:"guide/important-concepts/features.md"},o=n("",12),l=[o];function p(r,c,i,y,d,h){return a(),s("div",null,l)}var A=e(t,[["render",p]]);export{D as __pageData,A as default};
+import{_ as e,c as s,o as a,a as n}from"./app.ab34650d.js";const D=JSON.parse('{"title":"Features","description":"","frontmatter":{},"headers":[{"level":2,"title":"Tree Shaking","slug":"tree-shaking"}],"relativePath":"guide/important-concepts/features.md","lastUpdated":1658112388000}'),t={name:"guide/important-concepts/features.md"},o=n("",12),l=[o];function p(r,c,i,y,d,h){return a(),s("div",null,l)}var A=e(t,[["render",p]]);export{D as __pageData,A as default};
diff --git a/assets/guide_important-concepts_layers.md.4c778709.js b/assets/guide_important-concepts_layers.md.7de77bbd.js
similarity index 96%
rename from assets/guide_important-concepts_layers.md.4c778709.js
rename to assets/guide_important-concepts_layers.md.7de77bbd.js
index ab725385..96384950 100644
--- a/assets/guide_important-concepts_layers.md.4c778709.js
+++ b/assets/guide_important-concepts_layers.md.7de77bbd.js
@@ -1 +1 @@
-import{_ as e,c as a,o as t,a as r}from"./app.ab34650d.js";const f=JSON.parse('{"title":"Layers","description":"","frontmatter":{},"headers":[{"level":2,"title":"Lazy Proxies","slug":"lazy-proxies"}],"relativePath":"guide/important-concepts/layers.md","lastUpdated":1658112303000}'),s={name:"guide/important-concepts/layers.md"},i=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),o=[i];function n(c,l,d,h,y,p){return t(),a("div",null,o)}var _=e(s,[["render",n]]);export{f as __pageData,_ as default};
+import{_ as e,c as a,o as t,a as r}from"./app.ab34650d.js";const f=JSON.parse('{"title":"Layers","description":"","frontmatter":{},"headers":[{"level":2,"title":"Lazy Proxies","slug":"lazy-proxies"}],"relativePath":"guide/important-concepts/layers.md","lastUpdated":1658112388000}'),s={name:"guide/important-concepts/layers.md"},i=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),o=[i];function n(c,l,d,h,y,p){return t(),a("div",null,o)}var _=e(s,[["render",n]]);export{f as __pageData,_ as default};
diff --git a/assets/guide_important-concepts_layers.md.4c778709.lean.js b/assets/guide_important-concepts_layers.md.7de77bbd.lean.js
similarity index 86%
rename from assets/guide_important-concepts_layers.md.4c778709.lean.js
rename to assets/guide_important-concepts_layers.md.7de77bbd.lean.js
index 875bfabd..c978bc6e 100644
--- a/assets/guide_important-concepts_layers.md.4c778709.lean.js
+++ b/assets/guide_important-concepts_layers.md.7de77bbd.lean.js
@@ -1 +1 @@
-import{_ as e,c as a,o as t,a as r}from"./app.ab34650d.js";const f=JSON.parse('{"title":"Layers","description":"","frontmatter":{},"headers":[{"level":2,"title":"Lazy Proxies","slug":"lazy-proxies"}],"relativePath":"guide/important-concepts/layers.md","lastUpdated":1658112303000}'),s={name:"guide/important-concepts/layers.md"},i=r("",6),o=[i];function n(c,l,d,h,y,p){return t(),a("div",null,o)}var _=e(s,[["render",n]]);export{f as __pageData,_ as default};
+import{_ as e,c as a,o as t,a as r}from"./app.ab34650d.js";const f=JSON.parse('{"title":"Layers","description":"","frontmatter":{},"headers":[{"level":2,"title":"Lazy Proxies","slug":"lazy-proxies"}],"relativePath":"guide/important-concepts/layers.md","lastUpdated":1658112388000}'),s={name:"guide/important-concepts/layers.md"},i=r("",6),o=[i];function n(c,l,d,h,y,p){return t(),a("div",null,o)}var _=e(s,[["render",n]]);export{f as __pageData,_ as default};
diff --git a/assets/guide_important-concepts_persistence.md.b6543d35.js b/assets/guide_important-concepts_persistence.md.acc0f0ee.js
similarity index 97%
rename from assets/guide_important-concepts_persistence.md.b6543d35.js
rename to assets/guide_important-concepts_persistence.md.acc0f0ee.js
index 3e91a536..ef425d7a 100644
--- a/assets/guide_important-concepts_persistence.md.b6543d35.js
+++ b/assets/guide_important-concepts_persistence.md.acc0f0ee.js
@@ -1 +1 @@
-import{_ as e,c as t,o as a,a as s}from"./app.ab34650d.js";const m=JSON.parse('{"title":"Persistence","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/persistence.md","lastUpdated":1658112303000}'),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,p,u){return a(),t("div",null,i)}var y=e(r,[["render",o]]);export{m as __pageData,y as default};
+import{_ as e,c as t,o as a,a as s}from"./app.ab34650d.js";const m=JSON.parse('{"title":"Persistence","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/persistence.md","lastUpdated":1658112388000}'),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,p,u){return a(),t("div",null,i)}var y=e(r,[["render",o]]);export{m as __pageData,y as default};
diff --git a/assets/guide_important-concepts_persistence.md.b6543d35.lean.js b/assets/guide_important-concepts_persistence.md.acc0f0ee.lean.js
similarity index 84%
rename from assets/guide_important-concepts_persistence.md.b6543d35.lean.js
rename to assets/guide_important-concepts_persistence.md.acc0f0ee.lean.js
index 696d27c5..d7ea85ad 100644
--- a/assets/guide_important-concepts_persistence.md.b6543d35.lean.js
+++ b/assets/guide_important-concepts_persistence.md.acc0f0ee.lean.js
@@ -1 +1 @@
-import{_ as e,c as t,o as a,a as s}from"./app.ab34650d.js";const m=JSON.parse('{"title":"Persistence","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/persistence.md","lastUpdated":1658112303000}'),r={name:"guide/important-concepts/persistence.md"},n=s("",6),i=[n];function o(c,l,h,d,p,u){return a(),t("div",null,i)}var y=e(r,[["render",o]]);export{m as __pageData,y as default};
+import{_ as e,c as t,o as a,a as s}from"./app.ab34650d.js";const m=JSON.parse('{"title":"Persistence","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/persistence.md","lastUpdated":1658112388000}'),r={name:"guide/important-concepts/persistence.md"},n=s("",6),i=[n];function o(c,l,h,d,p,u){return a(),t("div",null,i)}var y=e(r,[["render",o]]);export{m as __pageData,y as default};
diff --git a/assets/guide_important-concepts_reactivity.md.7dc860e4.js b/assets/guide_important-concepts_reactivity.md.ff5cecab.js
similarity index 97%
rename from assets/guide_important-concepts_reactivity.md.7dc860e4.js
rename to assets/guide_important-concepts_reactivity.md.ff5cecab.js
index b9c59030..5a222875 100644
--- a/assets/guide_important-concepts_reactivity.md.7dc860e4.js
+++ b/assets/guide_important-concepts_reactivity.md.ff5cecab.js
@@ -1 +1 @@
-import{_ as e,c as t,o as a,a as o}from"./app.ab34650d.js";const f=JSON.parse('{"title":"Reactivity","description":"","frontmatter":{},"headers":[{"level":2,"title":"Computable","slug":"computable"}],"relativePath":"guide/important-concepts/reactivity.md","lastUpdated":1658112303000}'),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)}var y=e(r,[["render",n]]);export{f as __pageData,y as default};
+import{_ as e,c as t,o as a,a as o}from"./app.ab34650d.js";const f=JSON.parse('{"title":"Reactivity","description":"","frontmatter":{},"headers":[{"level":2,"title":"Computable","slug":"computable"}],"relativePath":"guide/important-concepts/reactivity.md","lastUpdated":1658112388000}'),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)}var y=e(r,[["render",n]]);export{f as __pageData,y as default};
diff --git a/assets/guide_important-concepts_reactivity.md.7dc860e4.lean.js b/assets/guide_important-concepts_reactivity.md.ff5cecab.lean.js
similarity index 86%
rename from assets/guide_important-concepts_reactivity.md.7dc860e4.lean.js
rename to assets/guide_important-concepts_reactivity.md.ff5cecab.lean.js
index 06a1d64c..d2061131 100644
--- a/assets/guide_important-concepts_reactivity.md.7dc860e4.lean.js
+++ b/assets/guide_important-concepts_reactivity.md.ff5cecab.lean.js
@@ -1 +1 @@
-import{_ as e,c as t,o as a,a as o}from"./app.ab34650d.js";const f=JSON.parse('{"title":"Reactivity","description":"","frontmatter":{},"headers":[{"level":2,"title":"Computable","slug":"computable"}],"relativePath":"guide/important-concepts/reactivity.md","lastUpdated":1658112303000}'),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)}var y=e(r,[["render",n]]);export{f as __pageData,y as default};
+import{_ as e,c as t,o as a,a as o}from"./app.ab34650d.js";const f=JSON.parse('{"title":"Reactivity","description":"","frontmatter":{},"headers":[{"level":2,"title":"Computable","slug":"computable"}],"relativePath":"guide/important-concepts/reactivity.md","lastUpdated":1658112388000}'),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)}var y=e(r,[["render",n]]);export{f as __pageData,y as default};
diff --git a/assets/guide_index.md.62e0b322.js b/assets/guide_index.md.908ddb01.js
similarity index 98%
rename from assets/guide_index.md.62e0b322.js
rename to assets/guide_index.md.908ddb01.js
index 6220ec80..bb307b7b 100644
--- a/assets/guide_index.md.62e0b322.js
+++ b/assets/guide_index.md.908ddb01.js
@@ -1 +1 @@
-import{_ as e,c as o,o as t,a as n}from"./app.ab34650d.js";const m=JSON.parse('{"title":"Introduction","description":"","frontmatter":{"title":"Introduction"},"headers":[{"level":2,"title":"Should you use Profectus?","slug":"should-you-use-profectus"},{"level":3,"title":"Alternative Engines","slug":"alternative-engines"},{"level":2,"title":"Design Philosophies","slug":"design-philosophies"}],"relativePath":"guide/index.md","lastUpdated":1658112303000}'),a={name:"guide/index.md"},r=n('
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,p,d,c){return t(),o("div",null,i)}var f=e(a,[["render",s]]);export{m as __pageData,f as default};
+import{_ as e,c as o,o as t,a as n}from"./app.ab34650d.js";const m=JSON.parse('{"title":"Introduction","description":"","frontmatter":{"title":"Introduction"},"headers":[{"level":2,"title":"Should you use Profectus?","slug":"should-you-use-profectus"},{"level":3,"title":"Alternative Engines","slug":"alternative-engines"},{"level":2,"title":"Design Philosophies","slug":"design-philosophies"}],"relativePath":"guide/index.md","lastUpdated":1658112388000}'),a={name:"guide/index.md"},r=n('
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,p,d,c){return t(),o("div",null,i)}var f=e(a,[["render",s]]);export{m as __pageData,f as default};
diff --git a/assets/guide_index.md.62e0b322.lean.js b/assets/guide_index.md.908ddb01.lean.js
similarity index 89%
rename from assets/guide_index.md.62e0b322.lean.js
rename to assets/guide_index.md.908ddb01.lean.js
index 8d508c5f..2da978b2 100644
--- a/assets/guide_index.md.62e0b322.lean.js
+++ b/assets/guide_index.md.908ddb01.lean.js
@@ -1 +1 @@
-import{_ as e,c as o,o as t,a as n}from"./app.ab34650d.js";const m=JSON.parse('{"title":"Introduction","description":"","frontmatter":{"title":"Introduction"},"headers":[{"level":2,"title":"Should you use Profectus?","slug":"should-you-use-profectus"},{"level":3,"title":"Alternative Engines","slug":"alternative-engines"},{"level":2,"title":"Design Philosophies","slug":"design-philosophies"}],"relativePath":"guide/index.md","lastUpdated":1658112303000}'),a={name:"guide/index.md"},r=n("",14),i=[r];function s(l,u,h,p,d,c){return t(),o("div",null,i)}var f=e(a,[["render",s]]);export{m as __pageData,f as default};
+import{_ as e,c as o,o as t,a as n}from"./app.ab34650d.js";const m=JSON.parse('{"title":"Introduction","description":"","frontmatter":{"title":"Introduction"},"headers":[{"level":2,"title":"Should you use Profectus?","slug":"should-you-use-profectus"},{"level":3,"title":"Alternative Engines","slug":"alternative-engines"},{"level":2,"title":"Design Philosophies","slug":"design-philosophies"}],"relativePath":"guide/index.md","lastUpdated":1658112388000}'),a={name:"guide/index.md"},r=n("",14),i=[r];function s(l,u,h,p,d,c){return t(),o("div",null,i)}var f=e(a,[["render",s]]);export{m as __pageData,f as default};
diff --git a/assets/guide_recipes_save-progress.md.1eb623d4.js b/assets/guide_recipes_save-progress.md.3e6a24a4.js
similarity index 99%
rename from assets/guide_recipes_save-progress.md.1eb623d4.js
rename to assets/guide_recipes_save-progress.md.3e6a24a4.js
index bb0353ec..986058da 100644
--- a/assets/guide_recipes_save-progress.md.1eb623d4.js
+++ b/assets/guide_recipes_save-progress.md.3e6a24a4.js
@@ -1,4 +1,4 @@
-import{_ as s,c as a,o as e,a as n}from"./app.ab34650d.js";var o="/assets/save-progress.2c9d1bae.png";const u=JSON.parse('{"title":"Display Save Progress","description":"","frontmatter":{},"headers":[{"level":2,"title":"Creating the component","slug":"creating-the-component"},{"level":2,"title":"Working with save data","slug":"working-with-save-data"},{"level":2,"title":"Displaying the component","slug":"displaying-the-component"}],"relativePath":"guide/recipes/save-progress.md","lastUpdated":1658112303000}'),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:
const progressDisplay =computeComponent(
+import{_ as s,c as a,o as e,a as n}from"./app.ab34650d.js";var o="/assets/save-progress.2c9d1bae.png";const u=JSON.parse('{"title":"Display Save Progress","description":"","frontmatter":{},"headers":[{"level":2,"title":"Creating the component","slug":"creating-the-component"},{"level":2,"title":"Working with save data","slug":"working-with-save-data"},{"level":2,"title":"Displaying the component","slug":"displaying-the-component"}],"relativePath":"guide/recipes/save-progress.md","lastUpdated":1658112388000}'),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:
const progressDisplay =computeComponent(computed(()=>{if (someCondition) {return"Just started";
diff --git a/assets/guide_recipes_save-progress.md.1eb623d4.lean.js b/assets/guide_recipes_save-progress.md.3e6a24a4.lean.js
similarity index 90%
rename from assets/guide_recipes_save-progress.md.1eb623d4.lean.js
rename to assets/guide_recipes_save-progress.md.3e6a24a4.lean.js
index ee83554e..a779e394 100644
--- a/assets/guide_recipes_save-progress.md.1eb623d4.lean.js
+++ b/assets/guide_recipes_save-progress.md.3e6a24a4.lean.js
@@ -1 +1 @@
-import{_ as s,c as a,o as e,a as n}from"./app.ab34650d.js";var o="/assets/save-progress.2c9d1bae.png";const u=JSON.parse('{"title":"Display Save Progress","description":"","frontmatter":{},"headers":[{"level":2,"title":"Creating the component","slug":"creating-the-component"},{"level":2,"title":"Working with save data","slug":"working-with-save-data"},{"level":2,"title":"Displaying the component","slug":"displaying-the-component"}],"relativePath":"guide/recipes/save-progress.md","lastUpdated":1658112303000}'),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)}var m=s(t,[["render",r]]);export{u as __pageData,m as default};
+import{_ as s,c as a,o as e,a as n}from"./app.ab34650d.js";var o="/assets/save-progress.2c9d1bae.png";const u=JSON.parse('{"title":"Display Save Progress","description":"","frontmatter":{},"headers":[{"level":2,"title":"Creating the component","slug":"creating-the-component"},{"level":2,"title":"Working with save data","slug":"working-with-save-data"},{"level":2,"title":"Displaying the component","slug":"displaying-the-component"}],"relativePath":"guide/recipes/save-progress.md","lastUpdated":1658112388000}'),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)}var m=s(t,[["render",r]]);export{u as __pageData,m as default};
diff --git a/assets/index.md.84447adb.js b/assets/index.md.f650a852.js
similarity index 94%
rename from assets/index.md.84447adb.js
rename to assets/index.md.f650a852.js
index bffcb80b..80988750 100644
--- a/assets/index.md.84447adb.js
+++ b/assets/index.md.f650a852.js
@@ -1 +1 @@
-import{_ as e,c as t,o as i}from"./app.ab34650d.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":1658112303000}'),o={name:"index.md"};function a(r,n,s,l,d,u){return i(),t("div")}var g=e(o,[["render",a]]);export{m as __pageData,g as default};
+import{_ as e,c as t,o as i}from"./app.ab34650d.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":1658112388000}'),o={name:"index.md"};function a(r,n,s,l,d,u){return i(),t("div")}var g=e(o,[["render",a]]);export{m as __pageData,g as default};
diff --git a/assets/index.md.84447adb.lean.js b/assets/index.md.f650a852.lean.js
similarity index 94%
rename from assets/index.md.84447adb.lean.js
rename to assets/index.md.f650a852.lean.js
index bffcb80b..80988750 100644
--- a/assets/index.md.84447adb.lean.js
+++ b/assets/index.md.f650a852.lean.js
@@ -1 +1 @@
-import{_ as e,c as t,o as i}from"./app.ab34650d.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":1658112303000}'),o={name:"index.md"};function a(r,n,s,l,d,u){return i(),t("div")}var g=e(o,[["render",a]]);export{m as __pageData,g as default};
+import{_ as e,c as t,o as i}from"./app.ab34650d.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":1658112388000}'),o={name:"index.md"};function a(r,n,s,l,d,u){return i(),t("div")}var g=e(o,[["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 7f74d891..214434d1 100644
--- a/guide/advanced-concepts/creating-features.html
+++ b/guide/advanced-concepts/creating-features.html
@@ -7,7 +7,7 @@
-
+
@@ -19,8 +19,8 @@
-
Because typescript does not emit JS, if a value is supposed to be a function it is impossible to determine if a given function is 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.
Because typescript does not emit JS, if a value is supposed to be a function it is impossible to determine if a given function is 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.
+
diff --git a/guide/advanced-concepts/dynamic-layers.html b/guide/advanced-concepts/dynamic-layers.html
index 7192fb80..ac47b018 100644
--- a/guide/advanced-concepts/dynamic-layers.html
+++ b/guide/advanced-concepts/dynamic-layers.html
@@ -7,7 +7,7 @@
-
+
@@ -24,8 +24,8 @@
if (!layer) throw"Layer does not exist";returnlayerasDynamicLayer;// you might need an "as unknown" after layer}
-
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 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 user's 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).
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 user's 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).
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.
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.
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.
Profectus requires a node development environment in order to work on a project. If you are comfortable with the command line, it is recommended to use a local development environment.
Create a new project from the Profectus repository via the "Use this template" button. You can then copy the link for the repository to clone it locally.
INFO
Since the repository is a template repository, you can easily create multiple projects from one repository. However, it does make updating an existing project to a newer version of Profectus more difficult.
It's recommended to create a new git branch for development, so you can push your changes without it affecting the live build. This allows you to continue working with smaller commits, and only release new versions when you're actually ready to. The github workflow will automatically rebuild the page whenever you push to the main branch.
The next step is to install Profectus' dependencies. This is as simple as running npm install.
You can now run npm run serve to start a local server that will host your project so you can work on it. As you change files the site will automatically reload them.
Also, you might consider following the steps to update Profectus before you start working, as it'll make updating in the future easier without needing to worry about unrelated histories.
If you're using git, deploying is as easy as pushing your changes to the main branch. In a couple minutes the site will be updated fully automatically. If you'd like to see progress on it, or look at any errors that happened, you can do so from the actions tab on your repository.
Before github knows to actually host the generated site, you'll have to enable github pages in the repo settings. This just means selecting the branch to use - gh-pages. You will only need to perform this step once. This will automatically start another github action to deploy the website.
If you don't already have a preferred IDE, Profectus is currently developed in Visual Studio Code and is known to work well with that IDE in particular.
As an alternative to local development, you may use replit, which will automatically set up your development environment for you and also host your project itself.
However, on the free plan you will be limited by the amount of resources you can use, and the program will need to start up occasionally.
To create a Profectus project on replit, all you have to do is click this button:
You can then start the developing by clicking the "Run" button at the top of the screen. This will also make the project run publicly, so you could consider it as automatically deployed. This does mean you cannot have a separate development environment from your production environment, however.
Glitch is a site similar to replit, with much the same pros and cons. To create a projectus project on glitch, select "New Project", "Import from GitHub", and then type in profectus-engine/Profectus. The new project will be created and should be automatically configured and ready to go.
Profectus requires a node development environment in order to work on a project. If you are comfortable with the command line, it is recommended to use a local development environment.
Create a new project from the Profectus repository via the "Use this template" button. You can then copy the link for the repository to clone it locally.
INFO
Since the repository is a template repository, you can easily create multiple projects from one repository. However, it does make updating an existing project to a newer version of Profectus more difficult. Consider updating Profectusbefore you start developing, to avoid the issue with unrelated histories.
It's recommended to create a new git branch for development, so you can push your changes without it affecting the live build. This allows you to continue working with smaller commits, and only release new versions when you're actually ready to. The github workflow will automatically rebuild the page whenever you push to the main branch.
The next step is to install Profectus' dependencies. This is as simple as running npm install.
You can now run npm run serve to start a local server that will host your project so you can work on it. As you change files the site will automatically reload them.
Also, you might consider following the steps to update Profectus before you start working, as it'll make updating in the future easier without needing to worry about unrelated histories.
If you're using git, deploying is as easy as pushing your changes to the main branch. In a couple minutes the site will be updated fully automatically. If you'd like to see progress on it, or look at any errors that happened, you can do so from the actions tab on your repository.
Before github knows to actually host the generated site, you'll have to enable github pages in the repo settings. This just means selecting the branch to use - gh-pages. You will only need to perform this step once. This will automatically start another github action to deploy the website.
If you don't already have a preferred IDE, Profectus is currently developed in Visual Studio Code and is known to work well with that IDE in particular.
As an alternative to local development, you may use replit, which will automatically set up your development environment for you and also host your project itself.
However, on the free plan you will be limited by the amount of resources you can use, and the program will need to start up occasionally.
To create a Profectus project on replit, all you have to do is click this button:
You can then start the developing by clicking the "Run" button at the top of the screen. This will also make the project run publicly, so you could consider it as automatically deployed. This does mean you cannot have a separate development environment from your production environment, however.
Glitch is a site similar to replit, with much the same pros and cons. To create a projectus project on glitch, select "New Project", "Import from GitHub", and then type in profectus-engine/Profectus. The new project will be created and should be automatically configured and ready to go.
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.
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.
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.
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.
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.
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.