Compare commits

...

67 commits
v0.1.0 ... main

Author SHA1 Message Date
2414fde703
Merge pull request #69 from Eldritch-Oliver/content/sprint-start
Add the Sprint Start content
2025-10-12 01:12:05 -06:00
Eldritch-Oliver
de0030a875 Update the workflow to build the compendia and include it in releases 2025-10-09 18:36:16 -06:00
Eldritch-Oliver
be04ab9a26 Add data for the compendia from the SprintStart zine 2025-10-09 02:04:38 -06:00
Eldritch-Oliver
8f206baf46 Add initial pack definitions 2025-10-09 02:03:56 -06:00
Eldritch-Oliver
5b9e808ea9 Fix version identifier 2025-10-08 23:14:18 -06:00
Eldritch-Oliver
d5899aa296 Update system.json 2025-10-08 23:10:58 -06:00
Eldritch-Oliver
3dd3139281 Update the brace format from 1tbs to stroustrup 2025-10-08 23:10:15 -06:00
1c7308e188
Merge pull request #65 from Eldritch-Oliver/bug/alert-cryptic-events-globally
Make the Cryptic Event notifications appear for all active players
2025-10-08 23:08:25 -06:00
Eldritch-Oliver
dad9ab860c Broadcast the notify event when a cryptic event happens 2025-10-08 23:06:40 -06:00
Eldritch-Oliver
e06c500b5c Add a notify websocket event 2025-10-08 23:06:17 -06:00
Eldritch-Oliver
822094077b Make the devMode game unpause be broadcasted 2025-10-08 23:05:57 -06:00
a6047ff009
Merge pull request #64 from Eldritch-Oliver/feature/haste-roll-shortcut
Added a button to roll for hasty turns in the Actor Stats sheet
2025-10-08 17:55:56 -06:00
Eldritch-Oliver
e18b01e425 Apply PR feedback 2025-10-08 17:53:36 -06:00
Eldritch-Oliver
f4969667f4 Update system manifest 2025-10-08 00:17:04 -06:00
Eldritch-Oliver
4eecd15acf Add the required actions from the component parts into the combined sheet 2025-10-08 00:06:19 -06:00
Eldritch-Oliver
6e77bdd949 Add an action button for Haste checks in the Stats card UI 2025-10-08 00:05:09 -06:00
Eldritch-Oliver
59c66c20a1 Add an action for rolling a Haste Check 2025-10-07 22:22:32 -06:00
Eldritch-Oliver
fa0b1078a1 Finish the helper in the public API and broadcast the socket event 2025-10-07 22:22:09 -06:00
Eldritch-Oliver
78d02400d0 Add short circuit to ensure that socket events through the API can't trigger the sands update while disabled 2025-10-07 22:21:33 -06:00
Eldritch-Oliver
77d43f28b4 Add a setting that makes it so player rolls don't auto-update the sands of fate 2025-10-07 22:21:01 -06:00
c23c67280f
Merge pull request #60 from Eldritch-Oliver/feature/embedded-item-creation
Add a way to create items from within the Hero sheet directly
2025-10-07 19:15:56 -06:00
cc917878c3
Merge pull request #59 from Eldritch-Oliver/feature/prompt-equip-on-drop
Add a prompt when you drag and drop an item onto an actor that asks you if you want to equip the item automatically
2025-10-07 18:31:55 -06:00
Eldritch-Oliver
c0a9731b02 Add socket event handling foundations and an updateSands event in anticipation of hasty rolls 2025-10-06 23:25:27 -06:00
Eldritch-Oliver
7c0fb75e0f Get the base functions set up that are required for the roll shortcut 2025-10-05 23:47:52 -06:00
Eldritch-Oliver
92e844341d Extract the Sands changing into a function of the public API for the HUD 2025-10-05 23:46:39 -06:00
Eldritch-Oliver
5c95fec201 Add the socket enabling to the manifest 2025-10-05 23:26:45 -06:00
Eldritch-Oliver
92cb1ed7ff Add a confirmation dialog whenever deleting an item from the sheet context menus (closes #57) 2025-10-05 23:07:12 -06:00
Eldritch-Oliver
898e01f215 Update icon credits 2025-10-05 22:56:51 -06:00
Eldritch-Oliver
7e70a512f7 Added improved shield icons 2025-10-05 19:42:06 -06:00
Eldritch-Oliver
797f473c59 Remove extraneous SVG stuff 2025-10-05 16:30:04 -06:00
Eldritch-Oliver
495e669ba6 Add the buttons for creating new embedded items from the sheet 2025-10-05 16:28:40 -06:00
Eldritch-Oliver
9b239831b8 Add a grow helper class for better flexbox positioning 2025-10-05 16:28:12 -06:00
Eldritch-Oliver
a2b6fd8dfc Pass in the showEquipPrompt flag since we don't want it when making items directly from the sheet 2025-10-05 16:27:50 -06:00
Eldritch-Oliver
9e7ef02f62 Add a plus icon 2025-10-05 16:27:06 -06:00
Eldritch-Oliver
cc61a0c3ac Add a way to skip the equip prompt for API-based creation when it's not desired 2025-10-05 15:48:15 -06:00
Eldritch-Oliver
08278655dc Update the preUpdate handling to not block the entire update, bringing it more inline with Armour/Shield 2025-10-05 15:25:52 -06:00
Eldritch-Oliver
f29ab8bdaa Add a prompt for equipping an item on drag and drop 2025-10-05 15:25:23 -06:00
Eldritch-Oliver
dfc8964296 Add a getter for retrieving equipped weapons easily 2025-10-05 15:24:37 -06:00
Eldritch-Oliver
2497c492bf Update the way that the force rerender calls the render method 2025-10-05 15:24:11 -06:00
Eldritch-Oliver
98b429f941 Finish making the intellisense work properly 2025-10-05 13:31:31 -06:00
Eldritch-Oliver
0917f28fcb Fix the tab change on ready 2025-10-05 12:55:10 -06:00
1228b32823
Merge pull request #52 from Eldritch-Oliver/feature/armour-sheet-improvements
Armour/Shield Sheet Improvements
2025-10-04 21:20:02 -06:00
Eldritch-Oliver
739652e346 Add missing localizations 2025-10-04 21:19:38 -06:00
Eldritch-Oliver
e552b7d041 Make the logic use an or instead nullish coalesce 2025-10-04 21:18:10 -06:00
Eldritch-Oliver
4d36cc29a5 Clean up the armour summary markup 2025-10-04 21:17:25 -06:00
Eldritch-Oliver
514103fe0b Remove download link 2025-10-04 20:45:58 -06:00
Eldritch-Oliver
8098ede72c Update action to use variable substitution 2025-10-04 20:44:01 -06:00
Eldritch-Oliver
bf06edc5c6 Add a script for linking Foundry into the project root 2025-10-04 20:43:41 -06:00
Eldritch-Oliver
d15301663c Update system.json 2025-10-04 20:43:29 -06:00
Oliver-Akins
ae0d4fb0a2 Provide provide proper limited accessor 2025-07-31 21:32:58 -06:00
Oliver-Akins
511481e608 Finish writing the GenericAppMixin jsdoc 2025-07-31 21:32:42 -06:00
Oliver-Akins
a01c79ea2f Remove unused import 2025-07-31 20:50:50 -06:00
Oliver-Akins
b1ba33919f Localize and add missing labels 2025-07-31 20:50:44 -06:00
Oliver-Akins
7dfc1bd0c0 Add IDs for the label associations 2025-07-31 10:32:42 -06:00
Oliver-Akins
e90e90bfe0 Remove log 2025-07-31 10:29:56 -06:00
Oliver-Akins
ca0c793b56 Add cursor pointer to events 2025-07-23 23:52:23 -06:00
Oliver-Akins
3c582c77bb Update the ArmourSheets to allow for the equipped toggle to be present and work 2025-07-23 23:52:07 -06:00
Oliver-Akins
b72c9d9739 Add meta properties that indicate if the document is embedded, and if the user is able to edit it 2025-07-23 22:17:05 -06:00
Oliver-Akins
0bd099cc28 Add todo so I don't forget 2025-07-20 21:45:10 -06:00
Oliver-Akins
2215ce503b Remove the sheet inputs from the data model 2025-07-20 21:44:27 -06:00
Oliver-Akins
bfa26edd5b Prevent the AllItemSheet from being used on armour/shields 2025-07-20 21:43:41 -06:00
Oliver-Akins
99c1281da8 Add lang entry for the new sheet 2025-07-20 21:43:23 -06:00
Oliver-Akins
caa3fbbda0 Localize name based on the core translation 2025-07-20 21:35:39 -06:00
Oliver-Akins
94942c8eab Make a unique ArmourSheet so that it can have a better UX for indicating protection locations 2025-07-20 21:35:27 -06:00
Oliver-Akins
2b88bcc2ef Add a component that handles displaying the person silhouette with some content inside of it 2025-07-20 21:32:46 -06:00
Oliver-Akins
26a2e0f3ff Cleanup a few logs 2025-07-20 21:28:25 -06:00
Oliver-Akins
e49fa03fed Rename the elements folder to components 2025-07-20 14:14:46 -06:00
131 changed files with 4035 additions and 216 deletions

2
.env.template Normal file
View file

@ -0,0 +1,2 @@
# The absolute path to the Foundry installation to create symlinks to
FOUNDRY_ROOT=""

View file

@ -29,16 +29,22 @@ jobs:
if: ${{ steps.check-tag.outputs.exists == 'true' }} if: ${{ steps.check-tag.outputs.exists == 'true' }}
run: exit 1 run: exit 1
- name: Move system.json to a temp file - name: "Building compendia"
id: manifest-move run: "npm run data:build"
run: mv system.json module.temp.json
- name: Update the download property in the manifest - name: "Removing compendium source"
run: "rm -rf packs/**/_source"
- name: Update the manifest with the relevant properties
id: manifest-update id: manifest-update
run: cat module.temp.json | jq -r --tab '.download = "https://github.com/${{ github.repository }}/releases/download/v${{ steps.version.outputs.version }}/release.zip"' > system.json uses: microsoft/variable-substitution@v1
with:
files: "system.json"
env:
download: "https://github.com/${{ github.repository }}/releases/download/v${{ steps.version.outputs.version }}/release.zip"
- name: Create the zip - name: Create the zip
run: zip -r release.zip system.json module langs assets templates README.md run: zip -r release.zip system.json packs module langs assets templates README.md
- name: Create the draft release - name: Create the draft release
uses: ncipollo/release-action@v1 uses: ncipollo/release-action@v1

8
.gitignore vendored
View file

@ -1,6 +1,7 @@
dist/ dist/
*.link *.link
*.txt *.txt
/foundry
# Dependency directories # Dependency directories
node_modules/ node_modules/
@ -12,3 +13,10 @@ jspm_packages/
.env.test.local .env.test.local
.env.production.local .env.production.local
.env.local .env.local
# Ignore all of the binaries and stuff that gets built for Foundry from the raw
# JSON data because it's annoying seeing it in my git changes when it isn't actually
# needed.
/packs/**/*
!/packs/**/*/
!/packs/**/*.json

View file

@ -1,9 +1,10 @@
{ {
"files.exclude": { "files.exclude": {
"**/node_modules": true "**/node_modules": true,
"foundry": true
}, },
"search.exclude": { "search.exclude": {
"foundry.*.link": true "foundry": true
}, },
"html.customData": [ "html.customData": [
"./.vscode/foundry.html-data.json", "./.vscode/foundry.html-data.json",

View file

@ -1,8 +1,11 @@
Oliver Akins: Eldritch-Oliver:
- geist-silhouette.v2.svg : All rights reserved. - geist-silhouette.v2.svg : All rights reserved.
- caster-silhouette.v1.svg : All rights reserved. - caster-silhouette.v1.svg : All rights reserved.
- icons/star-empty.svg : Modified from https://thenounproject.com/icon/star-7711815/ by Llisole - icons/star-empty.svg : Modified from https://thenounproject.com/icon/star-7711815/ by Llisole
- icons/star.svg : Modified from https://thenounproject.com/icon/star-7711815/ by Llisole - icons/star.svg : Modified from https://thenounproject.com/icon/star-7711815/ by Llisole
- icons/shield/checked.v1.svg : Modified from https://thenounproject.com/icon/shield-5565751/ by Corner Pixel
- icons/shield/crossed.v1.svg : Modified from https://thenounproject.com/icon/shield-5565751/ by Corner Pixel
- icons/shield/solid.v1.svg : Modified from https://thenounproject.com/icon/shield-5565751/ by Corner Pixel
Kýnan Antos (Gritsilk Games): Kýnan Antos (Gritsilk Games):
- hero-silhouette.svg : Licensed to Distribute and Modify within the bounds of the "Foundry-RipCrypt" system. - hero-silhouette.svg : Licensed to Distribute and Modify within the bounds of the "Foundry-RipCrypt" system.
@ -13,6 +16,9 @@ ARISO:
Abdulloh Fauzan: Abdulloh Fauzan:
- icons/info-circle.svg (https://thenounproject.com/icon/information-4176576/) : Rights Purchased - icons/info-circle.svg (https://thenounproject.com/icon/information-4176576/) : Rights Purchased
hanifmuhammad:
- icons/plus.svg (https://thenounproject.com/icon/plus-7363257/) : Rights Purchased
QOLBIN SALIIM: QOLBIN SALIIM:
- icons/arrow-left.svg (https://thenounproject.com/icon/arrow-1933583/) : Rights Purchased - icons/arrow-left.svg (https://thenounproject.com/icon/arrow-1933583/) : Rights Purchased
- icons/arrow-right.svg (https://thenounproject.com/icon/arrow-1933581/) : Rights Purchased - icons/arrow-right.svg (https://thenounproject.com/icon/arrow-1933581/) : Rights Purchased

3
assets/icons/plus.svg Normal file
View file

@ -0,0 +1,3 @@
<svg version="1.1" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<path d="m83.59 16.41c-18.547-18.547-48.633-18.547-67.18 0s-18.547 48.633 0 67.18 48.633 18.547 67.18 0 18.547-48.633 0-67.18zm-6.3438 39.922h-20.914v20.91c0 3.5039-2.8398 6.332-6.332 6.332s-6.332-2.8281-6.332-6.332v-20.91h-20.914c-3.5039 0-6.332-2.8398-6.332-6.332s2.8281-6.332 6.332-6.332h20.914v-20.914c0-3.5039 2.8398-6.332 6.332-6.332s6.332 2.8281 6.332 6.332v20.91h20.91c3.5039 0 6.332 2.8398 6.332 6.332 0.003906 3.4961-2.8242 6.3359-6.3281 6.3359z"/>
</svg>

After

Width:  |  Height:  |  Size: 544 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path d="M50 6.9q-2.1 0-3.9 1.4a47 47 0 0 1-24.8 10c-3.2.3-5.7 3-5.7 6.2v31c0 13 7.3 24.8 19 30.7l12.6 6.2a6 6 0 0 0 5.6 0l12.6-6.2a34 34 0 0 0 19-30.8V24.5c0-3.2-2.5-5.9-5.7-6.2a47 47 0 0 1-24.8-10A6 6 0 0 0 50 6.9m20.4 25.6a5 5 0 0 1 4 8.2L52.2 69a5 5 0 0 1-7.2.8L28.2 55.9a5 5 0 0 1 6.4-7.7l12.8 10.5 19.1-24.2a5 5 0 0 1 3.9-2"/></svg>

After

Width:  |  Height:  |  Size: 401 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path d="M50 6.9q-2.1 0-3.9 1.4a47 47 0 0 1-24.8 10c-3.2.3-5.7 3-5.7 6.2v31c0 13 7.3 24.8 19 30.7l12.6 6.2a6 6 0 0 0 5.6 0l12.6-6.2a34 34 0 0 0 19-30.8V24.5c0-3.2-2.5-5.9-5.7-6.2a47 47 0 0 1-24.8-10A6 6 0 0 0 50 6.9M36.7 27.6a6 6 0 0 1 4.6 2.3l9 11.2L59 30a6 6 0 0 1 8.4-1l.4.3a6 6 0 0 1 1 8.4L58.2 51.2l10.5 13.3a6 6 0 0 1-1 8.4l-.4.3a6 6 0 0 1-8.4-1l-8.7-11-8.7 11.1a6 6 0 0 1-8.4 1l-.3-.3a6 6 0 0 1-1-8.4l10.5-13.4-10.8-13.6a6 6 0 0 1 1-8.4l.4-.3a6 6 0 0 1 3.8-1.3"/></svg>

After

Width:  |  Height:  |  Size: 539 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path d="M49.996 6.9a6.2 6.2 0 0 0-3.869 1.362 46.9 46.9 0 0 1-24.828 10.035c-3.238.308-5.68 2.984-5.68 6.219V55.41c0 13.102 7.281 24.883 19.004 30.746l12.578 6.29a6.32 6.32 0 0 0 5.59 0l12.578-6.29c11.72-5.863 19.004-17.645 19.004-30.746l.004-30.894c0-3.239-2.441-5.91-5.68-6.22h-.002a46.9 46.9 0 0 1-24.83-10.034A6.2 6.2 0 0 0 49.996 6.9"/></svg>

After

Width:  |  Height:  |  Size: 411 B

14
augments.d.ts vendored Normal file
View file

@ -0,0 +1,14 @@
declare global {
class Hooks extends foundry.helpers.Hooks {};
const fromUuid = foundry.utils.fromUuid;
}
interface Actor {
/** The system-specific data */
system: any;
};
interface Item {
/** The system-specific data */
system: any;
};

View file

@ -4,7 +4,7 @@ import stylistic from "@stylistic/eslint-plugin";
export default [ export default [
// Tell eslint to ignore files that I don't mind being formatted slightly differently // Tell eslint to ignore files that I don't mind being formatted slightly differently
{ ignores: [ `scripts/` ] }, { ignores: [ `scripts/`, `foundry/` ] },
{ {
languageOptions: { languageOptions: {
globals: globals.browser, globals: globals.browser,
@ -75,7 +75,7 @@ export default [
"@stylistic/eol-last": `warn`, "@stylistic/eol-last": `warn`,
"@stylistic/operator-linebreak": [`warn`, `before`], "@stylistic/operator-linebreak": [`warn`, `before`],
"@stylistic/indent": [`warn`, `tab`], "@stylistic/indent": [`warn`, `tab`],
"@stylistic/brace-style": [`warn`, `1tbs`, { "allowSingleLine": true }], "@stylistic/brace-style": [`warn`, `stroustrup`, { "allowSingleLine": true }],
"@stylistic/quotes": [`warn`, `backtick`, { "avoidEscape": true }], "@stylistic/quotes": [`warn`, `backtick`, { "avoidEscape": true }],
"@stylistic/comma-dangle": [`warn`, { arrays: `always-multiline`, objects: `always-multiline`, imports: `always-multiline`, exports: `always-multiline`, functions: `always-multiline` }], "@stylistic/comma-dangle": [`warn`, { arrays: `always-multiline`, objects: `always-multiline`, imports: `always-multiline`, exports: `always-multiline`, functions: `always-multiline` }],
"@stylistic/comma-style": [`warn`, `last`], "@stylistic/comma-style": [`warn`, `last`],

View file

@ -1,11 +1,19 @@
{ {
"compilerOptions": { "compilerOptions": {
"module": "ES2020", "module": "es2022",
"target": "ES2020" "target": "es2022",
"types": [
"./augments.d.ts"
],
"paths": {
"@client/*": ["./foundry/client/*"],
"@common/*": ["./foundry/common/*"],
}
}, },
"exclude": ["node_modules", "**/node_modules/*"], "include": [
"include": ["module/**/*", "foundry.v13.link/client/**/*.js", "foundry.v13.link/**/*.mjs"], "module/**/*",
"typeAcquisition": { "foundry/client/client.mjs",
"include": ["jquery"] "foundry/client/global.d.mts",
} "foundry/common/primitives/global.d.mts"
]
} }

View file

@ -17,6 +17,7 @@
"RipCrypt": { "RipCrypt": {
"sheet-names": { "sheet-names": {
"AllItemsSheetV1": "RipCrypt Item Sheet", "AllItemsSheetV1": "RipCrypt Item Sheet",
"ArmourSheet": "Armour Sheet",
"CombinedHeroSheet": "Hero Sheet", "CombinedHeroSheet": "Hero Sheet",
"StatsCardV1": "Hero Stat Card", "StatsCardV1": "Hero Stat Card",
"CraftCardV1": "Hero Craft Card", "CraftCardV1": "Hero Craft Card",
@ -154,9 +155,14 @@
"both": "Notification and Pause Game", "both": "Notification and Pause Game",
"nothing": "Do Nothing" "nothing": "Do Nothing"
} }
},
"allowUpdateSandsSocket": {
"name": "Player Haste Updates the Sands of Fate",
"hint": "This setting determines if when a player makes a haste check that the result will automatically be applied to the global Sands of Fate. Disabling this is good if you want to let players roll without needing to worry about automation messing anything up while they spam rolls."
} }
}, },
"Apps": { "Apps": {
"damage-reduction": "@RipCrypt.common.damage reduction",
"traits-range": "@RipCrypt.common.traits & @RipCrypt.common.range", "traits-range": "@RipCrypt.common.traits & @RipCrypt.common.range",
"grit-skills": "@RipCrypt.common.abilities.grit Skills", "grit-skills": "@RipCrypt.common.abilities.grit Skills",
"gait-skills": "@RipCrypt.common.abilities.gait Skills", "gait-skills": "@RipCrypt.common.abilities.gait Skills",
@ -184,13 +190,18 @@
"star-button-tooltip": "Add Star", "star-button-tooltip": "Add Star",
"unstar-button": "Remove {name} as a starred ammo", "unstar-button": "Remove {name} as a starred ammo",
"unstar-button-tooltip": "Remove Star" "unstar-button-tooltip": "Remove Star"
} },
"protects-the-location": "Protects your {part}"
}, },
"notifs": { "notifs": {
"error": { "error": {
"cannot-equip": "Cannot equip the {itemType}, see console for more details.", "cannot-equip": "Cannot equip the {itemType}, see console for more details.",
"invalid-delta": "The delta for \"{name}\" is not a number, cannot finish processing the action.", "invalid-delta": "The delta for \"{name}\" is not a number, cannot finish processing the action.",
"at-favourite-limit": "Cannot favourite more than three items, unfavourite one to make space." "at-favourite-limit": "Cannot favourite more than three items, unfavourite one to make space.",
"invalid-socket": "Invalid socket data received, this means a module or system bug is present.",
"unknown-socket-event": "An unknown socket event was received: {event}",
"no-active-gm": "No active @USER.GM is logged in, you must wait for a @USER.GM to be active before you can do that.",
"malformed-socket-payload": "Socket event \"{event}\" received with malformed payload. Details: {details}"
}, },
"warn": { "warn": {
"cannot-go-negative": "\"{name}\" is unable to be a negative number." "cannot-go-negative": "\"{name}\" is unable to be a negative number."
@ -203,6 +214,7 @@
"shield-bonus": "Shield Bonus: {value}", "shield-bonus": "Shield Bonus: {value}",
"set-fate-to": "Set Fate to {ordinal}", "set-fate-to": "Set Fate to {ordinal}",
"current-tour": "Current Delve Tour", "current-tour": "Current Delve Tour",
"create-new-item": "Create new item",
"next-tour": "Next Delve Tour", "next-tour": "Next Delve Tour",
"prev-tour": "Previous Delve Tour", "prev-tour": "Previous Delve Tour",
"auras": { "auras": {

View file

@ -1,7 +1,6 @@
import { CraftCardV1 } from "./CraftCardV1.mjs"; import { CraftCardV1 } from "./CraftCardV1.mjs";
import { filePath } from "../../consts.mjs"; import { filePath } from "../../consts.mjs";
import { GenericAppMixin } from "../GenericApp.mjs"; import { GenericAppMixin } from "../GenericApp.mjs";
import { Logger } from "../../utils/Logger.mjs";
import { SkillsCardV1 } from "./SkillsCardV1.mjs"; import { SkillsCardV1 } from "./SkillsCardV1.mjs";
import { StatsCardV1 } from "./StatsCardV1.mjs"; import { StatsCardV1 } from "./StatsCardV1.mjs";
@ -23,7 +22,9 @@ export class CombinedHeroSheet extends GenericAppMixin(HandlebarsApplicationMixi
window: { window: {
resizable: false, resizable: false,
}, },
actions: {}, actions: {
...StatsCardV1.DEFAULT_OPTIONS.actions,
},
form: { form: {
submitOnChange: true, submitOnChange: true,
closeOnSubmit: false, closeOnSubmit: false,
@ -106,7 +107,6 @@ export class CombinedHeroSheet extends GenericAppMixin(HandlebarsApplicationMixi
}; };
}; };
Logger.debug(`Context keys:`, Object.keys(ctx));
return ctx; return ctx;
}; };
// #endregion // #endregion

View file

@ -115,7 +115,8 @@ export class CraftCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(Acto
const length = crafts.length; const length = crafts.length;
if (length >= limit) { if (length >= limit) {
crafts = crafts.slice(0, limit); crafts = crafts.slice(0, limit);
} else { }
else {
crafts = crafts crafts = crafts
.concat(Array(limit - length).fill(null)) .concat(Array(limit - length).fill(null))
.slice(0, limit); .slice(0, limit);

View file

@ -194,7 +194,8 @@ export class SkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(Act
const length = ctx.skills[ability].length; const length = ctx.skills[ability].length;
if (length >= limit) { if (length >= limit) {
ctx.skills[ability] = ctx.skills[ability].slice(0, limit); ctx.skills[ability] = ctx.skills[ability].slice(0, limit);
} else { }
else {
ctx.skills[ability] = ctx.skills[ability] ctx.skills[ability] = ctx.skills[ability]
.concat(Array(limit - length).fill(null)) .concat(Array(limit - length).fill(null))
.slice(0, limit); .slice(0, limit);

View file

@ -1,4 +1,5 @@
import { deleteItemFromElement, editItemFromElement } from "../utils.mjs"; import { deleteItemFromElement, editItemFromElement } from "../utils.mjs";
import { DelveDiceHUD } from "../DelveDiceHUD.mjs";
import { filePath } from "../../consts.mjs"; import { filePath } from "../../consts.mjs";
import { gameTerms } from "../../gameTerms.mjs"; import { gameTerms } from "../../gameTerms.mjs";
import { GenericAppMixin } from "../GenericApp.mjs"; import { GenericAppMixin } from "../GenericApp.mjs";
@ -25,6 +26,7 @@ export class StatsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(Acto
resizable: false, resizable: false,
}, },
actions: { actions: {
rollForHaste: DelveDiceHUD.rollForHaste,
}, },
form: { form: {
submitOnChange: true, submitOnChange: true,

View file

@ -6,6 +6,7 @@ import { Logger } from "../utils/Logger.mjs";
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
const { ContextMenu } = foundry.applications.ux; const { ContextMenu } = foundry.applications.ux;
const { Roll } = foundry.dice;
const { FatePath } = gameTerms; const { FatePath } = gameTerms;
const CompassRotations = { const CompassRotations = {
@ -87,7 +88,8 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) {
const existing = document.getElementById(element.id); const existing = document.getElementById(element.id);
if (existing) { if (existing) {
existing.replaceWith(element); existing.replaceWith(element);
} else { }
else {
const parent = document.getElementById(`ui-top`); const parent = document.getElementById(`ui-top`);
parent.prepend(element); parent.prepend(element);
}; };
@ -189,18 +191,7 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) {
/** @this {DelveDiceHUD} */ /** @this {DelveDiceHUD} */
static async #tourDelta(_event, element) { static async #tourDelta(_event, element) {
const delta = parseInt(element.dataset.delta); const delta = parseInt(element.dataset.delta);
const initial = game.settings.get(`ripcrypt`, `sandsOfFateInitial`); await this.sandsOfFateDelta(delta);
let newSands = this._sandsOfFate + delta;
if (newSands > initial) {
Logger.info(`Cannot go to a previous Delve Tour above the initial value`);
return;
};
if (newSands === 0) {
newSands = initial;
await this.alertCrypticEvent();
};
switch (Math.sign(delta)) { switch (Math.sign(delta)) {
case -1: { case -1: {
@ -212,9 +203,6 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) {
break; break;
} }
}; };
this.#animateSandsTo(newSands);
game.settings.set(`ripcrypt`, `sandsOfFate`, newSands);
}; };
/** @this {DelveDiceHUD} */ /** @this {DelveDiceHUD} */
@ -241,11 +229,77 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) {
localizer(`RipCrypt.notifs.info.cryptic-event-alert`), localizer(`RipCrypt.notifs.info.cryptic-event-alert`),
{ console: false }, { console: false },
); );
game.socket.emit(`system.ripcrypt`, {
event: `notify`,
payload: {
message: `RipCrypt.notifs.info.cryptic-event-alert`,
type: `info`,
},
});
}; };
if ([`both`, `pause`].includes(alertType) && game.user.isGM) { if ([`both`, `pause`].includes(alertType) && game.user.isGM) {
game.togglePause(true, { broadcast: true }); game.togglePause(true, { broadcast: true });
}; };
}; };
/**
* Changes the current Sands of Fate by an amount provided, animating the
* @param {number} delta The amount of change
*/
async sandsOfFateDelta(delta) {
const initial = game.settings.get(`ripcrypt`, `sandsOfFateInitial`);
let newSands = this._sandsOfFate + delta;
if (newSands > initial) {
Logger.info(`Cannot increase the Sands of Fate to a value about the initial`);
return;
};
if (newSands === 0) {
newSands = initial;
await this.alertCrypticEvent();
};
this.#animateSandsTo(newSands);
game.settings.set(`ripcrypt`, `sandsOfFate`, newSands);
};
/**
* A helper method that rolls the dice required for hasty turns while delving
* and adjusts the Sands of Fate accordingly
*/
static async rollForHaste() {
const shouldUpdateSands = game.settings.get(`ripcrypt`, `allowUpdateSandsSocket`);
if (shouldUpdateSands && game.users.activeGM == null) {
ui.notifications.error(localizer(`RipCrypt.notifs.error.no-active-gm`));
return;
};
const roll = new Roll(`1d8xo=1`);
await roll.evaluate();
let delta = 0;
if (roll.dice[0].results[0].exploded) {
delta = -1;
if (roll.dice[0].results[1].result === 1) {
delta = -2;
};
};
roll.toMessage({ flavor: `Haste Check` });
// Change the Sands of Fate setting if required
if (delta === 0 || !shouldUpdateSands) { return };
if (game.user.isActiveGM) {
ui.delveDice.sandsOfFateDelta(delta);
}
else {
game.socket.emit(`system.ripcrypt`, {
event: `updateSands`,
payload: { delta },
});
};
};
// #endregion // #endregion
}; };

View file

@ -4,7 +4,8 @@ import { RichEditor } from "./RichEditor.mjs";
import { toBoolean } from "../consts.mjs"; import { toBoolean } from "../consts.mjs";
/** /**
* A mixin that takes the class from HandlebarsApplicationMixin and * A mixin that takes the class from HandlebarsApplicationMixin and combines it
* with utility functions / data that is used across all RipCrypt applications
*/ */
export function GenericAppMixin(HandlebarsApp) { export function GenericAppMixin(HandlebarsApp) {
class GenericRipCryptApp extends HandlebarsApp { class GenericRipCryptApp extends HandlebarsApp {
@ -91,8 +92,9 @@ export function GenericAppMixin(HandlebarsApp) {
ctx.meta.idp = this.document?.uuid ?? this.id; ctx.meta.idp = this.document?.uuid ?? this.id;
if (this.document) { if (this.document) {
ctx.meta.limited = this.document.limited; ctx.meta.limited = this.document.limited;
ctx.meta.editable = ctx.editable; ctx.meta.editable = this.isEditable || game.user.isGM;
} ctx.meta.embedded = this.document.isEmbedded;
};
delete ctx.editable; delete ctx.editable;
return ctx; return ctx;

View file

@ -61,7 +61,7 @@ export class AllItemSheetV1 extends GenericAppMixin(HandlebarsApplicationMixin(I
await super._processSubmitData(...args); await super._processSubmitData(...args);
if (this.document.system.forceRerender) { if (this.document.system.forceRerender) {
await this.render(false); await this.render();
}; };
}; };
// #endregion // #endregion

View file

@ -0,0 +1,135 @@
import { filePath } from "../../consts.mjs";
import { gameTerms } from "../../gameTerms.mjs";
import { GenericAppMixin } from "../GenericApp.mjs";
const { HandlebarsApplicationMixin } = foundry.applications.api;
const { ItemSheetV2 } = foundry.applications.sheets;
const { getProperty, hasProperty, setProperty } = foundry.utils;
export class ArmourSheet extends GenericAppMixin(HandlebarsApplicationMixin(ItemSheetV2)) {
// #region Options
static DEFAULT_OPTIONS = {
classes: [
`ripcrypt--item`,
`ArmourSheet`,
],
position: {
width: `auto`,
height: `auto`,
},
window: {
resizable: false,
},
form: {
submitOnChange: true,
closeOnSubmit: false,
},
};
static PARTS = {
header: {
template: filePath(`templates/Apps/partials/item-header.hbs`),
},
content: {
template: filePath(`templates/Apps/ArmourSheet/content.hbs`),
},
};
// #endregion
// #region Lifecycle
async _onRender() {
// remove the flag if it exists when we render the sheet
delete this.document?.system?.forceRerender;
};
/**
* Used to make it so that items that don't get updated because of the
* _preUpdate hook removing/changing the data submitted, can still get
* re-rendered when the diff is empty. If the document does get updated,
* this rerendering does not happen.
*
* @override
*/
async _processSubmitData(...args) {
await super._processSubmitData(...args);
if (this.document.system.forceRerender) {
await this.render();
};
};
/**
* Customize how form data is extracted into an expanded object.
* @param {SubmitEvent|null} event The originating form submission event
* @param {HTMLFormElement} form The form element that was submitted
* @param {FormDataExtended} formData Processed data for the submitted form
* @returns {object} An expanded object of processed form data
* @throws {Error} Subclasses may throw validation errors here to prevent form submission
* @protected
*/
_processFormData(event, form, formData) {
const data = super._processFormData(event, form, formData);
if (hasProperty(data, `system.location`)) {
let locations = getProperty(data, `system.location`);
locations = locations.filter(value => value != null);
setProperty(data, `system.location`, locations);
};
return data;
};
// #endregion
// #region Data Prep
async _preparePartContext(partId, _, opts) {
const ctx = await super._preparePartContext(partId, {}, opts);
ctx.item = this.document;
ctx.system = this.document.system;
switch (partId) {
case `content`: {
this._prepareContentContext(ctx, opts);
break;
};
};
return ctx;
};
async _prepareContentContext(ctx) {
ctx.weights = [
{
label: `RipCrypt.common.empty`,
value: null,
},
...Object.values(gameTerms.WeightRatings).map(opt => ({
label: `RipCrypt.common.weightRatings.${opt}`,
value: opt,
})),
];
ctx.accesses = [
{
label: `RipCrypt.common.empty`,
value: ``,
},
...gameTerms.Access.map(opt => ({
label: `RipCrypt.common.accessLevels.${opt}`,
value: opt,
})),
];
ctx.protects = {
head: this.document.system.location.has(gameTerms.Anatomy.HEAD),
body: this.document.system.location.has(gameTerms.Anatomy.BODY),
arms: this.document.system.location.has(gameTerms.Anatomy.ARMS),
legs: this.document.system.location.has(gameTerms.Anatomy.LEGS),
};
};
// #endregion
// #region Actions
// #endregion
};

View file

@ -0,0 +1,56 @@
import { filePath } from "../../consts.mjs";
import { StyledShadowElement } from "./mixins/StyledShadowElement.mjs";
const { renderTemplate } = foundry.applications.handlebars;
export class ArmourSummary extends StyledShadowElement(HTMLElement) {
static elementName = `armour-summary`;
static formAssociated = false;
/* Stuff for the mixin to use */
static _stylePath = `css/components/armour-summary.css`;
#container;
get type() {
return this.getAttribute(`type`) ?? `hero`;
};
set type(newValue) {
this.setAttribute(`type`, newValue);
};
_mounted = false;
async connectedCallback() {
super.connectedCallback();
if (this._mounted) { return };
/*
This converts all of the double-dash prefixed properties on the element to
CSS variables so that they don't all need to be provided by doing style=""
*/
for (const attrVar of this.attributes) {
if (attrVar.name?.startsWith(`var:`)) {
const prop = attrVar.name.replace(`var:`, ``);
this.style.setProperty(`--` + prop, attrVar.value);
};
};
this.#container = document.createElement(`div`);
this.#container.classList = `person`;
this.#container.innerHTML = await renderTemplate(
filePath(`templates/components/armour-summary.hbs`),
{ type: this.type },
);
this._shadow.appendChild(this.#container);
this._mounted = true;
};
disconnectedCallback() {
super.disconnectedCallback();
if (!this._mounted) { return };
this._mounted = false;
};
};

View file

@ -1,9 +1,11 @@
import { ArmourSummary } from "./ArmourSummary.mjs";
import { Logger } from "../../utils/Logger.mjs"; import { Logger } from "../../utils/Logger.mjs";
import { RipCryptBorder } from "./RipCryptBorder.mjs"; import { RipCryptBorder } from "./RipCryptBorder.mjs";
import { RipCryptIcon } from "./Icon.mjs"; import { RipCryptIcon } from "./Icon.mjs";
import { RipCryptSVGLoader } from "./svgLoader.mjs"; import { RipCryptSVGLoader } from "./svgLoader.mjs";
const components = [ const components = [
ArmourSummary,
RipCryptIcon, RipCryptIcon,
RipCryptSVGLoader, RipCryptSVGLoader,
RipCryptBorder, RipCryptBorder,

View file

@ -51,7 +51,8 @@ export function StyledShadowElement(Base) {
const stylePath = this.constructor._stylePath; const stylePath = this.constructor._stylePath;
if (this.constructor._styles.has(stylePath)) { if (this.constructor._styles.has(stylePath)) {
this._style.innerHTML = this.constructor._styles.get(stylePath); this._style.innerHTML = this.constructor._styles.get(stylePath);
} else { }
else {
fetch(`./systems/${game.system.id}/templates/${stylePath}`) fetch(`./systems/${game.system.id}/templates/${stylePath}`)
.then(r => r.text()) .then(r => r.text())
.then(t => { .then(t => {

View file

@ -11,7 +11,7 @@ export async function createItemFromElement(target, { parent } = {}) {
const type = data.defaultItemType; const type = data.defaultItemType;
await Item.createDialog( await Item.createDialog(
{ type }, { type },
{ parent }, { parent, showEquipPrompt: false },
{ {
types, types,
folders: [], folders: [],
@ -40,7 +40,7 @@ export async function deleteItemFromElement(target) {
const itemId = itemEl.dataset.itemId; const itemId = itemEl.dataset.itemId;
if (!itemId) { return }; if (!itemId) { return };
const item = await fromUuid(itemId); const item = await fromUuid(itemId);
item.delete(); item.deleteDialog();
}; };
/** /**

View file

@ -42,9 +42,11 @@ export function toBoolean(val) {
export function documentSorter(a, b) { export function documentSorter(a, b) {
if (!a && !b) { if (!a && !b) {
return 0; return 0;
} else if (!a) { }
else if (!a) {
return 1; return 1;
} else if (!b) { }
else if (!b) {
return -1; return -1;
}; };

View file

@ -155,6 +155,11 @@ export class EntityData extends foundry.abstract.TypeDataModel {
}; };
// #region Getters // #region Getters
get equippedWeapons() {
const weapons = this.parent.itemTypes.weapon;
return weapons.filter(w => w.system.equipped);
};
get equippedArmour() { get equippedArmour() {
const armours = this.parent.itemTypes.armour; const armours = this.parent.itemTypes.armour;
const slots = Object.fromEntries( const slots = Object.fromEntries(

View file

@ -4,12 +4,13 @@ import { localizer } from "../../utils/Localizer.mjs";
import { Logger } from "../../utils/Logger.mjs"; import { Logger } from "../../utils/Logger.mjs";
import { requiredInteger } from "../helpers.mjs"; import { requiredInteger } from "../helpers.mjs";
const { diffObject, getProperty, setProperty } = foundry.utils;
const { DialogV2 } = foundry.applications.api;
const { fields } = foundry.data; const { fields } = foundry.data;
const { hasProperty, diffObject, mergeObject } = foundry.utils;
/** Used for Armour and Shields */ /** Used for Armour and Shields */
export class ArmourData extends CommonItemData { export class ArmourData extends CommonItemData {
// MARK: Schema // #region Schema
static defineSchema() { static defineSchema() {
return { return {
...super.defineSchema(), ...super.defineSchema(),
@ -19,16 +20,16 @@ export class ArmourData extends CommonItemData {
blank: false, blank: false,
trim: true, trim: true,
nullable: false, nullable: false,
required: true,
options: Object.values(gameTerms.Anatomy), options: Object.values(gameTerms.Anatomy),
}), }),
{ {
nullable: false, nullable: false,
required: true, initial: [],
}, },
), ),
equipped: new fields.BooleanField({ equipped: new fields.BooleanField({
initial: false, initial: false,
required: true,
nullable: false, nullable: false,
}), }),
weight: new fields.StringField({ weight: new fields.StringField({
@ -39,27 +40,30 @@ export class ArmourData extends CommonItemData {
}), }),
}; };
}; };
// #endregion Schema
// MARK: Base Data
prepareBaseData() {
super.prepareBaseData();
};
// MARK: Derived Data
prepareDerivedData() {
super.prepareDerivedData();
};
// #region Lifecycle // #region Lifecycle
async _preCreate(item, options) {
const showEquipPrompt = options.showEquipPrompt ?? true;
if (showEquipPrompt && this.parent.isEmbedded && this._canEquip()) {
const shouldEquip = await DialogV2.confirm({
window: { title: `Equip Item?` },
content: `Do you want to equip ${item.name}?`,
});
if (shouldEquip) {
this.updateSource({ "equipped": true });
};
};
};
async _preUpdate(changes, options, user) { async _preUpdate(changes, options, user) {
// return false
if (options.force && game.settings.get(`ripcrypt`, `devMode`)) { return }; if (options.force && game.settings.get(`ripcrypt`, `devMode`)) { return };
// Ensure changes is a diffed object // Ensure changes is a diffed object
const diff = diffObject(this.parent._source, changes); const diff = diffObject(this.parent._source, changes);
let valid = await super._preUpdate(changes, options, user); let valid = await super._preUpdate(changes, options, user);
if (hasProperty(diff, `system.equipped`) && !this._canEquip()) { if (getProperty(diff, `system.equipped`) && !this._canEquip()) {
ui.notifications.error( ui.notifications.error(
localizer( localizer(
`RipCrypt.notifs.error.cannot-equip`, `RipCrypt.notifs.error.cannot-equip`,
@ -69,9 +73,7 @@ export class ArmourData extends CommonItemData {
); );
// Don't stop the update, but don't allow changing the equipped status // Don't stop the update, but don't allow changing the equipped status
mergeObject(changes, { setProperty(changes, `system.equipped`, false);
"system.equipped": false,
});
// Set a flag so that we can tell the sheet that it needs to rerender // Set a flag so that we can tell the sheet that it needs to rerender
this.forceRerender = true; this.forceRerender = true;
@ -79,8 +81,13 @@ export class ArmourData extends CommonItemData {
return valid; return valid;
}; };
// #endregion Lifecycle
/** Used to tell the preUpdate logic whether or not to prevent the */ // #region Helpers
/**
* Used to tell the preUpdate logic whether or not to prevent the item from
* being equipped or not.
*/
_canEquip() { _canEquip() {
const parent = this.parent; const parent = this.parent;
if (!parent.isEmbedded || !(parent.parent instanceof Actor)) { if (!parent.isEmbedded || !(parent.parent instanceof Actor)) {
@ -94,7 +101,7 @@ export class ArmourData extends CommonItemData {
}; };
const slots = parent.parent.system.equippedArmour ?? {}; const slots = parent.parent.system.equippedArmour ?? {};
Logger.debug(`slots`, slots);
for (const locationTag of this.location) { for (const locationTag of this.location) {
if (slots[locationTag.toLowerCase()] != null) { if (slots[locationTag.toLowerCase()] != null) {
Logger.error(`Unable to equip multiple items in the same slot`); Logger.error(`Unable to equip multiple items in the same slot`);
@ -103,97 +110,9 @@ export class ArmourData extends CommonItemData {
}; };
return true; return true;
}; };
// #endregion
// #region Getters
get locationString() { get locationString() {
return [...this.location].join(`, `); return [...this.location].join(`, `);
}; };
// #endregion // #endregion Helpers
// #region Sheet Data
getFormFields(_ctx) {
const fields = [
{
id: `quantity`,
type: `integer`,
label: `RipCrypt.common.quantity`,
path: `system.quantity`,
value: this.quantity,
min: 0,
},
{
id: `access`,
type: `dropdown`,
label: `RipCrypt.common.access`,
path: `system.access`,
value: this.access,
limited: false,
options: [
{
label: `RipCrypt.common.empty`,
value: ``,
},
...gameTerms.Access.map(opt => ({
label: `RipCrypt.common.accessLevels.${opt}`,
value: opt,
})),
],
},
{
id: `cost`,
type: `cost`,
label: `RipCrypt.common.cost`,
gold: this.cost.gold,
silver: this.cost.silver,
copper: this.cost.copper,
},
{
id: `weight`,
type: `dropdown`,
label: `RipCrypt.common.weightRating`,
path: `system.weight`,
value: this.weight,
options: [
{
label: `RipCrypt.common.empty`,
value: null,
},
...Object.values(gameTerms.WeightRatings).map(opt => ({
label: `RipCrypt.common.weightRatings.${opt}`,
value: opt,
})),
],
},
{
id: `location`,
type: `string-set`,
label: `RipCrypt.common.location`,
placeholder: `RipCrypt.Apps.location-placeholder`,
path: `system.location`,
value: this.locationString,
},
{
id: `protection`,
type: `integer`,
label: `RipCrypt.common.protection`,
value: this.protection,
path: `system.protection`,
min: 0,
},
];
if (this.parent.isEmbedded) {
fields.push({
id: `equipped`,
type: `boolean`,
label: `RipCrypt.common.equipped`,
value: this.equipped,
path: `system.equipped`,
});
};
return fields;
};
// #endregion
}; };

View file

@ -2,12 +2,14 @@ import { barAttribute, optionalInteger, requiredInteger } from "../helpers.mjs";
import { CommonItemData } from "./Common.mjs"; import { CommonItemData } from "./Common.mjs";
import { gameTerms } from "../../gameTerms.mjs"; import { gameTerms } from "../../gameTerms.mjs";
import { localizer } from "../../utils/Localizer.mjs"; import { localizer } from "../../utils/Localizer.mjs";
import { Logger } from "../../utils/Logger.mjs";
const { diffObject, getProperty, setProperty } = foundry.utils;
const { DialogV2 } = foundry.applications.api;
const { fields } = foundry.data; const { fields } = foundry.data;
const { hasProperty, mergeObject } = foundry.utils;
export class WeaponData extends CommonItemData { export class WeaponData extends CommonItemData {
// MARK: Schema // #region Schema
static defineSchema() { static defineSchema() {
return { return {
...super.defineSchema(), ...super.defineSchema(),
@ -41,39 +43,69 @@ export class WeaponData extends CommonItemData {
}), }),
}; };
}; };
// #endregion Schema
// MARK: Base Data
prepareBaseData() {
super.prepareBaseData();
};
// MARK: Derived Data
prepareDerivedData() {
super.prepareDerivedData();
};
// #region Lifecycle // #region Lifecycle
async _preCreate(item, options) {
const showEquipPrompt = options.showEquipPrompt ?? true;
if (showEquipPrompt && this.parent.isEmbedded && this._canEquip()) {
const shouldEquip = await DialogV2.confirm({
window: { title: `Equip Item?` },
content: `Do you want to equip ${item.name}?`,
});
if (shouldEquip) {
this.updateSource({ "equipped": true });
};
};
};
/**
*
* @param {*} changes The expanded object that was used for the update
* @param {*} options
* @param {*} user
* @returns
*/
async _preUpdate(changes, options, user) { async _preUpdate(changes, options, user) {
if (options.force && game.settings.get(`ripcrypt`, `devMode`)) { return }; if (options.force && game.settings.get(`ripcrypt`, `devMode`)) { return };
const diff = diffObject(this.parent._source, changes);
let valid = super._preUpdate(changes, options, user); let valid = super._preUpdate(changes, options, user);
if (hasProperty(changes, `system.equipped`) && !this.parent.isEmbedded) { if (getProperty(diff, `system.equipped`) && !this._canEquip()) {
ui.notifications.error(localizer( ui.notifications.error(localizer(
`RipCrypt.notifs.error.cannot-equip-not-embedded`, `RipCrypt.notifs.error.cannot-equip`,
{ itemType: `@TYPES.Item.${this.parent.type}` }, { itemType: `@TYPES.Item.${this.parent.type}` },
)); ));
mergeObject(
changes, // Don't stop the update, but don't allow changing the equipped status
{ "-=system.equipped": null }, setProperty(changes, `system.equipped`, false);
{ inplace: true, performDeletions: true },
); // Set a flag so that we can tell the sheet that it needs to rerender
return false; this.forceRerender = true;
}; };
return valid; return valid;
}; };
// #endregion // #endregion Lifecycle
// #region Helpers
/**
* Used to tell the preUpdate logic whether or not to prevent the item from
* being equipped or not.
*/
_canEquip() {
const parent = this.parent;
if (!parent.isEmbedded || !(parent.parent instanceof Actor)) {
Logger.error(`Unable to equip item when it's not embedded`);
return false;
};
const actor = this.parent.parent.system;
if (actor.equippedWeapons?.length >= actor.limit.weapons) {
return false;
};
return true;
};
// #region Getters
get traitString() { get traitString() {
return [...this.traits].join(`, `); return [...this.traits].join(`, `);
}; };
@ -84,7 +116,7 @@ export class WeaponData extends CommonItemData {
}; };
return String(this.range.short ?? this.range.long ?? ``); return String(this.range.short ?? this.range.long ?? ``);
}; };
// #endregion // #endregion Helpers
// #region Sheet Data // #region Sheet Data
async getFormFields(_ctx) { async getFormFields(_ctx) {
@ -167,7 +199,8 @@ export class WeaponData extends CommonItemData {
value: this.range.long, value: this.range.long,
}, },
}); });
} else { }
else {
fields.push({ fields.push({
id: `short-range`, id: `short-range`,
type: `integer`, type: `integer`,
@ -225,5 +258,5 @@ export class WeaponData extends CommonItemData {
return fields; return fields;
}; };
// #endregion // #endregion Sheet Data
}; };

View file

@ -38,7 +38,8 @@ export class CryptDie extends Die {
if (almostCrypted) { if (almostCrypted) {
this.ripCryptState = `crypted`; this.ripCryptState = `crypted`;
break; break;
} else { }
else {
almostCrypted = true; almostCrypted = true;
} }
} }

View file

@ -16,7 +16,8 @@ export class RipCryptCombat extends Combat {
if (groups.has(groupKey)) { if (groups.has(groupKey)) {
groups.get(groupKey).push(combatant); groups.get(groupKey).push(combatant);
} else { }
else {
groups.set(groupKey, [combatant]); groups.set(groupKey, [combatant]);
}; };
}; };
@ -129,7 +130,8 @@ export class RipCryptCombat extends Combat {
const currentToken = this.combatant?.token?._object; const currentToken = this.combatant?.token?._object;
if (!tokenGroup && currentToken) { if (!tokenGroup && currentToken) {
currentToken.renderFlags.set({refreshTurnMarker: true}); currentToken.renderFlags.set({refreshTurnMarker: true});
} else { }
else {
const group = this.customGroups.get(tokenGroup) ?? []; const group = this.customGroups.get(tokenGroup) ?? [];
for (const combatant of group) { for (const combatant of group) {
combatant.token?._object?.renderFlags.set({ refreshTurnMarker: true }); combatant.token?._object?.renderFlags.set({ refreshTurnMarker: true });

View file

@ -28,7 +28,8 @@ export class RipCryptCombatant extends Combatant {
const disposition = this.disposition; const disposition = this.disposition;
if (disposition === `unknown`) { if (disposition === `unknown`) {
total += 0.25; total += 0.25;
} else if (whoFirst !== disposition) { }
else if (whoFirst !== disposition) {
total += 0.5; total += 0.5;
}; };
}; };

View file

@ -26,7 +26,8 @@ export class RipCryptToken extends Token {
}; };
canvas.tokens.turnMarkers.add(this); canvas.tokens.turnMarkers.add(this);
this.turnMarker.draw(); this.turnMarker.draw();
} else if (this.turnMarker) { }
else if (this.turnMarker) {
canvas.tokens.turnMarkers.delete(this); canvas.tokens.turnMarkers.delete(this);
this.turnMarker.destroy(); this.turnMarker.destroy();
this.turnMarker = null; this.turnMarker = null;

View file

@ -1,5 +1,6 @@
// Applications // Applications
import { AllItemSheetV1 } from "../Apps/ItemSheets/AllItemSheetV1.mjs"; import { AllItemSheetV1 } from "../Apps/ItemSheets/AllItemSheetV1.mjs";
import { ArmourSheet } from "../Apps/ItemSheets/ArmourSheet.mjs";
import { CombinedHeroSheet } from "../Apps/ActorSheets/CombinedHeroSheet.mjs"; import { CombinedHeroSheet } from "../Apps/ActorSheets/CombinedHeroSheet.mjs";
import { CraftCardV1 } from "../Apps/ActorSheets/CraftCardV1.mjs"; import { CraftCardV1 } from "../Apps/ActorSheets/CraftCardV1.mjs";
import { DelveDiceHUD } from "../Apps/DelveDiceHUD.mjs"; import { DelveDiceHUD } from "../Apps/DelveDiceHUD.mjs";
@ -30,14 +31,14 @@ import { RipCryptToken } from "../documents/token.mjs";
// Misc // Misc
import helpers from "../handlebarHelpers/_index.mjs"; import helpers from "../handlebarHelpers/_index.mjs";
import { Logger } from "../utils/Logger.mjs"; import { Logger } from "../utils/Logger.mjs";
import { registerCustomComponents } from "../Apps/elements/_index.mjs"; import { registerCustomComponents } from "../Apps/components/_index.mjs";
import { registerDevSettings } from "../settings/devSettings.mjs"; import { registerDevSettings } from "../settings/devSettings.mjs";
import { registerMetaSettings } from "../settings/metaSettings.mjs"; import { registerMetaSettings } from "../settings/metaSettings.mjs";
import { registerSockets } from "../sockets/_index.mjs";
import { registerUserSettings } from "../settings/userSettings.mjs"; import { registerUserSettings } from "../settings/userSettings.mjs";
import { registerWorldSettings } from "../settings/worldSettings.mjs"; import { registerWorldSettings } from "../settings/worldSettings.mjs";
const { Items, Actors } = foundry.documents.collections; const { Items, Actors } = foundry.documents.collections;
const { ItemSheet, ActorSheet } = foundry.appv1.sheets;
Hooks.once(`init`, () => { Hooks.once(`init`, () => {
Logger.log(`Initializing`); Logger.log(`Initializing`);
@ -74,10 +75,6 @@ Hooks.once(`init`, () => {
// #endregion // #endregion
// #region Sheets // #region Sheets
// Unregister core sheets
Items.unregisterSheet(`core`, ItemSheet);
Actors.unregisterSheet(`core`, ActorSheet);
// #region Actors // #region Actors
Actors.registerSheet(game.system.id, CombinedHeroSheet, { Actors.registerSheet(game.system.id, CombinedHeroSheet, {
makeDefault: true, makeDefault: true,
@ -114,6 +111,16 @@ Hooks.once(`init`, () => {
label: `RipCrypt.sheet-names.AllItemsSheetV1`, label: `RipCrypt.sheet-names.AllItemsSheetV1`,
themes: AllItemSheetV1.themes, themes: AllItemSheetV1.themes,
}); });
Items.registerSheet(game.system.id, ArmourSheet, {
makeDefault: true,
types: [`armour`, `shield`],
label: `RipCrypt.sheet-names.ArmourSheet`,
themes: ArmourSheet.themes,
});
Items.unregisterSheet(game.system.id, AllItemSheetV1, {
types: [`armour`, `shield`],
});
// #endregion // #endregion
// #endregion // #endregion
@ -121,6 +128,7 @@ Hooks.once(`init`, () => {
CONFIG.Actor.trackableAttributes.hero = HeroData.trackableAttributes; CONFIG.Actor.trackableAttributes.hero = HeroData.trackableAttributes;
// #endregion // #endregion
registerSockets();
registerCustomComponents(); registerCustomComponents();
Handlebars.registerHelper(helpers); Handlebars.registerHelper(helpers);
}); });

View file

@ -6,17 +6,18 @@ Hooks.once(`ready`, () => {
let defaultTab = game.settings.get(`ripcrypt`, `defaultTab`); let defaultTab = game.settings.get(`ripcrypt`, `defaultTab`);
if (defaultTab) { if (defaultTab) {
if (!ui.sidebar?.TABS?.[defaultTab]) { try {
Logger.error(`Couldn't find a sidebar tab with ID:`, defaultTab);
} else {
Logger.debug(`Switching sidebar tab to:`, defaultTab); Logger.debug(`Switching sidebar tab to:`, defaultTab);
ui.sidebar.activateTab(defaultTab); ui.sidebar.changeTab(defaultTab, `primary`);
}
catch {
Logger.error(`Failed to change to sidebar tab:`, defaultTab);
}; };
}; };
if (game.settings.get(`ripcrypt`, `devMode`)) { if (game.settings.get(`ripcrypt`, `devMode`)) {
ui.sidebar.expand(); ui.sidebar.expand();
if (game.paused) { game.togglePause() }; if (game.paused) { game.togglePause(false, { broadcast: true }) };
}; };
ui.delveDice.render({ force: true }); ui.delveDice.render({ force: true });
@ -28,7 +29,7 @@ Hooks.once(`ready`, () => {
combatConfig.turnMarker.src = filePath(`assets/turn-marker.png`); combatConfig.turnMarker.src = filePath(`assets/turn-marker.png`);
combatConfig.turnMarker.animation = `spinPulse`; combatConfig.turnMarker.animation = `spinPulse`;
game.settings.set(`core`, `combatTrackerConfig`, combatConfig); game.settings.set(`core`, `combatTrackerConfig`, combatConfig);
} };
game.settings.set(`ripcrypt`, `firstLoadFinished`, true); game.settings.set(`ripcrypt`, `firstLoadFinished`, true);
}); });

View file

@ -39,4 +39,14 @@ export function registerWorldSettings() {
}, },
}), }),
}); });
game.settings.register(`ripcrypt`, `allowUpdateSandsSocket`, {
name: `RipCrypt.setting.allowUpdateSandsSocket.name`,
hint: `RipCrypt.setting.allowUpdateSandsSocket.hint`,
scope: `world`,
config: true,
requiresReload: false,
type: Boolean,
default: true,
});
}; };

29
module/sockets/_index.mjs Normal file
View file

@ -0,0 +1,29 @@
import { localizer } from "../utils/Localizer.mjs";
import { Logger } from "../utils/Logger.mjs";
import { notify } from "./notify.mjs";
import { updateSands } from "./updateSands.mjs";
const events = {
notify,
updateSands,
};
export function registerSockets() {
Logger.info(`Setting up socket listener`);
game.socket.on(`system.ripcrypt`, (data, userID) => {
const { event, payload } = data ?? {};
if (event == null || payload === undefined) {
ui.notifications.error(localizer(`RipCrypt.notifs.error.invalid-socket`));
return;
};
if (events[event] == null) {
ui.notifications.error(localizer(`RipCrypt.notifs.error.unknown-socket-event`, { event }));
return;
};
const user = game.users.get(userID);
events[event](payload, user);
});
};

56
module/sockets/notify.mjs Normal file
View file

@ -0,0 +1,56 @@
import { localizer } from "../utils/Localizer.mjs";
export function notify(payload) {
// #region Payload Validity
const {
message,
users = [],
type = `info`,
permanent = false,
} = payload;
if (!message) {
ui.notifications.error(localizer(
`RipCrypt.notifs.error.malformed-socket-payload`,
{
event: `notify`,
details: `A message must be provided`,
},
));
return;
};
if (users && !Array.isArray(users)) {
ui.notifications.error(localizer(
`RipCrypt.notifs.error.malformed-socket-payload`,
{
event: `notify`,
details: `"users" must be an array of user IDs`,
},
));
return;
};
if (![`info`, `error`, `success`].includes(type)) {
ui.notifications.error(localizer(
`RipCrypt.notifs.error.malformed-socket-payload`,
{
event: `notify`,
details: `An invalid notification type was provided.`,
},
));
return;
}
// #endregion Payload Validity
// Act
if (users.length === 0 || users.includes(game.user.id)) {
ui.notifications[type]?.(
localizer(message),
{
console: false,
permanent,
},
);
};
};

View file

@ -0,0 +1,38 @@
import { clamp } from "../utils/clamp.mjs";
import { localizer } from "../utils/Localizer.mjs";
export function updateSands(payload) {
if (!game.user.isActiveGM) { return };
if (!game.settings.get(game.system.id, `allowUpdateSandsSocket`)) { return };
// Assert payload validity
const { value, delta } = payload;
if (value == null && delta == null) {
ui.notifications.error(localizer(
`RipCrypt.notifs.error.malformed-socket-payload`,
{
event: `updateSands`,
details: `Either value or delta must be provided`,
},
));
return;
};
// Take action
if (value != null) {
const initial = game.settings.get(game.system.id, `sandsOfFateInitial`);
let sands = clamp(0, value, initial);
if (sands === 0) {
ui.delveDice.alertCrypticEvent();
sands = initial;
};
game.settings.set(
game.system.id,
`sandsOfFate`,
sands,
);
}
else if (delta != null) {
ui.delveDice.sandsOfFateDelta(delta);
};
};

3
module/utils/clamp.mjs Normal file
View file

@ -0,0 +1,3 @@
export function clamp(min, ideal, max) {
return Math.max(min, Math.min(ideal, max));
};

14
package-lock.json generated
View file

@ -8,6 +8,7 @@
"@eslint/js": "^9.16.0", "@eslint/js": "^9.16.0",
"@foundryvtt/foundryvtt-cli": "^1.0.3", "@foundryvtt/foundryvtt-cli": "^1.0.3",
"@stylistic/eslint-plugin": "^2.12.0", "@stylistic/eslint-plugin": "^2.12.0",
"dotenv": "^17.2.3",
"eslint": "^9.16.0" "eslint": "^9.16.0"
} }
}, },
@ -775,6 +776,19 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/dotenv": {
"version": "17.2.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
"integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dunder-proto": { "node_modules/dunder-proto": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz",

View file

@ -3,9 +3,13 @@
"@eslint/js": "^9.16.0", "@eslint/js": "^9.16.0",
"@foundryvtt/foundryvtt-cli": "^1.0.3", "@foundryvtt/foundryvtt-cli": "^1.0.3",
"@stylistic/eslint-plugin": "^2.12.0", "@stylistic/eslint-plugin": "^2.12.0",
"dotenv": "^17.2.3",
"eslint": "^9.16.0" "eslint": "^9.16.0"
}, },
"scripts": { "scripts": {
"data:build": "node scripts/buildCompendia.mjs",
"data:extract": "node scripts/extractCompendia.mjs",
"link": "node scripts/linkFoundry.mjs",
"lint": "eslint --fix", "lint": "eslint --fix",
"lint:nofix": "eslint" "lint:nofix": "eslint"
} }

View file

@ -0,0 +1,23 @@
{
"type": "Item",
"folder": null,
"name": "Armour",
"color": "#04262a",
"sorting": "m",
"_id": "pZxc6QLgVWfnZlf7",
"description": "",
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994081362,
"modifiedTime": 1759994081362,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!folders!pZxc6QLgVWfnZlf7"
}

View file

@ -0,0 +1,41 @@
{
"folder": "BsNUpCnwmlhOWBhZ",
"name": "Breastplate",
"type": "armour",
"_id": "KQ6uyTPUOHuMTxDF",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 90,
"copper": null
},
"protection": 3,
"location": [
"body"
],
"equipped": false,
"weight": "heavy",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994459142,
"modifiedTime": 1759994468351,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!KQ6uyTPUOHuMTxDF"
}

View file

@ -0,0 +1,21 @@
{
"type": "Item",
"folder": "pZxc6QLgVWfnZlf7",
"name": "Heavy",
"color": "#06393f",
"sorting": "a",
"_id": "BsNUpCnwmlhOWBhZ",
"description": "",
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"lastModifiedBy": null
},
"_key": "!folders!BsNUpCnwmlhOWBhZ"
}

View file

@ -0,0 +1,44 @@
{
"folder": "RXPJBkzVxFnoT3Tm",
"name": "Heavy Shields",
"type": "shield",
"_id": "uUrCwjxV6Ihisb6V",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 50,
"copper": null
},
"protection": 1,
"location": [
"head",
"body",
"arms",
"legs"
],
"equipped": false,
"weight": "heavy",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994801184,
"modifiedTime": 1759994810086,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!uUrCwjxV6Ihisb6V"
}

View file

@ -0,0 +1,41 @@
{
"folder": "HRwiz1c1ZcQyPu4z",
"name": "Leather Cap",
"type": "armour",
"_id": "JMkV8kMnCXhW5KDh",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 10,
"copper": null
},
"protection": 1,
"location": [
"head"
],
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994118194,
"modifiedTime": 1759994130845,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!JMkV8kMnCXhW5KDh"
}

View file

@ -0,0 +1,41 @@
{
"folder": "HRwiz1c1ZcQyPu4z",
"name": "Leather, Hide Bracers",
"type": "armour",
"_id": "nz4DXXR4iU9CeMRA",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 10,
"copper": null
},
"protection": 1,
"location": [
"arms"
],
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994170968,
"modifiedTime": 1759994180395,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!nz4DXXR4iU9CeMRA"
}

View file

@ -0,0 +1,41 @@
{
"folder": "HRwiz1c1ZcQyPu4z",
"name": "Leather, Hide Jacket",
"type": "armour",
"_id": "zMyxSJ6VpaH3ddOO",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 20,
"copper": null
},
"protection": 1,
"location": [
"body"
],
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994151324,
"modifiedTime": 1759994160761,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!zMyxSJ6VpaH3ddOO"
}

View file

@ -0,0 +1,41 @@
{
"folder": "HRwiz1c1ZcQyPu4z",
"name": "Leather, Hide Leggings",
"type": "armour",
"_id": "14Omu9q2sMxW8GWB",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 20,
"copper": null
},
"protection": 1,
"location": [
"legs"
],
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994190989,
"modifiedTime": 1759994198011,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!14Omu9q2sMxW8GWB"
}

View file

@ -0,0 +1,21 @@
{
"type": "Item",
"folder": "pZxc6QLgVWfnZlf7",
"name": "Light",
"color": "#06393f",
"sorting": "a",
"_id": "HRwiz1c1ZcQyPu4z",
"description": "",
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"lastModifiedBy": null
},
"_key": "!folders!HRwiz1c1ZcQyPu4z"
}

View file

@ -0,0 +1,42 @@
{
"folder": "RXPJBkzVxFnoT3Tm",
"name": "Light Shields",
"type": "shield",
"_id": "a6vPAa25z8L9t79K",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 20,
"copper": null
},
"protection": 1,
"location": [
"head",
"arms"
],
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994549164,
"modifiedTime": 1759994761998,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!a6vPAa25z8L9t79K"
}

View file

@ -0,0 +1,42 @@
{
"folder": "cKN149ZGLqfyt0oi",
"name": "Mail, Link, Scale Coat",
"type": "armour",
"_id": "Sr40RFsPr2M0bTKK",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 180,
"copper": null
},
"protection": 2,
"location": [
"body",
"arms"
],
"equipped": false,
"weight": "modest",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994257751,
"modifiedTime": 1759994294312,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!Sr40RFsPr2M0bTKK"
}

View file

@ -0,0 +1,41 @@
{
"folder": "cKN149ZGLqfyt0oi",
"name": "Mail, Link, Scale Coif",
"type": "armour",
"_id": "HfG5Doxf7576Jgbt",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 180,
"copper": null
},
"protection": 2,
"location": [
"head"
],
"equipped": false,
"weight": "modest",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994210701,
"modifiedTime": 1759994221462,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!HfG5Doxf7576Jgbt"
}

View file

@ -0,0 +1,41 @@
{
"folder": "cKN149ZGLqfyt0oi",
"name": "Mail, Link, Scale Leggings",
"type": "armour",
"_id": "YBpElIVQ534pm3Mf",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 200,
"copper": null
},
"protection": 2,
"location": [
"legs"
],
"equipped": false,
"weight": null,
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994280754,
"modifiedTime": 1759994424980,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!YBpElIVQ534pm3Mf"
}

View file

@ -0,0 +1,41 @@
{
"folder": "cKN149ZGLqfyt0oi",
"name": "Mail, Link, Scale Shirt",
"type": "armour",
"_id": "wab6Bo8ngar4mBCN",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 90,
"copper": null
},
"protection": 2,
"location": [
"body"
],
"equipped": false,
"weight": "modest",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994235204,
"modifiedTime": 1759994246578,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!wab6Bo8ngar4mBCN"
}

View file

@ -0,0 +1,43 @@
{
"folder": "RXPJBkzVxFnoT3Tm",
"name": "Modest Shields",
"type": "shield",
"_id": "fyL8LZ8jpEQbjpM2",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 30,
"copper": null
},
"protection": 1,
"location": [
"head",
"body",
"arms"
],
"equipped": false,
"weight": "modest",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994777609,
"modifiedTime": 1759994784898,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!fyL8LZ8jpEQbjpM2"
}

View file

@ -0,0 +1,21 @@
{
"type": "Item",
"folder": "pZxc6QLgVWfnZlf7",
"name": "Modest",
"color": "#06393f",
"sorting": "a",
"_id": "cKN149ZGLqfyt0oi",
"description": "",
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"lastModifiedBy": null
},
"_key": "!folders!cKN149ZGLqfyt0oi"
}

View file

@ -0,0 +1,42 @@
{
"folder": "BsNUpCnwmlhOWBhZ",
"name": "Plate Bracers",
"type": "armour",
"_id": "e8JRJn5Blw3UrvnW",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 180,
"copper": null
},
"protection": 1,
"location": [
"body",
"arms"
],
"equipped": false,
"weight": "heavy",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994478040,
"modifiedTime": 1759994486947,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!e8JRJn5Blw3UrvnW"
}

View file

@ -0,0 +1,41 @@
{
"folder": "BsNUpCnwmlhOWBhZ",
"name": "Plate Leggings",
"type": "armour",
"_id": "v1y4RKGad2IXOu5e",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 200,
"copper": null
},
"protection": 3,
"location": [
"legs"
],
"equipped": false,
"weight": "heavy",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994497119,
"modifiedTime": 1759994506514,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!v1y4RKGad2IXOu5e"
}

View file

@ -0,0 +1,41 @@
{
"folder": "BsNUpCnwmlhOWBhZ",
"name": "Ring Coif, Helm",
"type": "armour",
"_id": "Z4NTsrX63JNjjZ8Z",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 440,
"copper": null
},
"protection": 3,
"location": [
"head"
],
"equipped": false,
"weight": "heavy",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994438779,
"modifiedTime": 1759994448846,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!Z4NTsrX63JNjjZ8Z"
}

View file

@ -0,0 +1,23 @@
{
"type": "Item",
"folder": null,
"name": "Shields",
"color": "#04262a",
"sorting": "m",
"_id": "RXPJBkzVxFnoT3Tm",
"description": "",
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994538745,
"modifiedTime": 1759994538745,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!folders!RXPJBkzVxFnoT3Tm"
}

View file

@ -0,0 +1,23 @@
{
"type": "Item",
"folder": null,
"name": "Ammo",
"color": "#04262a",
"sorting": "a",
"_id": "gvNPXXRBx2eGIzcU",
"description": "",
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993925940,
"modifiedTime": 1759993925940,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!folders!gvNPXXRBx2eGIzcU"
}

View file

@ -0,0 +1,47 @@
{
"folder": "dBAI76CApXH8qqjx",
"name": "Arming Sword",
"type": "weapon",
"_id": "xXUItaoHTQ2QiaX4",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 80,
"copper": null
},
"traits": [],
"range": {
"short": null,
"long": null
},
"damage": 2,
"wear": {
"value": 4,
"max": 4
},
"equipped": false,
"weight": "modest",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759992689272,
"modifiedTime": 1759992708712,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!xXUItaoHTQ2QiaX4"
}

View file

@ -0,0 +1,35 @@
{
"folder": "gvNPXXRBx2eGIzcU",
"name": "Arrow",
"type": "ammo",
"_id": "gN9JbmouUI7eOrSj",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 12,
"cost": {
"gold": null,
"silver": 3,
"copper": null
},
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993937035,
"modifiedTime": 1759993944077,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!gN9JbmouUI7eOrSj"
}

View file

@ -0,0 +1,49 @@
{
"folder": "mmd8siMKSLyOeILo",
"name": "Axe, Hammer, Pick",
"type": "weapon",
"_id": "cr35WzuPGDojuOuJ",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 10,
"copper": null
},
"traits": [
"Thrown"
],
"range": {
"short": null,
"long": null
},
"damage": 0,
"wear": {
"value": 2,
"max": 2
},
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759991980652,
"modifiedTime": 1759992328546,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!cr35WzuPGDojuOuJ"
}

View file

@ -0,0 +1,47 @@
{
"folder": "dBAI76CApXH8qqjx",
"name": "Battleaxe, Warhammer",
"type": "weapon",
"_id": "otIFI9TIDPWnT3cq",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 40,
"copper": null
},
"traits": [],
"range": {
"short": null,
"long": null
},
"damage": 3,
"wear": {
"value": 3,
"max": 3
},
"equipped": false,
"weight": "modest",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759992730036,
"modifiedTime": 1759992748112,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!otIFI9TIDPWnT3cq"
}

View file

@ -0,0 +1,35 @@
{
"folder": "gvNPXXRBx2eGIzcU",
"name": "Black Powder",
"type": "ammo",
"_id": "c86ht86Z9vOEBtNH",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 12,
"cost": {
"gold": null,
"silver": 40,
"copper": null
},
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994005587,
"modifiedTime": 1759994010244,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!c86ht86Z9vOEBtNH"
}

View file

@ -0,0 +1,35 @@
{
"folder": "gvNPXXRBx2eGIzcU",
"name": "Blowgun Darts",
"type": "ammo",
"_id": "FvtiEaQhJumPsCwb",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 12,
"cost": {
"gold": null,
"silver": 2,
"copper": null
},
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993963869,
"modifiedTime": 1759993969644,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!FvtiEaQhJumPsCwb"
}

View file

@ -0,0 +1,49 @@
{
"folder": "vPyj2cK1j66Zyrul",
"name": "Blowgun, Sling",
"type": "weapon",
"_id": "VrG2xer1quhjwUag",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 10,
"copper": null
},
"traits": [
"Ammo"
],
"range": {
"short": 5,
"long": 10
},
"damage": 1,
"wear": {
"value": 1,
"max": 1
},
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993449825,
"modifiedTime": 1759993468278,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!VrG2xer1quhjwUag"
}

View file

@ -0,0 +1,47 @@
{
"folder": "dBAI76CApXH8qqjx",
"name": "Broadsword",
"type": "weapon",
"_id": "I9QaJTU6O2E9WzUS",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 100,
"copper": null
},
"traits": [],
"range": {
"short": null,
"long": null
},
"damage": 3,
"wear": {
"value": 3,
"max": 3
},
"equipped": false,
"weight": "modest",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759992797603,
"modifiedTime": 1759992815028,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!I9QaJTU6O2E9WzUS"
}

View file

@ -0,0 +1,47 @@
{
"folder": "mmd8siMKSLyOeILo",
"name": "Club",
"type": "weapon",
"_id": "NlDJVbXeXRfoCZWp",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 2,
"copper": null
},
"traits": [],
"range": {
"short": null,
"long": null
},
"damage": 1,
"wear": {
"value": 2,
"max": 2
},
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759992392474,
"modifiedTime": 1759992619109,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!NlDJVbXeXRfoCZWp"
}

View file

@ -0,0 +1,49 @@
{
"folder": "8NNF9jBjpmPpmw1B",
"name": "Crossbow",
"type": "weapon",
"_id": "BNoYUrlpDk6oBeJt",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 90,
"copper": null
},
"traits": [
"Reload"
],
"range": {
"short": 10,
"long": 25
},
"damage": 3,
"wear": {
"value": 3,
"max": 3
},
"equipped": false,
"weight": "modest",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993621847,
"modifiedTime": 1759993694978,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!BNoYUrlpDk6oBeJt"
}

View file

@ -0,0 +1,35 @@
{
"folder": "gvNPXXRBx2eGIzcU",
"name": "Crossbow Bolts",
"type": "ammo",
"_id": "7cmLLV6o2pPAyyAg",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 12,
"cost": {
"gold": null,
"silver": 3,
"copper": null
},
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993977460,
"modifiedTime": 1759993982911,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!7cmLLV6o2pPAyyAg"
}

View file

@ -0,0 +1,49 @@
{
"folder": "dBAI76CApXH8qqjx",
"name": "Cutlass, Saber, Scimitar",
"type": "weapon",
"_id": "kSWrbdKdYIRxkWka",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 120,
"copper": null
},
"traits": [
"Agile"
],
"range": {
"short": null,
"long": null
},
"damage": 2,
"wear": {
"value": 2,
"max": 2
},
"equipped": false,
"weight": "modest",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759992861725,
"modifiedTime": 1759992882913,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!kSWrbdKdYIRxkWka"
}

View file

@ -0,0 +1,50 @@
{
"folder": "mmd8siMKSLyOeILo",
"name": "Dagger",
"type": "weapon",
"_id": "q8z2HptFaPmeHU9n",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 20,
"copper": null
},
"traits": [
"Agile",
"Thrown"
],
"range": {
"short": null,
"long": null
},
"damage": 1,
"wear": {
"value": 1,
"max": 1
},
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759992606052,
"modifiedTime": 1759992639628,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!q8z2HptFaPmeHU9n"
}

View file

@ -0,0 +1,49 @@
{
"folder": "vPyj2cK1j66Zyrul",
"name": "Darts",
"type": "weapon",
"_id": "rDxS6EJzg2zvSpxR",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 6,
"cost": {
"gold": null,
"silver": 30,
"copper": null
},
"traits": [
"Thrown"
],
"range": {
"short": 3,
"long": 6
},
"damage": 1,
"wear": {
"value": 1,
"max": 1
},
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993477954,
"modifiedTime": 1759993498878,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!rDxS6EJzg2zvSpxR"
}

View file

@ -0,0 +1,50 @@
{
"folder": "vPyj2cK1j66Zyrul",
"name": "Flintlock Pistol",
"type": "weapon",
"_id": "1vxM6KoEPrQ7pjcg",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 260,
"copper": null
},
"traits": [
"Reload",
"Loud"
],
"range": {
"short": 5,
"long": 10
},
"damage": 2,
"wear": {
"value": 2,
"max": 2
},
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993514814,
"modifiedTime": 1759993538727,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!1vxM6KoEPrQ7pjcg"
}

View file

@ -0,0 +1,50 @@
{
"folder": "IkSGLBUzPI9Jbcj7",
"name": "Flintlock Rifle",
"type": "weapon",
"_id": "xUGUgnjJsKUPgPpX",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 640,
"copper": null
},
"traits": [
"Reload",
"Loud"
],
"range": {
"short": 15,
"long": 30
},
"damage": 4,
"wear": {
"value": 3,
"max": 3
},
"equipped": false,
"weight": "heavy",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993769479,
"modifiedTime": 1759993794528,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!xUGUgnjJsKUPgPpX"
}

View file

@ -0,0 +1,35 @@
{
"folder": "gvNPXXRBx2eGIzcU",
"name": "Great Arrows",
"type": "ammo",
"_id": "FQ5VBjR0LdXf8Nh2",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 12,
"cost": {
"gold": null,
"silver": 12,
"copper": null
},
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993988701,
"modifiedTime": 1759993993428,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!FQ5VBjR0LdXf8Nh2"
}

View file

@ -0,0 +1,49 @@
{
"folder": "3tp9cwpArQNOpkAY",
"name": "Greataxe, Maul, Sword",
"type": "weapon",
"_id": "Pfm448hGPVSuyyrd",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 120,
"copper": null
},
"traits": [
"Long"
],
"range": {
"short": null,
"long": null
},
"damage": 4,
"wear": {
"value": 4,
"max": 4
},
"equipped": false,
"weight": "heavy",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993233713,
"modifiedTime": 1759993258414,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!Pfm448hGPVSuyyrd"
}

View file

@ -0,0 +1,49 @@
{
"folder": "IkSGLBUzPI9Jbcj7",
"name": "Greatbow",
"type": "weapon",
"_id": "N80F8sq9SaHrXBvS",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 320,
"copper": null
},
"traits": [
"Ammo"
],
"range": {
"short": 20,
"long": 40
},
"damage": 4,
"wear": {
"value": 4,
"max": 4
},
"equipped": false,
"weight": "heavy",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993806102,
"modifiedTime": 1759993839512,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!N80F8sq9SaHrXBvS"
}

View file

@ -0,0 +1,49 @@
{
"folder": "3tp9cwpArQNOpkAY",
"name": "Greatclub, Staff",
"type": "weapon",
"_id": "Ct9iNF9KPMGStSQo",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 30,
"copper": null
},
"traits": [
"Long"
],
"range": {
"short": null,
"long": null
},
"damage": 4,
"wear": {
"value": 4,
"max": 4
},
"equipped": false,
"weight": "heavy",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993276032,
"modifiedTime": 1759993292511,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!Ct9iNF9KPMGStSQo"
}

View file

@ -0,0 +1,49 @@
{
"folder": "vPyj2cK1j66Zyrul",
"name": "Hand Crossbow",
"type": "weapon",
"_id": "5sUeNom6dn6MaoAE",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 120,
"copper": null
},
"traits": [
"Reload"
],
"range": {
"short": 5,
"long": 10
},
"damage": 2,
"wear": {
"value": 1,
"max": 1
},
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993551322,
"modifiedTime": 1759993570977,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!5sUeNom6dn6MaoAE"
}

View file

@ -0,0 +1,23 @@
{
"type": "Item",
"folder": null,
"name": "Hand Weapons",
"color": "#04262a",
"sorting": "m",
"_id": "3mr6aZe43z7YBysA",
"description": "",
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759992349332,
"modifiedTime": 1759993205214,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!folders!3mr6aZe43z7YBysA"
}

View file

@ -0,0 +1,21 @@
{
"type": "Item",
"folder": "3mr6aZe43z7YBysA",
"name": "Heavy",
"color": "#06393f",
"sorting": "a",
"_id": "3tp9cwpArQNOpkAY",
"description": "",
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"lastModifiedBy": null
},
"_key": "!folders!3tp9cwpArQNOpkAY"
}

View file

@ -0,0 +1,49 @@
{
"folder": "IkSGLBUzPI9Jbcj7",
"name": "Heavy Crossbow",
"type": "weapon",
"_id": "xJzHTrYsJVL2WsSF",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 200,
"copper": null
},
"traits": [
"Ammo"
],
"range": {
"short": 20,
"long": 40
},
"damage": 4,
"wear": {
"value": 4,
"max": 4
},
"equipped": false,
"weight": "heavy",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993848517,
"modifiedTime": 1759993869078,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!xJzHTrYsJVL2WsSF"
}

View file

@ -0,0 +1,21 @@
{
"type": "Item",
"folder": "sjc6X9bKf7BY04Ar",
"name": "Heavy",
"color": "#06393f",
"sorting": "a",
"_id": "IkSGLBUzPI9Jbcj7",
"description": "",
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"lastModifiedBy": null
},
"_key": "!folders!IkSGLBUzPI9Jbcj7"
}

View file

@ -0,0 +1,49 @@
{
"folder": "mmd8siMKSLyOeILo",
"name": "Knife",
"type": "weapon",
"_id": "fv5D0xOJVpOwnyTn",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 6,
"copper": null
},
"traits": [
"Thrown"
],
"range": {
"short": null,
"long": null
},
"damage": 1,
"wear": {
"value": 1,
"max": 1
},
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759992653763,
"modifiedTime": 1759992669428,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!fv5D0xOJVpOwnyTn"
}

View file

@ -0,0 +1,23 @@
{
"type": "Item",
"folder": "3mr6aZe43z7YBysA",
"name": "Light",
"color": "#06393f",
"sorting": "a",
"_id": "mmd8siMKSLyOeILo",
"description": "",
"sort": -100000,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759991969340,
"modifiedTime": 1759993210807,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!folders!mmd8siMKSLyOeILo"
}

View file

@ -0,0 +1,21 @@
{
"type": "Item",
"folder": "sjc6X9bKf7BY04Ar",
"name": "Light",
"color": "#06393f",
"sorting": "a",
"_id": "vPyj2cK1j66Zyrul",
"description": "",
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"lastModifiedBy": null
},
"_key": "!folders!vPyj2cK1j66Zyrul"
}

View file

@ -0,0 +1,50 @@
{
"folder": "IkSGLBUzPI9Jbcj7",
"name": "Long Rifle",
"type": "weapon",
"_id": "dn1eja68NNuxB8K1",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 840,
"copper": null
},
"traits": [
"Reload",
"Loud"
],
"range": {
"short": 20,
"long": 40
},
"damage": 4,
"wear": {
"value": 3,
"max": 3
},
"equipped": false,
"weight": "heavy",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993878663,
"modifiedTime": 1759993902478,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!dn1eja68NNuxB8K1"
}

View file

@ -0,0 +1,49 @@
{
"folder": "8NNF9jBjpmPpmw1B",
"name": "Longbow",
"type": "weapon",
"_id": "oyVNU8XgMiZI0Uxr",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 80,
"copper": null
},
"traits": [
"Ammo"
],
"range": {
"short": 9,
"long": 30
},
"damage": 3,
"wear": {
"value": 3,
"max": 3
},
"equipped": false,
"weight": "modest",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993703627,
"modifiedTime": 1759993727378,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!oyVNU8XgMiZI0Uxr"
}

View file

@ -0,0 +1,49 @@
{
"folder": "dBAI76CApXH8qqjx",
"name": "Longsword",
"type": "weapon",
"_id": "7ezV0MrGA0duoGbl",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 220,
"copper": null
},
"traits": [
"Able"
],
"range": {
"short": null,
"long": null
},
"damage": 3,
"wear": {
"value": 3,
"max": 3
},
"equipped": false,
"weight": "modest",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759992894140,
"modifiedTime": 1759992913378,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!7ezV0MrGA0duoGbl"
}

View file

@ -0,0 +1,21 @@
{
"type": "Item",
"folder": "sjc6X9bKf7BY04Ar",
"name": "Modest",
"color": "#06393f",
"sorting": "a",
"_id": "8NNF9jBjpmPpmw1B",
"description": "",
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"lastModifiedBy": null
},
"_key": "!folders!8NNF9jBjpmPpmw1B"
}

View file

@ -0,0 +1,21 @@
{
"type": "Item",
"folder": "3mr6aZe43z7YBysA",
"name": "Modest",
"color": "#06393f",
"sorting": "a",
"_id": "dBAI76CApXH8qqjx",
"description": "",
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"lastModifiedBy": null
},
"_key": "!folders!dBAI76CApXH8qqjx"
}

View file

@ -0,0 +1,49 @@
{
"folder": "3tp9cwpArQNOpkAY",
"name": "Poleaxe, Glaive, Halberd",
"type": "weapon",
"_id": "j6C9IyebpKsk7M6R",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 60,
"copper": null
},
"traits": [
"Long"
],
"range": {
"short": null,
"long": null
},
"damage": 4,
"wear": {
"value": 4,
"max": 4
},
"equipped": false,
"weight": "heavy",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993308439,
"modifiedTime": 1759993324411,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!j6C9IyebpKsk7M6R"
}

Some files were not shown because too many files have changed in this diff Show more