diff --git a/Config/options.json b/Config/options.json
index a9c4f13a..c25b18e5 100644
--- a/Config/options.json
+++ b/Config/options.json
@@ -10,7 +10,7 @@
"proxyPort": 443,
"proxySSL": true,
"routePrefix": null,
- "updateChannel": "testing",
+ "updateChannel": "stable",
"upnp": true,
"upnpLeaseDuration": null,
"awsConfig": null,
diff --git a/Data/assets/akihiro_token.png b/Data/assets/akihiro_token.png
new file mode 100644
index 00000000..b11fb1d5
--- /dev/null
+++ b/Data/assets/akihiro_token.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b252216940acda244284faefb28311745fbee19d9025351d30b0c2eab5df7dd1
+size 94168
diff --git a/Data/assets/aysun_adara.jpg b/Data/assets/aysun_adara.jpg
new file mode 100644
index 00000000..adbe54e6
--- /dev/null
+++ b/Data/assets/aysun_adara.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5f915c018dd9ef37fcd8853b5a3342520422786ce23666c5dc45bf5095a79f25
+size 5083
diff --git a/Data/assets/aysun_adara_token.png b/Data/assets/aysun_adara_token.png
new file mode 100644
index 00000000..9d74436e
--- /dev/null
+++ b/Data/assets/aysun_adara_token.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1052672d87e1fff7889b52abe5b95b99134c5cbe709e6113cbd5915510173a37
+size 78210
diff --git a/Data/modules/pf2e-staves/LICENSE b/Data/modules/pf2e-staves/LICENSE
new file mode 100644
index 00000000..c8341585
--- /dev/null
+++ b/Data/modules/pf2e-staves/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 jessev14
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Data/modules/pf2e-staves/README.md b/Data/modules/pf2e-staves/README.md
new file mode 100644
index 00000000..9c1d86f9
--- /dev/null
+++ b/Data/modules/pf2e-staves/README.md
@@ -0,0 +1,30 @@
+![All Downloads](https://img.shields.io/github/downloads/jessev14/pf2e-staves/total?style=for-the-badge)
+
+![Latest Release Download Count](https://img.shields.io/github/downloads/jessev14/pf2e-staves/latest/module.zip)
+[![Forge Installs](https://img.shields.io/badge/dynamic/json?label=Forge%20Installs&query=package.installs&suffix=%25&url=https%3A%2F%2Fforge-vtt.com%2Fapi%2Fbazaar%2Fpackage%2Fpf2e-staves&colorB=4aa94a)](https://forge-vtt.com/bazaar#package=pf2e-staves)
+
+This module was funded by a commission. Donations help fund updates and new modules!
+
+[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/jessev14)
+
+# PF2e Staves
+
+This FoundryVTT module implements automations for Staves in the PF2e game system.
+
+## Usage
+
+When an item with the "magical" and "staff" traits are added to a character sheet, a corresponding spellcasting entry will be created.
+
+![](/images/pf2e-staves-1.png)
+
+Any spells linked in the text description will be automatically added to the spellcasting entry. This works for custom spells, as long as the spell exists in the item directory.
+
+![](/images/pf2e-staves-2.png)
+
+This spellcasting entry uses charges. The number of charges defaults to the highest level spell slot the character has. Casting spells in this spellcasting entry requires charges equal the level of the spell. If the character has Spontaneous spellcasting, the charge cost can be reduced by right clicking the Cast button. A spell slot of equal or higher level than the spell can be expended to reduce the charge cost to one charge.
+
+![](/images/pf2e-staves-3.png)
+
+When the character is ready to regain charges, the reset button next to the charge value will open a prompt to charge the stave. This will replenish the number of charges back to the default (equal to highest spell slot available). If the character has Prepared spellcasting, the player may choose a spell slot to expend to gain extra charges equal to the level of the spell slot.
+
+![](/images/pf2e-staves-4.png)
diff --git a/Data/modules/pf2e-staves/module.json b/Data/modules/pf2e-staves/module.json
new file mode 100644
index 00000000..5cd8877c
--- /dev/null
+++ b/Data/modules/pf2e-staves/module.json
@@ -0,0 +1,48 @@
+{
+ "id": "pf2e-staves",
+ "title": "PF2e Staves",
+ "description": "",
+ "authors": [
+ {
+ "name": "enso",
+ "discord": "enso#0361"
+ }
+ ],
+ "version": "1.3.1",
+ "compatibility": {
+ "minimum": "10",
+ "verified": "10"
+ },
+ "relationships": {
+ "systems": [
+ {
+ "id": "pf2e",
+ "type": "system",
+ "compatibility": {
+ "minimum": "4.3.0"
+ }
+ }
+ ],
+ "requires": [
+ {
+ "id": "lib-wrapper",
+ "type": "module",
+ "compatibility": {
+ "minimum": "1",
+ "verified": "1.12.7.1"
+ }
+ }
+ ]
+ },
+ "esmodules": [
+ "./scripts/main.js"
+ ],
+ "styles": [
+ "./styles/style.css"
+ ],
+ "languages": [],
+ "flags": {},
+ "url": "https://github.com/jessev14/pf2e-staves",
+ "manifest": "https://github.com/jessev14/pf2e-staves/releases/latest/download/module.json",
+ "download": "https://github.com/jessev14/pf2e-staves/releases/download/1.3.1/module.zip"
+}
\ No newline at end of file
diff --git a/Data/modules/pf2e-staves/scripts/main.js b/Data/modules/pf2e-staves/scripts/main.js
new file mode 100644
index 00000000..dec0c993
--- /dev/null
+++ b/Data/modules/pf2e-staves/scripts/main.js
@@ -0,0 +1,359 @@
+export const moduleID = 'pf2e-staves';
+
+
+export const lg = x => console.log(x);
+
+const mostCommonInList = (arr) => {
+ return arr.sort((a,b) =>
+ arr.filter(v => v===a).length
+ - arr.filter(v => v===b).length
+ ).pop();
+}
+
+
+Hooks.once('init', () => {
+ // Add Charge spell type.
+ CONFIG.PF2E.spellCategories.charge = 'Charge';
+ CONFIG.PF2E.preparationType.charge = 'Charge';
+
+ // Patch spellcastingEntry#cast to use charges instead of spell slots for staves.
+ libWrapper.register(moduleID, 'CONFIG.PF2E.Item.documentClasses.spellcastingEntry.prototype.cast', spellcastingEntry_cast, 'MIXED');
+});
+
+
+// When stave added to a character, also create corresponding spellcasting entry.
+Hooks.on('createItem', async (weapon, options, userID) => {
+ if (!weapon.actor) return;
+ if (userID !== game.user.id) return;
+
+ const traits = weapon.system.traits?.value;
+ const isStave = traits?.includes('magical') && traits?.includes('staff');
+ const isCoda = traits?.includes('coda') && traits?.includes('staff');
+ if (!isStave && !isCoda) return;
+
+ return createStaveSpellcastingEntry(weapon, weapon.actor);
+});
+
+// When stave updated on a character, create spellcasting entry if none found. Update existing entry if found.
+Hooks.on('updateItem', async (weapon, update, options, userID) => {
+ if (!weapon.actor) return;
+ if (userID !== game.user.id) return;
+
+ const traits = weapon.system.traits?.value;
+ const isStave = traits?.includes('magical') && traits?.includes('staff');
+ const isCoda = traits?.includes('coda') && traits?.includes('staff');
+ if (!isStave && !isCoda) return;
+
+ const { actor } = weapon;
+ const existingStaveEntry = actor.spellcasting.find(s => s.flags && s.flags[moduleID]?.staveID === weapon?.id);
+ return createStaveSpellcastingEntry(weapon, actor, existingStaveEntry);
+});
+
+// Delete spellcastingEntry associated with stave.
+Hooks.on('preDeleteItem', (weapon, options, userID) => {
+ const traits = weapon.system.traits?.value;
+ const isStave = traits?.includes('magical') && traits?.includes('staff');
+ const isCoda = traits?.includes('coda') && traits?.includes('staff');
+ if (!isStave && !isCoda) return;
+
+ const { actor } = weapon;
+ const spellcastingEntries = actor.items.filter(i => i.type === 'spellcastingEntry');
+ const spellcastingEntry = spellcastingEntries.find(i => i.getFlag(moduleID, 'staveID') === weapon.id);
+ if (spellcastingEntry) spellcastingEntry.delete();
+});
+
+// Implement charge spellcasting rules on character sheet.
+// Hooks.on('renderCharacterSheetPF2e', (sheet, [html], sheetData) => {
+Hooks.on('renderCreatureSheetPF2e', (sheet, [html], sheetData) => {
+ const actor = sheet.object;
+ const isPC = actor.type === 'character';
+
+ const spellcastingLis = html.querySelectorAll('li.spellcasting-entry');
+ for (const li of spellcastingLis) {
+ const spellcastingEntry = actor.spellcasting.get(li.dataset.containerId);
+ if (spellcastingEntry?.system?.prepared?.value !== 'charge') continue;
+
+ let chargeEl;
+ if (isPC) {
+ chargeEl = document.createElement('section');
+ chargeEl.innerHTML = `
+