Easy to Use
Everything is written to be as intuitive to use as possible, through consistent design.
From ce0cabf7b298b1aeed032238b1bd88d4ae324631 Mon Sep 17 00:00:00 2001
From: thepaperpilot 404 Profectus is designed to encourage the developer to eventually start designing their own features for use in specific games. Features are designed to work where they require minimal (and typically zero) modifications around the code base - you simply write a single file for the feature, and any vue components it needs, and the act of importing that feature will set everything up. This also means you can share these features with others in entire collections, and any they don't use won't be present in the build output, won't be loaded, and won't affect the project in any way. Every feature has a couple of types. They have the feature themselves, a generic version for convenience, and any constructor typically has an options type and a type that gets "added" to it to create the feature itself. These typically involve replacing some types to denote how various properties change from, for example, Most significantly, the base type should typically always have a The constructor itself should do several things. They should take their options within a function, so that they're not resolved when the object is constructed. It should return a lazy proxy of the feature, which allows features to reference each other and only resolve themselves once every feature is defined. The constructor should create any persistent refs it may require outside of the lazy proxy - it won't have access to the options at this point, so it should make any it potentially may require. Any that turn out not being needed can be deleted. Inside the lazy proxy the constructor should create the options object, assign onto it every property from the base type, call processComputable on every computable type, and setDefault on any property with a default value. Then you should be able to simply return the options object, likely with a type cast, and the constructor will be complete. Because typescript does not emit JS, if a property is supposed to be a function it is impossible to differentiate between a function that is itself the intended value or a function that returns the actual value. For this reason it is not recommended for any feature types to include properties that are Any vue components you write need to do a couple things. Typically they'll need to type any props that come from computed properties appropriately, for which you can use the processedPropType utility - using it will look something like To add a setting to the options menu specific to this feature, you'll need to do three things, all inside the feature's file. First, extend the settings type with the name of the new setting. For example, here's how the challenge feature adds a setting to hide completed challenges: Profectus is designed to encourage the developer to eventually start designing their own features for use in specific games. Features are designed to work where they require minimal (and typically zero) modifications around the code base - you simply write a single file for the feature, and any vue components it needs, and the act of importing that feature will set everything up. This also means you can share these features with others in entire collections, and any they don't use won't be present in the build output, won't be loaded, and won't affect the project in any way. Every feature has a couple of types. They have the feature themselves, a generic version for convenience, and any constructor typically has an options type and a type that gets "added" to it to create the feature itself. These typically involve replacing some types to denote how various properties change from, for example, Most significantly, the base type should typically always have a The constructor itself should do several things. They should take their options within a function, so that they're not resolved when the object is constructed. It should return a lazy proxy of the feature, which allows features to reference each other and only resolve themselves once every feature is defined. The constructor should create any persistent refs it may require outside of the lazy proxy - it won't have access to the options at this point, so it should make any it potentially may require. Any that turn out not being needed can be deleted. Inside the lazy proxy the constructor should create the options object, assign onto it every property from the base type, call processComputable on every computable type, and setDefault on any property with a default value. Then you should be able to simply return the options object, likely with a type cast, and the constructor will be complete. Because typescript does not emit JS, if a property is supposed to be a function it is impossible to differentiate between a function that is itself the intended value or a function that returns the actual value. For this reason it is not recommended for any feature types to include properties that are Any vue components you write need to do a couple things. Typically they'll need to type any props that come from computed properties appropriately, for which you can use the processedPropType utility - using it will look something like To add a setting to the options menu specific to this feature, you'll need to do three things, all inside the feature's file. First, extend the settings type with the name of the new setting. For example, here's how the challenge feature adds a setting to hide completed challenges: You can dynamically add and remove layers using the addLayer and removeLayer functions. It's important to note that removing a layer does not affect the player's save data. You can safely add and remove the same layer without losing any progress. For instances where the structure of a layer changes, such as when adding a new feature, use the reloadLayer function. When procedurally generating layers with similar structures, consider using a utility function like the one below. This function allows you to access a correctly typed reference to a layer with a specified ID easily: You can dynamically add and remove layers using the addLayer and removeLayer functions. It's important to note that removing a layer does not affect the player's save data. You can safely add and remove the same layer without losing any progress. For instances where the structure of a layer changes, such as when adding a new feature, use the reloadLayer function. When procedurally generating layers with similar structures, consider using a utility function like the one below. This function allows you to access a correctly typed reference to a layer with a specified ID easily: Features rendered in the DOM should include a This is useful for features with complex displays, such as particle effects positioned relative to another feature or drawing links between different nodes. To illustrate this, let's look at a complete example of using Features rendered in the DOM should include a This is useful for features with complex displays, such as particle effects positioned relative to another feature or drawing links between different nodes. To illustrate this, let's look at a complete example of using This is a Vue component stored at 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: This is a Vue component stored at 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: 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 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 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: 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 TypeScript file containing the non-static parts of your project, and acts as the entry point for it. It is stored at 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 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: 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 JSON file containing information that describes your project and configures parts of how Profectus should represent it. It is stored at The name of the project, which will appear in the info tab and the header, if enabled. The page title will also be set to this value. A description of the project, which will be used when the project is installed as a Progressive Web Application. 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 author of the project, which will appear in the info tab. The text to display for the discord server to point users to. This will appear when hovering over the discord icon, inside the info tab, the game over screen, as well as the NaN detected screen. By default, this is The Paper Pilot Community, which can act as a catch-all for any Profectus projects without their own servers. If you change the discord server with your own, The Paper Pilot Community will still display underneath the custom server when hovering over the discord icon and within the info tab. Those places will also contain a link to the Modding Tree discord server. The link for the discord server to point users to. See discordName for more details. 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 tabs (besides the first) to display a "back" button to close them (and any other tabs to the right of them). 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 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 A path to an image file to display as the logo of the app within the info tab. If left blank no logo will be shown. 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. The max amount of time that can be stored as offline time, in hours. 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 The encoding to use when exporting to the clipboard. Plain-text is fast to generate but is easiest for the player to manipulate and cheat with. Base 64 is slightly slower and the string will be longer but will offer a small barrier to people trying to cheat. LZ-String is the slowest method, but produces the smallest strings and still offers a small barrier to those trying to cheat. Some sharing platforms like pastebin may automatically delete base64 encoded text, and some sites might not support all the characters used in lz-string exports. This is a JSON file containing information that describes your project and configures parts of how Profectus should represent it. It is stored at The name of the project, which will appear in the info tab and the header, if enabled. The page title will also be set to this value. A description of the project, which will be used when the project is installed as a Progressive Web Application. 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 author of the project, which will appear in the info tab. The text to display for the discord server to point users to. This will appear when hovering over the discord icon, inside the info tab, the game over screen, as well as the NaN detected screen. By default, this is The Paper Pilot Community, which can act as a catch-all for any Profectus projects without their own servers. If you change the discord server with your own, The Paper Pilot Community will still display underneath the custom server when hovering over the discord icon and within the info tab. Those places will also contain a link to the Modding Tree discord server. The link for the discord server to point users to. See discordName for more details. 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 tabs (besides the first) to display a "back" button to close them (and any other tabs to the right of them). 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 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 A path to an image file to display as the logo of the app within the info tab. If left blank no logo will be shown. 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. The max amount of time that can be stored as offline time, in hours. 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 The encoding to use when exporting to the clipboard. Plain-text is fast to generate but is easiest for the player to manipulate and cheat with. Base 64 is slightly slower and the string will be longer but will offer a small barrier to people trying to cheat. LZ-String is the slowest method, but produces the smallest strings and still offers a small barrier to those trying to cheat. Some sharing platforms like pastebin may automatically delete base64 encoded text, and some sites might not support all the characters used in lz-string exports. Themes are objects that change how the project's interface should look. This is done mostly by changing the values of various CSS variables. You can look at the existing themes as a reference for the kind of values these CSS variables expect. They can also set various theme options that change how parts of the screen are laid out, which are described below. They are stored in You can add a theme by adding a property to the 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 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. They are stored in You can add a theme by adding a property to the 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 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. This page is a guide on where to start with creating your very first layer. It is intended for developers who are first learning to use the engine. INFO The template comes with a layer in To add a new layer, first create it via the createLayer function. You typically create a single layer per file, the first being in The This page is a guide on where to start with creating your very first layer. It is intended for developers who are first learning to use the engine. INFO The template comes with a layer in To add a new layer, first create it via the createLayer function. You typically create a single layer per file, the first being in The Profectus requires a Node.js development environment for working on a project. If you are comfortable with the command line, a local development environment is recommended. For local development, you will need the following tools: Create a new project from the Profectus repository by clicking the "Use this template" button. Then, clone the repository locally using the provided link. INFO The template repository allows easy creation of multiple projects from one repository. However, updating an existing project to a newer version of Profectus can be challenging. Consider updating Profectus before starting development to avoid issues with unrelated histories. It's recommended to create a new Git branch for development, allowing you to push changes without affecting the live build. The GitHub workflow will automatically rebuild the page when you push to the Next, install Profectus' dependencies by running Also, follow the steps to update Profectus before starting to make future updates easier without worrying about unrelated histories. Using Git, the repository's workflow action automates deployment. However, you need to grant write permissions for the action in the repository settings. Go to Actions, General, Workflow permissions, and select "Read and write permissions". To deploy, push changes to the main branch. The site will be updated automatically in a few minutes. Check progress or errors from the Actions tab on your repository. Enable GitHub Pages in the repo settings to host the generated site. Select the Upon action completion, your project should be available at If you don't have a preferred IDE, Profectus is developed in Visual Studio Code and is known to work well with it. Recommendations: As an alternative to local development, you may use Replit. Replit sets up your development and hosts your project. On the free plan, you'll face limitations, and the program may need occasional startups. To create a Profectus project on Replit, all you have to do is click this button: Click the "Run" button at the top of the screen to start development. This will also make the project publicly accessible, essentially auto-deploying it. However, this means you cannot separate your development and production environments. Glitch is a site similar to Replit, with many of the same pros and cons. To create a Profectus project on Glitch, select "New Project", "Import from GitHub", and enter Profectus requires a Node.js development environment for working on a project. If you are comfortable with the command line, a local development environment is recommended. For local development, you will need the following tools: Create a new project from the Profectus repository by clicking the "Use this template" button. Then, clone the repository locally using the provided link. INFO The template repository allows easy creation of multiple projects from one repository. However, updating an existing project to a newer version of Profectus can be challenging. Consider updating Profectus before starting development to avoid issues with unrelated histories. It's recommended to create a new Git branch for development, allowing you to push changes without affecting the live build. The GitHub workflow will automatically rebuild the page when you push to the Next, install Profectus' dependencies by running Also, follow the steps to update Profectus before starting to make future updates easier without worrying about unrelated histories. Using Git, the repository's workflow action automates deployment. However, you need to grant write permissions for the action in the repository settings. Go to Actions, General, Workflow permissions, and select "Read and write permissions". To deploy, push changes to the main branch. The site will be updated automatically in a few minutes. Check progress or errors from the Actions tab on your repository. Enable GitHub Pages in the repo settings to host the generated site. Select the Upon action completion, your project should be available at If you don't have a preferred IDE, Profectus is developed in Visual Studio Code and is known to work well with it. Recommendations: As an alternative to local development, you may use Replit. Replit sets up your development and hosts your project. On the free plan, you'll face limitations, and the program may need occasional startups. To create a Profectus project on Replit, all you have to do is click this button: Click the "Run" button at the top of the screen to start development. This will also make the project publicly accessible, essentially auto-deploying it. However, this means you cannot separate your development and production environments. Glitch is a site similar to Replit, with many of the same pros and cons. To create a Profectus project on Glitch, select "New Project", "Import from GitHub", and enter Due to Profectus being a template repository, your projects do not share a git history with Profectus. To update changes, you will need to run the following: Due to Profectus being a template repository, your projects do not share a git history with Profectus. 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 labeled "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. 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 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 Typically a feature will accept 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 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 Typically a feature will accept a 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: 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: Profectus utilizes formulas for various features, such as increasing requirements for repeatables and challenges or determining resource gains in conversions. These formulas often need to be inverted or integrated to enable features like buying multiple levels of a repeatable at once or determining when a conversion will increase resource gains. The Formula class can handle these operations, supporting every function Decimal does, while tracking the operations internally. For example, a cost function like Profectus utilizes formulas for various features, such as increasing requirements for repeatables and challenges or determining resource gains in conversions. These formulas often need to be inverted or integrated to enable features like buying multiple levels of a repeatable at once or determining when a conversion will increase resource gains. The Formula class can handle these operations, supporting every function Decimal does, while tracking the operations internally. For example, a cost function like 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 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 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 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 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 constructor, 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 it's 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 it's 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. As of Profectus 0.6, save data will now report warnings whenever there is redundancy - two locations for the same persistent data, which creates larger saves that can cause issues when loading after updates. To fix redundancies, wrap all but one location for the data in noPersist. 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 constructor, 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 it's 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 it's 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. As of Profectus 0.6, save data will now report warnings whenever there is redundancy - two locations for the same persistent data, which creates larger saves that can cause issues when loading after updates. To fix redundancies, wrap all but one location for the data in noPersist. 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 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 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 Because functions are automatically wrapped in 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 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 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 Because functions are automatically wrapped in The requirements system in Profectus is designed to handle various conditions that must be met before certain actions or features can be accessed by the player. These conditions can include resource amounts, completed challenges, or other milestones within the game. To create a requirement, you can use one of the provided utility functions like createCostRequirement, createVisibilityRequirement, or createBooleanRequirement. These functions return a Several features will have a When implementing requirements, you can use the displayRequirements utility. This utility is designed to give a human-readable string generated by the requirement(s) given, making it easier for players to understand the conditions needed to progress in the game. Typically features that support requirements will already use this utility internally. You may need to "pay" requirements upon meeting their conditions and performing the associated action. This action typically involves spending resources or making other adjustments to the game state. The payRequirements function simplifies this process by handling the payment for one or more requirements, considering the number of levels to pay for. Additionally, custom pay functions like payByDivision and payByReset can be passed into Requirements can have multiple "levels", which are typically used for things like multi-level challenges with scaling requirements. When checking if requirements are met, the requirementsMet function can be used. It accepts a single requirement or an array of requirements. Requirements that are just on/off, such as boolean or visibility requirements, will count as infinite levels when in the same array. This allows you to combine different types of requirements in the same array and use the maxRequirementsMet function to calculate the maximum number of levels that could be acquired with the current requirement states. The requirements system in Profectus is designed to handle various conditions that must be met before certain actions or features can be accessed by the player. These conditions can include resource amounts, completed challenges, or other milestones within the game. To create a requirement, you can use one of the provided utility functions like createCostRequirement, createVisibilityRequirement, or createBooleanRequirement. These functions return a Several features will have a When implementing requirements, you can use the displayRequirements utility. This utility is designed to give a human-readable string generated by the requirement(s) given, making it easier for players to understand the conditions needed to progress in the game. Typically features that support requirements will already use this utility internally. You may need to "pay" requirements upon meeting their conditions and performing the associated action. This action typically involves spending resources or making other adjustments to the game state. The payRequirements function simplifies this process by handling the payment for one or more requirements, considering the number of levels to pay for. Additionally, custom pay functions like payByDivision and payByReset can be passed into Requirements can have multiple "levels", which are typically used for things like multi-level challenges with scaling requirements. When checking if requirements are met, the requirementsMet function can be used. It accepts a single requirement or an array of requirements. Requirements that are just on/off, such as boolean or visibility requirements, will count as infinite levels when in the same array. This allows you to combine different types of requirements in the same array and use the maxRequirementsMet function to calculate the maximum number of levels that could be acquired with the current requirement states. Profectus is a web-based game engine. You can write your content using many built in features, write your own features, and build up complex gameplay quickly and easily. The purpose of creating profectus was to create an easy to use engine that does not create a ceiling for a programmer's personal growth. This engine will grow in complexity with you, empowering you to create increasingly complex designs and mechanics. While this engine is intended to make game development (and web app development) easier, it still requires you to be comfortable with programming in general and javascript in particular. Fortunately, that is a solvable problem. If you've never used Javascript before, learn-js.org is a good resource for learning the important concepts. If you'd like a more thorough lesson on all the ins and outs of web development, javascript.info and MDN's tutorials should have you covered. MDN is also a great resource to use as a reference - for example, if you want to know more about something and you google it, there'll typically be a MDN link that'll explain it thoroughly. Beyond the basics, Profectus uses a modern web development pipeline, using tools like node, typescript, and JSX. While most of it should be relatively easy to pick up through context, if this becomes too complicated you may be interested in The Modding Tree, a predecessor of Profectus that uses plain old javascript. There are many popular game engines out there, such as GameMaker Studio 2, Unity3D, Unreal Engine 4, and Godot, that are all general purpose and also more useful if you're planning on going into a career in game development. These are all more mature and robust game engines compared to Profectus, and are used by actual game development studios. Profectus, on the other hand, is a very opinionated engine that is better at specific types of projects. Where the above engines will require you to design and create your own interfaces, menus, save management systems, etc. Profectus will include those out of the box. Profectus will also output games that run natively in the browser, and will typically be easier for players to play than traditional engines. If you are not interested in programming but still want to get into game development, the above engines also all support "visual programming", which may be more amenable to you. Each engine will have varying levels of support, and of course with varying complexities, but ultimately any of these will help you learn the concepts of programming and game development, and all of them also offer traditional programming for when you think you're ready. While absolute purity is impossible, design decisions have been and will continue to be made using these principles in mind. 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. While absolute purity is impossible, design decisions have been and will continue to be made using these principles in mind. Alongside the standard steps for Updating Profectus, this update contains numerous large or breaking changes. This guide will cover additional steps to follow after updating Profectus. This update introduces a major change in save data collection and storage. The change reduces save data size and fixes issues that can cause persistent values to reset to default values. Unfortunately, developers will need to mark which persistent value uses should be included in the save data and which are merely references. Let's go through an example: Alongside the standard steps for Updating Profectus, this update contains numerous large or breaking changes. This guide will cover additional steps to follow after updating Profectus. This update introduces a major change in save data collection and storage. The change reduces save data size and fixes issues that can cause persistent values to reset to default values. Unfortunately, developers will need to mark which persistent value uses should be included in the save data and which are merely references. Let's go through an example: This is a more comprehensive example based on the Kronos example used in the nodes docs. You will design a particle effect, make it appear on another feature, and ensure it adapts to the game state. First, design the particle effect. Profectus uses pixi-particles, and you can use the online particle effect editor here. However, the editor returns an older format of the particle effect emitter config, so you'll need to convert it like this: This is a more comprehensive example based on the Kronos example used in the nodes docs. You will design a particle effect, make it appear on another feature, and ensure it adapts to the game state. First, design the particle effect. Profectus uses pixi-particles, and you can use the online particle effect editor here. However, the editor returns an older format of the particle effect emitter config, so you'll need to convert it like this: Next, create the particles feature and render it. You'll also want to track the bounding rect of the particle effects. Consider the following step: Prestige mechanics are a common feature in incremental games. They involve resetting a portion of the game's progress in exchange for a new currency that persists between these resets. Many games even have multiple layers of reset mechanics, each resetting everything that came before them. The first step is to create a conversion that will handle calculating the amount of prestige currency generated. In our example, let's assume there's a base layer called Prestige mechanics are a common feature in incremental games. They involve resetting a portion of the game's progress in exchange for a new currency that persists between these resets. Many games even have multiple layers of reset mechanics, each resetting everything that came before them. The first step is to create a conversion that will handle calculating the amount of prestige currency generated. In our example, let's assume there's a base layer called 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 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 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 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 PAGE NOT FOUND
But if you don't change your direction, and if you keep looking, you may end up where you are heading.
Creating Features ​
Creating the Feature ​
Computable<X>
to ProcessedComputable<X>
. You should be able to use any of the existing features as a reference for how these types look and work.type
property pointing to a symbol unique to this feature, so they can be easily differentiated at runtime. If it's a feature that should be renderable, then it'll also need [Component]
and [GatherProps]
properties, which describe the vue component to use and how to get the props for it from this feature, as well as a unique ID for the feature's node. You can use the getUniqueID utility to help.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.Vue Components ​
style: processedPropType<StyleValue>(String, Object, Array)
. You'll also want to make sure to unref
any of these props you use in the template. The template should make sure to include a Node
component with the feature's ID. Also, if there are custom displays this feature may have, you'll want to convert the CoercableComponent
into a Vue component inside the setup function, typically using the computeComponent or computeOptionalComponent utilities.Custom Settings ​
declare module "game/settings" {
+import{_ as s,c as e,o as a,N as n}from"./chunks/framework.0799945b.js";const d=JSON.parse('{"title":"Creating Features","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/creating-features.md","lastUpdated":1681907802000}'),o={name:"guide/advanced-concepts/creating-features.md"},t=n(`
Creating Features ​
Creating the Feature ​
Computable<X>
to ProcessedComputable<X>
. You should be able to use any of the existing features as a reference for how these types look and work.type
property pointing to a symbol unique to this feature, so they can be easily differentiated at runtime. If it's a feature that should be renderable, then it'll also need [Component]
and [GatherProps]
properties, which describe the vue component to use and how to get the props for it from this feature, as well as a unique ID for the feature's node. You can use the getUniqueID utility to help.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.Vue Components ​
style: processedPropType<StyleValue>(String, Object, Array)
. You'll also want to make sure to unref
any of these props you use in the template. The template should make sure to include a Node
component with the feature's ID. Also, if there are custom displays this feature may have, you'll want to convert the CoercableComponent
into a Vue component inside the setup function, typically using the computeComponent or computeOptionalComponent utilities.Custom Settings ​
declare module "game/settings" {
interface Settings {
hideChallenges: boolean;
}
diff --git a/assets/guide_advanced-concepts_creating-features.md.b9dbab89.lean.js b/assets/guide_advanced-concepts_creating-features.md.54fe9fdf.lean.js
similarity index 85%
rename from assets/guide_advanced-concepts_creating-features.md.b9dbab89.lean.js
rename to assets/guide_advanced-concepts_creating-features.md.54fe9fdf.lean.js
index fe42d2f1..fbe22a42 100644
--- a/assets/guide_advanced-concepts_creating-features.md.b9dbab89.lean.js
+++ b/assets/guide_advanced-concepts_creating-features.md.54fe9fdf.lean.js
@@ -1 +1 @@
-import{_ as s,c as e,o as a,N as n}from"./chunks/framework.0799945b.js";const d=JSON.parse('{"title":"Creating Features","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/creating-features.md","lastUpdated":1681880763000}'),o={name:"guide/advanced-concepts/creating-features.md"},t=n("",19),l=[t];function p(r,c,y,i,F,D){return a(),e("div",null,l)}const C=s(o,[["render",p]]);export{d as __pageData,C as default};
+import{_ as s,c as e,o as a,N as n}from"./chunks/framework.0799945b.js";const d=JSON.parse('{"title":"Creating Features","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/creating-features.md","lastUpdated":1681907802000}'),o={name:"guide/advanced-concepts/creating-features.md"},t=n("",19),l=[t];function p(r,c,y,i,F,D){return a(),e("div",null,l)}const C=s(o,[["render",p]]);export{d as __pageData,C as default};
diff --git a/assets/guide_advanced-concepts_dynamic-layers.md.11479b19.js b/assets/guide_advanced-concepts_dynamic-layers.md.4d77f5f3.js
similarity index 98%
rename from assets/guide_advanced-concepts_dynamic-layers.md.11479b19.js
rename to assets/guide_advanced-concepts_dynamic-layers.md.4d77f5f3.js
index ad785c3e..886c46c3 100644
--- a/assets/guide_advanced-concepts_dynamic-layers.md.11479b19.js
+++ b/assets/guide_advanced-concepts_dynamic-layers.md.4d77f5f3.js
@@ -1,4 +1,4 @@
-import{_ as a,c as s,o as e,N as n}from"./chunks/framework.0799945b.js";const D=JSON.parse('{"title":"Dynamic Layers","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/dynamic-layers.md","lastUpdated":1681880763000}'),o={name:"guide/advanced-concepts/dynamic-layers.md"},l=n(`
Dynamic Layers ​
function getDynLayer(id: string): DynamicLayer {
+import{_ as a,c as s,o as e,N as n}from"./chunks/framework.0799945b.js";const D=JSON.parse('{"title":"Dynamic Layers","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/dynamic-layers.md","lastUpdated":1681907802000}'),o={name:"guide/advanced-concepts/dynamic-layers.md"},l=n(`
Dynamic Layers ​
function getDynLayer(id: string): DynamicLayer {
const layer = layers[id];
if (!layer) throw "Layer does not exist";
return layer as DynamicLayer; // you might need an "as unknown" after layer
diff --git a/assets/guide_advanced-concepts_dynamic-layers.md.11479b19.lean.js b/assets/guide_advanced-concepts_dynamic-layers.md.4d77f5f3.lean.js
similarity index 85%
rename from assets/guide_advanced-concepts_dynamic-layers.md.11479b19.lean.js
rename to assets/guide_advanced-concepts_dynamic-layers.md.4d77f5f3.lean.js
index 99c60a3a..8da735d7 100644
--- a/assets/guide_advanced-concepts_dynamic-layers.md.11479b19.lean.js
+++ b/assets/guide_advanced-concepts_dynamic-layers.md.4d77f5f3.lean.js
@@ -1 +1 @@
-import{_ as a,c as s,o as e,N as n}from"./chunks/framework.0799945b.js";const D=JSON.parse('{"title":"Dynamic Layers","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/dynamic-layers.md","lastUpdated":1681880763000}'),o={name:"guide/advanced-concepts/dynamic-layers.md"},l=n("",6),t=[l];function r(p,c,y,i,d,u){return e(),s("div",null,t)}const h=a(o,[["render",r]]);export{D as __pageData,h as default};
+import{_ as a,c as s,o as e,N as n}from"./chunks/framework.0799945b.js";const D=JSON.parse('{"title":"Dynamic Layers","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/dynamic-layers.md","lastUpdated":1681907802000}'),o={name:"guide/advanced-concepts/dynamic-layers.md"},l=n("",6),t=[l];function r(p,c,y,i,d,u){return e(),s("div",null,t)}const h=a(o,[["render",r]]);export{D as __pageData,h as default};
diff --git a/assets/guide_advanced-concepts_nodes.md.7755470e.js b/assets/guide_advanced-concepts_nodes.md.deef0ba7.js
similarity index 99%
rename from assets/guide_advanced-concepts_nodes.md.7755470e.js
rename to assets/guide_advanced-concepts_nodes.md.deef0ba7.js
index 1aa3276f..8e29b887 100644
--- a/assets/guide_advanced-concepts_nodes.md.7755470e.js
+++ b/assets/guide_advanced-concepts_nodes.md.deef0ba7.js
@@ -1,4 +1,4 @@
-import{_ as s,c as n,o as a,N as o}from"./chunks/framework.0799945b.js";const C=JSON.parse('{"title":"Nodes","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/nodes.md","lastUpdated":1681880763000}'),l={name:"guide/advanced-concepts/nodes.md"},e=o(`
Nodes ​
Node
component, which registers itself to the nearest Context
component (usually within the Layer
's component) and tracks the bounding rect (both size and position) of the DOM element. Access the DOM element for a feature via its unique id
property within layer.nodes
, provided it currently exists.layer.nodes
to get a node's bounding rect and then placing a particle effect using it. Here's an example from Kronos:const particlesEmitter = ref(particles.addEmitter(element.particlesConfig));
+import{_ as s,c as n,o as a,N as o}from"./chunks/framework.0799945b.js";const C=JSON.parse('{"title":"Nodes","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/nodes.md","lastUpdated":1681907802000}'),l={name:"guide/advanced-concepts/nodes.md"},e=o(`
Nodes ​
Node
component, which registers itself to the nearest Context
component (usually within the Layer
's component) and tracks the bounding rect (both size and position) of the DOM element. Access the DOM element for a feature via its unique id
property within layer.nodes
, provided it currently exists.layer.nodes
to get a node's bounding rect and then placing a particle effect using it. Here's an example from Kronos:const particlesEmitter = ref(particles.addEmitter(element.particlesConfig));
const updateParticleEffect = async ([shouldEmit, rect, boundingRect]: [
boolean,
DOMRect | undefined,
diff --git a/assets/guide_advanced-concepts_nodes.md.7755470e.lean.js b/assets/guide_advanced-concepts_nodes.md.deef0ba7.lean.js
similarity index 84%
rename from assets/guide_advanced-concepts_nodes.md.7755470e.lean.js
rename to assets/guide_advanced-concepts_nodes.md.deef0ba7.lean.js
index 89b765ee..5fa3570d 100644
--- a/assets/guide_advanced-concepts_nodes.md.7755470e.lean.js
+++ b/assets/guide_advanced-concepts_nodes.md.deef0ba7.lean.js
@@ -1 +1 @@
-import{_ as s,c as n,o as a,N as o}from"./chunks/framework.0799945b.js";const C=JSON.parse('{"title":"Nodes","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/nodes.md","lastUpdated":1681880763000}'),l={name:"guide/advanced-concepts/nodes.md"},e=o("",6),p=[e];function t(c,r,y,D,F,i){return a(),n("div",null,p)}const d=s(l,[["render",t]]);export{C as __pageData,d as default};
+import{_ as s,c as n,o as a,N as o}from"./chunks/framework.0799945b.js";const C=JSON.parse('{"title":"Nodes","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced-concepts/nodes.md","lastUpdated":1681907802000}'),l={name:"guide/advanced-concepts/nodes.md"},e=o("",6),p=[e];function t(c,r,y,D,F,i){return a(),n("div",null,p)}const d=s(l,[["render",t]]);export{C as __pageData,d as default};
diff --git a/assets/guide_creating-your-project_changelog.md.6f39eb0d.js b/assets/guide_creating-your-project_changelog.md.b66e76f5.js
similarity index 98%
rename from assets/guide_creating-your-project_changelog.md.6f39eb0d.js
rename to assets/guide_creating-your-project_changelog.md.b66e76f5.js
index b59bed2d..6ca1a6e4 100644
--- a/assets/guide_creating-your-project_changelog.md.6f39eb0d.js
+++ b/assets/guide_creating-your-project_changelog.md.b66e76f5.js
@@ -1,4 +1,4 @@
-import{_ as s,c as a,o as n,N as e}from"./chunks/framework.0799945b.js";const g=JSON.parse('{"title":"Changelog","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/changelog.md","lastUpdated":1681880763000}'),l={name:"guide/creating-your-project/changelog.md"},o=e(`
Changelog ​
/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.<details open>
+import{_ as s,c as a,o as n,N as e}from"./chunks/framework.0799945b.js";const g=JSON.parse('{"title":"Changelog","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/changelog.md","lastUpdated":1681907802000}'),l={name:"guide/creating-your-project/changelog.md"},o=e(`
Changelog ​
/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.<details open>
<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.6f39eb0d.lean.js b/assets/guide_creating-your-project_changelog.md.b66e76f5.lean.js
similarity index 85%
rename from assets/guide_creating-your-project_changelog.md.6f39eb0d.lean.js
rename to assets/guide_creating-your-project_changelog.md.b66e76f5.lean.js
index 4ffd48fc..37932e13 100644
--- a/assets/guide_creating-your-project_changelog.md.6f39eb0d.lean.js
+++ b/assets/guide_creating-your-project_changelog.md.b66e76f5.lean.js
@@ -1 +1 @@
-import{_ as s,c as a,o as n,N as e}from"./chunks/framework.0799945b.js";const g=JSON.parse('{"title":"Changelog","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/changelog.md","lastUpdated":1681880763000}'),l={name:"guide/creating-your-project/changelog.md"},o=e("",6),t=[o];function p(c,r,D,i,F,y){return n(),a("div",null,t)}const h=s(l,[["render",p]]);export{g as __pageData,h as default};
+import{_ as s,c as a,o as n,N as e}from"./chunks/framework.0799945b.js";const g=JSON.parse('{"title":"Changelog","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/changelog.md","lastUpdated":1681907802000}'),l={name:"guide/creating-your-project/changelog.md"},o=e("",6),t=[o];function p(c,r,D,i,F,y){return n(),a("div",null,t)}const h=s(l,[["render",p]]);export{g as __pageData,h as default};
diff --git a/assets/guide_creating-your-project_project-entry.md.d9f79299.js b/assets/guide_creating-your-project_project-entry.md.0f59c9ba.js
similarity index 98%
rename from assets/guide_creating-your-project_project-entry.md.d9f79299.js
rename to assets/guide_creating-your-project_project-entry.md.0f59c9ba.js
index 70617f03..a41aec75 100644
--- a/assets/guide_creating-your-project_project-entry.md.d9f79299.js
+++ b/assets/guide_creating-your-project_project-entry.md.0f59c9ba.js
@@ -1 +1 @@
-import{_ as e,c as a,o as t,N as o}from"./chunks/framework.0799945b.js";const f=JSON.parse('{"title":"Project Entry","description":"","frontmatter":{},"headers":[],"relativePath":"guide/creating-your-project/project-entry.md","lastUpdated":1681880763000}'),s={name:"guide/creating-your-project/project-entry.md"},r=o('
Project Entry ​
/src/data/projEntry.jsx
.common.tsx
, which exists next to projEntry.tsx
.Required Exports ​
getInitialLayers ​
(player: Partial<PlayerData>) => GenericLayer[]
hasWon ​
ComputedRef<boolean>
export const hasWon = computed(() => Decimal.gte(resource.value, 10));
fixOldSave ​
(oldVersion: string | undefined, player: Partial<PlayerData>) => void
Project Entry ​
/src/data/projEntry.jsx
.common.tsx
, which exists next to projEntry.tsx
.Required Exports ​
getInitialLayers ​
(player: Partial<PlayerData>) => GenericLayer[]
hasWon ​
ComputedRef<boolean>
export const hasWon = computed(() => Decimal.gte(resource.value, 10));
fixOldSave ​
(oldVersion: string | undefined, player: Partial<PlayerData>) => void
Project Info ​
/src/data/projInfo.json
.Basic Config ​
title ​
string
Profectus
description ​
string
A project made in Profectus
id ​
string
""
author ​
string
""
discordName ​
string
The Paper Pilot Community
discordLink ​
string
https://discord.gg/WzejVAx
Version Config ​
versionNumber ​
string
0.0
versionTitle ​
string
Initial Commit
Display Config ​
allowGoBack ​
boolean
true
defaultShowSmall ​
boolean
false
defaultDecimalsShown ​
number
2
format
. Individual format
calls can override this value, and resources can be configured with a custom precision as well.useHeader ​
boolean
true
banner ​
string | null
null
useHeader
is true.logo ​
string
""
initialTabs ​
string[]
["main"]
Advanced Config ​
maxTickLength ​
number
3600
offlineLimit ​
number
1
enablePausing ​
boolean
true
player.devSpeed = 0
in console (or = 1
to resume).exportEncoding ​
base64 | lz | plain
base64
Project Info ​
/src/data/projInfo.json
.Basic Config ​
title ​
string
Profectus
description ​
string
A project made in Profectus
id ​
string
""
author ​
string
""
discordName ​
string
The Paper Pilot Community
discordLink ​
string
https://discord.gg/WzejVAx
Version Config ​
versionNumber ​
string
0.0
versionTitle ​
string
Initial Commit
Display Config ​
allowGoBack ​
boolean
true
defaultShowSmall ​
boolean
false
defaultDecimalsShown ​
number
2
format
. Individual format
calls can override this value, and resources can be configured with a custom precision as well.useHeader ​
boolean
true
banner ​
string | null
null
useHeader
is true.logo ​
string
""
initialTabs ​
string[]
["main"]
Advanced Config ​
maxTickLength ​
number
3600
offlineLimit ​
number
1
enablePausing ​
boolean
true
player.devSpeed = 0
in console (or = 1
to resume).exportEncoding ​
base64 | lz | plain
base64
Themes ​
/src/data/themes.ts
.Modifying Themes ​
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./src/game/settings.ts
file. Keep in mind you'll also want to change it in the hardResetSettings
function in the same file.Theme Options ​
floatingTabs ​
boolean
mergeAdjacent ​
boolean
Themes ​
/src/data/themes.ts
.Modifying Themes ​
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./src/game/settings.ts
file. Keep in mind you'll also want to change it in the hardResetSettings
function in the same file.Theme Options ​
floatingTabs ​
boolean
mergeAdjacent ​
boolean
Your First Layer ​
Creating the layer ​
projEntry.tsx
and another in prestige.tsx
. You can use those as a base instead of creating a new one from scratch.projEntry
. After it is created, you'll need to add it to the returned array in getInitialLayers.createLayer
function will need a unique ID for your layer and a function that constructs the layer. At a minimum, it needs a display
property so Profectus knows what to render. It will look something like this:const id = "p";
+import{_ as s,c as a,o as n,N as e}from"./chunks/framework.0799945b.js";const d=JSON.parse('{"title":"Your First Layer","description":"","frontmatter":{},"headers":[],"relativePath":"guide/getting-started/first-layer.md","lastUpdated":1681907802000}'),o={name:"guide/getting-started/first-layer.md"},l=e(`
Your First Layer ​
Creating the layer ​
projEntry.tsx
and another in prestige.tsx
. You can use those as a base instead of creating a new one from scratch.projEntry
. After it is created, you'll need to add it to the returned array in getInitialLayers.createLayer
function will need a unique ID for your layer and a function that constructs the layer. At a minimum, it needs a display
property so Profectus knows what to render. It will look something like this:const id = "p";
const layer = createLayer(id, function (this: BaseLayer) {
return {
display: jsx(() => <>My layer</>)
diff --git a/assets/guide_getting-started_first-layer.md.fd59573f.lean.js b/assets/guide_getting-started_first-layer.md.ca21e81f.lean.js
similarity index 85%
rename from assets/guide_getting-started_first-layer.md.fd59573f.lean.js
rename to assets/guide_getting-started_first-layer.md.ca21e81f.lean.js
index 43489a46..1701bc26 100644
--- a/assets/guide_getting-started_first-layer.md.fd59573f.lean.js
+++ b/assets/guide_getting-started_first-layer.md.ca21e81f.lean.js
@@ -1 +1 @@
-import{_ as s,c as a,o as n,N as e}from"./chunks/framework.0799945b.js";const d=JSON.parse('{"title":"Your First Layer","description":"","frontmatter":{},"headers":[],"relativePath":"guide/getting-started/first-layer.md","lastUpdated":1681880763000}'),o={name:"guide/getting-started/first-layer.md"},l=e("",32),p=[l];function t(r,c,i,y,D,F){return n(),a("div",null,p)}const C=s(o,[["render",t]]);export{d as __pageData,C as default};
+import{_ as s,c as a,o as n,N as e}from"./chunks/framework.0799945b.js";const d=JSON.parse('{"title":"Your First Layer","description":"","frontmatter":{},"headers":[],"relativePath":"guide/getting-started/first-layer.md","lastUpdated":1681907802000}'),o={name:"guide/getting-started/first-layer.md"},l=e("",32),p=[l];function t(r,c,i,y,D,F){return n(),a("div",null,p)}const C=s(o,[["render",t]]);export{d as __pageData,C as default};
diff --git a/assets/guide_getting-started_setup.md.114644a7.js b/assets/guide_getting-started_setup.md.a492a872.js
similarity index 99%
rename from assets/guide_getting-started_setup.md.114644a7.js
rename to assets/guide_getting-started_setup.md.a492a872.js
index a083cfb9..2b3407aa 100644
--- a/assets/guide_getting-started_setup.md.114644a7.js
+++ b/assets/guide_getting-started_setup.md.a492a872.js
@@ -1 +1 @@
-import{_ as e,c as t,o,N as a}from"./chunks/framework.0799945b.js";const r="/assets/workflow-perms.afbb596c.png",i="/assets/actionsbutton.f1ba9d8e.png",n="/assets/gh-pages.a24cefcf.png",y=JSON.parse('{"title":"Setting Up","description":"","frontmatter":{},"headers":[],"relativePath":"guide/getting-started/setup.md","lastUpdated":1681880763000}'),s={name:"guide/getting-started/setup.md"},l=a('
Setting Up ​
Local Development ​
main
branch.npm install
. Run npm run serve
to start a local server hosting your project. The site will automatically reload as you modify files.Deploying ​
gh-pages
branch. Perform this step once. This will automatically start another GitHub action to deploy the website.https://<YOUR_GITHUB_USERNAME>.github.io/<YOUR_REPO_NAME>/
. For example, the TMT Demo project hosted at https://github.com/profectus-engine/TMT-Demo is available at https://profectus-engine.github.io/TMT-Demo/.Visual Studio Code Setup ​
.value
autocomplete by running the Preferences: Open Settings
command and setting volar.autoCompleteRefs
to false
emmet.showExpandedAbbreviation
to "never"
, also in the preferencesReplit ​
Glitch ​
profectus-engine/Profectus
. The new project will be automatically configured and ready to go.Setting Up ​
Local Development ​
main
branch.npm install
. Run npm run serve
to start a local server hosting your project. The site will automatically reload as you modify files.Deploying ​
gh-pages
branch. Perform this step once. This will automatically start another GitHub action to deploy the website.https://<YOUR_GITHUB_USERNAME>.github.io/<YOUR_REPO_NAME>/
. For example, the TMT Demo project hosted at https://github.com/profectus-engine/TMT-Demo is available at https://profectus-engine.github.io/TMT-Demo/.Visual Studio Code Setup ​
.value
autocomplete by running the Preferences: Open Settings
command and setting volar.autoCompleteRefs
to false
emmet.showExpandedAbbreviation
to "never"
, also in the preferencesReplit ​
Glitch ​
profectus-engine/Profectus
. The new project will be automatically configured and ready to go.Updating Profectus ​
Github ​
git remote add template https://github.com/profectus-engine/Profectus
+import{_ as e,c as t,o as a,N as o}from"./chunks/framework.0799945b.js";const g=JSON.parse('{"title":"Updating Profectus","description":"","frontmatter":{},"headers":[],"relativePath":"guide/getting-started/updating.md","lastUpdated":1681907802000}'),s={name:"guide/getting-started/updating.md"},n=o(`
Updating Profectus ​
Github ​
git remote add template https://github.com/profectus-engine/Profectus
git fetch --all
git merge template/main --allow-unrelated-histories
Replit ​
Glitch ​
Coercable Components ​
Template Strings ​
<span>
element, although certain features may wrap things in div or header elements instead, as appropriate.Render Functions (JSX) ​
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.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.Example ​
{
+import{_ as e,c as a,o as s,N as n}from"./chunks/framework.0799945b.js";const h=JSON.parse('{"title":"Coercable Components","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/coercable.md","lastUpdated":1681907802000}'),o={name:"guide/important-concepts/coercable.md"},t=n(`
Coercable Components ​
Template Strings ​
<span>
element, although certain features may wrap things in div or header elements instead, as appropriate.Render Functions (JSX) ​
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.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.Example ​
{
display: jsx(() => (
<>
<MainDisplay resource={points} color={color} />
diff --git a/assets/guide_important-concepts_coercable.md.7f79c379.lean.js b/assets/guide_important-concepts_coercable.md.55c46b49.lean.js
similarity index 85%
rename from assets/guide_important-concepts_coercable.md.7f79c379.lean.js
rename to assets/guide_important-concepts_coercable.md.55c46b49.lean.js
index d8c2dcab..6da95278 100644
--- a/assets/guide_important-concepts_coercable.md.7f79c379.lean.js
+++ b/assets/guide_important-concepts_coercable.md.55c46b49.lean.js
@@ -1 +1 @@
-import{_ as e,c as a,o as s,N as n}from"./chunks/framework.0799945b.js";const h=JSON.parse('{"title":"Coercable Components","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/coercable.md","lastUpdated":1681880763000}'),o={name:"guide/important-concepts/coercable.md"},t=n("",13),l=[t];function p(r,c,i,d,u,y){return s(),a("div",null,l)}const F=e(o,[["render",p]]);export{h as __pageData,F as default};
+import{_ as e,c as a,o as s,N as n}from"./chunks/framework.0799945b.js";const h=JSON.parse('{"title":"Coercable Components","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/coercable.md","lastUpdated":1681907802000}'),o={name:"guide/important-concepts/coercable.md"},t=n("",13),l=[t];function p(r,c,i,d,u,y){return s(),a("div",null,l)}const F=e(o,[["render",p]]);export{h as __pageData,F as default};
diff --git a/assets/guide_important-concepts_features.md.96e4ae56.js b/assets/guide_important-concepts_features.md.9906a856.js
similarity index 99%
rename from assets/guide_important-concepts_features.md.96e4ae56.js
rename to assets/guide_important-concepts_features.md.9906a856.js
index aead1d4a..d030a796 100644
--- a/assets/guide_important-concepts_features.md.96e4ae56.js
+++ b/assets/guide_important-concepts_features.md.9906a856.js
@@ -1,4 +1,4 @@
-import{_ as e,c as s,o as a,N as n}from"./chunks/framework.0799945b.js";const D=JSON.parse('{"title":"Features","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/features.md","lastUpdated":1681880763000}'),t={name:"guide/important-concepts/features.md"},o=n(`
Features ​
const addGainUpgrade = createUpgrade(() => ({
+import{_ as e,c as s,o as a,N as n}from"./chunks/framework.0799945b.js";const D=JSON.parse('{"title":"Features","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/features.md","lastUpdated":1681907802000}'),t={name:"guide/important-concepts/features.md"},o=n(`
Features ​
const addGainUpgrade = createUpgrade(() => ({
display: {
title: "Generator of Genericness",
description: "Gain 1 point every second"
diff --git a/assets/guide_important-concepts_features.md.96e4ae56.lean.js b/assets/guide_important-concepts_features.md.9906a856.lean.js
similarity index 84%
rename from assets/guide_important-concepts_features.md.96e4ae56.lean.js
rename to assets/guide_important-concepts_features.md.9906a856.lean.js
index 8f34c03a..a7bd2573 100644
--- a/assets/guide_important-concepts_features.md.96e4ae56.lean.js
+++ b/assets/guide_important-concepts_features.md.9906a856.lean.js
@@ -1 +1 @@
-import{_ as e,c as s,o as a,N as n}from"./chunks/framework.0799945b.js";const D=JSON.parse('{"title":"Features","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/features.md","lastUpdated":1681880763000}'),t={name:"guide/important-concepts/features.md"},o=n("",12),l=[o];function p(r,c,i,y,d,u){return a(),s("div",null,l)}const C=e(t,[["render",p]]);export{D as __pageData,C as default};
+import{_ as e,c as s,o as a,N as n}from"./chunks/framework.0799945b.js";const D=JSON.parse('{"title":"Features","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/features.md","lastUpdated":1681907802000}'),t={name:"guide/important-concepts/features.md"},o=n("",12),l=[o];function p(r,c,i,y,d,u){return a(),s("div",null,l)}const C=e(t,[["render",p]]);export{D as __pageData,C as default};
diff --git a/assets/guide_important-concepts_formulas.md.580bb12f.js b/assets/guide_important-concepts_formulas.md.8be18e56.js
similarity index 99%
rename from assets/guide_important-concepts_formulas.md.580bb12f.js
rename to assets/guide_important-concepts_formulas.md.8be18e56.js
index 9f60adae..3331ee83 100644
--- a/assets/guide_important-concepts_formulas.md.580bb12f.js
+++ b/assets/guide_important-concepts_formulas.md.8be18e56.js
@@ -1,4 +1,4 @@
-import{_ as e,c as a,o as s,N as o}from"./chunks/framework.0799945b.js";const f=JSON.parse('{"title":"Formulas","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/formulas.md","lastUpdated":1681880763000}'),n={name:"guide/important-concepts/formulas.md"},t=o(`
Formulas ​
Decimal.pow(this.amount, 1.05).times(100)
can be represented using a Formula: Formula.variable(this.amount).pow(1.05).times(100)
.const myRepeatable = createRepeatable(() => ({
+import{_ as e,c as a,o as s,N as o}from"./chunks/framework.0799945b.js";const f=JSON.parse('{"title":"Formulas","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/formulas.md","lastUpdated":1681907802000}'),n={name:"guide/important-concepts/formulas.md"},t=o(`
Formulas ​
Decimal.pow(this.amount, 1.05).times(100)
can be represented using a Formula: Formula.variable(this.amount).pow(1.05).times(100)
.const myRepeatable = createRepeatable(() => ({
requirements: createCostRequirement(() => ({
resource: points,
cost: Formula.variable(myRepeatable.amount).pow(1.05).times(100)
diff --git a/assets/guide_important-concepts_formulas.md.580bb12f.lean.js b/assets/guide_important-concepts_formulas.md.8be18e56.lean.js
similarity index 84%
rename from assets/guide_important-concepts_formulas.md.580bb12f.lean.js
rename to assets/guide_important-concepts_formulas.md.8be18e56.lean.js
index aa91ab7f..06c33549 100644
--- a/assets/guide_important-concepts_formulas.md.580bb12f.lean.js
+++ b/assets/guide_important-concepts_formulas.md.8be18e56.lean.js
@@ -1 +1 @@
-import{_ as e,c as a,o as s,N as o}from"./chunks/framework.0799945b.js";const f=JSON.parse('{"title":"Formulas","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/formulas.md","lastUpdated":1681880763000}'),n={name:"guide/important-concepts/formulas.md"},t=o("",18),r=[t];function l(i,p,c,u,h,m){return s(),a("div",null,r)}const y=e(n,[["render",l]]);export{f as __pageData,y as default};
+import{_ as e,c as a,o as s,N as o}from"./chunks/framework.0799945b.js";const f=JSON.parse('{"title":"Formulas","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/formulas.md","lastUpdated":1681907802000}'),n={name:"guide/important-concepts/formulas.md"},t=o("",18),r=[t];function l(i,p,c,u,h,m){return s(),a("div",null,r)}const y=e(n,[["render",l]]);export{f as __pageData,y as default};
diff --git a/assets/guide_important-concepts_layers.md.75cf9109.js b/assets/guide_important-concepts_layers.md.af2bb708.js
similarity index 96%
rename from assets/guide_important-concepts_layers.md.75cf9109.js
rename to assets/guide_important-concepts_layers.md.af2bb708.js
index 481bd46d..539eb05c 100644
--- a/assets/guide_important-concepts_layers.md.75cf9109.js
+++ b/assets/guide_important-concepts_layers.md.af2bb708.js
@@ -1 +1 @@
-import{_ as e,c as a,o as t,N as r}from"./chunks/framework.0799945b.js";const f=JSON.parse('{"title":"Layers","description":"","frontmatter":{},"headers":[],"relativePath":"guide/important-concepts/layers.md","lastUpdated":1681880763000}'),o={name:"guide/important-concepts/layers.md"},s=r('
Layers ​
/src/data/layers
.Lazy Proxies ​
computed
, and allows for features to reference each other without worrying about cyclical dependencies.Layers ​
/src/data/layers
.Lazy Proxies ​
computed
, and allows for features to reference each other without worrying about cyclical dependencies.Persistence ​
Persistence ​
Reactivity ​
.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.unref
call.Computable ​
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.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.Reactivity ​
.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.unref
call.Computable ​
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.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.Requirements ​
Creating Requirements ​
Requirement
object with specific properties that define the requirement conditions.Using Requirements ​
requirements
property that takes one or more requirements that must be fulfilled for a certain action to be performed with that feature.createCostRequirement
for more specialized cases.Multi-Level Requirements ​
Requirements ​
Creating Requirements ​
Requirement
object with specific properties that define the requirement conditions.Using Requirements ​
requirements
property that takes one or more requirements that must be fulfilled for a certain action to be performed with that feature.createCostRequirement
for more specialized cases.Multi-Level Requirements ​
Introduction ​
Should you use Profectus? ​
Alternative Engines ​
Design Philosophies ​
',14),i=[r];function s(l,u,h,d,p,c){return a(),t("div",null,i)}const f=e(n,[["render",s]]);export{m as __pageData,f as default};
+import{_ as e,c as t,o as a,N as o}from"./chunks/framework.0799945b.js";const m=JSON.parse('{"title":"Introduction","description":"","frontmatter":{"title":"Introduction"},"headers":[],"relativePath":"guide/index.md","lastUpdated":1681907802000}'),n={name:"guide/index.md"},r=o('Introduction ​
Should you use Profectus? ​
Alternative Engines ​
Design Philosophies ​
',14),i=[r];function s(l,u,h,d,p,c){return a(),t("div",null,i)}const f=e(n,[["render",s]]);export{m as __pageData,f as default};
diff --git a/assets/guide_index.md.999c12b6.lean.js b/assets/guide_index.md.2def12e6.lean.js
similarity index 84%
rename from assets/guide_index.md.999c12b6.lean.js
rename to assets/guide_index.md.2def12e6.lean.js
index bf3d3272..6c447c40 100644
--- a/assets/guide_index.md.999c12b6.lean.js
+++ b/assets/guide_index.md.2def12e6.lean.js
@@ -1 +1 @@
-import{_ as e,c as t,o as a,N as o}from"./chunks/framework.0799945b.js";const m=JSON.parse('{"title":"Introduction","description":"","frontmatter":{"title":"Introduction"},"headers":[],"relativePath":"guide/index.md","lastUpdated":1681880763000}'),n={name:"guide/index.md"},r=o("",14),i=[r];function s(l,u,h,d,p,c){return a(),t("div",null,i)}const f=e(n,[["render",s]]);export{m as __pageData,f as default};
+import{_ as e,c as t,o as a,N as o}from"./chunks/framework.0799945b.js";const m=JSON.parse('{"title":"Introduction","description":"","frontmatter":{"title":"Introduction"},"headers":[],"relativePath":"guide/index.md","lastUpdated":1681907802000}'),n={name:"guide/index.md"},r=o("",14),i=[r];function s(l,u,h,d,p,c){return a(),t("div",null,i)}const f=e(n,[["render",s]]);export{m as __pageData,f as default};
diff --git a/assets/guide_migrations_0-6.md.aefb2bb2.js b/assets/guide_migrations_0-6.md.e4124555.js
similarity index 99%
rename from assets/guide_migrations_0-6.md.aefb2bb2.js
rename to assets/guide_migrations_0-6.md.e4124555.js
index ec7d2cd7..f92d3d7c 100644
--- a/assets/guide_migrations_0-6.md.aefb2bb2.js
+++ b/assets/guide_migrations_0-6.md.e4124555.js
@@ -1,4 +1,4 @@
-import{_ as s,c as e,o as a,N as n}from"./chunks/framework.0799945b.js";const o="/assets/persistence-error.537e237b.png",d=JSON.parse('{"title":"Migrating to Profectus 0.6","description":"","frontmatter":{},"headers":[],"relativePath":"guide/migrations/0-6.md","lastUpdated":1681880763000}'),l={name:"guide/migrations/0-6.md"},t=n(`Migrating to Profectus 0.6 ​
Fixing save data ​
const flowers = createResource<DecimalSource>(0, "moly");
+import{_ as s,c as e,o as a,N as n}from"./chunks/framework.0799945b.js";const o="/assets/persistence-error.537e237b.png",d=JSON.parse('{"title":"Migrating to Profectus 0.6","description":"","frontmatter":{},"headers":[],"relativePath":"guide/migrations/0-6.md","lastUpdated":1681907802000}'),l={name:"guide/migrations/0-6.md"},t=n(`
Migrating to Profectus 0.6 ​
Fixing save data ​
const flowers = createResource<DecimalSource>(0, "moly");
const job = createJob(name, () => ({
/** snip **/
resource: flowers
diff --git a/assets/guide_migrations_0-6.md.aefb2bb2.lean.js b/assets/guide_migrations_0-6.md.e4124555.lean.js
similarity index 86%
rename from assets/guide_migrations_0-6.md.aefb2bb2.lean.js
rename to assets/guide_migrations_0-6.md.e4124555.lean.js
index 1d0b944f..748526a4 100644
--- a/assets/guide_migrations_0-6.md.aefb2bb2.lean.js
+++ b/assets/guide_migrations_0-6.md.e4124555.lean.js
@@ -1 +1 @@
-import{_ as s,c as e,o as a,N as n}from"./chunks/framework.0799945b.js";const o="/assets/persistence-error.537e237b.png",d=JSON.parse('{"title":"Migrating to Profectus 0.6","description":"","frontmatter":{},"headers":[],"relativePath":"guide/migrations/0-6.md","lastUpdated":1681880763000}'),l={name:"guide/migrations/0-6.md"},t=n("",50),p=[t];function r(c,i,y,D,A,C){return a(),e("div",null,p)}const u=s(l,[["render",r]]);export{d as __pageData,u as default};
+import{_ as s,c as e,o as a,N as n}from"./chunks/framework.0799945b.js";const o="/assets/persistence-error.537e237b.png",d=JSON.parse('{"title":"Migrating to Profectus 0.6","description":"","frontmatter":{},"headers":[],"relativePath":"guide/migrations/0-6.md","lastUpdated":1681907802000}'),l={name:"guide/migrations/0-6.md"},t=n("",50),p=[t];function r(c,i,y,D,A,C){return a(),e("div",null,p)}const u=s(l,[["render",r]]);export{d as __pageData,u as default};
diff --git a/assets/guide_recipes_particles.md.9a58c965.js b/assets/guide_recipes_particles.md.018d5591.js
similarity index 99%
rename from assets/guide_recipes_particles.md.9a58c965.js
rename to assets/guide_recipes_particles.md.018d5591.js
index 25b30c4c..0fda9ad1 100644
--- a/assets/guide_recipes_particles.md.9a58c965.js
+++ b/assets/guide_recipes_particles.md.018d5591.js
@@ -1,4 +1,4 @@
-import{_ as s,c as a,o as n,N as l}from"./chunks/framework.0799945b.js";const i=JSON.parse('{"title":"Particles","description":"","frontmatter":{},"headers":[],"relativePath":"guide/recipes/particles.md","lastUpdated":1681880763000}'),p={name:"guide/recipes/particles.md"},o=l(`
Particles ​
Design the Effect ​
import myParticleEffect from "myParticleEffect.json";
+import{_ as s,c as a,o as n,N as l}from"./chunks/framework.0799945b.js";const i=JSON.parse('{"title":"Particles","description":"","frontmatter":{},"headers":[],"relativePath":"guide/recipes/particles.md","lastUpdated":1681907802000}'),p={name:"guide/recipes/particles.md"},o=l(`
Particles ​
Design the Effect ​
import myParticleEffect from "myParticleEffect.json";
import { upgradeConfig } from "@pixi/particle-emitter"
const particleEffect = upgradeConfig(myParticleEffect);
Create the Particles ​
const particles = createParticles(() => ({
diff --git a/assets/guide_recipes_particles.md.9a58c965.lean.js b/assets/guide_recipes_particles.md.018d5591.lean.js
similarity index 84%
rename from assets/guide_recipes_particles.md.9a58c965.lean.js
rename to assets/guide_recipes_particles.md.018d5591.lean.js
index 12e1e3d5..d4468610 100644
--- a/assets/guide_recipes_particles.md.9a58c965.lean.js
+++ b/assets/guide_recipes_particles.md.018d5591.lean.js
@@ -1 +1 @@
-import{_ as s,c as a,o as n,N as l}from"./chunks/framework.0799945b.js";const i=JSON.parse('{"title":"Particles","description":"","frontmatter":{},"headers":[],"relativePath":"guide/recipes/particles.md","lastUpdated":1681880763000}'),p={name:"guide/recipes/particles.md"},o=l("",18),e=[o];function t(c,r,y,F,D,A){return n(),a("div",null,e)}const d=s(p,[["render",t]]);export{i as __pageData,d as default};
+import{_ as s,c as a,o as n,N as l}from"./chunks/framework.0799945b.js";const i=JSON.parse('{"title":"Particles","description":"","frontmatter":{},"headers":[],"relativePath":"guide/recipes/particles.md","lastUpdated":1681907802000}'),p={name:"guide/recipes/particles.md"},o=l("",18),e=[o];function t(c,r,y,F,D,A){return n(),a("div",null,e)}const d=s(p,[["render",t]]);export{i as __pageData,d as default};
diff --git a/assets/guide_recipes_prestige.md.2325dec7.js b/assets/guide_recipes_prestige.md.442d7a30.js
similarity index 99%
rename from assets/guide_recipes_prestige.md.2325dec7.js
rename to assets/guide_recipes_prestige.md.442d7a30.js
index c311814f..28b916c9 100644
--- a/assets/guide_recipes_prestige.md.2325dec7.js
+++ b/assets/guide_recipes_prestige.md.442d7a30.js
@@ -1,4 +1,4 @@
-import{_ as s,c as e,o as a,N as n}from"./chunks/framework.0799945b.js";const F=JSON.parse('{"title":"Prestige Mechanic","description":"","frontmatter":{},"headers":[],"relativePath":"guide/recipes/prestige.md","lastUpdated":1681880763000}'),o={name:"guide/recipes/prestige.md"},t=n(`
Prestige Mechanic ​
Create a conversion ​
main
and a prestige layer called prestige
. This conversion will be based on the amount of points
the player has, a resource within main
. Here's an example conversion, using the formula from The Prestige Tree's prestige layer:const conversion = createCumulativeConversion(() => ({
+import{_ as s,c as e,o as a,N as n}from"./chunks/framework.0799945b.js";const F=JSON.parse('{"title":"Prestige Mechanic","description":"","frontmatter":{},"headers":[],"relativePath":"guide/recipes/prestige.md","lastUpdated":1681907802000}'),o={name:"guide/recipes/prestige.md"},t=n(`
Prestige Mechanic ​
Create a conversion ​
main
and a prestige layer called prestige
. This conversion will be based on the amount of points
the player has, a resource within main
. Here's an example conversion, using the formula from The Prestige Tree's prestige layer:const conversion = createCumulativeConversion(() => ({
baseResource: main.points,
gainResource: noPersist(points),
formula: x => x.div(10).sqrt()
diff --git a/assets/guide_recipes_prestige.md.2325dec7.lean.js b/assets/guide_recipes_prestige.md.442d7a30.lean.js
similarity index 84%
rename from assets/guide_recipes_prestige.md.2325dec7.lean.js
rename to assets/guide_recipes_prestige.md.442d7a30.lean.js
index 29a2f950..23167132 100644
--- a/assets/guide_recipes_prestige.md.2325dec7.lean.js
+++ b/assets/guide_recipes_prestige.md.442d7a30.lean.js
@@ -1 +1 @@
-import{_ as s,c as e,o as a,N as n}from"./chunks/framework.0799945b.js";const F=JSON.parse('{"title":"Prestige Mechanic","description":"","frontmatter":{},"headers":[],"relativePath":"guide/recipes/prestige.md","lastUpdated":1681880763000}'),o={name:"guide/recipes/prestige.md"},t=n("",18),l=[t];function p(r,c,i,y,D,A){return a(),e("div",null,l)}const h=s(o,[["render",p]]);export{F as __pageData,h as default};
+import{_ as s,c as e,o as a,N as n}from"./chunks/framework.0799945b.js";const F=JSON.parse('{"title":"Prestige Mechanic","description":"","frontmatter":{},"headers":[],"relativePath":"guide/recipes/prestige.md","lastUpdated":1681907802000}'),o={name:"guide/recipes/prestige.md"},t=n("",18),l=[t];function p(r,c,i,y,D,A){return a(),e("div",null,l)}const h=s(o,[["render",p]]);export{F as __pageData,h as default};
diff --git a/assets/guide_recipes_save-progress.md.ff3bd908.js b/assets/guide_recipes_save-progress.md.01fc1628.js
similarity index 99%
rename from assets/guide_recipes_save-progress.md.ff3bd908.js
rename to assets/guide_recipes_save-progress.md.01fc1628.js
index 3c14cb14..80463786 100644
--- a/assets/guide_recipes_save-progress.md.ff3bd908.js
+++ b/assets/guide_recipes_save-progress.md.01fc1628.js
@@ -1,4 +1,4 @@
-import{_ as s,c as a,o as e,N as n}from"./chunks/framework.0799945b.js";const o="/assets/save-progress.2c9d1bae.png",u=JSON.parse('{"title":"Display Save Progress","description":"","frontmatter":{},"headers":[],"relativePath":"guide/recipes/save-progress.md","lastUpdated":1681880763000}'),t={name:"guide/recipes/save-progress.md"},l=n('
Display Save Progress ​
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.Creating the component ​
<script>
tag in Save.vue
) similar to this example:const progressDisplay = computeComponent(
+import{_ as s,c as a,o as e,N as n}from"./chunks/framework.0799945b.js";const o="/assets/save-progress.2c9d1bae.png",u=JSON.parse('{"title":"Display Save Progress","description":"","frontmatter":{},"headers":[],"relativePath":"guide/recipes/save-progress.md","lastUpdated":1681907802000}'),t={name:"guide/recipes/save-progress.md"},l=n('
Display Save Progress ​
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.Creating the component ​
<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.ff3bd908.lean.js b/assets/guide_recipes_save-progress.md.01fc1628.lean.js
similarity index 86%
rename from assets/guide_recipes_save-progress.md.ff3bd908.lean.js
rename to assets/guide_recipes_save-progress.md.01fc1628.lean.js
index 2eb37c29..a1bb7e2b 100644
--- a/assets/guide_recipes_save-progress.md.ff3bd908.lean.js
+++ b/assets/guide_recipes_save-progress.md.01fc1628.lean.js
@@ -1 +1 @@
-import{_ as s,c as a,o as e,N as n}from"./chunks/framework.0799945b.js";const o="/assets/save-progress.2c9d1bae.png",u=JSON.parse('{"title":"Display Save Progress","description":"","frontmatter":{},"headers":[],"relativePath":"guide/recipes/save-progress.md","lastUpdated":1681880763000}'),t={name:"guide/recipes/save-progress.md"},l=n("",16),p=[l];function r(c,i,y,D,F,d){return e(),a("div",null,p)}const m=s(t,[["render",r]]);export{u as __pageData,m as default};
+import{_ as s,c as a,o as e,N as n}from"./chunks/framework.0799945b.js";const o="/assets/save-progress.2c9d1bae.png",u=JSON.parse('{"title":"Display Save Progress","description":"","frontmatter":{},"headers":[],"relativePath":"guide/recipes/save-progress.md","lastUpdated":1681907802000}'),t={name:"guide/recipes/save-progress.md"},l=n("",16),p=[l];function r(c,i,y,D,F,d){return e(),a("div",null,p)}const m=s(t,[["render",r]]);export{u as __pageData,m as default};
diff --git a/assets/index.md.c1c0481e.js b/assets/index.md.ea809597.js
similarity index 94%
rename from assets/index.md.c1c0481e.js
rename to assets/index.md.ea809597.js
index 35c31539..ce88c2a3 100644
--- a/assets/index.md.c1c0481e.js
+++ b/assets/index.md.ea809597.js
@@ -1 +1 @@
-import{_ as e,c as t,o}from"./chunks/framework.0799945b.js";const m=JSON.parse('{"title":"Home","description":"","frontmatter":{"layout":"home","title":"Home","hero":{"name":"Profectus","text":"A game engine that grows with you","tagline":"Starts at your skill level and encourages your ambition to make your projects bigger and better.","actions":[{"theme":"brand","text":"Get Started","link":"/guide/getting-started/setup"},{"theme":"alt","text":"Learn More","link":"/guide/"}]},"features":[{"title":"Easy to Use","details":"Everything is written to be as intuitive to use as possible, through consistent design."},{"title":"Helpful","details":"Built with TypeScript to guide you as you write. Seamlessly deploy your project with pre-configured github workflows, and more."},{"title":"Incremental","details":"Designed to actively encourage you to become better at programming. The engine will never limit you."}]},"headers":[],"relativePath":"index.md","lastUpdated":1681880763000}'),i={name:"index.md"};function a(r,n,s,l,d,u){return o(),t("div")}const g=e(i,[["render",a]]);export{m as __pageData,g as default};
+import{_ as e,c as t,o}from"./chunks/framework.0799945b.js";const m=JSON.parse('{"title":"Home","description":"","frontmatter":{"layout":"home","title":"Home","hero":{"name":"Profectus","text":"A game engine that grows with you","tagline":"Starts at your skill level and encourages your ambition to make your projects bigger and better.","actions":[{"theme":"brand","text":"Get Started","link":"/guide/getting-started/setup"},{"theme":"alt","text":"Learn More","link":"/guide/"}]},"features":[{"title":"Easy to Use","details":"Everything is written to be as intuitive to use as possible, through consistent design."},{"title":"Helpful","details":"Built with TypeScript to guide you as you write. Seamlessly deploy your project with pre-configured github workflows, and more."},{"title":"Incremental","details":"Designed to actively encourage you to become better at programming. The engine will never limit you."}]},"headers":[],"relativePath":"index.md","lastUpdated":1681907802000}'),i={name:"index.md"};function a(r,n,s,l,d,u){return o(),t("div")}const g=e(i,[["render",a]]);export{m as __pageData,g as default};
diff --git a/assets/index.md.c1c0481e.lean.js b/assets/index.md.ea809597.lean.js
similarity index 94%
rename from assets/index.md.c1c0481e.lean.js
rename to assets/index.md.ea809597.lean.js
index 35c31539..ce88c2a3 100644
--- a/assets/index.md.c1c0481e.lean.js
+++ b/assets/index.md.ea809597.lean.js
@@ -1 +1 @@
-import{_ as e,c as t,o}from"./chunks/framework.0799945b.js";const m=JSON.parse('{"title":"Home","description":"","frontmatter":{"layout":"home","title":"Home","hero":{"name":"Profectus","text":"A game engine that grows with you","tagline":"Starts at your skill level and encourages your ambition to make your projects bigger and better.","actions":[{"theme":"brand","text":"Get Started","link":"/guide/getting-started/setup"},{"theme":"alt","text":"Learn More","link":"/guide/"}]},"features":[{"title":"Easy to Use","details":"Everything is written to be as intuitive to use as possible, through consistent design."},{"title":"Helpful","details":"Built with TypeScript to guide you as you write. Seamlessly deploy your project with pre-configured github workflows, and more."},{"title":"Incremental","details":"Designed to actively encourage you to become better at programming. The engine will never limit you."}]},"headers":[],"relativePath":"index.md","lastUpdated":1681880763000}'),i={name:"index.md"};function a(r,n,s,l,d,u){return o(),t("div")}const g=e(i,[["render",a]]);export{m as __pageData,g as default};
+import{_ as e,c as t,o}from"./chunks/framework.0799945b.js";const m=JSON.parse('{"title":"Home","description":"","frontmatter":{"layout":"home","title":"Home","hero":{"name":"Profectus","text":"A game engine that grows with you","tagline":"Starts at your skill level and encourages your ambition to make your projects bigger and better.","actions":[{"theme":"brand","text":"Get Started","link":"/guide/getting-started/setup"},{"theme":"alt","text":"Learn More","link":"/guide/"}]},"features":[{"title":"Easy to Use","details":"Everything is written to be as intuitive to use as possible, through consistent design."},{"title":"Helpful","details":"Built with TypeScript to guide you as you write. Seamlessly deploy your project with pre-configured github workflows, and more."},{"title":"Incremental","details":"Designed to actively encourage you to become better at programming. The engine will never limit you."}]},"headers":[],"relativePath":"index.md","lastUpdated":1681907802000}'),i={name:"index.md"};function a(r,n,s,l,d,u){return o(),t("div")}const g=e(i,[["render",a]]);export{m as __pageData,g as default};
diff --git a/guide/advanced-concepts/creating-features.html b/guide/advanced-concepts/creating-features.html
index d7ff80ff..e5420fab 100644
--- a/guide/advanced-concepts/creating-features.html
+++ b/guide/advanced-concepts/creating-features.html
@@ -10,7 +10,7 @@
-
+
@@ -51,8 +51,8 @@
// unsubscribe from postUpdate
listeners[layer.id]?.();
listeners[layer.id] = undefined;
-});
A game engine that grows with you
Starts at your skill level and encourages your ambition to make your projects bigger and better.
Everything is written to be as intuitive to use as possible, through consistent design.
Built with TypeScript to guide you as you write. Seamlessly deploy your project with pre-configured github workflows, and more.
Designed to actively encourage you to become better at programming. The engine will never limit you.