diff --git a/.github/workflows/data-sync.yml b/.github/workflows/data-sync.yml new file mode 100644 index 000000000..736efa6d4 --- /dev/null +++ b/.github/workflows/data-sync.yml @@ -0,0 +1,69 @@ +name: PTR Data Sync +on: + push: + branches: + - master + workflow_dispatch: + +jobs: + export-to-json: + name: Export to JSON + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + path: ./Data/systems/ptu + - name: Install Foundry CLI + run: npm install -g @foundryvtt/foundryvtt-cli + - name: Setup Foundry CLI + run: | + fvtt configure set dataPath ./ + fvtt configure set currentPackageType System + fvtt configure set currentPackageId ptu + - name: Export Feats Compendium + run: | + fvtt package unpack abilities + fvtt package unpack capabilities + fvtt package unpack edges + fvtt package unpack effects + fvtt package unpack feats + fvtt package unpack habitats + fvtt package unpack items + fvtt package unpack macros + fvtt package unpack moves + fvtt package unpack poke-edges + fvtt package unpack references + fvtt package unpack species + fvtt package unpack spirit-actions + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: ptr-data-jsons + path: /home/runner/work/Foundry-Pokemon-Tabletop-United-System/Foundry-Pokemon-Tabletop-United-System/Data/systems/ptu/packs/**/*.json + retention-days: 1 + commit-to-ptr-data: + name: Commit to PTR-Data + needs: export-to-json + runs-on: ubuntu-latest + steps: + - name: Checkout PTR-Data Repository + uses: actions/checkout@v4 + with: + repository: pokemon-tabletop-reunited/PTR-Data + ref: master + token: ${{ secrets.ACTIONS_GITHUB_TOKEN }} + - name: Load Artifacts + uses: actions/download-artifact@v4 + with: + name: ptr-data-jsons + - name: Commit & Push + run: | + git config --global user.email "ashe@soaringnetwork.com" + git config --global user.name "Github Actions" + if [[ `git status --porcelain` ]]; then + git commit -am "Github-Actions Data Sync" + git push + else + echo "WARNING: No changes were detected. git commit push action aborted." + fi diff --git a/css/images/icons/automated_disabled_icon.png b/css/images/icons/automated_disabled_icon.png new file mode 100644 index 000000000..532cb99f6 Binary files /dev/null and b/css/images/icons/automated_disabled_icon.png differ diff --git a/images/sprites/1011.webp b/images/sprites/1011.webp new file mode 100644 index 000000000..783048a40 Binary files /dev/null and b/images/sprites/1011.webp differ diff --git a/images/sprites/1012.webp b/images/sprites/1012.webp new file mode 100644 index 000000000..b6116ee50 Binary files /dev/null and b/images/sprites/1012.webp differ diff --git a/images/sprites/1013.webp b/images/sprites/1013.webp new file mode 100644 index 000000000..5e8552459 Binary files /dev/null and b/images/sprites/1013.webp differ diff --git a/images/sprites/1014.webp b/images/sprites/1014.webp new file mode 100644 index 000000000..8e3fa4561 Binary files /dev/null and b/images/sprites/1014.webp differ diff --git a/images/sprites/1015.webp b/images/sprites/1015.webp new file mode 100644 index 000000000..096a9f5e6 Binary files /dev/null and b/images/sprites/1015.webp differ diff --git a/images/sprites/1016.webp b/images/sprites/1016.webp new file mode 100644 index 000000000..e35078839 Binary files /dev/null and b/images/sprites/1016.webp differ diff --git a/images/sprites/1017.webp b/images/sprites/1017.webp new file mode 100644 index 000000000..98aa8d79e Binary files /dev/null and b/images/sprites/1017.webp differ diff --git a/images/sprites/1017_cornerstone.webp b/images/sprites/1017_cornerstone.webp new file mode 100644 index 000000000..b37dba6b6 Binary files /dev/null and b/images/sprites/1017_cornerstone.webp differ diff --git a/images/sprites/1017_hearthflame.webp b/images/sprites/1017_hearthflame.webp new file mode 100644 index 000000000..658514f6b Binary files /dev/null and b/images/sprites/1017_hearthflame.webp differ diff --git a/images/sprites/1017_wellspring.webp b/images/sprites/1017_wellspring.webp new file mode 100644 index 000000000..0e09de5fd Binary files /dev/null and b/images/sprites/1017_wellspring.webp differ diff --git a/images/sprites/1018.webp b/images/sprites/1018.webp new file mode 100644 index 000000000..a5b012a20 Binary files /dev/null and b/images/sprites/1018.webp differ diff --git a/images/sprites/1019.webp b/images/sprites/1019.webp new file mode 100644 index 000000000..4f441731e Binary files /dev/null and b/images/sprites/1019.webp differ diff --git a/images/sprites/1020.webp b/images/sprites/1020.webp new file mode 100644 index 000000000..5383b84a9 Binary files /dev/null and b/images/sprites/1020.webp differ diff --git a/images/sprites/1021.webp b/images/sprites/1021.webp new file mode 100644 index 000000000..e980b2708 Binary files /dev/null and b/images/sprites/1021.webp differ diff --git a/images/sprites/1022.webp b/images/sprites/1022.webp new file mode 100644 index 000000000..4fabbaa20 Binary files /dev/null and b/images/sprites/1022.webp differ diff --git a/images/sprites/1023.webp b/images/sprites/1023.webp new file mode 100644 index 000000000..551061176 Binary files /dev/null and b/images/sprites/1023.webp differ diff --git a/images/sprites/1024_normal.webp b/images/sprites/1024_normal.webp new file mode 100644 index 000000000..f197c6cad Binary files /dev/null and b/images/sprites/1024_normal.webp differ diff --git a/images/sprites/1024_stellar.webp b/images/sprites/1024_stellar.webp new file mode 100644 index 000000000..0363dbdff Binary files /dev/null and b/images/sprites/1024_stellar.webp differ diff --git a/images/sprites/1024_terastal.webp b/images/sprites/1024_terastal.webp new file mode 100644 index 000000000..d03dfcfcc Binary files /dev/null and b/images/sprites/1024_terastal.webp differ diff --git a/images/sprites/1025.webp b/images/sprites/1025.webp new file mode 100644 index 000000000..ef7fc6ab3 Binary files /dev/null and b/images/sprites/1025.webp differ diff --git a/images/sprites/901_bloodmoon.webp b/images/sprites/901_bloodmoon.webp new file mode 100644 index 000000000..3be312c7e Binary files /dev/null and b/images/sprites/901_bloodmoon.webp differ diff --git a/packs/abilities/000545.ldb b/packs/abilities/000545.ldb deleted file mode 100644 index 12de92c0a..000000000 Binary files a/packs/abilities/000545.ldb and /dev/null differ diff --git a/packs/abilities/000597.log b/packs/abilities/001069.log similarity index 100% rename from packs/abilities/000597.log rename to packs/abilities/001069.log diff --git a/packs/abilities/001071.ldb b/packs/abilities/001071.ldb new file mode 100644 index 000000000..8f39781ed Binary files /dev/null and b/packs/abilities/001071.ldb differ diff --git a/packs/abilities/CURRENT b/packs/abilities/CURRENT index 23755cb30..3e6c4763a 100644 --- a/packs/abilities/CURRENT +++ b/packs/abilities/CURRENT @@ -1 +1 @@ -MANIFEST-000595 +MANIFEST-001067 diff --git a/packs/abilities/LOG b/packs/abilities/LOG index a0d656d3d..466a50c7b 100644 --- a/packs/abilities/LOG +++ b/packs/abilities/LOG @@ -1,7 +1,14 @@ -2023/11/06-12:17:57.317 6d34 Recovering log #593 -2023/11/06-12:17:57.322 6d34 Delete type=0 #593 -2023/11/06-12:17:57.322 6d34 Delete type=3 #591 -2023/11/06-12:19:30.527 6d70 Level-0 table #598: started -2023/11/06-12:19:30.527 6d70 Level-0 table #598: 0 bytes OK -2023/11/06-12:19:30.529 6d70 Delete type=0 #596 -2023/11/06-12:19:30.533 6d70 Manual compaction at level-0 from '!folders!rAhydZslb0d4Cvio' @ 72057594037927935 : 1 .. '!items!ztjoTjB46kPDTEfV' @ 0 : 0; will stop at (end) +2024/05/04-20:44:08.993 7d00 Recovering log #1064 +2024/05/04-20:44:08.997 7d00 Delete type=0 #1064 +2024/05/04-20:44:08.997 7d00 Delete type=3 #1062 +2024/05/04-20:48:30.725 a684 Level-0 table #1070: started +2024/05/04-20:48:30.733 a684 Level-0 table #1070: 262626 bytes OK +2024/05/04-20:48:30.735 a684 Delete type=0 #1068 +2024/05/04-20:48:30.761 a684 Manual compaction at level-0 from '!folders!MsdL8dYgE7JbY1ya' @ 72057594037927935 : 1 .. '!items!ztjoTjB46kPDTEfV' @ 0 : 0; will stop at '!items!ztjoTjB46kPDTEfV' @ 13882 : 1 +2024/05/04-20:48:30.761 a684 Compacting 1@0 + 1@1 files +2024/05/04-20:48:30.771 a684 Generated table #1071@0: 634 keys, 263160 bytes +2024/05/04-20:48:30.771 a684 Compacted 1@0 + 1@1 files => 263160 bytes +2024/05/04-20:48:30.773 a684 compacted to: files[ 0 1 0 0 0 0 0 ] +2024/05/04-20:48:30.774 a684 Delete type=2 #1066 +2024/05/04-20:48:30.774 a684 Delete type=2 #1070 +2024/05/04-20:48:30.783 a684 Manual compaction at level-0 from '!items!ztjoTjB46kPDTEfV' @ 13882 : 1 .. '!items!ztjoTjB46kPDTEfV' @ 0 : 0; will stop at (end) diff --git a/packs/abilities/LOG.old b/packs/abilities/LOG.old index 7a23bb04a..749e4035d 100644 --- a/packs/abilities/LOG.old +++ b/packs/abilities/LOG.old @@ -1,7 +1,14 @@ -2023/11/06-11:44:25.669 5940 Recovering log #589 -2023/11/06-11:44:25.673 5940 Delete type=0 #589 -2023/11/06-11:44:25.673 5940 Delete type=3 #587 -2023/11/06-12:14:26.843 378c Level-0 table #594: started -2023/11/06-12:14:26.843 378c Level-0 table #594: 0 bytes OK -2023/11/06-12:14:26.845 378c Delete type=0 #592 -2023/11/06-12:14:26.845 378c Manual compaction at level-0 from '!folders!rAhydZslb0d4Cvio' @ 72057594037927935 : 1 .. '!items!ztjoTjB46kPDTEfV' @ 0 : 0; will stop at (end) +2024/04/07-14:24:18.012 98e4 Recovering log #1059 +2024/04/07-14:24:18.017 98e4 Delete type=0 #1059 +2024/04/07-14:24:18.017 98e4 Delete type=3 #1057 +2024/04/07-16:38:21.681 ad90 Level-0 table #1065: started +2024/04/07-16:38:21.686 ad90 Level-0 table #1065: 236813 bytes OK +2024/04/07-16:38:21.688 ad90 Delete type=0 #1063 +2024/04/07-16:38:21.692 ad90 Manual compaction at level-0 from '!folders!MsdL8dYgE7JbY1ya' @ 72057594037927935 : 1 .. '!items!ztjoTjB46kPDTEfV' @ 0 : 0; will stop at '!items!zh8PTv5UFMRuL3tN' @ 13074 : 1 +2024/04/07-16:38:21.692 ad90 Compacting 1@0 + 1@1 files +2024/04/07-16:38:21.697 ad90 Generated table #1066@0: 634 keys, 255428 bytes +2024/04/07-16:38:21.697 ad90 Compacted 1@0 + 1@1 files => 255428 bytes +2024/04/07-16:38:21.698 ad90 compacted to: files[ 0 1 0 0 0 0 0 ] +2024/04/07-16:38:21.699 ad90 Delete type=2 #1061 +2024/04/07-16:38:21.699 ad90 Delete type=2 #1065 +2024/04/07-16:38:21.717 ad90 Manual compaction at level-0 from '!items!zh8PTv5UFMRuL3tN' @ 13074 : 1 .. '!items!ztjoTjB46kPDTEfV' @ 0 : 0; will stop at (end) diff --git a/packs/abilities/MANIFEST-000595 b/packs/abilities/MANIFEST-000595 deleted file mode 100644 index fed7218ab..000000000 Binary files a/packs/abilities/MANIFEST-000595 and /dev/null differ diff --git a/packs/abilities/MANIFEST-001067 b/packs/abilities/MANIFEST-001067 new file mode 100644 index 000000000..fed8c0246 Binary files /dev/null and b/packs/abilities/MANIFEST-001067 differ diff --git a/packs/capabilities/000540.ldb b/packs/capabilities/000540.ldb deleted file mode 100644 index fbd0b6aae..000000000 Binary files a/packs/capabilities/000540.ldb and /dev/null differ diff --git a/packs/capabilities/000591.log b/packs/capabilities/001042.log similarity index 100% rename from packs/capabilities/000591.log rename to packs/capabilities/001042.log diff --git a/packs/capabilities/001044.ldb b/packs/capabilities/001044.ldb new file mode 100644 index 000000000..8dcd8569a Binary files /dev/null and b/packs/capabilities/001044.ldb differ diff --git a/packs/capabilities/CURRENT b/packs/capabilities/CURRENT index 9c7b9eaf8..dd123db1c 100644 --- a/packs/capabilities/CURRENT +++ b/packs/capabilities/CURRENT @@ -1 +1 @@ -MANIFEST-000589 +MANIFEST-001040 diff --git a/packs/capabilities/LOG b/packs/capabilities/LOG index d07797099..4a3051836 100644 --- a/packs/capabilities/LOG +++ b/packs/capabilities/LOG @@ -1,7 +1,14 @@ -2023/11/06-12:17:57.370 6d34 Recovering log #587 -2023/11/06-12:17:57.376 6d34 Delete type=0 #587 -2023/11/06-12:17:57.376 6d34 Delete type=3 #585 -2023/11/06-12:19:30.539 6d70 Level-0 table #592: started -2023/11/06-12:19:30.539 6d70 Level-0 table #592: 0 bytes OK -2023/11/06-12:19:30.540 6d70 Delete type=0 #590 -2023/11/06-12:19:30.543 6d70 Manual compaction at level-0 from '!folders!4yLFZsM42OJ7FDLn' @ 72057594037927935 : 1 .. '!items!zzJWpOj40dLelBm5' @ 0 : 0; will stop at (end) +2024/05/04-20:44:09.079 a7a8 Recovering log #1038 +2024/05/04-20:44:09.084 a7a8 Delete type=0 #1038 +2024/05/04-20:44:09.084 a7a8 Delete type=3 #1036 +2024/05/04-20:48:30.927 a684 Level-0 table #1043: started +2024/05/04-20:48:30.931 a684 Level-0 table #1043: 70443 bytes OK +2024/05/04-20:48:30.933 a684 Delete type=0 #1041 +2024/05/04-20:48:30.950 a684 Manual compaction at level-0 from '!folders!4yLFZsM42OJ7FDLn' @ 72057594037927935 : 1 .. '!items!zzJWpOj40dLelBm5' @ 0 : 0; will stop at '!items!zzJWpOj40dLelBm5' @ 2030 : 1 +2024/05/04-20:48:30.950 a684 Compacting 1@0 + 1@1 files +2024/05/04-20:48:30.955 a684 Generated table #1044@0: 148 keys, 71182 bytes +2024/05/04-20:48:30.955 a684 Compacted 1@0 + 1@1 files => 71182 bytes +2024/05/04-20:48:30.956 a684 compacted to: files[ 0 1 0 0 0 0 0 ] +2024/05/04-20:48:30.956 a684 Delete type=2 #1001 +2024/05/04-20:48:30.957 a684 Delete type=2 #1043 +2024/05/04-20:48:31.023 a684 Manual compaction at level-0 from '!items!zzJWpOj40dLelBm5' @ 2030 : 1 .. '!items!zzJWpOj40dLelBm5' @ 0 : 0; will stop at (end) diff --git a/packs/capabilities/LOG.old b/packs/capabilities/LOG.old index 93c1eb384..ebcb12f8f 100644 --- a/packs/capabilities/LOG.old +++ b/packs/capabilities/LOG.old @@ -1,7 +1,7 @@ -2023/11/06-11:44:25.719 5940 Recovering log #583 -2023/11/06-11:44:25.723 5940 Delete type=0 #583 -2023/11/06-11:44:25.723 5940 Delete type=3 #581 -2023/11/06-12:14:26.881 378c Level-0 table #588: started -2023/11/06-12:14:26.881 378c Level-0 table #588: 0 bytes OK -2023/11/06-12:14:26.883 378c Delete type=0 #586 -2023/11/06-12:14:26.885 378c Manual compaction at level-0 from '!folders!4yLFZsM42OJ7FDLn' @ 72057594037927935 : 1 .. '!items!zzJWpOj40dLelBm5' @ 0 : 0; will stop at (end) +2024/04/07-14:24:18.068 98e4 Recovering log #1034 +2024/04/07-14:24:18.072 98e4 Delete type=0 #1034 +2024/04/07-14:24:18.072 98e4 Delete type=3 #1032 +2024/04/07-16:38:21.749 ad90 Level-0 table #1039: started +2024/04/07-16:38:21.749 ad90 Level-0 table #1039: 0 bytes OK +2024/04/07-16:38:21.751 ad90 Delete type=0 #1037 +2024/04/07-16:38:21.762 ad90 Manual compaction at level-0 from '!folders!4yLFZsM42OJ7FDLn' @ 72057594037927935 : 1 .. '!items!zzJWpOj40dLelBm5' @ 0 : 0; will stop at (end) diff --git a/packs/capabilities/MANIFEST-000589 b/packs/capabilities/MANIFEST-000589 deleted file mode 100644 index a6b26abd0..000000000 Binary files a/packs/capabilities/MANIFEST-000589 and /dev/null differ diff --git a/packs/capabilities/MANIFEST-001040 b/packs/capabilities/MANIFEST-001040 new file mode 100644 index 000000000..1e903ca79 Binary files /dev/null and b/packs/capabilities/MANIFEST-001040 differ diff --git a/packs/dex-entries/000011.ldb b/packs/dex-entries/000011.ldb deleted file mode 100644 index 8180a7ae8..000000000 Binary files a/packs/dex-entries/000011.ldb and /dev/null differ diff --git a/packs/dex-entries/000511.log b/packs/dex-entries/000909.log similarity index 100% rename from packs/dex-entries/000511.log rename to packs/dex-entries/000909.log diff --git a/packs/dex-entries/000911.ldb b/packs/dex-entries/000911.ldb new file mode 100644 index 000000000..71677b47f Binary files /dev/null and b/packs/dex-entries/000911.ldb differ diff --git a/packs/dex-entries/CURRENT b/packs/dex-entries/CURRENT index 0329ab5d7..429c0841e 100644 --- a/packs/dex-entries/CURRENT +++ b/packs/dex-entries/CURRENT @@ -1 +1 @@ -MANIFEST-000509 +MANIFEST-000907 diff --git a/packs/dex-entries/LOG b/packs/dex-entries/LOG index 7dc60ea62..dc0b45792 100644 --- a/packs/dex-entries/LOG +++ b/packs/dex-entries/LOG @@ -1,7 +1,14 @@ -2023/11/06-12:17:57.342 85dc Recovering log #507 -2023/11/06-12:17:57.346 85dc Delete type=0 #507 -2023/11/06-12:17:57.346 85dc Delete type=3 #505 -2023/11/06-12:19:30.535 6d70 Level-0 table #512: started -2023/11/06-12:19:30.535 6d70 Level-0 table #512: 0 bytes OK -2023/11/06-12:19:30.537 6d70 Delete type=0 #510 -2023/11/06-12:19:30.540 6d70 Manual compaction at level-0 from '!items!00NxeCghePCTFezd' @ 72057594037927935 : 1 .. '!items!zskGVWQJ7ih85MQp' @ 0 : 0; will stop at (end) +2024/05/04-20:44:09.039 83ec Recovering log #905 +2024/05/04-20:44:09.044 83ec Delete type=0 #905 +2024/05/04-20:44:09.044 83ec Delete type=3 #903 +2024/05/04-20:48:30.807 a684 Level-0 table #910: started +2024/05/04-20:48:30.825 a684 Level-0 table #910: 1032900 bytes OK +2024/05/04-20:48:30.827 a684 Delete type=0 #908 +2024/05/04-20:48:30.872 a684 Manual compaction at level-0 from '!items!00NxeCghePCTFezd' @ 72057594037927935 : 1 .. '!items!zskGVWQJ7ih85MQp' @ 0 : 0; will stop at '!items!zskGVWQJ7ih85MQp' @ 4000 : 1 +2024/05/04-20:48:30.872 a684 Compacting 1@0 + 1@1 files +2024/05/04-20:48:30.896 a684 Generated table #911@0: 1000 keys, 1032900 bytes +2024/05/04-20:48:30.896 a684 Compacted 1@0 + 1@1 files => 1032900 bytes +2024/05/04-20:48:30.898 a684 compacted to: files[ 0 1 0 0 0 0 0 ] +2024/05/04-20:48:30.898 a684 Delete type=2 #848 +2024/05/04-20:48:30.898 a684 Delete type=2 #910 +2024/05/04-20:48:30.920 a684 Manual compaction at level-0 from '!items!zskGVWQJ7ih85MQp' @ 4000 : 1 .. '!items!zskGVWQJ7ih85MQp' @ 0 : 0; will stop at (end) diff --git a/packs/dex-entries/LOG.old b/packs/dex-entries/LOG.old index 85d68a9ba..7abb59da2 100644 --- a/packs/dex-entries/LOG.old +++ b/packs/dex-entries/LOG.old @@ -1,7 +1,7 @@ -2023/11/06-11:44:25.693 85b4 Recovering log #503 -2023/11/06-11:44:25.697 85b4 Delete type=0 #503 -2023/11/06-11:44:25.697 85b4 Delete type=3 #501 -2023/11/06-12:14:26.850 378c Level-0 table #508: started -2023/11/06-12:14:26.850 378c Level-0 table #508: 0 bytes OK -2023/11/06-12:14:26.852 378c Delete type=0 #506 -2023/11/06-12:14:26.867 378c Manual compaction at level-0 from '!items!00NxeCghePCTFezd' @ 72057594037927935 : 1 .. '!items!zskGVWQJ7ih85MQp' @ 0 : 0; will stop at (end) +2024/04/07-14:24:18.042 8dd4 Recovering log #901 +2024/04/07-14:24:18.047 8dd4 Delete type=0 #901 +2024/04/07-14:24:18.047 8dd4 Delete type=3 #899 +2024/04/07-16:38:21.762 ad90 Level-0 table #906: started +2024/04/07-16:38:21.762 ad90 Level-0 table #906: 0 bytes OK +2024/04/07-16:38:21.764 ad90 Delete type=0 #904 +2024/04/07-16:38:21.770 ad90 Manual compaction at level-0 from '!items!00NxeCghePCTFezd' @ 72057594037927935 : 1 .. '!items!zskGVWQJ7ih85MQp' @ 0 : 0; will stop at (end) diff --git a/packs/dex-entries/MANIFEST-000509 b/packs/dex-entries/MANIFEST-000509 deleted file mode 100644 index 70789e5f6..000000000 Binary files a/packs/dex-entries/MANIFEST-000509 and /dev/null differ diff --git a/packs/dex-entries/MANIFEST-000907 b/packs/dex-entries/MANIFEST-000907 new file mode 100644 index 000000000..c32ec7803 Binary files /dev/null and b/packs/dex-entries/MANIFEST-000907 differ diff --git a/packs/edges/000530.ldb b/packs/edges/000530.ldb deleted file mode 100644 index c99e18627..000000000 Binary files a/packs/edges/000530.ldb and /dev/null differ diff --git a/packs/edges/000600.log b/packs/edges/001055.log similarity index 100% rename from packs/edges/000600.log rename to packs/edges/001055.log diff --git a/packs/edges/001057.ldb b/packs/edges/001057.ldb new file mode 100644 index 000000000..b180f71cb Binary files /dev/null and b/packs/edges/001057.ldb differ diff --git a/packs/edges/CURRENT b/packs/edges/CURRENT index 5f5d53473..3bfbabd3b 100644 --- a/packs/edges/CURRENT +++ b/packs/edges/CURRENT @@ -1 +1 @@ -MANIFEST-000598 +MANIFEST-001053 diff --git a/packs/edges/LOG b/packs/edges/LOG index b6776e92d..1bec279f7 100644 --- a/packs/edges/LOG +++ b/packs/edges/LOG @@ -1,7 +1,14 @@ -2023/11/06-12:17:57.352 85dc Recovering log #596 -2023/11/06-12:17:57.356 85dc Delete type=0 #596 -2023/11/06-12:17:57.356 85dc Delete type=3 #594 -2023/11/06-12:19:30.533 6d70 Level-0 table #601: started -2023/11/06-12:19:30.533 6d70 Level-0 table #601: 0 bytes OK -2023/11/06-12:19:30.535 6d70 Delete type=0 #599 -2023/11/06-12:19:30.537 6d70 Manual compaction at level-0 from '!folders!KvpdGqvbgL2nQUcL' @ 72057594037927935 : 1 .. '!items!zixXV4XTysXtm8vK' @ 0 : 0; will stop at (end) +2024/05/04-20:44:09.056 a7a8 Recovering log #1050 +2024/05/04-20:44:09.061 a7a8 Delete type=0 #1050 +2024/05/04-20:44:09.061 a7a8 Delete type=3 #1048 +2024/05/04-20:48:30.838 a684 Level-0 table #1056: started +2024/05/04-20:48:30.841 a684 Level-0 table #1056: 44934 bytes OK +2024/05/04-20:48:30.844 a684 Delete type=0 #1054 +2024/05/04-20:48:30.913 a684 Manual compaction at level-0 from '!folders!KvpdGqvbgL2nQUcL' @ 72057594037927935 : 1 .. '!items!zixXV4XTysXtm8vK' @ 0 : 0; will stop at '!items!zixXV4XTysXtm8vK' @ 1900 : 1 +2024/05/04-20:48:30.913 a684 Compacting 1@0 + 1@1 files +2024/05/04-20:48:30.917 a684 Generated table #1057@0: 86 keys, 45068 bytes +2024/05/04-20:48:30.917 a684 Compacted 1@0 + 1@1 files => 45068 bytes +2024/05/04-20:48:30.919 a684 compacted to: files[ 0 1 0 0 0 0 0 ] +2024/05/04-20:48:30.919 a684 Delete type=2 #1052 +2024/05/04-20:48:30.919 a684 Delete type=2 #1056 +2024/05/04-20:48:30.920 a684 Manual compaction at level-0 from '!items!zixXV4XTysXtm8vK' @ 1900 : 1 .. '!items!zixXV4XTysXtm8vK' @ 0 : 0; will stop at (end) diff --git a/packs/edges/LOG.old b/packs/edges/LOG.old index 18b9dc876..dbda0128d 100644 --- a/packs/edges/LOG.old +++ b/packs/edges/LOG.old @@ -1,7 +1,14 @@ -2023/11/06-11:44:25.704 76b8 Recovering log #592 -2023/11/06-11:44:25.708 76b8 Delete type=0 #592 -2023/11/06-11:44:25.708 76b8 Delete type=3 #590 -2023/11/06-12:14:26.852 378c Level-0 table #597: started -2023/11/06-12:14:26.852 378c Level-0 table #597: 0 bytes OK -2023/11/06-12:14:26.853 378c Delete type=0 #595 -2023/11/06-12:14:26.867 378c Manual compaction at level-0 from '!folders!KvpdGqvbgL2nQUcL' @ 72057594037927935 : 1 .. '!items!zixXV4XTysXtm8vK' @ 0 : 0; will stop at (end) +2024/04/07-14:24:18.053 8dd4 Recovering log #1046 +2024/04/07-14:24:18.057 8dd4 Delete type=0 #1046 +2024/04/07-14:24:18.057 8dd4 Delete type=3 #1044 +2024/04/07-16:38:21.712 ad90 Level-0 table #1051: started +2024/04/07-16:38:21.713 ad90 Level-0 table #1051: 3534 bytes OK +2024/04/07-16:38:21.715 ad90 Delete type=0 #1049 +2024/04/07-16:38:21.717 ad90 Manual compaction at level-0 from '!folders!KvpdGqvbgL2nQUcL' @ 72057594037927935 : 1 .. '!items!zixXV4XTysXtm8vK' @ 0 : 0; will stop at '!items!DlrPhx5QApDOSCy0' @ 1803 : 1 +2024/04/07-16:38:21.717 ad90 Compacting 1@0 + 1@1 files +2024/04/07-16:38:21.720 ad90 Generated table #1052@0: 86 keys, 44404 bytes +2024/04/07-16:38:21.720 ad90 Compacted 1@0 + 1@1 files => 44404 bytes +2024/04/07-16:38:21.722 ad90 compacted to: files[ 0 1 0 0 0 0 0 ] +2024/04/07-16:38:21.722 ad90 Delete type=2 #1013 +2024/04/07-16:38:21.722 ad90 Delete type=2 #1051 +2024/04/07-16:38:21.751 ad90 Manual compaction at level-0 from '!items!DlrPhx5QApDOSCy0' @ 1803 : 1 .. '!items!zixXV4XTysXtm8vK' @ 0 : 0; will stop at (end) diff --git a/packs/edges/MANIFEST-000598 b/packs/edges/MANIFEST-000598 deleted file mode 100644 index d15941fe0..000000000 Binary files a/packs/edges/MANIFEST-000598 and /dev/null differ diff --git a/packs/edges/MANIFEST-001053 b/packs/edges/MANIFEST-001053 new file mode 100644 index 000000000..54d745ff1 Binary files /dev/null and b/packs/edges/MANIFEST-001053 differ diff --git a/packs/effects/000594.ldb b/packs/effects/000594.ldb deleted file mode 100644 index 3c99e51c5..000000000 Binary files a/packs/effects/000594.ldb and /dev/null differ diff --git a/packs/effects/000597.log b/packs/effects/001071.log similarity index 100% rename from packs/effects/000597.log rename to packs/effects/001071.log diff --git a/packs/effects/001073.ldb b/packs/effects/001073.ldb new file mode 100644 index 000000000..8bd8255a4 Binary files /dev/null and b/packs/effects/001073.ldb differ diff --git a/packs/effects/CURRENT b/packs/effects/CURRENT index 23755cb30..16ccce9ab 100644 --- a/packs/effects/CURRENT +++ b/packs/effects/CURRENT @@ -1 +1 @@ -MANIFEST-000595 +MANIFEST-001069 diff --git a/packs/effects/LOG b/packs/effects/LOG index 5c2bd5a80..facb619fd 100644 --- a/packs/effects/LOG +++ b/packs/effects/LOG @@ -1,8 +1,15 @@ -2023/11/06-12:17:57.401 6d34 Recovering log #592 -2023/11/06-12:17:57.406 6d34 Delete type=0 #592 -2023/11/06-12:17:57.406 6d34 Delete type=3 #590 -2023/11/06-12:19:30.545 6d70 Level-0 table #598: started -2023/11/06-12:19:30.545 6d70 Level-0 table #598: 0 bytes OK -2023/11/06-12:19:30.546 6d70 Delete type=0 #596 -2023/11/06-12:19:30.551 6d70 Manual compaction at level-0 from '!folders!845I85D1jrVh7RBM' @ 72057594037927935 : 1 .. '!items!znvgEviVbL2WnCGJ' @ 0 : 0; will stop at (end) -2023/11/06-12:19:30.553 6d70 Manual compaction at level-1 from '!folders!845I85D1jrVh7RBM' @ 72057594037927935 : 1 .. '!items!znvgEviVbL2WnCGJ' @ 0 : 0; will stop at (end) +2024/05/04-20:44:09.123 a7a8 Recovering log #1066 +2024/05/04-20:44:09.128 a7a8 Delete type=0 #1066 +2024/05/04-20:44:09.128 a7a8 Delete type=3 #1064 +2024/05/04-20:48:31.025 a684 Level-0 table #1072: started +2024/05/04-20:48:31.034 a684 Level-0 table #1072: 405797 bytes OK +2024/05/04-20:48:31.038 a684 Delete type=0 #1070 +2024/05/04-20:48:31.172 a684 Manual compaction at level-0 from '!folders!0TZD9QPsChqsbVo7' @ 72057594037927935 : 1 .. '!items!znvgEviVbL2WnCGJ' @ 0 : 0; will stop at (end) +2024/05/04-20:48:31.311 a684 Manual compaction at level-1 from '!folders!0TZD9QPsChqsbVo7' @ 72057594037927935 : 1 .. '!items!znvgEviVbL2WnCGJ' @ 0 : 0; will stop at '!items!znvgEviVbL2WnCGJ' @ 4874 : 1 +2024/05/04-20:48:31.311 a684 Compacting 1@1 + 1@2 files +2024/05/04-20:48:31.321 a684 Generated table #1073@1: 673 keys, 411777 bytes +2024/05/04-20:48:31.321 a684 Compacted 1@1 + 1@2 files => 411777 bytes +2024/05/04-20:48:31.323 a684 compacted to: files[ 0 0 1 0 0 0 0 ] +2024/05/04-20:48:31.323 a684 Delete type=2 #1068 +2024/05/04-20:48:31.323 a684 Delete type=2 #1072 +2024/05/04-20:48:31.334 a684 Manual compaction at level-1 from '!items!znvgEviVbL2WnCGJ' @ 4874 : 1 .. '!items!znvgEviVbL2WnCGJ' @ 0 : 0; will stop at (end) diff --git a/packs/effects/LOG.old b/packs/effects/LOG.old index 874764737..005be4232 100644 --- a/packs/effects/LOG.old +++ b/packs/effects/LOG.old @@ -1,15 +1,15 @@ -2023/11/06-11:44:25.741 5940 Recovering log #588 -2023/11/06-11:44:25.744 5940 Delete type=0 #588 -2023/11/06-11:44:25.744 5940 Delete type=3 #586 -2023/11/06-12:14:26.887 378c Level-0 table #593: started -2023/11/06-12:14:26.890 378c Level-0 table #593: 1902 bytes OK -2023/11/06-12:14:26.892 378c Delete type=0 #591 -2023/11/06-12:14:26.901 378c Manual compaction at level-0 from '!folders!845I85D1jrVh7RBM' @ 72057594037927935 : 1 .. '!items!znvgEviVbL2WnCGJ' @ 0 : 0; will stop at (end) -2023/11/06-12:14:26.905 378c Manual compaction at level-1 from '!folders!845I85D1jrVh7RBM' @ 72057594037927935 : 1 .. '!items!znvgEviVbL2WnCGJ' @ 0 : 0; will stop at '!items!gsePGwVgjU3VgjIK' @ 702 : 1 -2023/11/06-12:14:26.905 378c Compacting 1@1 + 1@2 files -2023/11/06-12:14:26.909 378c Generated table #594@1: 145 keys, 78904 bytes -2023/11/06-12:14:26.909 378c Compacted 1@1 + 1@2 files => 78904 bytes -2023/11/06-12:14:26.912 378c compacted to: files[ 0 0 1 0 0 0 0 ] -2023/11/06-12:14:26.912 378c Delete type=2 #585 -2023/11/06-12:14:26.912 378c Delete type=2 #593 -2023/11/06-12:14:26.919 378c Manual compaction at level-1 from '!items!gsePGwVgjU3VgjIK' @ 702 : 1 .. '!items!znvgEviVbL2WnCGJ' @ 0 : 0; will stop at (end) +2024/04/07-14:24:18.100 98e4 Recovering log #1061 +2024/04/07-14:24:18.104 98e4 Delete type=0 #1061 +2024/04/07-14:24:18.104 98e4 Delete type=3 #1059 +2024/04/07-16:38:21.781 ad90 Level-0 table #1067: started +2024/04/07-16:38:21.784 ad90 Level-0 table #1067: 168188 bytes OK +2024/04/07-16:38:21.787 ad90 Delete type=0 #1065 +2024/04/07-16:38:21.860 ad90 Manual compaction at level-0 from '!folders!0TZD9QPsChqsbVo7' @ 72057594037927935 : 1 .. '!items!znvgEviVbL2WnCGJ' @ 0 : 0; will stop at (end) +2024/04/07-16:38:21.862 ad90 Manual compaction at level-1 from '!folders!0TZD9QPsChqsbVo7' @ 72057594037927935 : 1 .. '!items!znvgEviVbL2WnCGJ' @ 0 : 0; will stop at '!items!zYlXw6cdHtfeKo58' @ 3857 : 1 +2024/04/07-16:38:21.862 ad90 Compacting 1@1 + 1@2 files +2024/04/07-16:38:21.869 ad90 Generated table #1068@1: 673 keys, 397267 bytes +2024/04/07-16:38:21.869 ad90 Compacted 1@1 + 1@2 files => 397267 bytes +2024/04/07-16:38:21.871 ad90 compacted to: files[ 0 0 1 0 0 0 0 ] +2024/04/07-16:38:21.871 ad90 Delete type=2 #1063 +2024/04/07-16:38:21.871 ad90 Delete type=2 #1067 +2024/04/07-16:38:21.873 ad90 Manual compaction at level-1 from '!items!zYlXw6cdHtfeKo58' @ 3857 : 1 .. '!items!znvgEviVbL2WnCGJ' @ 0 : 0; will stop at (end) diff --git a/packs/effects/MANIFEST-000595 b/packs/effects/MANIFEST-000595 deleted file mode 100644 index 793e33170..000000000 Binary files a/packs/effects/MANIFEST-000595 and /dev/null differ diff --git a/packs/effects/MANIFEST-001069 b/packs/effects/MANIFEST-001069 new file mode 100644 index 000000000..eab7945dd Binary files /dev/null and b/packs/effects/MANIFEST-001069 differ diff --git a/packs/feats/000612.ldb b/packs/feats/000612.ldb deleted file mode 100644 index 14a1bb179..000000000 Binary files a/packs/feats/000612.ldb and /dev/null differ diff --git a/packs/feats/000615.log b/packs/feats/001087.log similarity index 100% rename from packs/feats/000615.log rename to packs/feats/001087.log diff --git a/packs/feats/001089.ldb b/packs/feats/001089.ldb new file mode 100644 index 000000000..b706b3fb4 Binary files /dev/null and b/packs/feats/001089.ldb differ diff --git a/packs/feats/CURRENT b/packs/feats/CURRENT index 25a646369..50d467c31 100644 --- a/packs/feats/CURRENT +++ b/packs/feats/CURRENT @@ -1 +1 @@ -MANIFEST-000613 +MANIFEST-001085 diff --git a/packs/feats/LOG b/packs/feats/LOG index 22dfada41..d9c7c6425 100644 --- a/packs/feats/LOG +++ b/packs/feats/LOG @@ -1,7 +1,14 @@ -2023/11/06-12:17:57.333 85dc Recovering log #610 -2023/11/06-12:17:57.337 85dc Delete type=0 #610 -2023/11/06-12:17:57.337 85dc Delete type=3 #608 -2023/11/06-12:19:30.531 6d70 Level-0 table #616: started -2023/11/06-12:19:30.531 6d70 Level-0 table #616: 0 bytes OK -2023/11/06-12:19:30.533 6d70 Delete type=0 #614 -2023/11/06-12:19:30.535 6d70 Manual compaction at level-0 from '!folders!NlEB7XJnDWyGbZnQ' @ 72057594037927935 : 1 .. '!items!zy3F5IZmiosjU2HJ' @ 0 : 0; will stop at (end) +2024/05/04-20:44:09.020 7d00 Recovering log #1082 +2024/05/04-20:44:09.025 7d00 Delete type=0 #1082 +2024/05/04-20:44:09.025 7d00 Delete type=3 #1080 +2024/05/04-20:48:30.784 a684 Level-0 table #1088: started +2024/05/04-20:48:30.804 a684 Level-0 table #1088: 983824 bytes OK +2024/05/04-20:48:30.806 a684 Delete type=0 #1086 +2024/05/04-20:48:30.844 a684 Manual compaction at level-0 from '!folders!18LvSoVBoPmxJnOL' @ 72057594037927935 : 1 .. '!items!zy3F5IZmiosjU2HJ' @ 0 : 0; will stop at '!items!zy3F5IZmiosjU2HJ' @ 25753 : 1 +2024/05/04-20:48:30.844 a684 Compacting 1@0 + 1@1 files +2024/05/04-20:48:30.869 a684 Generated table #1089@0: 1402 keys, 991596 bytes +2024/05/04-20:48:30.869 a684 Compacted 1@0 + 1@1 files => 991596 bytes +2024/05/04-20:48:30.871 a684 compacted to: files[ 0 1 0 0 0 0 0 ] +2024/05/04-20:48:30.871 a684 Delete type=2 #1084 +2024/05/04-20:48:30.872 a684 Delete type=2 #1088 +2024/05/04-20:48:30.919 a684 Manual compaction at level-0 from '!items!zy3F5IZmiosjU2HJ' @ 25753 : 1 .. '!items!zy3F5IZmiosjU2HJ' @ 0 : 0; will stop at (end) diff --git a/packs/feats/LOG.old b/packs/feats/LOG.old index 17f4297f8..255f60681 100644 --- a/packs/feats/LOG.old +++ b/packs/feats/LOG.old @@ -1,14 +1,14 @@ -2023/11/06-11:44:25.685 76b8 Recovering log #606 -2023/11/06-11:44:25.689 76b8 Delete type=0 #606 -2023/11/06-11:44:25.689 76b8 Delete type=3 #604 -2023/11/06-12:14:26.854 378c Level-0 table #611: started -2023/11/06-12:14:26.855 378c Level-0 table #611: 7091 bytes OK -2023/11/06-12:14:26.857 378c Delete type=0 #609 -2023/11/06-12:14:26.867 378c Manual compaction at level-0 from '!folders!NlEB7XJnDWyGbZnQ' @ 72057594037927935 : 1 .. '!items!zy3F5IZmiosjU2HJ' @ 0 : 0; will stop at '!items!xdpnroki9dAARIRb' @ 7334 : 1 -2023/11/06-12:14:26.867 378c Compacting 1@0 + 1@1 files -2023/11/06-12:14:26.877 378c Generated table #612@0: 718 keys, 466273 bytes -2023/11/06-12:14:26.877 378c Compacted 1@0 + 1@1 files => 466273 bytes -2023/11/06-12:14:26.878 378c compacted to: files[ 0 1 0 0 0 0 0 ] -2023/11/06-12:14:26.878 378c Delete type=2 #563 -2023/11/06-12:14:26.879 378c Delete type=2 #611 -2023/11/06-12:14:26.883 378c Manual compaction at level-0 from '!items!xdpnroki9dAARIRb' @ 7334 : 1 .. '!items!zy3F5IZmiosjU2HJ' @ 0 : 0; will stop at (end) +2024/04/07-14:24:18.031 8dd4 Recovering log #1077 +2024/04/07-14:24:18.035 8dd4 Delete type=0 #1077 +2024/04/07-14:24:18.035 8dd4 Delete type=3 #1075 +2024/04/07-16:38:21.715 ad90 Level-0 table #1083: started +2024/04/07-16:38:21.715 ad90 Level-0 table #1083: 1429 bytes OK +2024/04/07-16:38:21.717 ad90 Delete type=0 #1081 +2024/04/07-16:38:21.722 ad90 Manual compaction at level-0 from '!folders!18LvSoVBoPmxJnOL' @ 72057594037927935 : 1 .. '!items!zy3F5IZmiosjU2HJ' @ 0 : 0; will stop at '!items!wofek8HCsEo9ILtw' @ 24429 : 1 +2024/04/07-16:38:21.722 ad90 Compacting 1@0 + 1@1 files +2024/04/07-16:38:21.736 ad90 Generated table #1084@0: 1402 keys, 962453 bytes +2024/04/07-16:38:21.736 ad90 Compacted 1@0 + 1@1 files => 962453 bytes +2024/04/07-16:38:21.738 ad90 compacted to: files[ 0 1 0 0 0 0 0 ] +2024/04/07-16:38:21.738 ad90 Delete type=2 #1079 +2024/04/07-16:38:21.738 ad90 Delete type=2 #1083 +2024/04/07-16:38:21.751 ad90 Manual compaction at level-0 from '!items!wofek8HCsEo9ILtw' @ 24429 : 1 .. '!items!zy3F5IZmiosjU2HJ' @ 0 : 0; will stop at (end) diff --git a/packs/feats/MANIFEST-000613 b/packs/feats/MANIFEST-000613 deleted file mode 100644 index e896dc04e..000000000 Binary files a/packs/feats/MANIFEST-000613 and /dev/null differ diff --git a/packs/feats/MANIFEST-001085 b/packs/feats/MANIFEST-001085 new file mode 100644 index 000000000..b3d8bbf65 Binary files /dev/null and b/packs/feats/MANIFEST-001085 differ diff --git a/packs/habitats/000225.ldb b/packs/habitats/000225.ldb deleted file mode 100644 index 168173f59..000000000 Binary files a/packs/habitats/000225.ldb and /dev/null differ diff --git a/packs/habitats/000580.log b/packs/habitats/001023.log similarity index 100% rename from packs/habitats/000580.log rename to packs/habitats/001023.log diff --git a/packs/habitats/001025.ldb b/packs/habitats/001025.ldb new file mode 100644 index 000000000..f863931fc Binary files /dev/null and b/packs/habitats/001025.ldb differ diff --git a/packs/habitats/CURRENT b/packs/habitats/CURRENT index fc730a2c9..b9a0323ff 100644 --- a/packs/habitats/CURRENT +++ b/packs/habitats/CURRENT @@ -1 +1 @@ -MANIFEST-000578 +MANIFEST-001021 diff --git a/packs/habitats/LOG b/packs/habitats/LOG index 0d6578447..d4191530c 100644 --- a/packs/habitats/LOG +++ b/packs/habitats/LOG @@ -1,7 +1,14 @@ -2023/11/06-12:17:57.384 6d34 Recovering log #576 -2023/11/06-12:17:57.388 6d34 Delete type=0 #576 -2023/11/06-12:17:57.388 6d34 Delete type=3 #574 -2023/11/06-12:19:30.543 6d70 Level-0 table #581: started -2023/11/06-12:19:30.543 6d70 Level-0 table #581: 0 bytes OK -2023/11/06-12:19:30.544 6d70 Delete type=0 #579 -2023/11/06-12:19:30.550 6d70 Manual compaction at level-0 from '!tables!0DJQye8xDvA2tYYY' @ 72057594037927935 : 1 .. '!tables.results!yedzrd0JurrPv9Me.zKvMuUG3hHYIyZQp' @ 0 : 0; will stop at (end) +2024/05/04-20:44:09.098 7d00 Recovering log #1019 +2024/05/04-20:44:09.103 7d00 Delete type=0 #1019 +2024/05/04-20:44:09.103 7d00 Delete type=3 #1017 +2024/05/04-20:48:30.933 a684 Level-0 table #1024: started +2024/05/04-20:48:30.943 a684 Level-0 table #1024: 311006 bytes OK +2024/05/04-20:48:30.944 a684 Delete type=0 #1022 +2024/05/04-20:48:30.957 a684 Manual compaction at level-0 from '!tables!0DJQye8xDvA2tYYY' @ 72057594037927935 : 1 .. '!tables.results!yedzrd0JurrPv9Me.zKvMuUG3hHYIyZQp' @ 0 : 0; will stop at '!tables.results!yedzrd0JurrPv9Me.zKvMuUG3hHYIyZQp' @ 33071 : 1 +2024/05/04-20:48:30.957 a684 Compacting 1@0 + 1@1 files +2024/05/04-20:48:30.967 a684 Generated table #1025@0: 1962 keys, 235322 bytes +2024/05/04-20:48:30.967 a684 Compacted 1@0 + 1@1 files => 235322 bytes +2024/05/04-20:48:30.969 a684 compacted to: files[ 0 1 0 0 0 0 0 ] +2024/05/04-20:48:30.969 a684 Delete type=2 #978 +2024/05/04-20:48:30.970 a684 Delete type=2 #1024 +2024/05/04-20:48:31.023 a684 Manual compaction at level-0 from '!tables.results!yedzrd0JurrPv9Me.zKvMuUG3hHYIyZQp' @ 33071 : 1 .. '!tables.results!yedzrd0JurrPv9Me.zKvMuUG3hHYIyZQp' @ 0 : 0; will stop at (end) diff --git a/packs/habitats/LOG.old b/packs/habitats/LOG.old index 4aa565117..36e166641 100644 --- a/packs/habitats/LOG.old +++ b/packs/habitats/LOG.old @@ -1,7 +1,7 @@ -2023/11/06-11:44:25.731 5940 Recovering log #572 -2023/11/06-11:44:25.734 5940 Delete type=0 #572 -2023/11/06-11:44:25.735 5940 Delete type=3 #570 -2023/11/06-12:14:26.885 378c Level-0 table #577: started -2023/11/06-12:14:26.885 378c Level-0 table #577: 0 bytes OK -2023/11/06-12:14:26.887 378c Delete type=0 #575 -2023/11/06-12:14:26.894 378c Manual compaction at level-0 from '!tables!0DJQye8xDvA2tYYY' @ 72057594037927935 : 1 .. '!tables.results!yedzrd0JurrPv9Me.zKvMuUG3hHYIyZQp' @ 0 : 0; will stop at (end) +2024/04/07-14:24:18.081 98e4 Recovering log #1015 +2024/04/07-14:24:18.086 98e4 Delete type=0 #1015 +2024/04/07-14:24:18.086 98e4 Delete type=3 #1013 +2024/04/07-16:38:21.768 ad90 Level-0 table #1020: started +2024/04/07-16:38:21.768 ad90 Level-0 table #1020: 0 bytes OK +2024/04/07-16:38:21.770 ad90 Delete type=0 #1018 +2024/04/07-16:38:21.776 ad90 Manual compaction at level-0 from '!tables!0DJQye8xDvA2tYYY' @ 72057594037927935 : 1 .. '!tables.results!yedzrd0JurrPv9Me.zKvMuUG3hHYIyZQp' @ 0 : 0; will stop at (end) diff --git a/packs/habitats/MANIFEST-000578 b/packs/habitats/MANIFEST-000578 deleted file mode 100644 index 298bc179e..000000000 Binary files a/packs/habitats/MANIFEST-000578 and /dev/null differ diff --git a/packs/habitats/MANIFEST-001021 b/packs/habitats/MANIFEST-001021 new file mode 100644 index 000000000..3d2243906 Binary files /dev/null and b/packs/habitats/MANIFEST-001021 differ diff --git a/packs/items/000578.ldb b/packs/items/000578.ldb deleted file mode 100644 index ff15da4b2..000000000 Binary files a/packs/items/000578.ldb and /dev/null differ diff --git a/packs/items/000589.log b/packs/items/001052.log similarity index 100% rename from packs/items/000589.log rename to packs/items/001052.log diff --git a/packs/items/001054.ldb b/packs/items/001054.ldb new file mode 100644 index 000000000..f2ba227a1 Binary files /dev/null and b/packs/items/001054.ldb differ diff --git a/packs/items/CURRENT b/packs/items/CURRENT index 474021af3..9bffde85c 100644 --- a/packs/items/CURRENT +++ b/packs/items/CURRENT @@ -1 +1 @@ -MANIFEST-000587 +MANIFEST-001050 diff --git a/packs/items/LOG b/packs/items/LOG index 53a0d32f8..ccb884132 100644 --- a/packs/items/LOG +++ b/packs/items/LOG @@ -1,7 +1,14 @@ -2023/11/06-12:17:57.358 6d34 Recovering log #585 -2023/11/06-12:17:57.366 6d34 Delete type=0 #585 -2023/11/06-12:17:57.366 6d34 Delete type=3 #583 -2023/11/06-12:19:30.537 6d70 Level-0 table #590: started -2023/11/06-12:19:30.537 6d70 Level-0 table #590: 0 bytes OK -2023/11/06-12:19:30.538 6d70 Delete type=0 #588 -2023/11/06-12:19:30.543 6d70 Manual compaction at level-0 from '!folders!IT5MlkeTVPcxSRHe' @ 72057594037927935 : 1 .. '!items!zyrvCDXykruLOC8Y' @ 0 : 0; will stop at (end) +2024/05/04-20:44:09.065 83ec Recovering log #1047 +2024/05/04-20:44:09.070 83ec Delete type=0 #1047 +2024/05/04-20:44:09.071 83ec Delete type=3 #1045 +2024/05/04-20:48:30.828 a684 Level-0 table #1053: started +2024/05/04-20:48:30.836 a684 Level-0 table #1053: 396211 bytes OK +2024/05/04-20:48:30.838 a684 Delete type=0 #1051 +2024/05/04-20:48:30.899 a684 Manual compaction at level-0 from '!folders!55ncmIvjbjv3PZZ8' @ 72057594037927935 : 1 .. '!items!zwuNF6cJs843IqJj' @ 0 : 0; will stop at '!items!zwuNF6cJs843IqJj' @ 16776 : 1 +2024/05/04-20:48:30.899 a684 Compacting 1@0 + 1@1 files +2024/05/04-20:48:30.911 a684 Generated table #1054@0: 854 keys, 397630 bytes +2024/05/04-20:48:30.911 a684 Compacted 1@0 + 1@1 files => 397630 bytes +2024/05/04-20:48:30.913 a684 compacted to: files[ 0 1 0 0 0 0 0 ] +2024/05/04-20:48:30.913 a684 Delete type=2 #1049 +2024/05/04-20:48:30.913 a684 Delete type=2 #1053 +2024/05/04-20:48:30.920 a684 Manual compaction at level-0 from '!items!zwuNF6cJs843IqJj' @ 16776 : 1 .. '!items!zwuNF6cJs843IqJj' @ 0 : 0; will stop at (end) diff --git a/packs/items/LOG.old b/packs/items/LOG.old index 43b4f0540..25d6cb883 100644 --- a/packs/items/LOG.old +++ b/packs/items/LOG.old @@ -1,7 +1,14 @@ -2023/11/06-11:44:25.709 5940 Recovering log #581 -2023/11/06-11:44:25.714 5940 Delete type=0 #581 -2023/11/06-11:44:25.714 5940 Delete type=3 #579 -2023/11/06-12:14:26.879 378c Level-0 table #586: started -2023/11/06-12:14:26.879 378c Level-0 table #586: 0 bytes OK -2023/11/06-12:14:26.881 378c Delete type=0 #584 -2023/11/06-12:14:26.885 378c Manual compaction at level-0 from '!folders!IT5MlkeTVPcxSRHe' @ 72057594037927935 : 1 .. '!items!zyrvCDXykruLOC8Y' @ 0 : 0; will stop at (end) +2024/04/07-14:24:18.059 98e4 Recovering log #1042 +2024/04/07-14:24:18.063 98e4 Delete type=0 #1042 +2024/04/07-14:24:18.063 98e4 Delete type=3 #1040 +2024/04/07-16:38:21.739 ad90 Level-0 table #1048: started +2024/04/07-16:38:21.746 ad90 Level-0 table #1048: 556434 bytes OK +2024/04/07-16:38:21.749 ad90 Delete type=0 #1046 +2024/04/07-16:38:21.751 ad90 Manual compaction at level-0 from '!folders!55ncmIvjbjv3PZZ8' @ 72057594037927935 : 1 .. '!items!zwuNF6cJs843IqJj' @ 0 : 0; will stop at '!items!zyrvCDXykruLOC8Y' @ 14982 : 1 +2024/04/07-16:38:21.751 ad90 Compacting 1@0 + 1@1 files +2024/04/07-16:38:21.760 ad90 Generated table #1049@0: 854 keys, 386090 bytes +2024/04/07-16:38:21.760 ad90 Compacted 1@0 + 1@1 files => 386090 bytes +2024/04/07-16:38:21.761 ad90 compacted to: files[ 0 1 0 0 0 0 0 ] +2024/04/07-16:38:21.762 ad90 Delete type=2 #1044 +2024/04/07-16:38:21.762 ad90 Delete type=2 #1048 +2024/04/07-16:38:21.767 ad90 Manual compaction at level-0 from '!items!zyrvCDXykruLOC8Y' @ 14982 : 1 .. '!items!zwuNF6cJs843IqJj' @ 0 : 0; will stop at (end) diff --git a/packs/items/MANIFEST-000587 b/packs/items/MANIFEST-000587 deleted file mode 100644 index 2bbe39c20..000000000 Binary files a/packs/items/MANIFEST-000587 and /dev/null differ diff --git a/packs/items/MANIFEST-001050 b/packs/items/MANIFEST-001050 new file mode 100644 index 000000000..d9aa9b7dd Binary files /dev/null and b/packs/items/MANIFEST-001050 differ diff --git a/packs/macros/000368.log b/packs/journals/000378.log similarity index 100% rename from packs/macros/000368.log rename to packs/journals/000378.log diff --git a/packs/journals/000380.ldb b/packs/journals/000380.ldb new file mode 100644 index 000000000..26fe86ba6 Binary files /dev/null and b/packs/journals/000380.ldb differ diff --git a/packs/journals/CURRENT b/packs/journals/CURRENT new file mode 100644 index 000000000..561f1eb98 --- /dev/null +++ b/packs/journals/CURRENT @@ -0,0 +1 @@ +MANIFEST-000376 diff --git a/packs/maneuvers/000146.log b/packs/journals/LOCK similarity index 100% rename from packs/maneuvers/000146.log rename to packs/journals/LOCK diff --git a/packs/journals/LOG b/packs/journals/LOG new file mode 100644 index 000000000..6e8d8a0aa --- /dev/null +++ b/packs/journals/LOG @@ -0,0 +1,15 @@ +2024/05/04-20:44:09.221 a7a8 Recovering log #374 +2024/05/04-20:44:09.226 a7a8 Delete type=0 #374 +2024/05/04-20:44:09.226 a7a8 Delete type=3 #372 +2024/05/04-20:48:31.340 a684 Level-0 table #379: started +2024/05/04-20:48:31.350 a684 Level-0 table #379: 744212 bytes OK +2024/05/04-20:48:31.352 a684 Delete type=0 #377 +2024/05/04-20:48:31.358 a684 Manual compaction at level-0 from '!journal!XaqlAbknAD3Tpx5E' @ 72057594037927935 : 1 .. '!journal.pages!cP772AChmDjr486z.zfGYRNnCuY6X2sS2' @ 0 : 0; will stop at (end) +2024/05/04-20:48:31.478 a684 Manual compaction at level-1 from '!journal!XaqlAbknAD3Tpx5E' @ 72057594037927935 : 1 .. '!journal.pages!cP772AChmDjr486z.zfGYRNnCuY6X2sS2' @ 0 : 0; will stop at '!journal.pages!cP772AChmDjr486z.zfGYRNnCuY6X2sS2' @ 61296 : 1 +2024/05/04-20:48:31.478 a684 Compacting 1@1 + 1@2 files +2024/05/04-20:48:31.488 a684 Generated table #380@1: 273 keys, 744212 bytes +2024/05/04-20:48:31.488 a684 Compacted 1@1 + 1@2 files => 744212 bytes +2024/05/04-20:48:31.489 a684 compacted to: files[ 0 0 1 0 0 0 0 ] +2024/05/04-20:48:31.490 a684 Delete type=2 #337 +2024/05/04-20:48:31.490 a684 Delete type=2 #379 +2024/05/04-20:48:31.494 a684 Manual compaction at level-1 from '!journal.pages!cP772AChmDjr486z.zfGYRNnCuY6X2sS2' @ 61296 : 1 .. '!journal.pages!cP772AChmDjr486z.zfGYRNnCuY6X2sS2' @ 0 : 0; will stop at (end) diff --git a/packs/journals/LOG.old b/packs/journals/LOG.old new file mode 100644 index 000000000..3f17f9f98 --- /dev/null +++ b/packs/journals/LOG.old @@ -0,0 +1,8 @@ +2024/04/07-14:24:18.158 8dd4 Recovering log #370 +2024/04/07-14:24:18.162 8dd4 Delete type=0 #370 +2024/04/07-14:24:18.162 8dd4 Delete type=3 #368 +2024/04/07-16:38:21.875 ad90 Level-0 table #375: started +2024/04/07-16:38:21.875 ad90 Level-0 table #375: 0 bytes OK +2024/04/07-16:38:21.879 ad90 Delete type=0 #373 +2024/04/07-16:38:21.879 ad90 Manual compaction at level-0 from '!journal!XaqlAbknAD3Tpx5E' @ 72057594037927935 : 1 .. '!journal.pages!cP772AChmDjr486z.zfGYRNnCuY6X2sS2' @ 0 : 0; will stop at (end) +2024/04/07-16:38:21.879 ad90 Manual compaction at level-1 from '!journal!XaqlAbknAD3Tpx5E' @ 72057594037927935 : 1 .. '!journal.pages!cP772AChmDjr486z.zfGYRNnCuY6X2sS2' @ 0 : 0; will stop at (end) diff --git a/packs/journals/MANIFEST-000376 b/packs/journals/MANIFEST-000376 new file mode 100644 index 000000000..1e2f3aa34 Binary files /dev/null and b/packs/journals/MANIFEST-000376 differ diff --git a/packs/macros/000365.ldb b/packs/macros/000365.ldb deleted file mode 100644 index e7fead229..000000000 Binary files a/packs/macros/000365.ldb and /dev/null differ diff --git a/packs/maneuvers/000149.log b/packs/macros/000816.log similarity index 100% rename from packs/maneuvers/000149.log rename to packs/macros/000816.log diff --git a/packs/macros/000818.ldb b/packs/macros/000818.ldb new file mode 100644 index 000000000..cf9dca302 Binary files /dev/null and b/packs/macros/000818.ldb differ diff --git a/packs/macros/CURRENT b/packs/macros/CURRENT index 13b386c93..290aa6d9f 100644 --- a/packs/macros/CURRENT +++ b/packs/macros/CURRENT @@ -1 +1 @@ -MANIFEST-000366 +MANIFEST-000814 diff --git a/packs/macros/LOG b/packs/macros/LOG index 30dd21218..9ae6066ec 100644 --- a/packs/macros/LOG +++ b/packs/macros/LOG @@ -1,8 +1,15 @@ -2023/11/06-12:17:57.436 6d34 Recovering log #363 -2023/11/06-12:17:57.440 6d34 Delete type=0 #363 -2023/11/06-12:17:57.440 6d34 Delete type=3 #361 -2023/11/06-12:19:30.552 6d70 Level-0 table #369: started -2023/11/06-12:19:30.552 6d70 Level-0 table #369: 0 bytes OK -2023/11/06-12:19:30.553 6d70 Delete type=0 #367 -2023/11/06-12:19:30.558 6d70 Manual compaction at level-0 from '!macros!1RNSAw20k8gBHhUd' @ 72057594037927935 : 1 .. '!macros!sYZbyUkyC8f4nCek' @ 0 : 0; will stop at (end) -2023/11/06-12:19:30.562 6d70 Manual compaction at level-1 from '!macros!1RNSAw20k8gBHhUd' @ 72057594037927935 : 1 .. '!macros!sYZbyUkyC8f4nCek' @ 0 : 0; will stop at (end) +2024/05/04-20:44:09.190 7d00 Recovering log #812 +2024/05/04-20:44:09.195 7d00 Delete type=0 #812 +2024/05/04-20:44:09.195 7d00 Delete type=3 #810 +2024/05/04-20:48:31.160 a684 Level-0 table #817: started +2024/05/04-20:48:31.163 a684 Level-0 table #817: 3596 bytes OK +2024/05/04-20:48:31.165 a684 Delete type=0 #815 +2024/05/04-20:48:31.311 a684 Manual compaction at level-0 from '!macros!1RNSAw20k8gBHhUd' @ 72057594037927935 : 1 .. '!macros!sYZbyUkyC8f4nCek' @ 0 : 0; will stop at (end) +2024/05/04-20:48:31.323 a684 Manual compaction at level-1 from '!macros!1RNSAw20k8gBHhUd' @ 72057594037927935 : 1 .. '!macros!sYZbyUkyC8f4nCek' @ 0 : 0; will stop at '!macros!sYZbyUkyC8f4nCek' @ 78 : 1 +2024/05/04-20:48:31.323 a684 Compacting 1@1 + 1@2 files +2024/05/04-20:48:31.326 a684 Generated table #818@1: 6 keys, 3596 bytes +2024/05/04-20:48:31.326 a684 Compacted 1@1 + 1@2 files => 3596 bytes +2024/05/04-20:48:31.328 a684 compacted to: files[ 0 0 1 0 0 0 0 ] +2024/05/04-20:48:31.328 a684 Delete type=2 #755 +2024/05/04-20:48:31.328 a684 Delete type=2 #817 +2024/05/04-20:48:31.340 a684 Manual compaction at level-1 from '!macros!sYZbyUkyC8f4nCek' @ 78 : 1 .. '!macros!sYZbyUkyC8f4nCek' @ 0 : 0; will stop at (end) diff --git a/packs/macros/LOG.old b/packs/macros/LOG.old index 1b8736d3f..1148e10b4 100644 --- a/packs/macros/LOG.old +++ b/packs/macros/LOG.old @@ -1,17 +1,8 @@ -2023/11/06-11:44:25.775 5940 Recovering log #359 -2023/11/06-11:44:25.779 5940 Delete type=0 #359 -2023/11/06-11:44:25.779 5940 Delete type=3 #357 -2023/11/06-12:14:26.894 378c Level-0 table #364: started -2023/11/06-12:14:26.898 378c Level-0 table #364: 5978 bytes OK -2023/11/06-12:14:26.900 378c Delete type=0 #362 -2023/11/06-12:14:26.905 378c Manual compaction at level-0 from '!macros!1RNSAw20k8gBHhUd' @ 72057594037927935 : 1 .. '!macros!sYZbyUkyC8f4nCek' @ 0 : 0; will stop at (end) -2023/11/06-12:14:26.914 378c Manual compaction at level-1 from '!macros!1RNSAw20k8gBHhUd' @ 72057594037927935 : 1 .. '!macros!sYZbyUkyC8f4nCek' @ 0 : 0; will stop at '!macros!sYZbyUkyC8f4nCek' @ 17 : 1 -2023/11/06-12:14:26.914 378c Compacting 1@1 + 3@2 files -2023/11/06-12:14:26.917 378c Generated table #365@1: 6 keys, 3555 bytes -2023/11/06-12:14:26.917 378c Compacted 1@1 + 3@2 files => 3555 bytes -2023/11/06-12:14:26.919 378c compacted to: files[ 0 0 1 0 0 0 0 ] -2023/11/06-12:14:26.919 378c Delete type=2 #163 -2023/11/06-12:14:26.919 378c Delete type=2 #168 -2023/11/06-12:14:26.919 378c Delete type=2 #352 -2023/11/06-12:14:26.919 378c Delete type=2 #364 -2023/11/06-12:14:26.920 378c Manual compaction at level-1 from '!macros!sYZbyUkyC8f4nCek' @ 17 : 1 .. '!macros!sYZbyUkyC8f4nCek' @ 0 : 0; will stop at (end) +2024/04/07-14:24:18.137 98e4 Recovering log #808 +2024/04/07-14:24:18.141 98e4 Delete type=0 #808 +2024/04/07-14:24:18.142 98e4 Delete type=3 #806 +2024/04/07-16:38:21.858 ad90 Level-0 table #813: started +2024/04/07-16:38:21.858 ad90 Level-0 table #813: 0 bytes OK +2024/04/07-16:38:21.860 ad90 Delete type=0 #811 +2024/04/07-16:38:21.862 ad90 Manual compaction at level-0 from '!macros!1RNSAw20k8gBHhUd' @ 72057594037927935 : 1 .. '!macros!sYZbyUkyC8f4nCek' @ 0 : 0; will stop at (end) +2024/04/07-16:38:21.873 ad90 Manual compaction at level-1 from '!macros!1RNSAw20k8gBHhUd' @ 72057594037927935 : 1 .. '!macros!sYZbyUkyC8f4nCek' @ 0 : 0; will stop at (end) diff --git a/packs/macros/MANIFEST-000366 b/packs/macros/MANIFEST-000366 deleted file mode 100644 index 0c69779de..000000000 Binary files a/packs/macros/MANIFEST-000366 and /dev/null differ diff --git a/packs/macros/MANIFEST-000814 b/packs/macros/MANIFEST-000814 new file mode 100644 index 000000000..ab8198f52 Binary files /dev/null and b/packs/macros/MANIFEST-000814 differ diff --git a/packs/moves/000596.log b/packs/maneuvers/000154.log similarity index 100% rename from packs/moves/000596.log rename to packs/maneuvers/000154.log diff --git a/packs/maneuvers/000011.ldb b/packs/maneuvers/000156.ldb similarity index 83% rename from packs/maneuvers/000011.ldb rename to packs/maneuvers/000156.ldb index 55a374eda..a2203630f 100644 Binary files a/packs/maneuvers/000011.ldb and b/packs/maneuvers/000156.ldb differ diff --git a/packs/maneuvers/CURRENT b/packs/maneuvers/CURRENT index 89e2564a8..cbc14b779 100644 --- a/packs/maneuvers/CURRENT +++ b/packs/maneuvers/CURRENT @@ -1 +1 @@ -MANIFEST-000148 +MANIFEST-000152 diff --git a/packs/maneuvers/LOG b/packs/maneuvers/LOG index 9e55d0a3c..62cc47ff4 100644 --- a/packs/maneuvers/LOG +++ b/packs/maneuvers/LOG @@ -1,3 +1,14 @@ -2023/08/28-10:03:58.920 10754 Recovering log #146 -2023/08/28-10:03:58.924 10754 Delete type=0 #146 -2023/08/28-10:03:58.924 10754 Delete type=3 #144 +2024/03/18-17:47:51.548 9e48 Recovering log #151 +2024/03/18-17:47:51.553 9e48 Delete type=0 #151 +2024/03/18-17:47:51.553 9e48 Delete type=3 #150 +2024/03/18-17:47:51.556 a0a0 Level-0 table #155: started +2024/03/18-17:47:51.571 a0a0 Level-0 table #155: 7835 bytes OK +2024/03/18-17:47:51.573 a0a0 Delete type=0 #153 +2024/03/18-17:47:51.573 a0a0 Manual compaction at level-0 from '!items!2zNJC8bz6YgPiyvB' @ 72057594037927935 : 1 .. '!items!y7uyBGncotyLp8zg' @ 0 : 0; will stop at '!items!y7uyBGncotyLp8zg' @ 17 : 1 +2024/03/18-17:47:51.573 a0a0 Compacting 1@0 + 1@1 files +2024/03/18-17:47:51.574 a0a0 Generated table #156@0: 15 keys, 7835 bytes +2024/03/18-17:47:51.574 a0a0 Compacted 1@0 + 1@1 files => 7835 bytes +2024/03/18-17:47:51.576 a0a0 compacted to: files[ 0 1 0 0 0 0 0 ] +2024/03/18-17:47:51.576 a0a0 Delete type=2 #11 +2024/03/18-17:47:51.576 a0a0 Delete type=2 #155 +2024/03/18-17:47:51.576 a0a0 Manual compaction at level-0 from '!items!y7uyBGncotyLp8zg' @ 17 : 1 .. '!items!y7uyBGncotyLp8zg' @ 0 : 0; will stop at (end) diff --git a/packs/maneuvers/LOG.old b/packs/maneuvers/LOG.old index 42fdb8e2a..5d16a1432 100644 --- a/packs/maneuvers/LOG.old +++ b/packs/maneuvers/LOG.old @@ -1,7 +1,5 @@ -2023/08/15-17:58:29.869 1c94 Recovering log #142 -2023/08/15-17:58:29.874 1c94 Delete type=0 #142 -2023/08/15-17:58:29.874 1c94 Delete type=3 #140 -2023/08/15-18:19:01.057 47b0 Level-0 table #147: started -2023/08/15-18:19:01.057 47b0 Level-0 table #147: 0 bytes OK -2023/08/15-18:19:01.060 47b0 Delete type=0 #145 -2023/08/15-18:19:01.064 47b0 Manual compaction at level-0 from '!items!2zNJC8bz6YgPiyvB' @ 72057594037927935 : 1 .. '!items!y7uyBGncotyLp8zg' @ 0 : 0; will stop at (end) +2024/03/18-17:44:43.077 8e90 Recovering log #149 +2024/03/18-17:44:43.081 8e90 Delete type=0 #146 +2024/03/18-17:44:43.081 8e90 Delete type=0 #149 +2024/03/18-17:44:43.081 8e90 Delete type=3 #144 +2024/03/18-17:44:43.081 8e90 Delete type=3 #148 diff --git a/packs/maneuvers/MANIFEST-000144 b/packs/maneuvers/MANIFEST-000144 deleted file mode 100644 index 2e76db985..000000000 Binary files a/packs/maneuvers/MANIFEST-000144 and /dev/null differ diff --git a/packs/maneuvers/MANIFEST-000148 b/packs/maneuvers/MANIFEST-000148 deleted file mode 100644 index 55a83a1fd..000000000 Binary files a/packs/maneuvers/MANIFEST-000148 and /dev/null differ diff --git a/packs/maneuvers/MANIFEST-000152 b/packs/maneuvers/MANIFEST-000152 new file mode 100644 index 000000000..904c18cb4 Binary files /dev/null and b/packs/maneuvers/MANIFEST-000152 differ diff --git a/packs/moves/000593.ldb b/packs/moves/000593.ldb deleted file mode 100644 index 873b07641..000000000 Binary files a/packs/moves/000593.ldb and /dev/null differ diff --git a/packs/poke-edges/000595.log b/packs/moves/001079.log similarity index 100% rename from packs/poke-edges/000595.log rename to packs/moves/001079.log diff --git a/packs/moves/001081.ldb b/packs/moves/001081.ldb new file mode 100644 index 000000000..417c4c71b Binary files /dev/null and b/packs/moves/001081.ldb differ diff --git a/packs/moves/CURRENT b/packs/moves/CURRENT index 2eef8aa5d..15e225969 100644 --- a/packs/moves/CURRENT +++ b/packs/moves/CURRENT @@ -1 +1 @@ -MANIFEST-000594 +MANIFEST-001077 diff --git a/packs/moves/LOG b/packs/moves/LOG index 26ccca0af..55f92b7ee 100644 --- a/packs/moves/LOG +++ b/packs/moves/LOG @@ -1,7 +1,14 @@ -2023/11/06-12:17:57.325 85dc Recovering log #591 -2023/11/06-12:17:57.329 85dc Delete type=0 #591 -2023/11/06-12:17:57.329 85dc Delete type=3 #589 -2023/11/06-12:19:30.529 6d70 Level-0 table #597: started -2023/11/06-12:19:30.529 6d70 Level-0 table #597: 0 bytes OK -2023/11/06-12:19:30.531 6d70 Delete type=0 #595 -2023/11/06-12:19:30.535 6d70 Manual compaction at level-0 from '!folders!3AhTAywjx4AYtRjv' @ 72057594037927935 : 1 .. '!items!zxUzqjCE0wNYL1er' @ 0 : 0; will stop at (end) +2024/05/04-20:44:09.003 85d4 Recovering log #1074 +2024/05/04-20:44:09.008 85d4 Delete type=0 #1074 +2024/05/04-20:44:09.008 85d4 Delete type=3 #1072 +2024/05/04-20:48:30.707 a684 Level-0 table #1080: started +2024/05/04-20:48:30.723 a684 Level-0 table #1080: 704471 bytes OK +2024/05/04-20:48:30.725 a684 Delete type=0 #1078 +2024/05/04-20:48:30.735 a684 Manual compaction at level-0 from '!folders!3AhTAywjx4AYtRjv' @ 72057594037927935 : 1 .. '!items!zxUzqjCE0wNYL1er' @ 0 : 0; will stop at '!items!zxUzqjCE0wNYL1er' @ 30167 : 1 +2024/05/04-20:48:30.735 a684 Compacting 1@0 + 1@1 files +2024/05/04-20:48:30.759 a684 Generated table #1081@0: 1310 keys, 707336 bytes +2024/05/04-20:48:30.759 a684 Compacted 1@0 + 1@1 files => 707336 bytes +2024/05/04-20:48:30.760 a684 compacted to: files[ 0 1 0 0 0 0 0 ] +2024/05/04-20:48:30.761 a684 Delete type=2 #1076 +2024/05/04-20:48:30.761 a684 Delete type=2 #1080 +2024/05/04-20:48:30.783 a684 Manual compaction at level-0 from '!items!zxUzqjCE0wNYL1er' @ 30167 : 1 .. '!items!zxUzqjCE0wNYL1er' @ 0 : 0; will stop at (end) diff --git a/packs/moves/LOG.old b/packs/moves/LOG.old index 24afe52dc..1985933d5 100644 --- a/packs/moves/LOG.old +++ b/packs/moves/LOG.old @@ -1,14 +1,14 @@ -2023/11/06-11:44:25.676 76b8 Recovering log #587 -2023/11/06-11:44:25.680 76b8 Delete type=0 #587 -2023/11/06-11:44:25.680 76b8 Delete type=3 #585 -2023/11/06-12:14:26.846 378c Level-0 table #592: started -2023/11/06-12:14:26.848 378c Level-0 table #592: 7499 bytes OK -2023/11/06-12:14:26.850 378c Delete type=0 #590 -2023/11/06-12:14:26.857 378c Manual compaction at level-0 from '!folders!3AhTAywjx4AYtRjv' @ 72057594037927935 : 1 .. '!items!zxUzqjCE0wNYL1er' @ 0 : 0; will stop at '!items!z5hgetbLhLvsd800' @ 7387 : 1 -2023/11/06-12:14:26.857 378c Compacting 1@0 + 1@1 files -2023/11/06-12:14:26.865 378c Generated table #593@0: 1099 keys, 463730 bytes -2023/11/06-12:14:26.865 378c Compacted 1@0 + 1@1 files => 463730 bytes -2023/11/06-12:14:26.867 378c compacted to: files[ 0 1 0 0 0 0 0 ] -2023/11/06-12:14:26.867 378c Delete type=2 #543 -2023/11/06-12:14:26.867 378c Delete type=2 #592 -2023/11/06-12:14:26.879 378c Manual compaction at level-0 from '!items!z5hgetbLhLvsd800' @ 7387 : 1 .. '!items!zxUzqjCE0wNYL1er' @ 0 : 0; will stop at (end) +2024/04/07-14:24:18.021 8dd4 Recovering log #1069 +2024/04/07-14:24:18.025 8dd4 Delete type=0 #1069 +2024/04/07-14:24:18.025 8dd4 Delete type=3 #1067 +2024/04/07-16:38:21.688 ad90 Level-0 table #1075: started +2024/04/07-16:38:21.690 ad90 Level-0 table #1075: 31812 bytes OK +2024/04/07-16:38:21.692 ad90 Delete type=0 #1073 +2024/04/07-16:38:21.699 ad90 Manual compaction at level-0 from '!folders!3AhTAywjx4AYtRjv' @ 72057594037927935 : 1 .. '!items!zxUzqjCE0wNYL1er' @ 0 : 0; will stop at '!items!uomsKYZy3Di509MG' @ 28839 : 1 +2024/04/07-16:38:21.699 ad90 Compacting 1@0 + 1@1 files +2024/04/07-16:38:21.709 ad90 Generated table #1076@0: 1310 keys, 688443 bytes +2024/04/07-16:38:21.709 ad90 Compacted 1@0 + 1@1 files => 688443 bytes +2024/04/07-16:38:21.711 ad90 compacted to: files[ 0 1 0 0 0 0 0 ] +2024/04/07-16:38:21.711 ad90 Delete type=2 #1071 +2024/04/07-16:38:21.711 ad90 Delete type=2 #1075 +2024/04/07-16:38:21.717 ad90 Manual compaction at level-0 from '!items!uomsKYZy3Di509MG' @ 28839 : 1 .. '!items!zxUzqjCE0wNYL1er' @ 0 : 0; will stop at (end) diff --git a/packs/moves/MANIFEST-000594 b/packs/moves/MANIFEST-000594 deleted file mode 100644 index 49ea2498b..000000000 Binary files a/packs/moves/MANIFEST-000594 and /dev/null differ diff --git a/packs/moves/MANIFEST-001077 b/packs/moves/MANIFEST-001077 new file mode 100644 index 000000000..ab18dcd79 Binary files /dev/null and b/packs/moves/MANIFEST-001077 differ diff --git a/packs/poke-edges/000580.ldb b/packs/poke-edges/000580.ldb deleted file mode 100644 index d06d27c98..000000000 Binary files a/packs/poke-edges/000580.ldb and /dev/null differ diff --git a/packs/references/000321.log b/packs/poke-edges/001047.log similarity index 100% rename from packs/references/000321.log rename to packs/poke-edges/001047.log diff --git a/packs/poke-edges/001049.ldb b/packs/poke-edges/001049.ldb new file mode 100644 index 000000000..e4c982493 Binary files /dev/null and b/packs/poke-edges/001049.ldb differ diff --git a/packs/poke-edges/CURRENT b/packs/poke-edges/CURRENT index c4c83ef46..55e9c6ad7 100644 --- a/packs/poke-edges/CURRENT +++ b/packs/poke-edges/CURRENT @@ -1 +1 @@ -MANIFEST-000593 +MANIFEST-001045 diff --git a/packs/poke-edges/LOG b/packs/poke-edges/LOG index 7386aef1e..a51a3c48f 100644 --- a/packs/poke-edges/LOG +++ b/packs/poke-edges/LOG @@ -1,7 +1,14 @@ -2023/11/06-12:17:57.378 85dc Recovering log #591 -2023/11/06-12:17:57.382 85dc Delete type=0 #591 -2023/11/06-12:17:57.382 85dc Delete type=3 #589 -2023/11/06-12:19:30.540 6d70 Level-0 table #596: started -2023/11/06-12:19:30.540 6d70 Level-0 table #596: 0 bytes OK -2023/11/06-12:19:30.542 6d70 Delete type=0 #594 -2023/11/06-12:19:30.544 6d70 Manual compaction at level-0 from '!items!04UCY4s8EF3iZnCN' @ 72057594037927935 : 1 .. '!items!ybZJ0nAa5nVWBokc' @ 0 : 0; will stop at (end) +2024/05/04-20:44:09.089 83ec Recovering log #1042 +2024/05/04-20:44:09.093 83ec Delete type=0 #1042 +2024/05/04-20:44:09.093 83ec Delete type=3 #1040 +2024/05/04-20:48:30.946 a684 Level-0 table #1048: started +2024/05/04-20:48:30.947 a684 Level-0 table #1048: 20254 bytes OK +2024/05/04-20:48:30.950 a684 Delete type=0 #1046 +2024/05/04-20:48:30.970 a684 Manual compaction at level-0 from '!folders!GxOcRgTdTg3A1OUR' @ 72057594037927935 : 1 .. '!items!ybZJ0nAa5nVWBokc' @ 0 : 0; will stop at '!items!ybZJ0nAa5nVWBokc' @ 924 : 1 +2024/05/04-20:48:30.970 a684 Compacting 1@0 + 1@1 files +2024/05/04-20:48:31.006 a684 Generated table #1049@0: 41 keys, 20583 bytes +2024/05/04-20:48:31.006 a684 Compacted 1@0 + 1@1 files => 20583 bytes +2024/05/04-20:48:31.008 a684 compacted to: files[ 0 1 0 0 0 0 0 ] +2024/05/04-20:48:31.009 a684 Delete type=2 #1044 +2024/05/04-20:48:31.009 a684 Delete type=2 #1048 +2024/05/04-20:48:31.023 a684 Manual compaction at level-0 from '!items!ybZJ0nAa5nVWBokc' @ 924 : 1 .. '!items!ybZJ0nAa5nVWBokc' @ 0 : 0; will stop at (end) diff --git a/packs/poke-edges/LOG.old b/packs/poke-edges/LOG.old index 22bf241cf..c3f6f99d1 100644 --- a/packs/poke-edges/LOG.old +++ b/packs/poke-edges/LOG.old @@ -1,7 +1,14 @@ -2023/11/06-11:44:25.725 85b4 Recovering log #587 -2023/11/06-11:44:25.729 85b4 Delete type=0 #587 -2023/11/06-11:44:25.729 85b4 Delete type=3 #585 -2023/11/06-12:14:26.883 378c Level-0 table #592: started -2023/11/06-12:14:26.883 378c Level-0 table #592: 0 bytes OK -2023/11/06-12:14:26.884 378c Delete type=0 #590 -2023/11/06-12:14:26.887 378c Manual compaction at level-0 from '!items!04UCY4s8EF3iZnCN' @ 72057594037927935 : 1 .. '!items!ybZJ0nAa5nVWBokc' @ 0 : 0; will stop at (end) +2024/04/07-14:24:18.075 8dd4 Recovering log #1038 +2024/04/07-14:24:18.079 8dd4 Delete type=0 #1038 +2024/04/07-14:24:18.079 8dd4 Delete type=3 #1036 +2024/04/07-16:38:21.764 ad90 Level-0 table #1043: started +2024/04/07-16:38:21.766 ad90 Level-0 table #1043: 12850 bytes OK +2024/04/07-16:38:21.767 ad90 Delete type=0 #1041 +2024/04/07-16:38:21.770 ad90 Manual compaction at level-0 from '!folders!GxOcRgTdTg3A1OUR' @ 72057594037927935 : 1 .. '!items!ybZJ0nAa5nVWBokc' @ 0 : 0; will stop at '!items!ybZJ0nAa5nVWBokc' @ 886 : 1 +2024/04/07-16:38:21.770 ad90 Compacting 1@0 + 1@1 files +2024/04/07-16:38:21.772 ad90 Generated table #1044@0: 41 keys, 19668 bytes +2024/04/07-16:38:21.772 ad90 Compacted 1@0 + 1@1 files => 19668 bytes +2024/04/07-16:38:21.773 ad90 compacted to: files[ 0 1 0 0 0 0 0 ] +2024/04/07-16:38:21.774 ad90 Delete type=2 #1017 +2024/04/07-16:38:21.774 ad90 Delete type=2 #1043 +2024/04/07-16:38:21.781 ad90 Manual compaction at level-0 from '!items!ybZJ0nAa5nVWBokc' @ 886 : 1 .. '!items!ybZJ0nAa5nVWBokc' @ 0 : 0; will stop at (end) diff --git a/packs/poke-edges/MANIFEST-000593 b/packs/poke-edges/MANIFEST-000593 deleted file mode 100644 index 1198bfa44..000000000 Binary files a/packs/poke-edges/MANIFEST-000593 and /dev/null differ diff --git a/packs/poke-edges/MANIFEST-001045 b/packs/poke-edges/MANIFEST-001045 new file mode 100644 index 000000000..69bf2a699 Binary files /dev/null and b/packs/poke-edges/MANIFEST-001045 differ diff --git a/packs/references/000110.ldb b/packs/references/000110.ldb deleted file mode 100644 index c0daa5007..000000000 Binary files a/packs/references/000110.ldb and /dev/null differ diff --git a/packs/rolltables/000008.log b/packs/references/000780.log similarity index 100% rename from packs/rolltables/000008.log rename to packs/references/000780.log diff --git a/packs/references/000782.ldb b/packs/references/000782.ldb new file mode 100644 index 000000000..700ae9696 Binary files /dev/null and b/packs/references/000782.ldb differ diff --git a/packs/references/CURRENT b/packs/references/CURRENT index 8b1ad216d..2d7d47f14 100644 --- a/packs/references/CURRENT +++ b/packs/references/CURRENT @@ -1 +1 @@ -MANIFEST-000319 +MANIFEST-000778 diff --git a/packs/references/LOG b/packs/references/LOG index 0034f5c35..cadfbeb87 100644 --- a/packs/references/LOG +++ b/packs/references/LOG @@ -1,8 +1,15 @@ -2023/11/06-12:17:57.442 85dc Recovering log #317 -2023/11/06-12:17:57.446 85dc Delete type=0 #317 -2023/11/06-12:17:57.447 85dc Delete type=3 #315 -2023/11/06-12:19:30.558 6d70 Level-0 table #322: started -2023/11/06-12:19:30.558 6d70 Level-0 table #322: 0 bytes OK -2023/11/06-12:19:30.560 6d70 Delete type=0 #320 -2023/11/06-12:19:30.562 6d70 Manual compaction at level-0 from '!items!0EVKSbyPCs2XLT8d' @ 72057594037927935 : 1 .. '!items!zFN2psnsbSBIeGrm' @ 0 : 0; will stop at (end) -2023/11/06-12:19:30.562 6d70 Manual compaction at level-1 from '!items!0EVKSbyPCs2XLT8d' @ 72057594037927935 : 1 .. '!items!zFN2psnsbSBIeGrm' @ 0 : 0; will stop at (end) +2024/05/04-20:44:09.198 85d4 Recovering log #776 +2024/05/04-20:44:09.204 85d4 Delete type=0 #776 +2024/05/04-20:44:09.204 85d4 Delete type=3 #774 +2024/05/04-20:48:31.165 a684 Level-0 table #781: started +2024/05/04-20:48:31.170 a684 Level-0 table #781: 145917 bytes OK +2024/05/04-20:48:31.172 a684 Delete type=0 #779 +2024/05/04-20:48:31.311 a684 Manual compaction at level-0 from '!folders!3mspjPD8gPzP1jmO' @ 72057594037927935 : 1 .. '!items!zFN2psnsbSBIeGrm' @ 0 : 0; will stop at (end) +2024/05/04-20:48:31.328 a684 Manual compaction at level-1 from '!folders!3mspjPD8gPzP1jmO' @ 72057594037927935 : 1 .. '!items!zFN2psnsbSBIeGrm' @ 0 : 0; will stop at '!items!zFN2psnsbSBIeGrm' @ 3973 : 1 +2024/05/04-20:48:31.328 a684 Compacting 1@1 + 1@2 files +2024/05/04-20:48:31.333 a684 Generated table #782@1: 336 keys, 146029 bytes +2024/05/04-20:48:31.333 a684 Compacted 1@1 + 1@2 files => 146029 bytes +2024/05/04-20:48:31.334 a684 compacted to: files[ 0 0 1 0 0 0 0 ] +2024/05/04-20:48:31.334 a684 Delete type=2 #739 +2024/05/04-20:48:31.334 a684 Delete type=2 #781 +2024/05/04-20:48:31.340 a684 Manual compaction at level-1 from '!items!zFN2psnsbSBIeGrm' @ 3973 : 1 .. '!items!zFN2psnsbSBIeGrm' @ 0 : 0; will stop at (end) diff --git a/packs/references/LOG.old b/packs/references/LOG.old index 6f748953b..6779fb5c7 100644 --- a/packs/references/LOG.old +++ b/packs/references/LOG.old @@ -1,8 +1,8 @@ -2023/11/06-11:44:25.781 85b4 Recovering log #313 -2023/11/06-11:44:25.785 85b4 Delete type=0 #313 -2023/11/06-11:44:25.785 85b4 Delete type=3 #311 -2023/11/06-12:14:26.901 378c Level-0 table #318: started -2023/11/06-12:14:26.901 378c Level-0 table #318: 0 bytes OK -2023/11/06-12:14:26.904 378c Delete type=0 #316 -2023/11/06-12:14:26.914 378c Manual compaction at level-0 from '!items!0EVKSbyPCs2XLT8d' @ 72057594037927935 : 1 .. '!items!zFN2psnsbSBIeGrm' @ 0 : 0; will stop at (end) -2023/11/06-12:14:26.920 378c Manual compaction at level-1 from '!items!0EVKSbyPCs2XLT8d' @ 72057594037927935 : 1 .. '!items!zFN2psnsbSBIeGrm' @ 0 : 0; will stop at (end) +2024/04/07-14:24:18.143 8dd4 Recovering log #772 +2024/04/07-14:24:18.148 8dd4 Delete type=0 #772 +2024/04/07-14:24:18.148 8dd4 Delete type=3 #770 +2024/04/07-16:38:21.860 ad90 Level-0 table #777: started +2024/04/07-16:38:21.860 ad90 Level-0 table #777: 0 bytes OK +2024/04/07-16:38:21.862 ad90 Delete type=0 #775 +2024/04/07-16:38:21.873 ad90 Manual compaction at level-0 from '!folders!3mspjPD8gPzP1jmO' @ 72057594037927935 : 1 .. '!items!zFN2psnsbSBIeGrm' @ 0 : 0; will stop at (end) +2024/04/07-16:38:21.873 ad90 Manual compaction at level-1 from '!folders!3mspjPD8gPzP1jmO' @ 72057594037927935 : 1 .. '!items!zFN2psnsbSBIeGrm' @ 0 : 0; will stop at (end) diff --git a/packs/references/MANIFEST-000319 b/packs/references/MANIFEST-000319 deleted file mode 100644 index af4f868a2..000000000 Binary files a/packs/references/MANIFEST-000319 and /dev/null differ diff --git a/packs/references/MANIFEST-000778 b/packs/references/MANIFEST-000778 new file mode 100644 index 000000000..40c91f791 Binary files /dev/null and b/packs/references/MANIFEST-000778 differ diff --git a/packs/rolltables/000010.ldb b/packs/rolltables/000010.ldb deleted file mode 100644 index d2c037863..000000000 Binary files a/packs/rolltables/000010.ldb and /dev/null differ diff --git a/packs/species/000714.log b/packs/rolltables/000415.log similarity index 100% rename from packs/species/000714.log rename to packs/rolltables/000415.log diff --git a/packs/rolltables/000417.ldb b/packs/rolltables/000417.ldb new file mode 100644 index 000000000..101a1a1e1 Binary files /dev/null and b/packs/rolltables/000417.ldb differ diff --git a/packs/rolltables/CURRENT b/packs/rolltables/CURRENT index f7753e22a..c9f778218 100644 --- a/packs/rolltables/CURRENT +++ b/packs/rolltables/CURRENT @@ -1 +1 @@ -MANIFEST-000006 +MANIFEST-000413 diff --git a/packs/rolltables/LOG b/packs/rolltables/LOG index 46cdac730..754aea79d 100644 --- a/packs/rolltables/LOG +++ b/packs/rolltables/LOG @@ -1,15 +1,15 @@ -2023/11/06-12:17:57.394 6d34 Recovering log #4 -2023/11/06-12:17:57.398 6d34 Delete type=0 #4 -2023/11/06-12:17:57.398 6d34 Delete type=3 #2 -2023/11/06-12:19:30.546 6d70 Level-0 table #9: started -2023/11/06-12:19:30.548 6d70 Level-0 table #9: 4743 bytes OK -2023/11/06-12:19:30.550 6d70 Delete type=0 #7 -2023/11/06-12:19:30.551 6d70 Manual compaction at level-0 from '!folders!KdbYHm255fVoO8XG' @ 72057594037927935 : 1 .. '!tables.results!wdOETkq5aY7M7hJg.rSBY0YfY0tm73sIu' @ 0 : 0; will stop at (end) -2023/11/06-12:19:30.553 6d70 Manual compaction at level-1 from '!folders!KdbYHm255fVoO8XG' @ 72057594037927935 : 1 .. '!tables.results!wdOETkq5aY7M7hJg.rSBY0YfY0tm73sIu' @ 0 : 0; will stop at '!tables.results!s0b5aT318cnMx8fX.yw9U1CNF3SMJxpZu' @ 587 : 0 -2023/11/06-12:19:30.553 6d70 Compacting 1@1 + 1@2 files -2023/11/06-12:19:30.556 6d70 Generated table #10@1: 583 keys, 68998 bytes -2023/11/06-12:19:30.556 6d70 Compacted 1@1 + 1@2 files => 68998 bytes -2023/11/06-12:19:30.558 6d70 compacted to: files[ 0 0 1 0 0 0 0 ] -2023/11/06-12:19:30.558 6d70 Delete type=2 #5 -2023/11/06-12:19:30.558 6d70 Delete type=2 #9 -2023/11/06-12:19:30.562 6d70 Manual compaction at level-1 from '!tables.results!s0b5aT318cnMx8fX.yw9U1CNF3SMJxpZu' @ 587 : 0 .. '!tables.results!wdOETkq5aY7M7hJg.rSBY0YfY0tm73sIu' @ 0 : 0; will stop at (end) +2024/05/04-20:44:09.113 83ec Recovering log #411 +2024/05/04-20:44:09.117 83ec Delete type=0 #411 +2024/05/04-20:44:09.118 83ec Delete type=3 #409 +2024/05/04-20:48:30.921 a684 Level-0 table #416: started +2024/05/04-20:48:30.926 a684 Level-0 table #416: 98941 bytes OK +2024/05/04-20:48:30.927 a684 Delete type=0 #414 +2024/05/04-20:48:30.950 a684 Manual compaction at level-0 from '!folders!2QGVmITcIgGCeRtD' @ 72057594037927935 : 1 .. '!tables.results!wdOETkq5aY7M7hJg.rSBY0YfY0tm73sIu' @ 0 : 0; will stop at (end) +2024/05/04-20:48:31.010 a684 Manual compaction at level-1 from '!folders!2QGVmITcIgGCeRtD' @ 72057594037927935 : 1 .. '!tables.results!wdOETkq5aY7M7hJg.rSBY0YfY0tm73sIu' @ 0 : 0; will stop at '!tables.results!wdOETkq5aY7M7hJg.rSBY0YfY0tm73sIu' @ 8219 : 1 +2024/05/04-20:48:31.010 a684 Compacting 1@1 + 1@2 files +2024/05/04-20:48:31.020 a684 Generated table #417@1: 604 keys, 73950 bytes +2024/05/04-20:48:31.020 a684 Compacted 1@1 + 1@2 files => 73950 bytes +2024/05/04-20:48:31.022 a684 compacted to: files[ 0 0 1 0 0 0 0 ] +2024/05/04-20:48:31.022 a684 Delete type=2 #370 +2024/05/04-20:48:31.023 a684 Delete type=2 #416 +2024/05/04-20:48:31.024 a684 Manual compaction at level-1 from '!tables.results!wdOETkq5aY7M7hJg.rSBY0YfY0tm73sIu' @ 8219 : 1 .. '!tables.results!wdOETkq5aY7M7hJg.rSBY0YfY0tm73sIu' @ 0 : 0; will stop at (end) diff --git a/packs/rolltables/LOG.old b/packs/rolltables/LOG.old index 321d74589..f189988f5 100644 --- a/packs/rolltables/LOG.old +++ b/packs/rolltables/LOG.old @@ -1,5 +1,8 @@ -2023/11/06-11:14:10.125577 7faaed672640 Delete type=3 #1 -2023/11/06-11:14:19.396729 7faaeac6e640 Level-0 table #5: started -2023/11/06-11:14:19.398473 7faaeac6e640 Level-0 table #5: 68952 bytes OK -2023/11/06-11:14:19.398936 7faaeac6e640 Delete type=0 #3 -2023/11/06-11:14:19.399080 7faaeac6e640 Manual compaction at level-0 from '!folders!KdbYHm255fVoO8XG' @ 72057594037927935 : 1 .. '!tables.results!wdOETkq5aY7M7hJg.rSBY0YfY0tm73sIu' @ 0 : 0; will stop at (end) +2024/04/07-14:24:18.091 98e4 Recovering log #407 +2024/04/07-14:24:18.096 98e4 Delete type=0 #407 +2024/04/07-14:24:18.096 98e4 Delete type=3 #405 +2024/04/07-16:38:21.774 ad90 Level-0 table #412: started +2024/04/07-16:38:21.774 ad90 Level-0 table #412: 0 bytes OK +2024/04/07-16:38:21.776 ad90 Delete type=0 #410 +2024/04/07-16:38:21.781 ad90 Manual compaction at level-0 from '!folders!2QGVmITcIgGCeRtD' @ 72057594037927935 : 1 .. '!tables.results!wdOETkq5aY7M7hJg.rSBY0YfY0tm73sIu' @ 0 : 0; will stop at (end) +2024/04/07-16:38:21.858 ad90 Manual compaction at level-1 from '!folders!2QGVmITcIgGCeRtD' @ 72057594037927935 : 1 .. '!tables.results!wdOETkq5aY7M7hJg.rSBY0YfY0tm73sIu' @ 0 : 0; will stop at (end) diff --git a/packs/rolltables/MANIFEST-000006 b/packs/rolltables/MANIFEST-000006 deleted file mode 100644 index e4e83f0f7..000000000 Binary files a/packs/rolltables/MANIFEST-000006 and /dev/null differ diff --git a/packs/rolltables/MANIFEST-000413 b/packs/rolltables/MANIFEST-000413 new file mode 100644 index 000000000..b53d6ec45 Binary files /dev/null and b/packs/rolltables/MANIFEST-000413 differ diff --git a/packs/species/000666.ldb b/packs/species/000666.ldb deleted file mode 100644 index 6df794158..000000000 Binary files a/packs/species/000666.ldb and /dev/null differ diff --git a/packs/spirit-actions/000251.log b/packs/species/001281.log similarity index 100% rename from packs/spirit-actions/000251.log rename to packs/species/001281.log diff --git a/packs/species/000663.ldb b/packs/species/001283.ldb similarity index 58% rename from packs/species/000663.ldb rename to packs/species/001283.ldb index 6539fcb41..a30891c75 100644 Binary files a/packs/species/000663.ldb and b/packs/species/001283.ldb differ diff --git a/packs/species/000664.ldb b/packs/species/001284.ldb similarity index 58% rename from packs/species/000664.ldb rename to packs/species/001284.ldb index e2ea1b46c..88d7b174e 100644 Binary files a/packs/species/000664.ldb and b/packs/species/001284.ldb differ diff --git a/packs/species/000665.ldb b/packs/species/001285.ldb similarity index 53% rename from packs/species/000665.ldb rename to packs/species/001285.ldb index c31730457..0e758e9b1 100644 Binary files a/packs/species/000665.ldb and b/packs/species/001285.ldb differ diff --git a/packs/species/001286.ldb b/packs/species/001286.ldb new file mode 100644 index 000000000..fd3bc0d41 Binary files /dev/null and b/packs/species/001286.ldb differ diff --git a/packs/species/CURRENT b/packs/species/CURRENT index 08a716f56..a10228121 100644 --- a/packs/species/CURRENT +++ b/packs/species/CURRENT @@ -1 +1 @@ -MANIFEST-000712 +MANIFEST-001279 diff --git a/packs/species/LOG b/packs/species/LOG index 3e19473ab..4d1cc8c83 100644 --- a/packs/species/LOG +++ b/packs/species/LOG @@ -1,7 +1,20 @@ -2023/11/06-12:17:57.409 85dc Recovering log #710 -2023/11/06-12:17:57.413 85dc Delete type=0 #710 -2023/11/06-12:17:57.413 85dc Delete type=3 #708 -2023/11/06-12:19:30.550 6d70 Level-0 table #715: started -2023/11/06-12:19:30.550 6d70 Level-0 table #715: 0 bytes OK -2023/11/06-12:19:30.551 6d70 Delete type=0 #713 -2023/11/06-12:19:30.553 6d70 Manual compaction at level-0 from '!folders!CMymFngAhNsaPlS5' @ 72057594037927935 : 1 .. '!items!zyXhWVwTmu553jFn' @ 0 : 0; will stop at (end) +2024/05/04-20:44:09.137 83ec Recovering log #1274 +2024/05/04-20:44:09.142 83ec Delete type=0 #1274 +2024/05/04-20:44:09.142 83ec Delete type=3 #1272 +2024/05/04-20:48:31.038 a684 Level-0 table #1282: started +2024/05/04-20:48:31.152 a684 Level-0 table #1282: 7687713 bytes OK +2024/05/04-20:48:31.158 a684 Delete type=0 #1280 +2024/05/04-20:48:31.173 a684 Manual compaction at level-0 from '!folders!CMymFngAhNsaPlS5' @ 72057594037927935 : 1 .. '!items!zyXhWVwTmu553jFn' @ 0 : 0; will stop at '!items!zyXhWVwTmu553jFn' @ 50812 : 1 +2024/05/04-20:48:31.173 a684 Compacting 1@0 + 4@1 files +2024/05/04-20:48:31.217 a684 Generated table #1283@0: 499 keys, 2118393 bytes +2024/05/04-20:48:31.256 a684 Generated table #1284@0: 500 keys, 2118941 bytes +2024/05/04-20:48:31.284 a684 Generated table #1285@0: 500 keys, 2119628 bytes +2024/05/04-20:48:31.308 a684 Generated table #1286@0: 315 keys, 1330455 bytes +2024/05/04-20:48:31.308 a684 Compacted 1@0 + 4@1 files => 7687417 bytes +2024/05/04-20:48:31.309 a684 compacted to: files[ 0 4 0 0 0 0 0 ] +2024/05/04-20:48:31.309 a684 Delete type=2 #1271 +2024/05/04-20:48:31.309 a684 Delete type=2 #1276 +2024/05/04-20:48:31.310 a684 Delete type=2 #1277 +2024/05/04-20:48:31.310 a684 Delete type=2 #1278 +2024/05/04-20:48:31.310 a684 Delete type=2 #1282 +2024/05/04-20:48:31.323 a684 Manual compaction at level-0 from '!items!zyXhWVwTmu553jFn' @ 50812 : 1 .. '!items!zyXhWVwTmu553jFn' @ 0 : 0; will stop at (end) diff --git a/packs/species/LOG.old b/packs/species/LOG.old index d520d9ee0..fbd3b5b44 100644 --- a/packs/species/LOG.old +++ b/packs/species/LOG.old @@ -1,7 +1,18 @@ -2023/11/06-11:44:25.747 85b4 Recovering log #706 -2023/11/06-11:44:25.751 85b4 Delete type=0 #706 -2023/11/06-11:44:25.752 85b4 Delete type=3 #704 -2023/11/06-12:14:26.892 378c Level-0 table #711: started -2023/11/06-12:14:26.892 378c Level-0 table #711: 0 bytes OK -2023/11/06-12:14:26.894 378c Delete type=0 #709 -2023/11/06-12:14:26.901 378c Manual compaction at level-0 from '!folders!CMymFngAhNsaPlS5' @ 72057594037927935 : 1 .. '!items!zyXhWVwTmu553jFn' @ 0 : 0; will stop at (end) +2024/04/07-14:24:18.108 8dd4 Recovering log #1266 +2024/04/07-14:24:18.112 8dd4 Delete type=0 #1266 +2024/04/07-14:24:18.112 8dd4 Delete type=3 #1264 +2024/04/07-16:38:21.776 ad90 Level-0 table #1275: started +2024/04/07-16:38:21.779 ad90 Level-0 table #1275: 32921 bytes OK +2024/04/07-16:38:21.780 ad90 Delete type=0 #1273 +2024/04/07-16:38:21.787 ad90 Manual compaction at level-0 from '!folders!CMymFngAhNsaPlS5' @ 72057594037927935 : 1 .. '!items!zyXhWVwTmu553jFn' @ 0 : 0; will stop at '!items!li4DWNMhGxOQrjUs' @ 49002 : 1 +2024/04/07-16:38:21.787 ad90 Compacting 1@0 + 3@1 files +2024/04/07-16:38:21.810 ad90 Generated table #1276@0: 502 keys, 2118752 bytes +2024/04/07-16:38:21.833 ad90 Generated table #1277@0: 503 keys, 2119262 bytes +2024/04/07-16:38:21.856 ad90 Generated table #1278@0: 505 keys, 2118483 bytes +2024/04/07-16:38:21.856 ad90 Compacted 1@0 + 3@1 files => 6356497 bytes +2024/04/07-16:38:21.857 ad90 compacted to: files[ 0 4 0 0 0 0 0 ] +2024/04/07-16:38:21.857 ad90 Delete type=2 #1268 +2024/04/07-16:38:21.857 ad90 Delete type=2 #1269 +2024/04/07-16:38:21.858 ad90 Delete type=2 #1270 +2024/04/07-16:38:21.858 ad90 Delete type=2 #1275 +2024/04/07-16:38:21.860 ad90 Manual compaction at level-0 from '!items!li4DWNMhGxOQrjUs' @ 49002 : 1 .. '!items!zyXhWVwTmu553jFn' @ 0 : 0; will stop at (end) diff --git a/packs/species/MANIFEST-000712 b/packs/species/MANIFEST-000712 deleted file mode 100644 index dbe92c11b..000000000 Binary files a/packs/species/MANIFEST-000712 and /dev/null differ diff --git a/packs/species/MANIFEST-001279 b/packs/species/MANIFEST-001279 new file mode 100644 index 000000000..30fabdbdb Binary files /dev/null and b/packs/species/MANIFEST-001279 differ diff --git a/packs/spirit-actions/000086.ldb b/packs/spirit-actions/000086.ldb deleted file mode 100644 index f8ccf3631..000000000 Binary files a/packs/spirit-actions/000086.ldb and /dev/null differ diff --git a/packs/spirit-actions/000699.log b/packs/spirit-actions/000699.log new file mode 100644 index 000000000..e69de29bb diff --git a/packs/spirit-actions/000701.ldb b/packs/spirit-actions/000701.ldb new file mode 100644 index 000000000..a5f584331 Binary files /dev/null and b/packs/spirit-actions/000701.ldb differ diff --git a/packs/spirit-actions/CURRENT b/packs/spirit-actions/CURRENT index 7ad179778..0b27d8dab 100644 --- a/packs/spirit-actions/CURRENT +++ b/packs/spirit-actions/CURRENT @@ -1 +1 @@ -MANIFEST-000249 +MANIFEST-000697 diff --git a/packs/spirit-actions/LOG b/packs/spirit-actions/LOG index ac0b05bdb..aed0189f0 100644 --- a/packs/spirit-actions/LOG +++ b/packs/spirit-actions/LOG @@ -1,8 +1,15 @@ -2023/11/06-12:17:57.449 6d34 Recovering log #247 -2023/11/06-12:17:57.453 6d34 Delete type=0 #247 -2023/11/06-12:17:57.453 6d34 Delete type=3 #245 -2023/11/06-12:19:30.560 6d70 Level-0 table #252: started -2023/11/06-12:19:30.560 6d70 Level-0 table #252: 0 bytes OK -2023/11/06-12:19:30.561 6d70 Delete type=0 #250 -2023/11/06-12:19:30.562 6d70 Manual compaction at level-0 from '!folders!DIBPjL86EbMDaAMi' @ 72057594037927935 : 1 .. '!items!zW8DWXGrCPQAtxWZ' @ 0 : 0; will stop at (end) -2023/11/06-12:19:30.562 6d70 Manual compaction at level-1 from '!folders!DIBPjL86EbMDaAMi' @ 72057594037927935 : 1 .. '!items!zW8DWXGrCPQAtxWZ' @ 0 : 0; will stop at (end) +2024/05/04-20:44:09.212 83ec Recovering log #695 +2024/05/04-20:44:09.217 83ec Delete type=0 #695 +2024/05/04-20:44:09.217 83ec Delete type=3 #693 +2024/05/04-20:48:31.334 a684 Level-0 table #700: started +2024/05/04-20:48:31.338 a684 Level-0 table #700: 11368 bytes OK +2024/05/04-20:48:31.340 a684 Delete type=0 #698 +2024/05/04-20:48:31.353 a684 Manual compaction at level-0 from '!folders!DIBPjL86EbMDaAMi' @ 72057594037927935 : 1 .. '!items!zW8DWXGrCPQAtxWZ' @ 0 : 0; will stop at (end) +2024/05/04-20:48:31.358 a684 Manual compaction at level-1 from '!folders!DIBPjL86EbMDaAMi' @ 72057594037927935 : 1 .. '!items!zW8DWXGrCPQAtxWZ' @ 0 : 0; will stop at '!items!zW8DWXGrCPQAtxWZ' @ 596 : 1 +2024/05/04-20:48:31.358 a684 Compacting 1@1 + 1@2 files +2024/05/04-20:48:31.363 a684 Generated table #701@1: 42 keys, 12694 bytes +2024/05/04-20:48:31.363 a684 Compacted 1@1 + 1@2 files => 12694 bytes +2024/05/04-20:48:31.364 a684 compacted to: files[ 0 0 1 0 0 0 0 ] +2024/05/04-20:48:31.364 a684 Delete type=2 #638 +2024/05/04-20:48:31.366 a684 Delete type=2 #700 +2024/05/04-20:48:31.490 a684 Manual compaction at level-1 from '!items!zW8DWXGrCPQAtxWZ' @ 596 : 1 .. '!items!zW8DWXGrCPQAtxWZ' @ 0 : 0; will stop at (end) diff --git a/packs/spirit-actions/LOG.old b/packs/spirit-actions/LOG.old index f3a88f6e6..6b97d6fab 100644 --- a/packs/spirit-actions/LOG.old +++ b/packs/spirit-actions/LOG.old @@ -1,8 +1,8 @@ -2023/11/06-11:44:25.788 5940 Recovering log #243 -2023/11/06-11:44:25.792 5940 Delete type=0 #243 -2023/11/06-11:44:25.792 5940 Delete type=3 #241 -2023/11/06-12:14:26.912 378c Level-0 table #248: started -2023/11/06-12:14:26.912 378c Level-0 table #248: 0 bytes OK -2023/11/06-12:14:26.914 378c Delete type=0 #246 -2023/11/06-12:14:26.920 378c Manual compaction at level-0 from '!folders!DIBPjL86EbMDaAMi' @ 72057594037927935 : 1 .. '!items!zW8DWXGrCPQAtxWZ' @ 0 : 0; will stop at (end) -2023/11/06-12:14:26.920 378c Manual compaction at level-1 from '!folders!DIBPjL86EbMDaAMi' @ 72057594037927935 : 1 .. '!items!zW8DWXGrCPQAtxWZ' @ 0 : 0; will stop at (end) +2024/04/07-14:24:18.151 98e4 Recovering log #691 +2024/04/07-14:24:18.155 98e4 Delete type=0 #691 +2024/04/07-14:24:18.155 98e4 Delete type=3 #689 +2024/04/07-16:38:21.871 ad90 Level-0 table #696: started +2024/04/07-16:38:21.871 ad90 Level-0 table #696: 0 bytes OK +2024/04/07-16:38:21.873 ad90 Delete type=0 #694 +2024/04/07-16:38:21.873 ad90 Manual compaction at level-0 from '!folders!DIBPjL86EbMDaAMi' @ 72057594037927935 : 1 .. '!items!zW8DWXGrCPQAtxWZ' @ 0 : 0; will stop at (end) +2024/04/07-16:38:21.873 ad90 Manual compaction at level-1 from '!folders!DIBPjL86EbMDaAMi' @ 72057594037927935 : 1 .. '!items!zW8DWXGrCPQAtxWZ' @ 0 : 0; will stop at (end) diff --git a/packs/spirit-actions/MANIFEST-000249 b/packs/spirit-actions/MANIFEST-000249 deleted file mode 100644 index 5d2e5c35a..000000000 Binary files a/packs/spirit-actions/MANIFEST-000249 and /dev/null differ diff --git a/packs/spirit-actions/MANIFEST-000697 b/packs/spirit-actions/MANIFEST-000697 new file mode 100644 index 000000000..e148f9e78 Binary files /dev/null and b/packs/spirit-actions/MANIFEST-000697 differ diff --git a/src/module/active-effect.js b/src/module/active-effect.js index e731cff04..1aa94fe68 100644 --- a/src/module/active-effect.js +++ b/src/module/active-effect.js @@ -58,8 +58,6 @@ export class ActiveEffectPTU extends ActiveEffect { /** @override */ static async deleteDocuments(ids, context={}) { - console.log(ids, context); - return super.deleteDocuments(ids, context); } diff --git a/src/module/actor/base.js b/src/module/actor/base.js index ebc8bc5c6..a3d659185 100644 --- a/src/module/actor/base.js +++ b/src/module/actor/base.js @@ -1,4 +1,5 @@ import { sluggify } from "../../util/misc.js"; +import { Weather } from "../apps/weather.js"; import { PTUCombatant } from "../combat/combatant.js"; import { PTUCondition } from "../item/index.js"; import { ChatMessagePTU } from "../message/base.js"; @@ -27,7 +28,7 @@ class PTUActor extends Actor { } get rollOptions() { - return this.flags.ptu?.rollOptions; + return this.flags.ptu?.rollOptions; } get combatant() { @@ -64,7 +65,7 @@ class PTUActor extends Actor { get isPrivate() { // TODO : make this a meta knowledge setting - return !(this.permission >= CONST.DOCUMENT_PERMISSION_LEVELS.OBSERVER); + return !(this.permission >= CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER); } get types() { @@ -145,7 +146,7 @@ class PTUActor extends Actor { const effectiveness = {}; for (const type of this.types) { - const iwr = duplicate(CONFIG.PTU.data.typeEffectiveness[type]?.effectiveness ?? {}); + const iwr = foundry.utils.duplicate(CONFIG.PTU.data.typeEffectiveness[type]?.effectiveness ?? {}); for (const [key, value] of Object.entries(iwr)) { effectiveness[key] = (effectiveness[key] ?? 1) * value; @@ -158,13 +159,20 @@ class PTUActor extends Actor { delete effectiveness["Shadow"]; } else { - if (effectiveness["Shadow"] > 2) effectiveness["Shadow"] = 2; + if (this.types.includes("Shadow")) effectiveness["Shadow"] = 0.5; + else if (this.types.includes("Untyped")) effectiveness["Shadow"] = 1; + else effectiveness["Shadow"] = 2; } - const effectivenessMod = this.system.modifiers?.resistanceSteps?.total ?? 0; - for (const [key, value] of Object.entries(effectiveness)) { + const effectivenessMod = (() => { + const mod = this.system.modifiers?.resistanceSteps?.total ?? 0; + if(mod > 0) return 1 / (2 ** mod); + if(mod < 0) return 2 ** Math.abs(mod); + return 1; + })(); + for (let [key, value] of Object.entries(effectiveness)) { + if (effectivenessMod) value *= effectivenessMod; const type = key.toLocaleLowerCase(game.i18n.locale) - if (effectivenessMod) effectiveness[type] *= effectivenessMod; if (value == 0) { results.immunities.push(new ImmunityData({ type, value, source: "type" })); @@ -231,7 +239,8 @@ class PTUActor extends Actor { speciesOverride: {}, typeOverride: {}, effectiveness: [], - apAdjustments: {drained: [], bound: []} + apAdjustments: { drained: [], bound: [] }, + applyEffects: {} } super._initialize(); @@ -243,6 +252,12 @@ class PTUActor extends Actor { super.prepareData(); this.constructed = true; + // Extra Rolloptions before 'After Derived' hooks get called + if (!this.types.includes("Untyped")) delete this.flags.ptu.rollOptions.all["self:types:untyped"] + for (const type of this.types) { + this.flags.ptu.rollOptions.all["self:types:" + type.toLowerCase()] = true; + } + // Call post-derived-preparation `RuleElement` hooks for (const rule of this.rules) { rule.afterPrepareData?.(); @@ -271,7 +286,7 @@ class PTUActor extends Actor { const { flags } = this; // Setup the basic structure of PTU flags with roll options - this.flags.ptu = mergeObject(flags.ptu ?? {}, { + this.flags.ptu = foundry.utils.mergeObject(flags.ptu ?? {}, { rollOptions: { all: { [`self:type:${this.type}`]: true, @@ -293,12 +308,6 @@ class PTUActor extends Actor { prepareDerivedData() { this.prepareSynthetics(); - // Extra Rolloptions - if (!this.types.includes("Untyped")) delete this.flags.ptu.rollOptions.all["self:types:untyped"] - for (const type of this.types) { - this.flags.ptu.rollOptions.all["self:types:" + type.toLowerCase()] = true; - } - if (this.allowedItemTypes.includes('move')) { this.system.attacks = this.prepareMoves(); } @@ -316,7 +325,8 @@ class PTUActor extends Actor { this.prepareDataFromItems(); - for (const rule of this.rules) { + + for (const rule of this.rules.sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0))) { rule.onApplyActiveEffects?.(); } } @@ -334,8 +344,35 @@ class PTUActor extends Actor { } prepareRuleElements() { - return this.items.contents - .flatMap((item) => item.prepareRuleElements()) + const globalEffects = []; + try { + for (const effect of Weather.globalEffects?.values?.() ?? []) { + switch(effect.system.mode) { + case "disabled": + continue; + case "all": + break; + case "players": + if (this.alliance !== "party") continue; + break; + case "opposition": + if (this.alliance !== "opposition") continue; + break; + } + + const item = new CONFIG.PTU.Item.proxy(effect.toObject(), { temporary: true, parent: this }) + item.updateSource({ "flags.core.sourceId": effect.flags?.core?.sourceId ?? effect.uuid }); + globalEffects.push(item); + } + } + catch (error) { + console.error("PTU | Failed to prepare global effects.", error); + } + return [ + this.items.contents.flatMap((item) => item.prepareRuleElements()), + globalEffects.flatMap((effect) => effect.prepareRuleElements()) + ] + .flat() .filter((rule) => !rule.ignored) .sort((a, b) => a.priority - b.priority); } @@ -426,10 +463,23 @@ class PTUActor extends Actor { return super.updateDocuments(updates, context); } + _onCreate(data, options, user) { + super._onCreate(data, options, user); + if(user !== game.user.id) return; + if(data.type === 'character') { + if(!data.items.some(i => i.name.endsWith("Training"))) { + // Grant lvl 1 training + fromUuid('Compendium.ptu.effects.Item.fm0TZUuQK0uRhkJA').then((effect) => { + this.createEmbeddedDocuments('Item', [effect.toObject()]); + }); + } + } + } + /** @override */ async _preUpdate(changed, options, user) { if (changed?.system?.spirit?.value !== undefined) { - changed.system.spirit.value = Math.clamped( + changed.system.spirit.value = Math.clamp( changed.system.spirit.value, this.system.spirit.min ?? this.system.spirit.min, changed.system.spirit.max ?? this.system.spirit.max @@ -444,7 +494,7 @@ class PTUActor extends Actor { return this.clone( { - items: [deepClone(this._source.items), ephemeralEffects].flat(), + items: [foundry.utils.deepClone(this._source.items), ephemeralEffects].flat(), flags: { ptu: { rollOptions: { all: rollOptionsAll } } }, }, { keepId: true } @@ -567,7 +617,7 @@ class PTUActor extends Actor { if (currentPercentage > i && i >= newPercentage) { injuries++; - const percentageShortened = Math.floor(i*1000)/1000 + const percentageShortened = Math.floor(i * 1000) / 1000 injuryStatements.push(game.i18n.format("PTU.ApplyDamage.HpThresholdInjury", { actor: this.link, percentage: percentageShortened })); } else break; @@ -590,10 +640,10 @@ class PTUActor extends Actor { if (bars > 0) { const newBars = Math.max(bars - 1, 0); hpUpdate.updates["system.boss.bars"] = newBars; - hpUpdate.updates["system.health.value"] = + hpUpdate.updates["system.health.value"] = (injuries > this.system.health.injuries) - ? Math.trunc(this.system.health.total * (1 - ((this.system.modifiers.hardened ? Math.min(this.system.health.injuries, 5) : this.system.health.injuries) / 10))) - : this.system.health.max; + ? Math.trunc(this.system.health.total * (1 - ((this.system.modifiers.hardened ? Math.min(this.system.health.injuries, 5) : this.system.health.injuries) / 10))) + : this.system.health.max; //TODO: Apply Injuries await this.update(hpUpdate.updates); bossStatement = game.i18n.format("PTU.ApplyDamage.BossBarBroken", { actor: this.link, bars: newBars }); @@ -644,7 +694,7 @@ class PTUActor extends Actor { isHealing: hpDamage < 0, updates: Object.entries(hpUpdate.updates) .map(([path, newValue]) => { - const preUpdateValue = getProperty(preUpdateSource, path); + const preUpdateValue = foundry.utils.getProperty(preUpdateSource, path); if (typeof preUpdateValue === "number") { const difference = preUpdateValue - newValue; if (difference === 0) return []; @@ -682,7 +732,7 @@ class PTUActor extends Actor { const actorUpdates = {}; for (const update of updates) { - const currentValue = getProperty(this, update.path); + const currentValue = foundry.utils.getProperty(this, update.path); if (typeof currentValue === "number") { actorUpdates[update.path] = currentValue + Number(update.value); } @@ -851,10 +901,12 @@ class PTUActor extends Actor { if (data.system?.health?.value !== undefined) { if (data.system.health.value <= 0 && game.settings.get("ptu", "automation.autoFaint")) { + if (this.primaryUpdater?.id !== game.user.id) return; const fainted = this.conditions.bySlug("fainted"); if (fainted.length === 0) PTUCondition.FromEffects([{ id: "fainted" }]).then(items => this.createEmbeddedDocuments("Item", items)); } else if (data.system.health.value > 0 && game.settings.get("ptu", "automation.autoFaintRecovery")) { + if (this.primaryUpdater?.id !== game.user.id) return; const fainted = this.conditions.bySlug("fainted"); if (fainted.length > 0) fainted.forEach(f => f.delete()); } @@ -948,7 +1000,7 @@ class PTUActor extends Actor { // Get the data common between all Struggles out of the way first const strugglePlusRollOptions = this.rollOptions.struggle ? Object.keys(this.rollOptions.struggle).filter(o => o.startsWith("skill:")) : []; const isStrugglePlus = (() => { - for(const skill of strugglePlusRollOptions) { + for (const skill of strugglePlusRollOptions) { if (this.system.skills?.[skill.replace("skill:", "")]?.value?.total > 4) return true; } return this.system.skills?.combat?.value?.total > 4; @@ -956,24 +1008,24 @@ class PTUActor extends Actor { const constructStruggleItem = (type, category, range, ptuFlags, isRangedStruggle = false) => { return new Item.implementation({ - name: `Struggle (${type})`, - type: "move", - img: CONFIG.PTU.data.typeEffectiveness[type].images.icon, - system: { - ac: isStrugglePlus ? 3 : 4, - damageBase: isStrugglePlus ? 5 : 4, - stab: false, - frequency: "At-Will", - isStruggle: true, - isRangedStruggle: isRangedStruggle, - category: category, - range: range, - type: type - }, - flags: { - ptu: ptuFlags || {} - } + name: `Struggle (${type})`, + type: "move", + img: CONFIG.PTU.data.typeEffectiveness[type].images.icon, + system: { + ac: isStrugglePlus ? 3 : 4, + damageBase: isStrugglePlus ? 5 : 4, + stab: false, + frequency: "At-Will", + isStruggle: true, + isRangedStruggle: isRangedStruggle, + category: category, + range: range, + type: type }, + flags: { + ptu: ptuFlags || {} + } + }, { parent: this, temporary: true @@ -1076,7 +1128,7 @@ class PTUActor extends Actor { const statistic = new StatisticModifier(move.slug, modifiers) - const action = mergeObject(statistic, { + const action = foundry.utils.mergeObject(statistic, { label: move.name, img: move.img, domains: selectors, @@ -1113,6 +1165,7 @@ class PTUActor extends Actor { action.damage = async (params = {}) => { const domains = selectors.map(s => s.replace("attack", "damage")); + const accuracyRollResult = params.rollResult; const preTargets = params.targets?.length > 0 ? params.targets : [...game.user.targets]; const targets = []; @@ -1140,6 +1193,7 @@ class PTUActor extends Actor { outcomes, selectors: domains, event: params.event, + accuracyRollResult }) return await check.executeDamage(params.callback, action); @@ -1209,7 +1263,7 @@ class PTUActor extends Actor { const options = [...this.getRollOptions(selectors), `skill:${skill}`, `skill:${this.system.skills[skill].rank.toLocaleLowerCase(game.i18n.lang)}`]; const statistic = new StatisticModifier(skill, []); - const action = mergeObject(statistic, { + const action = foundry.utils.mergeObject(statistic, { label: game.i18n.format("PTU.Check.SkillCheck", { skill: game.i18n.localize(`PTU.Skills.${skill}`) }), domains: selectors, type: "skill", @@ -1248,7 +1302,7 @@ class PTUActor extends Actor { const statistic = new StatisticModifier("initiative", []); - const action = mergeObject(statistic, { + const action = foundry.utils.mergeObject(statistic, { label: game.i18n.localize("PTU.Check.Initiative"), domains: selectors, type: "initiative", @@ -1432,6 +1486,9 @@ class PTUActor extends Actor { if (itemOptions.includes("move:target:sky") && targetRollOptions.includes("target:location:sky")) { options["ignore+Z"] = true; } + if (itemOptions.some(option => option.startsWith("move:range:burst"))) { + options["burst"] = true; + } const isFlanked = targetToken.isFlanked(); if (isFlanked && !targetRollOptions.includes("target:immune:flanked")) { @@ -1443,6 +1500,10 @@ class PTUActor extends Actor { typeof distance === "number" ? [`origin:distance:${distance}`, `target:distance:${distance}`] : [null, null]; + const rangeOptions = (() => { + if (distance >= 4) return ["target:distance:4+"]; + return []; + })(); const targetEphemeralEffects = await extractEphemeralEffects({ affects: "target", @@ -1450,7 +1511,7 @@ class PTUActor extends Actor { target: targetToken?.actor ?? null, item: selfItem, domains, - options: [...selfOptions, ...itemOptions, ...targetRollOptions] + options: [...selfOptions, ...itemOptions, ...targetRollOptions, ...rangeOptions] }); const targetActor = (targetToken?.actor)?.getContextualClone( @@ -1458,11 +1519,12 @@ class PTUActor extends Actor { ...selfActor.getSelfRollOptions("origin"), ...itemOptions, ...(originDistance ? [originDistance] : []), + ...rangeOptions ], targetEphemeralEffects ) ?? null; - const targetOptions = new Set(targetActor ? getTargetRollOptions(targetActor) : targetRollOptions); + const targetOptions = new Set([...(targetActor ? getTargetRollOptions(targetActor) : targetRollOptions), ...rangeOptions]); if (targetOptions.has("target:immune:flanked")) targetOptions.delete("target:flanked"); else if (isFlanked) targetOptions.add("target:flanked"); @@ -1472,6 +1534,7 @@ class PTUActor extends Actor { ...itemOptions, ...targetOptions, ...(targetDistance ? [targetDistance] : []), + ...rangeOptions ]) return { @@ -1617,6 +1680,10 @@ class PTUActor extends Actor { typeof distance === "number" ? [`origin:distance:${distance}`, `target:distance:${distance}`] : [null, null]; + const rangeOptions = (() => { + if (distance >= 4) return ["target:distance:4+"]; + return []; + })(); const targetEphemeralEffects = await extractEphemeralEffects({ affects: "target", @@ -1634,6 +1701,7 @@ class PTUActor extends Actor { ...selfActor.getSelfRollOptions("origin"), ...itemOptions, ...(originDistance ? [originDistance] : []), + ...rangeOptions ], targetEphemeralEffects ) ?? null; @@ -1643,6 +1711,7 @@ class PTUActor extends Actor { ...selfOptions, ...itemOptions, ...(targetActor ? getTargetRollOptions(targetActor) : targetRollOptions), + ...rangeOptions ]) if (targetDistance) rollOptions.add(targetDistance); @@ -1714,7 +1783,7 @@ class PTUActor extends Actor { if (existing.value === null) return null; if (min && max && min > max) throw new Error("Min cannot be greater than max."); return min && max - ? Math.clamped(existing.value + 1, min, max) + ? Math.clamp(existing.value + 1, min, max) : max ? Math.min(existing.value + 1, max) : existing.value + 1; @@ -1731,6 +1800,9 @@ class PTUActor extends Actor { async modifyTokenAttribute(attribute, value, isDelta = false, isBar = true) { const token = this.getActiveTokens(true, true).shift(); const health = this.system.health; + + + const isDamage = !!( attribute === "health" && health && @@ -1741,82 +1813,10 @@ class PTUActor extends Actor { return this.applyDamage({ damage, token, skipIWR: true }); } return super.modifyTokenAttribute(attribute, value, isDelta, isBar); - - console.debug( - "Modifying Token Attribute", - attribute, - value, - isDelta, - isBar - ); - - const current = duplicate(getProperty(this.system, attribute)); - if (isBar) { - if (attribute == "health") { - const temp = duplicate(getProperty(this.system, "tempHp")); - if (isDelta) { - if (value < 0 && Number(temp.value) > 0) { - temp.value = Number(temp.value) + value; - if (temp.value >= 0) - return this.update({ [`data.tempHp.value`]: temp.value }); - - let totalValue = Number(current.value) + temp.value; - value = Math.clamped( - totalValue, - Math.min(-50, current.total * -2), - current.max - ); - temp.value = 0; - temp.max = 0; - } else { - let totalValue = Number(current.value) + value; - value = Math.clamped( - totalValue, - Math.min(-50, current.total * -2), - current.max - ); - if (totalValue > value) { - temp.value = totalValue - value; - temp.max = temp.value; - } - } - } else { - if (value > current.max) { - temp.value = value - current.max; - temp.max = temp.value; - value = current.max; - } - } - console.debug("Updating Character HP with args:", this, { - oldValue: current.value, - newValue: value, - tempHp: temp, - }); - return this.update({ - [`data.${attribute}.value`]: value, - [`data.tempHp.value`]: temp.value, - [`data.tempHp.max`]: temp.max, - }); - } else { - if (isDelta) { - let totalValue = Number(current.value) + value; - value = Math.clamped(0, totalValue, current.max); - } - if (attribute == "tempHp") - return this.update({ - [`data.${attribute}.value`]: value, - [`data.${attribute}.max`]: value, - }); - return this.update({ [`data.${attribute}.value`]: value }); - } - } else { - if (isDelta) value = Number(current) + value; - return this.update({ [`data.${attribute}`]: value }); - } } _setDefaultChanges() { - this.system.changes = mergeObject( + this.system.changes = foundry.utils.mergeObject( { system: { levelUpPoints: { @@ -1828,7 +1828,10 @@ class PTUActor extends Actor { 2: { source: "Level", mode: "add", - value: (this.type === "character" && game.settings.get("ptu", "variant.trainerRevamp") ? (this.system.level.current + this.system.level.current) : this.system.level.current), + value: ( + this.type === "character" && ["data-revamp", "short-track"].includes(game.settings.get("ptu", "variant.trainerAdvancement")) + ? (this.system.level.current + this.system.level.current) + : this.system.level.current), }, 3: { source: "HP Stat", diff --git a/src/module/actor/character/capabilities.js b/src/module/actor/character/capabilities.js index 33833596c..c83a4eb0d 100644 --- a/src/module/actor/character/capabilities.js +++ b/src/module/actor/character/capabilities.js @@ -103,7 +103,6 @@ function calculateTrainerCapabilities(trainerSkills, items, speedCombatStages, m if (spcsChanges > 0 || spcsChanges < 0) { if (capabilities["overland"] > 0) { capabilities["overland"] = Math.max(capabilities["overland"] + spcsChanges, capabilities["overland"] > 1 ? 2 : 1) - if(isSlowed) capabilities["overland"] = Math.max(1, Math.floor(capabilities["overland"] * 0.5)); } } @@ -117,6 +116,12 @@ function calculateTrainerCapabilities(trainerSkills, items, speedCombatStages, m if(key === "all") continue; if(capabilities[key] === 0 || capabilities[key] === undefined) capabilities[key] = modifiers[key] + Number(modifiers["all"] ?? 0); } + if(isSlowed) { + for(const key of Object.keys(capabilities)) { + if(CONFIG.PTU.Capabilities.numericNonMovement.includes(key)) continue; + capabilities[key] = Math.max(1, Math.floor(capabilities[key] * 0.5)); + } + } return capabilities; } diff --git a/src/module/actor/character/document.js b/src/module/actor/character/document.js index 3093e6b61..c6e5eb797 100644 --- a/src/module/actor/character/document.js +++ b/src/module/actor/character/document.js @@ -30,19 +30,27 @@ class PTUTrainerActor extends PTUActor { system.skills[novice].value.mod += 1; } - system.level.dexexp = game.settings.get("ptu", "variant.useDexExp") == true - ? (this.system.dex?.owned?.length || 0) - : game.settings.get("ptu", "variant.advancementRework") && game.settings.get("ptu", "variant.trainerRevamp") - ? (this.system.dex?.owned?.length || 0) - : 0; - const levelUpRequirement = game.settings.get("ptu", "variant.advancementRework") && game.settings.get("ptu", "variant.trainerRevamp") ? 20 : 10; + system.level.dexexp = game.settings.get("ptu", "variant.useDexExp") == true + ? (this.system.dex?.owned?.length || 0) + : 0 + + const levelUpRequirement = game.settings.get("ptu", "variant.trainerAdvancement") === "short-track" ? 20 : 10; + + const maxLevel = { + "original": 50, + "data-revamp": 25, + "short-track": 25, + "ptr-update": 50, + "long-track": 100, + } + system.level.current = - Math.clamped( + Math.clamp( 1 + Number(system.level.milestones) + Math.trunc((Number(system.level.miscexp) / levelUpRequirement) + (Number(system.level.dexexp) / levelUpRequirement)), 1, - (game.settings.get("ptu", "variant.trainerRevamp") ? 25 : 50) + maxLevel[game.settings.get("ptu", "variant.trainerAdvancement")] ?? 50 ); // Set attributes which are underrived data @@ -82,6 +90,7 @@ class PTUTrainerActor extends PTUActor { } for (let [key, skill] of Object.entries(system.skills)) { + skill["slug"] = key; skill["value"]["total"] = skill["value"]["value"] + skill["value"]["mod"]; skill["rank"] = PTUSkills.getRankSlug(skill["value"]["total"]); skill["modifier"]["total"] = skill["modifier"]["value"] + skill["modifier"]["mod"] + (system.modifiers.skillBonus?.total ?? 0); @@ -106,14 +115,39 @@ class PTUTrainerActor extends PTUActor { } // Use Data - system.levelUpPoints = (game.settings.get("ptu", "variant.trainerRevamp") ? (system.level.current * 2) : system.level.current) + system.modifiers.statPoints.total + 9; + system.levelUpPoints = (() => { + switch (game.settings.get("ptu", "variant.trainerAdvancement")) { + case "original": return system.level.current; + case "data-revamp": return system.level.current * 2; + case "short-track": { + let points = system.level.current * 2; + if (system.level.current >= 5) points += 5; + if (system.level.current >= 10) points += 5; + if (system.level.current >= 15) points += 5; + if (system.level.current >= 20) points += 5; + if (system.level.current == 25) points += 15; + return points; + }; + case "ptr-update": { + let points = system.level.current; + if (system.level.current >= 5) points += 3; + if (system.level.current >= 10) points += 5; + if (system.level.current >= 20) points += 5; + if (system.level.current >= 25) points += 3; + if (system.level.current >= 35) points += 5; + return points; + } + case "long-track": return system.level.current; + } + })() + system.modifiers.statPoints.total + 9; + system.stats = this._calcBaseStats(); const leftoverLevelUpPoints = system.levelUpPoints - Object.values(system.stats).reduce((a, v) => v.levelUp + a, 0); - const actualLevel = Math.max(1, system.level.current - Math.max(0, Math.clamped(0, leftoverLevelUpPoints, leftoverLevelUpPoints - system.modifiers.statPoints.total ?? 0))); + const actualLevel = Math.max(1, system.level.current - Math.max(0, Math.clamp(0, leftoverLevelUpPoints, leftoverLevelUpPoints - system.modifiers.statPoints.total ?? 0))); const result = calculateStatTotal({ - level: game.settings.get("ptu", "variant.trainerRevamp") ? actualLevel * 2 : actualLevel, + level: ["data-revamp", "short-track"].includes(game.settings.get("ptu", "variant.trainerAdvancement")) ? actualLevel * 2 : (game.settings.get("ptu", "variant.trainerAdvancement") === "long-track" ? actualLevel * 0.5 : actualLevel), actorStats: system.stats, nature: null, isTrainer: true, @@ -124,7 +158,7 @@ class PTUTrainerActor extends PTUActor { system.stats = result.stats; system.levelUpPoints = system.levelUpPoints - result.pointsSpend; - system.health.total = 10 + (system.level.current * (game.settings.get("ptu", "variant.trainerRevamp") ? 4 : 2)) + (system.stats.hp.total * 3); + system.health.total = 10 + (system.level.current * (["data-revamp", "short-track"].includes(game.settings.get("ptu", "variant.trainerAdvancement")) ? 4 : (game.settings.get("ptu", "variant.trainerAdvancement") === "long-track" ? 1 : 2))) + (system.stats.hp.total * 3); system.health.max = system.health.injuries > 0 ? Math.trunc(system.health.total * (1 - ((system.modifiers.hardened ? Math.min(system.health.injuries, 5) : system.health.injuries) / 10))) : system.health.total; system.health.percent = Math.round((system.health.value / system.health.max) * 100); @@ -136,25 +170,85 @@ class PTUTrainerActor extends PTUActor { system.feats = { total: this.items.filter(x => x.type == "feat" && !x.system.free).length, - max: 4 + (game.settings.get("ptu", "variant.trainerRevamp") ? system.level.current : Math.ceil(system.level.current / 2)) + max: ((level) => { + switch (game.settings.get("ptu", "variant.trainerAdvancement")) { + case "original": return 4 + Math.ceil(level / 2); + case "data-revamp": return 4 + level; + case "short-track": { + let feats = 4 + level; + if (level >= 5) feats += 1; + if (level >= 10) feats += 1; + if (level >= 15) feats += 1; + if (level >= 20) feats += 1; + if (level == 25) feats += 3; + return feats; + } + case "ptr-update": { + let feats = 4 + Math.ceil(level / 2); + if (level >= 5) feats += 1; + if (level >= 15) feats += 1; + if (level >= 25) feats += 1; + if (level >= 45) feats += 1; + if (level == 50) feats += 2; + return feats; + }; + case "long-track": return 4 + Math.ceil(level / 2); + } + })(Number(system.level.current)) + (system.modifiers.featPoints?.total ?? 0) } system.edges = { total: this.items.filter(x => x.type == "edge" && !x.system.free).length, - max: 4 - + (game.settings.get("ptu", "variant.trainerRevamp") ? system.level.current : Math.floor(system.level.current / 2)) - + (system.level.current >= 2 ? 1 : 0) - + (system.level.current >= 6 ? 1 : 0) - + (system.level.current >= 12 ? 1 : 0) - + (game.settings.get("ptu", "variant.trainerRevamp") ? (system.level.current >= 5 ? 1 : 0) : 0) - + (game.settings.get("ptu", "variant.trainerRevamp") ? (system.level.current >= 10 ? 1 : 0) : 0) - + (game.settings.get("ptu", "variant.trainerRevamp") ? (system.level.current >= 15 ? 1 : 0) : 0) - + (game.settings.get("ptu", "variant.trainerRevamp") ? (system.level.current >= 20 ? 1 : 0) : 0) - + (game.settings.get("ptu", "variant.trainerRevamp") ? (system.level.current >= 25 ? 1 : 0) : 0) + max: ((level) => { + switch (game.settings.get("ptu", "variant.trainerAdvancement")) { + case "original": { + let edges = 4 + Math.floor(level / 2); + if (level >= 2) edges += 1; + if (level >= 6) edges += 1; + if (level >= 12) edges += 1; + return edges; + } + case "data-revamp": { + let edges = 4 + level; + if (level >= 2) edges += 1; + if (level >= 6) edges += 1; + if (level >= 12) edges += 1; + return edges; + } + case "short-track": { + let edges = 4 + level; + if (level >= 2) edges += 1; + if (level >= 6) edges += 1; + if (level >= 10) edges += 1; + if (level >= 12) edges += 1; + if (level >= 15) edges += 1; + if (level >= 20) edges += 2; + if (level == 25) edges += 3; + return edges; + } + case "ptr-update": { + let edges = 4 + Math.floor(level / 2); + if (level >= 2) edges += 1; + if (level >= 8) edges += 1; + if (level >= 10) edges += 1; + if (level >= 16) edges += 1; + if (level >= 20) edges += 1; + if (level >= 35) edges += 2; + if (level == 50) edges += 3; + return edges; + }; + case "long-track": { + let edges = 4 + Math.floor(level / 2); + if (level >= 10) edges += 1; + if (level >= 20) edges += 1; + return edges; + } + } + })(Number(system.level.current)) + (system.modifiers.edgePoints?.total ?? 0) } - system.ap.bound = Number(this.synthetics.apAdjustments.bound.map(b => b.value).reduce((a,b)=> a+b, 0)) || 0 - system.ap.drained = Number(this.synthetics.apAdjustments.drained.map(d => d.value).reduce((a,b)=> a+b, 0)) || 0 + system.ap.bound = Number(this.synthetics.apAdjustments.bound.map(b => b.value).reduce((a, b) => a + b, 0)) || 0 + system.ap.drained = Number(this.synthetics.apAdjustments.drained.map(d => d.value).reduce((a, b) => a + b, 0)) || 0 system.ap.max = this.baseMaxAp - system.ap.bound - system.ap.drained system.initiative = { value: system.stats.spd.total + system.modifiers.initiative.total }; @@ -196,7 +290,7 @@ class PTUTrainerActor extends PTUActor { } _calcBaseStats() { - const stats = duplicate(this.system.stats); + const stats = foundry.utils.duplicate(this.system.stats); for (const stat of Object.keys(stats)) { if (stat === "hp") stats[stat].base = 10; @@ -218,7 +312,7 @@ class PTUTrainerActor extends PTUActor { if (!changes["system"]["skills"][value]) changes["system"]["skills"][value] = {} if (!changes["system"]["skills"][value]['value']) changes["system"]["skills"][value]['value'] = {} if (!changes["system"]["skills"][value]['value']['mod']) changes["system"]["skills"][value]['value']['mod'] = {} - changes["system"]["skills"][value]['value']['mod'][randomID()] = { mode: 'add', value: -1, source: "Pathetic Background Skills" }; + changes["system"]["skills"][value]['value']['mod'][foundry.utils.randomID()] = { mode: 'add', value: -1, source: "Pathetic Background Skills" }; } } const { adept, novice } = this.system.background; @@ -227,14 +321,14 @@ class PTUTrainerActor extends PTUActor { if (!changes["system"]["skills"][adept]) changes["system"]["skills"][adept] = {} if (!changes["system"]["skills"][adept]['value']) changes["system"]["skills"][adept]['value'] = {} if (!changes["system"]["skills"][adept]['value']['mod']) changes["system"]["skills"][adept]['value']['mod'] = {} - changes["system"]["skills"][adept]['value']['mod'][randomID()] = { mode: 'add', value: 2, source: "Adept Background Skill" }; + changes["system"]["skills"][adept]['value']['mod'][foundry.utils.randomID()] = { mode: 'add', value: 2, source: "Adept Background Skill" }; } if (novice && novice != "blank") { if (!changes["system"]["skills"]) changes["system"]["skills"] = {} if (!changes["system"]["skills"][novice]) changes["system"]["skills"][novice] = {} if (!changes["system"]["skills"][novice]['value']) changes["system"]["skills"][novice]['value'] = {} if (!changes["system"]["skills"][novice]['value']['mod']) changes["system"]["skills"][novice]['value']['mod'] = {} - changes["system"]["skills"][novice]['value']['mod'][randomID()] = { mode: 'add', value: 1, source: "Novice Background Skill" }; + changes["system"]["skills"][novice]['value']['mod'][foundry.utils.randomID()] = { mode: 'add', value: 1, source: "Novice Background Skill" }; } changes.system.maxAp = { 1: { @@ -258,7 +352,7 @@ class PTUTrainerActor extends PTUActor { value: - b.value } }) - this.system.changes = mergeObject( + this.system.changes = foundry.utils.mergeObject( this.system.changes, changes ); diff --git a/src/module/actor/character/sheet.js b/src/module/actor/character/sheet.js index 8a0153f8a..784cd2cbd 100644 --- a/src/module/actor/character/sheet.js +++ b/src/module/actor/character/sheet.js @@ -9,7 +9,7 @@ export class PTUCharacterSheet extends PTUActorSheet { /** @override */ static get defaultOptions() { - const options = mergeObject(super.defaultOptions, { + const options = foundry.utils.mergeObject(super.defaultOptions, { classes: ['ptu', 'sheet', 'actor', 'gen8'], template: 'systems/ptu/static/templates/actor/trainer-sheet.hbs', width: 1200, @@ -64,6 +64,27 @@ export class PTUCharacterSheet extends PTUActorSheet { } else data.columns = this.actor.getFlag("ptu", "itemColumns"); + const IWR = this.actor.iwr; + data.effectiveness = { + weaknesses: [], + resistances: [], + immunities: [] + } + for(const [type, value] of Object.entries(IWR.all)) { + if(value === 0) { + data.effectiveness.immunities.push({type: type.capitalize(), value: IWR.getRealValue(type)}); + continue; + } + if(value > 1) { + data.effectiveness.weaknesses.push({type: type.capitalize(), value: IWR.getRealValue(type)}); + continue; + } + if(value < 1) { + data.effectiveness.resistances.push({type: type.capitalize(), value: IWR.getRealValue(type)}); + continue; + } + } + return data; } @@ -245,7 +266,8 @@ export class PTUCharacterSheet extends PTUActorSheet { event, options: msg.context.options ?? [], actor: msg.actor, - targets: msg.targets + targets: msg.targets, + rollResult: msg.context.rollResult ?? null, } const result = await attack.damage?.(params); if (result === null) { @@ -293,6 +315,13 @@ export class PTUCharacterSheet extends PTUActorSheet { } }); + html.find('.item-enable').click((ev) => { + const li = $(ev.currentTarget).parents('.item'); + /** @type {PTUItem} */ + const item = this.actor.items.get(li.data('itemId')); + return item?.toggleEnableState?.(); + }); + html.find('.item-to-chat').click((ev) => { const li = $(ev.currentTarget).parents('.item'); const item = this.actor.items.get(li.data('itemId')); @@ -302,7 +331,7 @@ export class PTUCharacterSheet extends PTUActorSheet { html.find(".item-quantity input[type='number']").change((ev) => { const value = Number(ev.currentTarget.value); const id = ev.currentTarget.dataset.itemId; - if (value > 0 && id) { + if (value >= 0 && id) { const item = this.actor.items.get(id); item?.update({ "system.quantity": value }); } @@ -409,7 +438,7 @@ export class PTUCharacterSheet extends PTUActorSheet { // If duplicate item gets added instead increase the quantity const existingItem = this.actor.items.getName(item.name); if (existingItem && existingItem.id != item.id && existingItem.system.quantity) { - const quantity = duplicate(existingItem.system.quantity); + const quantity = foundry.utils.duplicate(existingItem.system.quantity); await existingItem.update({ "system.quantity": Number(quantity) + (item.system.quantity > 0 ? Number(item.system.quantity) : 1) }); return false; } @@ -435,7 +464,7 @@ export class PTUCharacterSheet extends PTUActorSheet { // Get the type of item to create. const type = header.dataset.type; // Grab any data associated with this control. - const data = duplicate(header.dataset); + const data = foundry.utils.duplicate(header.dataset); // Initialize a default name. const name = `New ${game.i18n.localize(`TYPES.Item.${type}`)}`; // Prepare the item object. diff --git a/src/module/actor/helpers.js b/src/module/actor/helpers.js index c5e603a01..dc46d8d19 100644 --- a/src/module/actor/helpers.js +++ b/src/module/actor/helpers.js @@ -1,5 +1,5 @@ function calcBaseStats(stats, speciesData, nature, baseStatModifier, ignoreStages = false) { - const newStats = duplicate(stats); + const newStats = foundry.utils.duplicate(stats); newStats.hp.base = _calcBaseStat(speciesData, nature, "HP", ignoreStages); newStats.hp.value = (baseStatModifier?.hp.total ?? 0) + newStats.hp.base; @@ -152,7 +152,7 @@ function dev(stats) { globalThis.dev = dev; function calculateStatTotal({ level, actorStats, nature, isTrainer, twistedPower, hybridArmor }) { - const stats = duplicate(actorStats); + const stats = foundry.utils.duplicate(actorStats); const levelDivisionConstant = isTrainer ? 25 : 50; const longShortStatDict = { "HP": "hp", @@ -196,15 +196,15 @@ function calculateStatTotal({ level, actorStats, nature, isTrainer, twistedPower } if (twistedPower) { - const atkTotal = duplicate(stats.atk.total); - const spatkTotal = duplicate(stats.spatk.total); + const atkTotal = foundry.utils.duplicate(stats.atk.total); + const spatkTotal = foundry.utils.duplicate(stats.spatk.total); stats.atk.total += Math.floor(spatkTotal / 3); stats.spatk.total += Math.floor(atkTotal / 3); } if (hybridArmor) { - const defTotal = duplicate(stats.def.total); - const spdefTotal = duplicate(stats.spdef.total); + const defTotal = foundry.utils.duplicate(stats.def.total); + const spdefTotal = foundry.utils.duplicate(stats.spdef.total); stats.def.total += Math.floor(spdefTotal / 3); stats.spdef.total += Math.floor(defTotal / 3); @@ -223,7 +223,7 @@ function calculateStatTotal({ level, actorStats, nature, isTrainer, twistedPower for (const [key, value] of Object.entries(stats)) { const sub = value["total"] + value["mod"].value + value["mod"].mod; - if (key != "hp") value["stage"].total = Math.clamped((value["stage"]?.value ?? 0) + (value["stage"]?.mod ?? 0), -6, 6); + if (key != "hp") value["stage"].total = Math.clamp((value["stage"]?.value ?? 0) + (value["stage"]?.mod ?? 0), -6, 6); if (value["stage"]?.total > 0) { if (playtestStats) { value["total"] = Math.floor(sub * value["stage"].total * (0.275 * Math.log10(130 - (isTrainer ? level * 2 : level)) - 0.325) + sub) diff --git a/src/module/actor/modifiers.js b/src/module/actor/modifiers.js index 5a4edee58..143e2077a 100644 --- a/src/module/actor/modifiers.js +++ b/src/module/actor/modifiers.js @@ -64,8 +64,8 @@ class PTUModifier { this.#originalValue = this.modifier = modifier ?? 0; - this.adjustments = deepClone(adjustments ?? []); - this.enabled = enabled ?? true; + this.adjustments = foundry.utils.deepClone(adjustments ?? []); + this.enabled = enabled ?? item?.enabled ?? true; this.ignored = ignored ?? false; this.source = source ?? null; this.predicate = new PTUPredicate(predicate ?? []); @@ -124,7 +124,7 @@ class PTUModifier { } toObject() { - return duplicate({...this, item: undefined}); + return foundry.utils.duplicate({...this, item: undefined}); } toString() { @@ -335,7 +335,7 @@ class PTUDiceModifier { } toObject() { - return duplicate({...this}); + return foundry.utils.duplicate({...this}); } toString() { diff --git a/src/module/actor/pokemon/capabilities.js b/src/module/actor/pokemon/capabilities.js deleted file mode 100644 index 6c43766f9..000000000 --- a/src/module/actor/pokemon/capabilities.js +++ /dev/null @@ -1,65 +0,0 @@ -function calculatePokemonCapabilities(speciesData, items, speedCombatStages = 0, capabilityMod = 0, isSlowed = false) { - if (speciesData?.Capabilities == null) return []; - - if (typeof (speciesData.Capabilities.Overland) === "string") { - console.warn("(Custom) Species Data contains faulty values. Converting to integers.", speciesData._id, speciesData.number) - for (let key of Object.keys(speciesData.Capabilities)) { - if (key == "Weight Class" || key == "Naturewalk" || key == "Other") continue; - speciesData.Capabilities[key] = parseInt(speciesData.Capabilities[key]) - } - } - - for (let item of items) { - // Abilities - if (item.name == "Levitate" && item.type == "ability") { - if (speciesData.Capabilities["Levitate"] > 0) speciesData.Capabilities["Levitate"] += 2; - else speciesData.Capabilities["Levitate"] += 4; - } - - // Moves - if (item.name == "Bounce" && item.type == "move") speciesData.Capabilities["High Jump"] += 1; - if (item.name == "Splash" && item.type == "move") speciesData.Capabilities["Long Jump"] += 1; - if (item.name == "Strength" && item.type == "move") speciesData.Capabilities["Power"] += 1; - if (item.name == "Teleport" && item.type == "move") { - if (speciesData.Capabilities["Teleporter"]) speciesData.Capabilities["Teleporter"] += 4; - else speciesData.Capabilities["Teleporter"] = 4; - } - if (item.name == "Dive" && item.type == "move") speciesData.Capabilities["Swim"] += 3; - if (item.name == "Fly" && item.type == "move") speciesData.Capabilities["Sky"] += 3; - if (item.name == "Dig" && item.type == "move") speciesData.Capabilities["Burrow"] += 3; - - // Poké Edges - if (item.name == "Advanced Mobility (Sky)" && item.type == "pokeedge") { - if (speciesData.Capabilities["Sky"] > 0) speciesData.Capabilities["Sky"] += 2; - } - if (item.name == "Advanced Mobility (Burrow)" && item.type == "pokeedge") { - if (speciesData.Capabilities["Burrow"] > 0) speciesData.Capabilities["Burrow"] += 2; - } - if (item.name == "Advanced Mobility (Levitate)" && item.type == "pokeedge") { - if (speciesData.Capabilities["Levitate"] > 0) speciesData.Capabilities["Levitate"] += 2; - } - if (item.name == "Advanced Mobility (Teleporter)" && item.type == "pokeedge") { - if (speciesData.Capabilities["Teleporter"] > 0) speciesData.Capabilities["Teleporter"] += 2; - } - } - - let spcsChanges = speedCombatStages > 0 ? Math.floor(speedCombatStages / 2) : speedCombatStages < 0 ? Math.ceil(speedCombatStages / 2) : 0; - // if (spcsChanges > 0 || spcsChanges < 0) { - for (let key of Object.keys(speciesData.Capabilities)) { - if (key == "High Jump" || key == "Long Jump") { - continue; - // If High & Long Jump should be halved by slowed move continue statement to end of this if statement. - if(isSlowed) speciesData.Capabilities[key] = Math.max(1, Math.floor(speciesData.Capabilities[key] * 0.5)); - }; - if (key == "Power" || key == "Weight Class" || key == "Naturewalk" || key == "Other") continue; - if (speciesData.Capabilities[key] > 0) { - speciesData.Capabilities[key] = Math.max(speciesData.Capabilities[key] + spcsChanges + capabilityMod ?? 0, speciesData.Capabilities[key] > 1 ? 2 : 1) - if(isSlowed) speciesData.Capabilities[key] = Math.max(1, Math.floor(speciesData.Capabilities[key] * 0.5)); - } - } - // } - - return speciesData.Capabilities; -} - -export { calculatePokemonCapabilities } \ No newline at end of file diff --git a/src/module/actor/pokemon/document.js b/src/module/actor/pokemon/document.js index 2cb1183d6..333de5b15 100644 --- a/src/module/actor/pokemon/document.js +++ b/src/module/actor/pokemon/document.js @@ -22,7 +22,7 @@ class PTUPokemonActor extends PTUActor { } get allowedItemTypes() { - return ["species", "pokeedge", "move", "contestmove", "ability", "capability", "effect", "condition", "spiritaction"] + return ["species", "pokeedge", "move", "contestmove", "ability", "capability", "effect", "condition", "spiritaction", "item"] } get nature() { @@ -193,7 +193,7 @@ class PTUPokemonActor extends PTUActor { system.stats = this._calcBaseStats(); const leftoverLevelUpPoints = system.levelUpPoints - Object.values(system.stats).reduce((a, v) => v.levelUp + a, 0); - const actualLevel = Math.max(1, system.level.current - Math.max(0, Math.clamped(0, leftoverLevelUpPoints, leftoverLevelUpPoints - system.modifiers.statPoints.total ?? 0))); + const actualLevel = Math.max(1, system.level.current - Math.max(0, Math.clamp(0, leftoverLevelUpPoints, leftoverLevelUpPoints - system.modifiers.statPoints.total ?? 0))); const result = calculateStatTotal({ level: actualLevel, @@ -252,6 +252,7 @@ class PTUPokemonActor extends PTUActor { // Calculate Skill Ranks for (const [key, skill] of Object.entries(speciesSystem?.skills ?? {})) { + system.skills[key].slug = key; system.skills[key]["value"]["value"] = skill["value"] system.skills[key]["value"]["total"] = skill["value"] + system.skills[key]["value"]["mod"]; system.skills[key]["modifier"]["value"] = skill["modifier"] @@ -297,7 +298,11 @@ class PTUPokemonActor extends PTUActor { this.attributes.health.max = system.health.max; - this.attributes.level.cap = Math.ceil(5 + (1.58 * ((this.trainer?.system.level.current ?? 0) * (game.settings.get("ptu", "variant.trainerRevamp") ? 2 : 1))) + ((4 / 3) * (system.friendship ?? 0) * Math.pow(1 + (((this.trainer?.system.level.current ?? 0) * (game.settings.get("ptu", "variant.trainerRevamp") ? 2 : 1)) / 34), 2))); + const calcLevelCap = (friendship) => Math.ceil(5 + (1.58 * ((this.trainer?.system.level.current ?? 0) * (["data-revamp", "short-track"].includes(game.settings.get("ptu", "variant.trainerAdvancement")) ? 2 : ["long-track"].includes(game.settings.get("ptu", "variant.trainerAdvancement")) ? 0.5 : 1))) + ((4 / 3) * (friendship) * Math.pow(1 + (((this.trainer?.system.level.current ?? 0) * (["data-revamp", "short-track"].includes(game.settings.get("ptu", "variant.trainerAdvancement")) ? 2 : ["long-track"].includes(game.settings.get("ptu", "variant.trainerAdvancement")) ? 0.5 : 1)) / 34), 2))); + this.attributes.level.cap = { + current: calcLevelCap(system.friendship ?? 0), + training: calcLevelCap(0), + } /* The Corner of Exceptions */ @@ -309,7 +314,7 @@ class PTUPokemonActor extends PTUActor { } _calcBaseStats() { - const stats = duplicate(this.system.stats); + const stats = foundry.utils.duplicate(this.system.stats); const speciesStats = this.species?.system?.stats; for (const stat of Object.keys(stats)) { @@ -356,12 +361,12 @@ class PTUPokemonActor extends PTUActor { // TODO: Implement rules for capability changing items _calcCapabilities() { - const speciesCapabilities = duplicate(this.species?.system?.capabilities ?? {}); + const speciesCapabilities = foundry.utils.duplicate(this.species?.system?.capabilities ?? {}); if (!speciesCapabilities) return {}; const finalCapabilities = {} // Anything that is not a part of CONFIG.PTU.Capabilities.numericNonMovement or CONFIG.PTU.Capabilities.stringArray is considered movement, without explicitly listing them hardcoded. - const capsFromSpeciesOrModifiers = [...Object.keys(this.system.modifiers.capabilities), ...Object.keys(speciesCapabilities)] + const capsFromSpeciesOrModifiers = [...Object.keys(this.system.modifiers.capabilities), ...Object.keys(speciesCapabilities)].filter(cap => cap !== "all") const movementCapabilities = capsFromSpeciesOrModifiers.filter(cap => !CONFIG.PTU.Capabilities.stringArray.includes(cap) || !CONFIG.PTU.Capabilities.numericNonMovement.includes(cap)) const speedCombatStages = this.system.stats.spd.stage.value + this.system.stats.spd.stage.mod; @@ -374,6 +379,7 @@ class PTUPokemonActor extends PTUActor { if (this.system.modifiers.capabilities[moveCap] || speciesCapabilities[moveCap]) { const mod = this.system.modifiers?.capabilities[moveCap] ? this.system.modifiers?.capabilities[moveCap] : 0 const speciesCap = speciesCapabilities[moveCap] ? speciesCapabilities[moveCap] : 0 + if(speciesCap <= 0 && mod <= 0) continue; finalCapabilities[moveCap] = Math.max(1, Math.floor(slowedMultiplier * (speciesCap + spdCsChanges + omniMovementMod + mod))) } else { delete finalCapabilities[moveCap]; @@ -400,6 +406,9 @@ class PTUPokemonActor extends PTUActor { finalCapabilities[cap] = speciesCapabilities[cap] } + if(finalCapabilities["levitate"] === undefined) finalCapabilities["levitate"] = 0; + if(finalCapabilities["sky"] === undefined) finalCapabilities["sky"] = 0; + return finalCapabilities; } @@ -413,7 +422,7 @@ class PTUPokemonActor extends PTUActor { if (!changes["system"]["skills"][value]) changes["system"]["skills"][value] = {} if (!changes["system"]["skills"][value]['value']) changes["system"]["skills"][value]['value'] = {} if (!changes["system"]["skills"][value]['value']['mod']) changes["system"]["skills"][value]['value']['mod'] = {} - changes["system"]["skills"][value]['value']['mod'][randomID()] = { mode: 'add', value: 1, source: "Skill Background" }; + changes["system"]["skills"][value]['value']['mod'][foundry.utils.randomID()] = { mode: 'add', value: 1, source: "Skill Background" }; } } for (const value of Object.values(this.system.background.decreased)) { @@ -422,7 +431,7 @@ class PTUPokemonActor extends PTUActor { if (!changes["system"]["skills"][value]) changes["system"]["skills"][value] = {} if (!changes["system"]["skills"][value]['value']) changes["system"]["skills"][value]['value'] = {} if (!changes["system"]["skills"][value]['value']['mod']) changes["system"]["skills"][value]['value']['mod'] = {} - changes["system"]["skills"][value]['value']['mod'][randomID()] = { mode: 'add', value: -1, source: "Skill Background" }; + changes["system"]["skills"][value]['value']['mod'][foundry.utils.randomID()] = { mode: 'add', value: -1, source: "Skill Background" }; } } if (game.settings.get("ptu", "variant.spiritPlaytest")) { @@ -446,46 +455,46 @@ class PTUPokemonActor extends PTUActor { case 5: case 4: case 3: { - changes["system"]["modifiers"]["acBonus"]["mod"][randomID()] = { mode: 'add', value: 1, source: "Spirit" }; - changes["system"]["modifiers"]["evasion"]["physical"]["mod"][randomID()] = { mode: 'add', value: 1, source: "Spirit" }; - changes["system"]["modifiers"]["evasion"]["special"]["mod"][randomID()] = { mode: 'add', value: 1, source: "Spirit" }; - changes["system"]["modifiers"]["evasion"]["speed"]["mod"][randomID()] = { mode: 'add', value: 1, source: "Spirit" }; - changes["system"]["modifiers"]["critRange"]["mod"][randomID()] = { mode: 'add', value: 1, source: "Spirit" }; - changes["system"]["modifiers"]["saveChecks"]["mod"][randomID()] = { mode: 'add', value: 2, source: "Spirit" }; - changes["system"]["modifiers"]["skillBonus"]["mod"][randomID()] = { mode: 'add', value: 2, source: "Spirit" }; + changes["system"]["modifiers"]["acBonus"]["mod"][foundry.utils.randomID()] = { mode: 'add', value: 1, source: "Spirit" }; + changes["system"]["modifiers"]["evasion"]["physical"]["mod"][foundry.utils.randomID()] = { mode: 'add', value: 1, source: "Spirit" }; + changes["system"]["modifiers"]["evasion"]["special"]["mod"][foundry.utils.randomID()] = { mode: 'add', value: 1, source: "Spirit" }; + changes["system"]["modifiers"]["evasion"]["speed"]["mod"][foundry.utils.randomID()] = { mode: 'add', value: 1, source: "Spirit" }; + changes["system"]["modifiers"]["critRange"]["mod"][foundry.utils.randomID()] = { mode: 'add', value: 1, source: "Spirit" }; + changes["system"]["modifiers"]["saveChecks"]["mod"][foundry.utils.randomID()] = { mode: 'add', value: 2, source: "Spirit" }; + changes["system"]["modifiers"]["skillBonus"]["mod"][foundry.utils.randomID()] = { mode: 'add', value: 2, source: "Spirit" }; break; } case 2: { - changes["system"]["modifiers"]["saveChecks"]["mod"][randomID()] = { mode: 'add', value: 2, source: "Spirit" }; - changes["system"]["modifiers"]["skillBonus"]["mod"][randomID()] = { mode: 'add', value: 2, source: "Spirit" }; + changes["system"]["modifiers"]["saveChecks"]["mod"][foundry.utils.randomID()] = { mode: 'add', value: 2, source: "Spirit" }; + changes["system"]["modifiers"]["skillBonus"]["mod"][foundry.utils.randomID()] = { mode: 'add', value: 2, source: "Spirit" }; break; } case 1: { - changes["system"]["modifiers"]["skillBonus"]["mod"][randomID()] = { mode: 'add', value: 2, source: "Spirit" }; + changes["system"]["modifiers"]["skillBonus"]["mod"][foundry.utils.randomID()] = { mode: 'add', value: 2, source: "Spirit" }; break; } case -1: { - changes["system"]["modifiers"]["skillBonus"]["mod"][randomID()] = { mode: 'add', value: -2, source: "Spirit" }; + changes["system"]["modifiers"]["skillBonus"]["mod"][foundry.utils.randomID()] = { mode: 'add', value: -2, source: "Spirit" }; break; } case -2: { - changes["system"]["modifiers"]["saveChecks"]["mod"][randomID()] = { mode: 'add', value: -2, source: "Spirit" }; - changes["system"]["modifiers"]["skillBonus"]["mod"][randomID()] = { mode: 'add', value: -2, source: "Spirit" }; + changes["system"]["modifiers"]["saveChecks"]["mod"][foundry.utils.randomID()] = { mode: 'add', value: -2, source: "Spirit" }; + changes["system"]["modifiers"]["skillBonus"]["mod"][foundry.utils.randomID()] = { mode: 'add', value: -2, source: "Spirit" }; break; } case -3: { - changes["system"]["modifiers"]["acBonus"]["mod"][randomID()] = { mode: 'add', value: -1, source: "Spirit" }; - changes["system"]["modifiers"]["evasion"]["physical"]["mod"][randomID()] = { mode: 'add', value: -1, source: "Spirit" }; - changes["system"]["modifiers"]["evasion"]["special"]["mod"][randomID()] = { mode: 'add', value: -1, source: "Spirit" }; - changes["system"]["modifiers"]["evasion"]["speed"]["mod"][randomID()] = { mode: 'add', value: -1, source: "Spirit" }; - changes["system"]["modifiers"]["critRange"]["mod"][randomID()] = { mode: 'add', value: -1, source: "Spirit" }; - changes["system"]["modifiers"]["saveChecks"]["mod"][randomID()] = { mode: 'add', value: -2, source: "Spirit" }; - changes["system"]["modifiers"]["skillBonus"]["mod"][randomID()] = { mode: 'add', value: -2, source: "Spirit" }; + changes["system"]["modifiers"]["acBonus"]["mod"][foundry.utils.randomID()] = { mode: 'add', value: -1, source: "Spirit" }; + changes["system"]["modifiers"]["evasion"]["physical"]["mod"][foundry.utils.randomID()] = { mode: 'add', value: -1, source: "Spirit" }; + changes["system"]["modifiers"]["evasion"]["special"]["mod"][foundry.utils.randomID()] = { mode: 'add', value: -1, source: "Spirit" }; + changes["system"]["modifiers"]["evasion"]["speed"]["mod"][foundry.utils.randomID()] = { mode: 'add', value: -1, source: "Spirit" }; + changes["system"]["modifiers"]["critRange"]["mod"][foundry.utils.randomID()] = { mode: 'add', value: -1, source: "Spirit" }; + changes["system"]["modifiers"]["saveChecks"]["mod"][foundry.utils.randomID()] = { mode: 'add', value: -2, source: "Spirit" }; + changes["system"]["modifiers"]["skillBonus"]["mod"][foundry.utils.randomID()] = { mode: 'add', value: -2, source: "Spirit" }; break; } } } - this.system.changes = mergeObject( + this.system.changes = foundry.utils.mergeObject( this.system.changes, changes ); @@ -511,7 +520,7 @@ class PTUPokemonActor extends PTUActor { }); if (result) { - if (result.changed) mergeObject(changed, result.changed); + if (result.changed) foundry.utils.mergeObject(changed, result.changed); } await super._preUpdate(changed, options, userId); @@ -525,13 +534,26 @@ class PTUPokemonActor extends PTUActor { const tokenUpdates = {}; const curImg = await PokemonGenerator.getImage(this.species, { gender: this.system.gender, shiny: this.system.shiny }); + const curTokenImg = (() => { + const tokenImageExtension = game.settings.get("ptu", "generation.defaultTokenImageExtension"); + if(curImg.endsWith(tokenImageExtension)) return curImg; + const actorImageExtension = game.settings.get("ptu", "generation.defaultImageExtension"); + return curImg.replace(actorImageExtension, tokenImageExtension); + })(); const newImg = await PokemonGenerator.getImage(result.evolution, { gender: this.system.gender, shiny: this.system.shiny }); + const newTokenImg = (() => { + const tokenImageExtension = game.settings.get("ptu", "generation.defaultTokenImageExtension"); + if(newImg.endsWith(tokenImageExtension)) return newImg; + const actorImageExtension = game.settings.get("ptu", "generation.defaultImageExtension"); + return newImg.replace(actorImageExtension, tokenImageExtension); + })(); if (this.img === curImg) update.img = newImg; - if (this.prototypeToken.texture.src === curImg) { - update["prototypeToken.texture.src"] = newImg; + if (this.prototypeToken.texture.src === curTokenImg) { + update["prototypeToken.texture.src"] = newTokenImg; tokenUpdates["texture.src"] = update["prototypeToken.texture.src"]; } + if (sluggify(this.name) == this.species.slug) { update.name = Handlebars.helpers.capitalizeFirst(result.evolution.name); tokenUpdates["name"] = update.name; @@ -580,7 +602,7 @@ class PTUPokemonActor extends PTUActor { } else if (!currentAbility) { const newAbility = (await fromUuid(ability.uuid)).toObject(); - newAbility.flags.ptu = mergeObject(newAbility.flags?.ptu ?? {}, { + newAbility.flags.ptu = foundry.utils.mergeObject(newAbility.flags?.ptu ?? {}, { abilityChosen: ability.tier }); toAdd.push(newAbility); diff --git a/src/module/actor/pokemon/generator.js b/src/module/actor/pokemon/generator.js index eff998e5d..362e42e2e 100644 --- a/src/module/actor/pokemon/generator.js +++ b/src/module/actor/pokemon/generator.js @@ -24,7 +24,7 @@ export class PokemonGenerator { if (!this.level) { this.level = (() => { if (minLevel == maxLevel) return minLevel; - return Math.clamped(0, (Math.floor(Math.random() * (maxLevel - minLevel + 1)) + minLevel), 100); + return Math.clamp(0, (Math.floor(Math.random() * (maxLevel - minLevel + 1)) + minLevel), 100); })(); } @@ -50,9 +50,16 @@ export class PokemonGenerator { if (!this.abilities) this.prepareAbilities(); if (!this.capabilities) this.prepareCapabilities(); if (!this.shiny) this.prepareShinyness(shinyChance); - if (!this.species.system.form) this.prepareForm(); + if (!this.form) this.prepareForm(); this.img = await PokemonGenerator.getImage(this.species, { gender: this.gender, shiny: this.shiny }); + this.tokenImg = (() => { + if(!this.img) return; + const tokenImageExtension = game.settings.get("ptu", "generation.defaultTokenImageExtension"); + if(this.img.endsWith(tokenImageExtension)) return this.img; + const actorImageExtension = game.settings.get("ptu", "generation.defaultImageExtension"); + return this.img.replace(actorImageExtension, tokenImageExtension); + })(); if (saveDefault) { //TODO: Save Default Settings to system @@ -78,14 +85,15 @@ export class PokemonGenerator { const foundryDefaultSettings = {...game.settings.get("core", "defaultToken")} ?? {}; - const prototypeToken = mergeObject(foundryDefaultSettings, { + const prototypeToken = foundry.utils.mergeObject(foundryDefaultSettings, { width: this.size.width, height: this.size.height, actorLink: true, displayBars: foundryDefaultSettings.displayBars ?? CONST.TOKEN_DISPLAY_MODES.OWNER_HOVER, displayName: foundryDefaultSettings.displayName ?? CONST.TOKEN_DISPLAY_MODES.OWNER, bar1: { attribute: foundryDefaultSettings.bar1?.attribute || "health" }, - img: this.img + img: this.tokenImg, + "texture.src": this.tokenImg, }); const actorData = { @@ -101,11 +109,12 @@ export class PokemonGenerator { nature: { value: this.nature }, - gender: this.gender + gender: this.gender, }, folder: folder?.id, prototypeToken } + if(this.form) actorData.system.form = this.form; const species = this.species.toObject(); species.flags.core = { @@ -312,11 +321,13 @@ export class PokemonGenerator { prepareForm() { //unown const unown_types = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "!", "Qu"]; - if (this.species.system.number === 201) return this.species.system.form = unown_types[Math.floor(Math.random() * unown_types.length)]; + if (this.species.system.number === 201) return this.form = this.species.system.form = unown_types[Math.floor(Math.random() * unown_types.length)]; //toxtricity const lowKeyNatures = ["lonely", "bold", "relaxed", "timid", "serious", "modest", "mild", "quiet", "bashful", "calm", "gentle", "careful"] - if (this.species.system.number === 849 && lowKeyNatures.includes(this.nature.toLowerCase())) return this.species.system.form = "LowKey"; + if (this.species.system.number === 849 && lowKeyNatures.includes(this.nature.toLowerCase())) return this.form = this.species.system.form = "LowKey"; + + return this.form = this.species?.system?.form; } static isEvolutionRestricted(stage, { gender } = {}) { diff --git a/src/module/actor/pokemon/sheet.js b/src/module/actor/pokemon/sheet.js index 1d22e1fa4..7239349e6 100644 --- a/src/module/actor/pokemon/sheet.js +++ b/src/module/actor/pokemon/sheet.js @@ -6,7 +6,7 @@ import { ItemSummaryRenderer } from "../sheet/item-summary.js"; export class PTUPokemonSheet extends PTUActorSheet { /** @override */ static get defaultOptions() { - const options = mergeObject(super.defaultOptions, { + const options = foundry.utils.mergeObject(super.defaultOptions, { classes: ['ptu', 'sheet', 'actor', 'gen8'], template: 'systems/ptu/static/templates/actor/pokemon-sheet.hbs', width: 1200, @@ -109,6 +109,7 @@ export class PTUPokemonSheet extends PTUActorSheet { // Initialize containers. const abilities = []; const capabilities = []; + const items = []; const edges = []; const effects = []; const conditions = this.actor.conditions; @@ -139,6 +140,9 @@ export class PTUPokemonSheet extends PTUActorSheet { case 'spiritaction': spiritactions.push(i); break; + case 'item': + items.push(i); + break; } } @@ -150,6 +154,7 @@ export class PTUPokemonSheet extends PTUActorSheet { sheetData.conditions = conditions; sheetData.contestmoves = contestmoves; sheetData.spiritactions = spiritactions; + sheetData.items = items; sheetData.actions = await (async () => { const moves = []; @@ -237,7 +242,8 @@ export class PTUPokemonSheet extends PTUActorSheet { event, options: msg.context.options ?? [], actor: msg.actor, - targets: msg.targets + targets: msg.targets, + rollResult: msg.context.rollResult ?? null, } const result = await attack.damage?.(params); if (result === null) { @@ -251,6 +257,13 @@ export class PTUPokemonSheet extends PTUActorSheet { // Add Inventory Item html.find('.item-create').click(this._onItemCreate.bind(this)); + html.find('.item-enable').click((ev) => { + const li = $(ev.currentTarget).parents('.item'); + /** @type {PTUItem} */ + const item = this.actor.items.get(li.data('itemId')); + return item?.toggleEnableState?.(); + }); + html.find('.item-to-chat').click((ev) => { const li = $(ev.currentTarget).parents('.item'); const item = this.actor.items.get(li.data('itemId')); @@ -264,7 +277,51 @@ export class PTUPokemonSheet extends PTUActorSheet { item.sheet.render(true); }); + html.find(".item-quantity input[type='number']").change((ev) => { + const value = Number(ev.currentTarget.value); + const id = ev.currentTarget.dataset.itemId; + if (value >= 0 && id) { + const item = this.actor.items.get(id); + item?.update({ "system.quantity": value }); + } + }); + html.find('.item-delete').click(this._onItemDelete.bind(this)); + + this._contextMenu(html); + } + + _contextMenu(html) { + ContextMenu.create(this, html, ".move-item", [ + { + name: "Roll", + icon: '', + callback: this.#onMoveRoll.bind(this), + }, + { + name: "Send to Chat", + icon: '', + callback: (ev) => { + const li = $(ev.currentTarget).parents('.item'); + const item = this.actor.items.get(li.data('itemId')); + return item?.sendToChat?.(); + } + }, + { + name: "Edit", + icon: '', + callback: (ev) => { + const li = $(ev.currentTarget).parents('.item'); + const item = this.actor.items.get(li.data('itemId')); + item.sheet.render(true); + } + }, + { + name: "Delete", + icon: '', + callback: this._onItemDelete.bind(this), + }, + ], {}) } /** @@ -278,7 +335,7 @@ export class PTUPokemonSheet extends PTUActorSheet { // Get the type of item to create. const type = header.dataset.type; // Grab any data associated with this control. - const data = duplicate(header.dataset); + const data = foundry.utils.duplicate(header.dataset); // Initialize a default name. const name = `New ${game.i18n.localize(`TYPES.Item.${type}`)}`; // Prepare the item object. diff --git a/src/module/actor/sheet.js b/src/module/actor/sheet.js index d94bdbc52..fd05a00aa 100644 --- a/src/module/actor/sheet.js +++ b/src/module/actor/sheet.js @@ -18,6 +18,44 @@ class PTUActorSheet extends ActorSheet { icon: "fas fa-cog", onclick: () => this._onConfigureActor(), }); + + // Add a button to heal the character as if Pokecenter Healing was done + buttons.unshift({ + label: "Heal", + class: "heal-character", + icon: "fas fa-heart", + onclick: async () => { + const hp = this.actor.system.health.value; + const maxHp = this.actor.system.health.max; + const totalHp = this.actor.system.health.total; + const injuries = this.actor.system.health.injuries; + if(injuries === 0 && hp === maxHp) return ui.notifications.info(`${this.actor.name} is already at full health!`); + if(injuries <= 3) { + await this.actor.update({ + "system.health.value": totalHp, + "system.health.injuries": 0 + }); + await ChatMessage.create({ + speaker: {alias: this.actor.name}, + content: `${this.actor.name} was healed to full health! (${hp} -> ${totalHp}) and healed ${injuries} injuries! (${injuries} -> 0)` + }) + } + else { + await this.actor.update({ + "system.health.injuries": Math.max(0, injuries - 3) + }) + + const newMax = this.actor.system.health.max; + await this.actor.update({ + "system.health.value": newMax + }); + await ChatMessage.create({ + speaker: {alias: this.actor.name}, + content: `${this.actor.name} was healed to full health! (${hp} -> ${newMax}) and healed 3 injuries! (${injuries} -> ${Math.max(0, injuries - 3)})` + }) + } + } + }) } // Add notes button @@ -37,32 +75,32 @@ class PTUActorSheet extends ActorSheet { /** Emulate a sheet item drop from the canvas */ async emulateItemDrop(data) { - return this._onDropItem({ preventDefault: () => {} }, data); + return this._onDropItem({ preventDefault: () => { } }, data); } async openNotes() { const folder = await (async () => { const folderId = game.settings.get("ptu", "worldNotesFolder"); if (!folderId) { - if(game.user.isGM) return game.ptu.macros.initializeWorldNotes(); + if (game.user.isGM) return game.ptu.macros.initializeWorldNotes(); else return null; } const folder = game.folders.get(folderId); if (!folder) { - if(game.user.isGM) return game.ptu.macros.initializeWorldNotes(); + if (game.user.isGM) return game.ptu.macros.initializeWorldNotes(); else return null; } return folder; })(); - if(!folder) return ui.notifications.error("No folder found for world notes; Please ask your GM to login and not to delete the folder \"Actor Notes\""); + if (!folder) return ui.notifications.error("No folder found for world notes; Please ask your GM to login and not to delete the folder \"Actor Notes\""); const journalEntry = await (async () => { const journalId = this.actor.getFlag("ptu", "notesId"); if (!journalId) { - const journal = await JournalEntry.create({ - name: this.actor.name, + const journal = await JournalEntry.create({ + name: this.actor.name, folder: folder.id, ownership: this.actor.ownership, pages: [ @@ -89,10 +127,51 @@ class PTUActorSheet extends ActorSheet { return journal; })(); - if(!journalEntry) return; + if (!journalEntry) return; journalEntry.sheet.render(true); } + + /** @override */ + async _onDrop(event) { + const data = TextEditor.getDragEventData(event); + const actor = this.actor; + const allowed = Hooks.call("dropActorSheetData", actor, this, data); + if (allowed === false) return; + + // Case 1 - Money + if (data.type === "pokedollar") { + const amount = parseInt(data.data.amount); + if (!amount) return ui.notifications.error("Invalid amount of money dropped"); + + if (data.data.item) { + const item = await fromUuid(data.data.item); + if (!item) return ui.notifications.error("Invalid item dropped"); + + if ((actor.system.money ?? 0) < amount) return ui.notifications.error(`${actor.name} does not have enough money to pay for ${item.name} (Cost: ${amount} Poké, Current: ${actor.system.money})`); + await actor.update({ + "system.money": actor.system.money - amount, + }); + // If duplicate item gets added instead increase the quantity + const existingItem = actor.items.getName(item.name); + if (existingItem && existingItem.system.quantity) { + const quantity = foundry.utils.duplicate(existingItem.system.quantity); + await existingItem.update({ "system.quantity": Number(quantity) + (item.system.quantity > 0 ? Number(item.system.quantity) : 1) }); + } + else { + await Item.create(item.toObject(), { parent: actor }); + } + return ui.notifications.info(`${actor.name} Paid ${amount} Poké for ${item.name} (New Total: ${actor.system.money})`); + } + await actor.update({ + "system.money": actor.system.money + amount + }); + return ui.notifications.info(`${actor.name} Gained ${amount} Poké (New Total: ${actor.system.money})`); + } + else { + return super._onDrop(event); + } + } } export { PTUActorSheet } \ No newline at end of file diff --git a/src/module/actor/sheet/actor-config.js b/src/module/actor/sheet/actor-config.js index b1734c5ab..e85492b0f 100644 --- a/src/module/actor/sheet/actor-config.js +++ b/src/module/actor/sheet/actor-config.js @@ -13,7 +13,7 @@ class ActorConfig extends DocumentSheet { /** @override */ static get defaultOptions() { - return mergeObject(super.defaultOptions, { + return foundry.utils.mergeObject(super.defaultOptions, { template: 'systems/ptu/static/templates/config/actor-config-sheet.hbs', width: 450 }); diff --git a/src/module/actor/sheet/inventory-config.js b/src/module/actor/sheet/inventory-config.js index c318ff5c7..75af55c5f 100644 --- a/src/module/actor/sheet/inventory-config.js +++ b/src/module/actor/sheet/inventory-config.js @@ -2,7 +2,7 @@ class InventoryConfigSheet extends FormApplication { /** @override */ static get defaultOptions() { - return mergeObject(super.defaultOptions, { + return foundry.utils.mergeObject(super.defaultOptions, { title: "Inventory Configuration", classes: ['ptu', 'sheet', 'actor', 'gen8', "inventory", "config"], template: 'systems/ptu/static/templates/config/inventory-config-sheet.hbs', @@ -65,7 +65,7 @@ class InventoryConfigSheet extends FormApplication { const category = event.currentTarget.parentElement.parentElement.dataset.category; if(!destination || !column || !category) return; - const columns = duplicate(this.object.actor.getFlag("ptu", "itemColumns")); + const columns = foundry.utils.duplicate(this.object.actor.getFlag("ptu", "itemColumns")); columns[column] = columns[column].filter(c => c !== category); columns[destination].push(category); this.object.actor.setFlag("ptu", "itemColumns", columns).then(() => this.render(true, {source: this.id})); @@ -90,7 +90,7 @@ class InventoryConfigSheet extends FormApplication { if(data.type === "category" && !this._dropBlock) { const column = event.currentTarget.dataset.column; const category = event.currentTarget.dataset.category; - const columns = duplicate(this.object.actor.getFlag("ptu", "itemColumns")); + const columns = foundry.utils.duplicate(this.object.actor.getFlag("ptu", "itemColumns")); // Remove from old column columns[data.column] = columns[data.column].filter(c => c !== data.category); diff --git a/src/module/actor/sheet/item-summary.js b/src/module/actor/sheet/item-summary.js index 274fbf947..befa0295a 100644 --- a/src/module/actor/sheet/item-summary.js +++ b/src/module/actor/sheet/item-summary.js @@ -11,9 +11,25 @@ class ItemSummaryRenderer { const element = el.closest('[data-item-id], .expandable'); if(element) await this.toggleSummary(element); }); + + this.initialOpen(el); } } + initialOpen(el) { + const element = el.closest('[data-item-id], .expandable'); + if(!element) return; + + const item = this.sheet.actor.items.get(element.dataset.itemId); + if(!item) return; + + const sheetState = game.user.getFlag("ptu", "sheetStates")?.[this.sheet.actor.id]; + if(!sheetState) return; + + const state = sheetState[item.type]?.[item.id]; + if(state) return this.toggleSummary(element); + } + async toggleSummary(element) { const actor = this.sheet.actor; @@ -51,6 +67,13 @@ class ItemSummaryRenderer { summary.hidden = false; await new Promise(resolve => setTimeout(resolve, 1)); summary.classList.add('show'); + await game.user.setFlag("ptu", "sheetStates", foundry.utils.mergeObject(game.user.getFlag("ptu", "sheetStates") || {}, { + [actor.id]: { + [item.type]: { + [item.id]: true + } + } + })); } else { element.classList.remove('expanded'); @@ -59,6 +82,13 @@ class ItemSummaryRenderer { await new Promise(resolve => setTimeout(resolve, duration * 1000)); summary.classList.remove('transitioning') summary.hidden = true; + await game.user.setFlag("ptu", "sheetStates", foundry.utils.mergeObject(game.user.getFlag("ptu", "sheetStates") || {}, { + [actor.id]: { + [item.type]: { + [item.id]: false + } + } + })); } } diff --git a/src/module/actor/skills.js b/src/module/actor/skills.js index 31a0a23a2..559472a32 100644 --- a/src/module/actor/skills.js +++ b/src/module/actor/skills.js @@ -10,14 +10,14 @@ class PTUSkills { */ static getRankSlug(skillRank) { switch (skillRank) { - case 1: return game.i18n.localize("PTU.SkillPathetic"); - case 2: return game.i18n.localize("PTU.SkillUntrained"); - case 3: return game.i18n.localize("PTU.SkillNovice"); - case 4: return game.i18n.localize("PTU.SkillAdept"); - case 5: return game.i18n.localize("PTU.SkillExpert"); - case 6: return game.i18n.localize("PTU.SkillMaster"); - case 8: return game.i18n.localize("PTU.SkillVirtuoso"); - default: return game.i18n.localize("PTU.SkillInvalid"); + case 1: return "pathetic"//game.i18n.localize("PTU.SkillPathetic"); + case 2: return "untrained"//game.i18n.localize("PTU.SkillUntrained"); + case 3: return "novice"//game.i18n.localize("PTU.SkillNovice"); + case 4: return "adept"//game.i18n.localize("PTU.SkillAdept"); + case 5: return "expert"//game.i18n.localize("PTU.SkillExpert"); + case 6: return "master"//game.i18n.localize("PTU.SkillMaster"); + case 8: return "virtuoso"//game.i18n.localize("PTU.SkillVirtuoso"); + default: return "invalid"//game.i18n.localize("PTU.SkillInvalid"); } } diff --git a/src/module/apps/compendium-browser/index.js b/src/module/apps/compendium-browser/index.js index 98452654b..6e43d2fc9 100644 --- a/src/module/apps/compendium-browser/index.js +++ b/src/module/apps/compendium-browser/index.js @@ -1,7 +1,7 @@ import { sluggify } from "../../../util/misc.js"; import { Progress } from "../../../util/progress.js"; import * as browserTabs from "./tabs/index.js"; - +import noUiSlider from "../../../../static/js/nouislider.mjs" class PackLoader { /** @@ -61,7 +61,7 @@ class PackLoader { } yield data; } - progress.close(game.i18n.localize("PTU.CompendiumBrowser.ProgressBar.LoadingCompleted")); + progress.close(game.i18n.localize("PTU.CompendiumBrowser.ProgressBar.LoadingComplete")); } /** @@ -135,7 +135,7 @@ class PackLoader { } } } - progress.close(game.i18n.localize("PTU.CompendiumBrowser.ProgressBar.LoadingCompleted")); + progress.close(game.i18n.localize("PTU.CompendiumBrowser.ProgressBar.LoadingComplete")); const loadedSourcesArray = Array.from(loadedSources).sort(); this.loadedSources = loadedSourcesArray; } @@ -688,54 +688,54 @@ class CompendiumBrowser extends Application { } } - // if (filterType === "sliders") { - // // Slider filters - // const sliders = currentTab.filterData.sliders; - // if (!sliders) continue; - - // if (objectHasKey(sliders, filterName)) { - // const sliderElement = container.querySelector(`div.slider-${filterName}`); - // if (!sliderElement) continue; - // const data = sliders[filterName]; - - // const slider = noUiSlider.create(sliderElement, { - // range: { - // min: data.values.lowerLimit, - // max: data.values.upperLimit, - // }, - // start: [data.values.min, data.values.max], - // tooltips: { - // to(value:) { - // return Math.floor(value).toString(); - // }, - // }, - // connect: [false, true, false], - // behaviour: "snap", - // step: data.values.step, - // }); - - // slider.on("change", (values) => { - // const [min, max] = values.map((value) => Number(value)); - // data.values.min = min; - // data.values.max = max; - - // const $minLabel = $html.find(`label.${name}-min-label`); - // const $maxLabel = $html.find(`label.${name}-max-label`); - // $minLabel.text(min); - // $maxLabel.text(max); - - // this.#clearScrollLimit(true); - // }); - - // // Set styling - // sliderElement.querySelectorAll(".noUi-handle").forEach((element) => { - // element.classList.add("handle"); - // }); - // sliderElement.querySelectorAll(".noUi-connect").forEach((element) => { - // element.classList.add("range_selected"); - // }); - // } - // } + if (filterType === "sliders") { + // Slider filters + const sliders = currentTab.filterData.sliders; + if (!sliders) continue; + + if (objectHasKey(sliders, filterName)) { + const sliderElement = container.querySelector(`div.slider-${filterName}`); + if (!sliderElement) continue; + const data = sliders[filterName]; + + const slider = noUiSlider.create(sliderElement, { + range: { + min: data.values.lowerLimit, + max: data.values.upperLimit, + }, + start: [data.values.min, data.values.max], + tooltips: { + to(value) { + return Math.floor(value).toString(); + }, + }, + connect: [false, true, false], + behaviour: "snap", + step: data.values.step, + }); + + slider.on("change", (values) => { + const [min, max] = values.map((value) => Number(value)); + data.values.min = min; + data.values.max = max; + + const $minLabel = $html.find(`label.${name}-min-label`); + const $maxLabel = $html.find(`label.${name}-max-label`); + $minLabel.text(min); + $maxLabel.text(max); + + this.#clearScrollLimit(true); + }); + + // Set styling + sliderElement.querySelectorAll(".noUi-handle").forEach((element) => { + element.classList.add("handle"); + }); + sliderElement.querySelectorAll(".noUi-connect").forEach((element) => { + element.classList.add("range_selected"); + }); + } + } } const list = html.querySelector(".tab.active ul.item-list"); @@ -745,7 +745,7 @@ class CompendiumBrowser extends Application { const currentValue = currentTab.scrollLimit; const maxValue = currentTab.totalItemCount ?? 0; if (currentValue < maxValue) { - currentTab.scrollLimit = Math.clamped(currentValue + 100, 100, maxValue); + currentTab.scrollLimit = Math.clamp(currentValue + 100, 100, maxValue); this.#renderResultList({ list, start: currentValue }) } } diff --git a/src/module/apps/compendium-browser/tabs/abilities.js b/src/module/apps/compendium-browser/tabs/abilities.js index a8537cc8b..235b1f949 100644 --- a/src/module/apps/compendium-browser/tabs/abilities.js +++ b/src/module/apps/compendium-browser/tabs/abilities.js @@ -6,9 +6,9 @@ export class CompendiumBrowserAbilitiesTab extends CompendiumBrowserTab { super(browser); this.searchFields = ["name"] - this.storeFields = ["name", "uuid", "type", "source", "img"]; + this.storeFields = ["name", "uuid", "type", "source", "img", "keywords"]; - this.index = ["img", "system.source.value"]; + this.index = ["img", "system.source.value", "system.keywords"]; this.filterData = this.prepareFilterData(); } @@ -23,9 +23,11 @@ export class CompendiumBrowserAbilitiesTab extends CompendiumBrowserTab { async loadData() { const abilities = []; - const indexFields = duplicate(this.index); + const indexFields = foundry.utils.duplicate(this.index); const sources = new Set(); + const allKeywordsSeen = new Set(); + for await (const { pack, index } of this.browser.packLoader.loadPacks( "Item", this.browser.loadedPacks(this.tabName), @@ -39,12 +41,17 @@ export class CompendiumBrowserAbilitiesTab extends CompendiumBrowserTab { const sourceSlug = sluggify(source); if (source) sources.add(source); + for(const keyword of abilityData.system.keywords) { + allKeywordsSeen.add(keyword); + } + abilities.push({ name: abilityData.name, type: abilityData.type, img: abilityData.img, uuid: `Compendium.${pack.collection}.${abilityData._id}`, source: sourceSlug, + keywords: abilityData.system.keywords }) } } @@ -53,15 +60,41 @@ export class CompendiumBrowserAbilitiesTab extends CompendiumBrowserTab { // Set filters if necessary this.filterData.checkboxes.source.options = this.generateSourceCheckboxOptions(sources); + this.filterData.multiselects.keywords.options = this.filterOptionsFromSet(allKeywordsSeen); } filterIndexData(entry) { - const { checkboxes } = this.filterData; + const { checkboxes, multiselects } = this.filterData; if(checkboxes.source.selected.length) { if(!checkboxes.source.selected.includes(entry.source)) return false; } + if(!this.isEntryHonoringMultiselect(multiselects.keywords, entry.keywords)) return false; + + return true; + } + + filterOptionsFromSet(set) { + return [...set].map(value => ({ value, label: value })).sort((a, b) => a.label.localeCompare(b.label)); + } + + /** + * @param multiselectFilter - the `selected` from a filter, e.g. `filterData.multiselects.types` + * @param entrySetToCheck - the set of an entry corresponding to the filter, e.g. `entry.types` + * @return {boolean} - True if the entry honors the filter, i.e. would be valid result + */ + isEntryHonoringMultiselect(multiselectFilter, entrySetToCheck) { + const selected = multiselectFilter.selected.filter(s => !s.not).map(s => s.value); + const notSelected = multiselectFilter.selected.filter(s => s.not).map(s => s.value); + if (selected.length || notSelected.length) { + if (notSelected.some(ns => entrySetToCheck.some(e => sluggify(e) === sluggify(ns)))) return false; + const fulfilled = + multiselectFilter.conjunction === "and" + ? selected.every(s => entrySetToCheck.some(e => sluggify(e) === sluggify(s))) + : selected.some(s => entrySetToCheck.some(e => sluggify(e) === sluggify(s))); + if (!fulfilled) return false; + } return true; } @@ -75,6 +108,14 @@ export class CompendiumBrowserAbilitiesTab extends CompendiumBrowserTab { selected: [] } }, + multiselects: { + keywords: { + conjunction: "and", + label: "PTU.CompendiumBrowser.FilterOptions.Keywords", + options: [], + selected: [{value: 'Obsolete', label: 'Obsolete', not: true}] + } + }, order: { by: "name", direction: "asc", diff --git a/src/module/apps/compendium-browser/tabs/base.js b/src/module/apps/compendium-browser/tabs/base.js index 9bf0afc41..671d02d13 100644 --- a/src/module/apps/compendium-browser/tabs/base.js +++ b/src/module/apps/compendium-browser/tabs/base.js @@ -47,11 +47,11 @@ export class CompendiumBrowserTab { extractField: (document, fieldName) => { const getValue = (document, path) => { const split = path.split("."); - const fullValue = getProperty(document, path); + const fullValue = foundry.utils.getProperty(document, path); if(fullValue !== undefined) return fullValue; const currPath = split[0]; - const currValue = getProperty(document, currPath); + const currValue = foundry.utils.getProperty(document, currPath); if(currValue === undefined) return undefined; if(Array.isArray(currValue)) { @@ -64,7 +64,7 @@ export class CompendiumBrowserTab { } }) this.searchEngine.addAll(this.indexData); - this.defaultFilterData = deepClone(this.filterData); + this.defaultFilterData = foundry.utils.deepClone(this.filterData); this.isInitialized = true; } @@ -99,11 +99,11 @@ export class CompendiumBrowserTab { /** Returns a clean copy of the filterData for this tab. Initializes the tab if necessary. */ async getFilterData() { if (!this.isInitialized) await this.init(); - return deepClone(this.defaultFilterData); + return foundry.utils.deepClone(this.defaultFilterData); } resetFilters() { - this.filterData = deepClone(this.defaultFilterData); + this.filterData = foundry.utils.deepClone(this.defaultFilterData); } isOfType(...types) { @@ -237,7 +237,7 @@ export class CompendiumBrowserTab { hasAllIndexFields(data, indexFields) { for(const field of indexFields) { if(["system.source", "system.source.value"].includes(field)) continue; - if(getProperty(data, field) === undefined) return false; + if(foundry.utils.getProperty(data, field) === undefined) return false; } return true; } diff --git a/src/module/apps/compendium-browser/tabs/edges.js b/src/module/apps/compendium-browser/tabs/edges.js index ea2046640..31d336282 100644 --- a/src/module/apps/compendium-browser/tabs/edges.js +++ b/src/module/apps/compendium-browser/tabs/edges.js @@ -6,9 +6,9 @@ export class CompendiumBrowserEdgesTab extends CompendiumBrowserTab { super(browser); this.searchFields = ["name", "prerequisites.label", "prerequisites.tier"] - this.storeFields = ["name", "uuid", "type", "source", "img", "prerequisites"]; + this.storeFields = ["name", "uuid", "type", "source", "img", "prerequisites", "keywords"]; - this.index = ["img", "system.source.value", "system.prerequisites"]; + this.index = ["img", "system.source.value", "system.prerequisites", "system.keywords"]; this.filterData = this.prepareFilterData(); } @@ -23,9 +23,11 @@ export class CompendiumBrowserEdgesTab extends CompendiumBrowserTab { async loadData() { const abilities = []; - const indexFields = duplicate(this.index); + const indexFields = foundry.utils.duplicate(this.index); const sources = new Set(); + const allKeywordsSeen = new Set(); + for await (const { pack, index } of this.browser.packLoader.loadPacks( "Item", this.browser.loadedPacks(this.tabName), @@ -41,13 +43,18 @@ export class CompendiumBrowserEdgesTab extends CompendiumBrowserTab { const prerequisites = edgeData.system.prerequisites ?? []; + for(const keyword of edgeData.system.keywords) { + allKeywordsSeen.add(keyword); + } + abilities.push({ name: edgeData.name, type: edgeData.type, img: edgeData.img, uuid: `Compendium.${pack.collection}.${edgeData._id}`, source: sourceSlug, - prerequisites: this.#prerequisitesStringToEntries(prerequisites) + prerequisites: this.#prerequisitesStringToEntries(prerequisites), + keywords: edgeData.system.keywords }) } } @@ -56,6 +63,7 @@ export class CompendiumBrowserEdgesTab extends CompendiumBrowserTab { // Set filters if necessary this.filterData.checkboxes.source.options = this.generateSourceCheckboxOptions(sources); + this.filterData.multiselects.keywords.options = this.filterOptionsFromSet(allKeywordsSeen) } #prerequisitesStringToEntries(prerequisites) { @@ -95,12 +103,37 @@ export class CompendiumBrowserEdgesTab extends CompendiumBrowserTab { } filterIndexData(entry) { - const { checkboxes } = this.filterData; + const { checkboxes, multiselects } = this.filterData; if(checkboxes.source.selected.length) { if(!checkboxes.source.selected.includes(entry.source)) return false; } + if(!this.isEntryHonoringMultiselect(multiselects.keywords, entry.keywords)) return false; + + return true; + } + + filterOptionsFromSet(set) { + return [...set].map(value => ({ value, label: value })).sort((a, b) => a.label.localeCompare(b.label)); + } + + /** + * @param multiselectFilter - the `selected` from a filter, e.g. `filterData.multiselects.types` + * @param entrySetToCheck - the set of an entry corresponding to the filter, e.g. `entry.types` + * @return {boolean} - True if the entry honors the filter, i.e. would be valid result + */ + isEntryHonoringMultiselect(multiselectFilter, entrySetToCheck) { + const selected = multiselectFilter.selected.filter(s => !s.not).map(s => s.value); + const notSelected = multiselectFilter.selected.filter(s => s.not).map(s => s.value); + if (selected.length || notSelected.length) { + if (notSelected.some(ns => entrySetToCheck.some(e => sluggify(e) === sluggify(ns)))) return false; + const fulfilled = + multiselectFilter.conjunction === "and" + ? selected.every(s => entrySetToCheck.some(e => sluggify(e) === sluggify(s))) + : selected.some(s => entrySetToCheck.some(e => sluggify(e) === sluggify(s))); + if (!fulfilled) return false; + } return true; } @@ -114,6 +147,14 @@ export class CompendiumBrowserEdgesTab extends CompendiumBrowserTab { selected: [] } }, + multiselects: { + keywords: { + conjunction: "and", + label: "PTU.CompendiumBrowser.FilterOptions.Keywords", + options: [], + selected: [{value: 'Obsolete', label: 'Obsolete', not: true}] + } + }, order: { by: "name", direction: "asc", diff --git a/src/module/apps/compendium-browser/tabs/feats.js b/src/module/apps/compendium-browser/tabs/feats.js index f8916a20a..09a70ecdd 100644 --- a/src/module/apps/compendium-browser/tabs/feats.js +++ b/src/module/apps/compendium-browser/tabs/feats.js @@ -6,9 +6,9 @@ export class CompendiumBrowserFeatsTab extends CompendiumBrowserTab { super(browser); this.searchFields = ["name", "prerequisites.label", "prerequisites.tier", "class"] - this.storeFields = ["name", "uuid", "type", "source", "img", "prerequisites", "class"]; + this.storeFields = ["name", "uuid", "type", "source", "img", "prerequisites", "class", "classPretty", "keywords"]; - this.index = ["img", "system.source.value", "system.prerequisites", "system.class"]; + this.index = ["img", "system.source.value", "system.prerequisites", "system.class", "system.keywords"]; this.filterData = this.prepareFilterData(); } @@ -23,11 +23,12 @@ export class CompendiumBrowserFeatsTab extends CompendiumBrowserTab { async loadData() { const feats = []; - const indexFields = duplicate(this.index); + const indexFields = foundry.utils.duplicate(this.index); const sources = new Set(); const classes = new Set(); const featNames = new Set(); + const allKeywordsSeen = new Set(); for await (const { pack, index } of this.browser.packLoader.loadPacks( "Item", @@ -42,10 +43,14 @@ export class CompendiumBrowserFeatsTab extends CompendiumBrowserTab { const sourceSlug = sluggify(source); if (source) sources.add(source); - const isClass = featData.img.includes("class"); - const _class = isClass ? featData.name.trim().toLowerCase().capitalize() : (featData.system.class?.trim()?.toLowerCase()?.capitalize() ?? ""); + const isClass = featData.system.keywords.includes("Class"); + const _class = isClass ? featData.name.trim(): (featData.system.class?.trim() ?? ""); const prerequisites = featData.system?.prerequisites ?? ""; + for(const keyword of featData.system.keywords) { + allKeywordsSeen.add(keyword); + } + feats.push({ name: featData.name, type: featData.type, @@ -53,7 +58,9 @@ export class CompendiumBrowserFeatsTab extends CompendiumBrowserTab { uuid: `Compendium.${pack.collection}.${featData._id}`, source: sourceSlug, prerequisites: this.#prerequisitesStringToEntries(prerequisites), - class: _class, + class: sluggify(_class), + classPretty: _class, + keywords: featData.system.keywords }) if (_class) classes.add(_class); @@ -66,6 +73,7 @@ export class CompendiumBrowserFeatsTab extends CompendiumBrowserTab { for (const prereq of feat.prerequisites) { if (classes.has(prereq.label.toLowerCase().capitalize())) { feat.class ||= prereq.label; + feat.classPretty ||= prereq.label; continue; } if (featNames.has(prereq.label)) prereq.feat = true; @@ -79,6 +87,7 @@ export class CompendiumBrowserFeatsTab extends CompendiumBrowserTab { // Set filters if necessary this.filterData.checkboxes.class.options = this.#generateCheckboxOptions([...classes].sort()); this.filterData.checkboxes.source.options = this.generateSourceCheckboxOptions(sources); + this.filterData.multiselects.keywords.options = this.filterOptionsFromSet(allKeywordsSeen) } #prerequisitesStringToEntries(prerequisites) { @@ -86,6 +95,7 @@ export class CompendiumBrowserFeatsTab extends CompendiumBrowserTab { const entries = []; for (const prereq of prerequisites) { + if(prereq === 'Rune Master') continue; let tierFound = false; const entry = prereq.split(" ").map(p => p.trim()).reduce((acc, curr) => { if (!tierFound && tiers.has(curr.toLowerCase())) { @@ -107,7 +117,7 @@ export class CompendiumBrowserFeatsTab extends CompendiumBrowserTab { #generateCheckboxOptions(classSet) { return classSet.reduce((result, _class) => ({ ...result, - [_class.toLowerCase()]: { + [sluggify(_class)]: { label: _class, selected: false } @@ -115,7 +125,7 @@ export class CompendiumBrowserFeatsTab extends CompendiumBrowserTab { } filterIndexData(entry) { - const { checkboxes } = this.filterData; + const { multiselects, checkboxes } = this.filterData; if (checkboxes.source.selected.length) { if (!checkboxes.source.selected.includes(entry.source)) return false; @@ -126,6 +136,31 @@ export class CompendiumBrowserFeatsTab extends CompendiumBrowserTab { if (!checkboxes.class.selected.includes(entry.class.toLowerCase())) return false; } + if (!this.isEntryHonoringMultiselect(multiselects.keywords, entry.keywords)) return false; + + return true; + } + + filterOptionsFromSet(set) { + return [...set].map(value => ({ value, label: value })).sort((a, b) => a.label.localeCompare(b.label)); + } + + /** + * @param multiselectFilter - the `selected` from a filter, e.g. `filterData.multiselects.types` + * @param entrySetToCheck - the set of an entry corresponding to the filter, e.g. `entry.types` + * @return {boolean} - True if the entry honors the filter, i.e. would be valid result + */ + isEntryHonoringMultiselect(multiselectFilter, entrySetToCheck) { + const selected = multiselectFilter.selected.filter(s => !s.not).map(s => s.value); + const notSelected = multiselectFilter.selected.filter(s => s.not).map(s => s.value); + if (selected.length || notSelected.length) { + if (notSelected.some(ns => entrySetToCheck.some(e => sluggify(e) === sluggify(ns)))) return false; + const fulfilled = + multiselectFilter.conjunction === "and" + ? selected.every(s => entrySetToCheck.some(e => sluggify(e) === sluggify(s))) + : selected.some(s => entrySetToCheck.some(e => sluggify(e) === sluggify(s))); + if (!fulfilled) return false; + } return true; } @@ -138,12 +173,6 @@ export class CompendiumBrowserFeatsTab extends CompendiumBrowserTab { options: {}, selected: [] }, - // skills: { - // isExpanded: false, - // label: "PTU.CompendiumBrowser.FilterOptions.Skills", - // options: CONFIG.PTU.data.skills.keys.reduce((result, skill) => ({ ...result, [skill.toLowerCase()]: { label: skill, selected: false } }), {}), - // selected: [] - // }, source: { isExpanded: false, label: "PTU.CompendiumBrowser.FilterOptions.Source", @@ -151,6 +180,14 @@ export class CompendiumBrowserFeatsTab extends CompendiumBrowserTab { selected: [] } }, + multiselects: { + keywords: { + conjunction: "and", + label: "PTU.CompendiumBrowser.FilterOptions.Keywords", + options: [], + selected: [{value: 'Obsolete', label: 'Obsolete', not: true}] + } + }, order: { by: "class", direction: "asc", @@ -164,47 +201,4 @@ export class CompendiumBrowserFeatsTab extends CompendiumBrowserTab { } } } - - // const skills = new Set(['acrobatics', 'athletics', 'charm', 'combat', 'command', 'generalEd', 'generalEducation', 'medicineEd', 'medicineEducation', 'occultEd', 'occultEducation', 'pokemonEd', 'pokemonEducation', 'pokémonEducation', 'techEd', 'technologyEducation', 'focus', 'guile', 'intimidate', 'intuition', 'perception', 'stealth', 'survival']); - - // const entries = []; - // const statements = prerequisites.split(",").map(s => s.trim()); - // let lastTier = ""; - // for (let statement of statements) { - // if (entries.length === 3) { - // entries.push({ label: "..." }) - // break; - // } - - // if(statement.trim() === "or") continue; - - // const splits = statement.split(" ").map(s => s.trim()); - // const tier = splits.at(0); - // const skill = splits.slice(1).join(" "); - // const tierSlug = sluggify(tier ?? "", { camel: "dromedary" }); - // const skillSlug = sluggify(skill ?? "", { camel: "dromedary" }); - // const isTierOr = tierSlug === "or"; - // const skillIncludesOr = skill.includes("or"); - - // if (tiers.has(tierSlug) && skills.has(skillSlug)) { - // entries.push({ label: `${tier} ${skill}`, tier }) - // lastTier = tier; - // continue; - // } - // else if (tierSlug && (tierSlug.length > 3 && (!skill || skillIncludesOr)) || isTierOr) { - // if (skills.has(isTierOr ? skillSlug : tierSlug) && lastTier.length > 0) { - // entries.at(-1).label += ` or ${isTierOr ? skill : statement}`; - // continue; - // } - // } - - // const statementTier = statement.split(' ').find(word => tiers.has(sluggify(word ?? "", { camel: "dromedary" }))); - // entries.push({ label: statement, tier: statementTier ?? "" }) - // if(statementTier) { - // lastTier = statementTier; - // continue; - // } - - // lastTier = ""; - // } } \ No newline at end of file diff --git a/src/module/apps/compendium-browser/tabs/items.js b/src/module/apps/compendium-browser/tabs/items.js index 9423ba0d3..55b7b3d63 100644 --- a/src/module/apps/compendium-browser/tabs/items.js +++ b/src/module/apps/compendium-browser/tabs/items.js @@ -6,9 +6,9 @@ export class CompendiumBrowserItemsTab extends CompendiumBrowserTab { super(browser); this.searchFields = ["name"] - this.storeFields = ["name", "uuid", "type", "source", "img", "cost", "subtype"]; + this.storeFields = ["name", "uuid", "type", "source", "img", "cost", "subtype", "keywords"]; - this.index = ["img", "system.source.value", "system.cost", "system.subtype"]; + this.index = ["img", "system.source.value", "system.cost", "system.subtype", "system.keywords"]; this.filterData = this.prepareFilterData(); } @@ -23,8 +23,9 @@ export class CompendiumBrowserItemsTab extends CompendiumBrowserTab { async loadData() { const items = []; - const indexFields = duplicate(this.index); + const indexFields = foundry.utils.duplicate(this.index); const sources = new Set(); + const allKeywordsSeen = new Set(); for await (const { pack, index } of this.browser.packLoader.loadPacks( "Item", @@ -39,6 +40,10 @@ export class CompendiumBrowserItemsTab extends CompendiumBrowserTab { const sourceSlug = sluggify(source); if (source) sources.add(source); + for(const keyword of itemData.system.keywords) { + allKeywordsSeen.add(keyword); + } + items.push({ name: itemData.name, type: itemData.type, @@ -47,6 +52,7 @@ export class CompendiumBrowserItemsTab extends CompendiumBrowserTab { source: sourceSlug, cost: itemData.system.cost || 0, subtype: itemData.system.subtype || "", + keywords: itemData.system.keywords }) } } @@ -55,29 +61,41 @@ export class CompendiumBrowserItemsTab extends CompendiumBrowserTab { // Set filters if necessary this.filterData.checkboxes.source.options = this.generateSourceCheckboxOptions(sources); + this.filterData.multiselects.keywords.options = this.filterOptionsFromSet(allKeywordsSeen) } filterIndexData(entry) { - const { selects, checkboxes } = this.filterData; + const { multiselects, checkboxes } = this.filterData; - if(checkboxes.source.selected.length) { - if(!checkboxes.source.selected.includes(entry.source)) return false; + if (checkboxes.source.selected.length) { + if (!checkboxes.source.selected.includes(entry.source)) return false; } - if(selects.type.selected) { - switch (selects.type.selected) { - case "pokeball": - if (entry.subtype !== "pokeball") return false; - break; - case "tms": - if (/TM(\d{1,3})\s/.test(entry.name) === false) return false; - break; - case "berries": - if (!entry.name.trim().endsWith("Berry")) return false; - break; - } - } + if (!this.isEntryHonoringMultiselect(multiselects.keywords, entry.keywords)) return false; + + return true; + } + + filterOptionsFromSet(set) { + return [...set].map(value => ({ value, label: value })).sort((a, b) => a.label.localeCompare(b.label)); + } + /** + * @param multiselectFilter - the `selected` from a filter, e.g. `filterData.multiselects.types` + * @param entrySetToCheck - the set of an entry corresponding to the filter, e.g. `entry.types` + * @return {boolean} - True if the entry honors the filter, i.e. would be valid result + */ + isEntryHonoringMultiselect(multiselectFilter, entrySetToCheck) { + const selected = multiselectFilter.selected.filter(s => !s.not).map(s => s.value); + const notSelected = multiselectFilter.selected.filter(s => s.not).map(s => s.value); + if (selected.length || notSelected.length) { + if (notSelected.some(ns => entrySetToCheck.some(e => sluggify(e) === sluggify(ns)))) return false; + const fulfilled = + multiselectFilter.conjunction === "and" + ? selected.every(s => entrySetToCheck.some(e => sluggify(e) === sluggify(s))) + : selected.some(s => entrySetToCheck.some(e => sluggify(e) === sluggify(s))); + if (!fulfilled) return false; + } return true; } @@ -91,13 +109,13 @@ export class CompendiumBrowserItemsTab extends CompendiumBrowserTab { selected: [] } }, - selects: { - type: { - isExpanded: false, - label: "PTU.CompendiumBrowser.FilterOptions.ItemType", - options: { pokeball: "Pokéball", tms: "TMs", berries: "Berries" }, - selected: "" - }, + multiselects: { + keywords: { + conjunction: "and", + label: "PTU.CompendiumBrowser.FilterOptions.Keywords", + options: [], + selected: [{value: 'Obsolete', label: 'Obsolete', not: true}] + } }, order: { by: "name", diff --git a/src/module/apps/compendium-browser/tabs/moves.js b/src/module/apps/compendium-browser/tabs/moves.js index 27288f119..20b672352 100644 --- a/src/module/apps/compendium-browser/tabs/moves.js +++ b/src/module/apps/compendium-browser/tabs/moves.js @@ -5,10 +5,10 @@ export class CompendiumBrowserMovesTab extends CompendiumBrowserTab { constructor(browser) { super(browser); - this.searchFields = ["name"] - this.storeFields = ["name", "uuid", "type", "source", "img", "moveType", "category", "damageBase"]; + this.searchFields = ["name", "range"] + this.storeFields = ["name", "uuid", "type", "source", "img", "moveType", "category", "damageBase", "range", "keywords"]; - this.index = ["img", "system.source.value", "system.type", "system.category", "system.damageBase"]; + this.index = ["img", "system.source.value", "system.type", "system.category", "system.damageBase", "system.range", "system.keywords"]; this.filterData = this.prepareFilterData(); } @@ -23,8 +23,9 @@ export class CompendiumBrowserMovesTab extends CompendiumBrowserTab { async loadData() { const moves = []; - const indexFields = duplicate(this.index); + const indexFields = foundry.utils.duplicate(this.index); const sources = new Set(); + const allKeywordsSeen = new Set(); for await (const { pack, index } of this.browser.packLoader.loadPacks( "Item", @@ -41,6 +42,10 @@ export class CompendiumBrowserMovesTab extends CompendiumBrowserTab { const db = Number(moveData.system.damageBase); + for(const keyword of moveData.system.keywords) { + allKeywordsSeen.add(keyword); + } + moves.push({ name: moveData.name, type: moveData.type, @@ -49,7 +54,9 @@ export class CompendiumBrowserMovesTab extends CompendiumBrowserTab { source: sourceSlug, category: moveData.system.category, damageBase: isNaN(db) ? 0 : db, - moveType: moveData.system.type + moveType: moveData.system.type, + range: moveData.system.range, + keywords: moveData.system.keywords }) } } @@ -58,10 +65,11 @@ export class CompendiumBrowserMovesTab extends CompendiumBrowserTab { // Set filters if necessary this.filterData.checkboxes.source.options = this.generateSourceCheckboxOptions(sources); + this.filterData.multiselects.keywords.options = this.filterOptionsFromSet(allKeywordsSeen) } filterIndexData(entry) { - const { selects, checkboxes } = this.filterData; + const { selects, multiselects, checkboxes } = this.filterData; if(checkboxes.source.selected.length) { if(!checkboxes.source.selected.includes(entry.source)) return false; @@ -74,6 +82,8 @@ export class CompendiumBrowserMovesTab extends CompendiumBrowserTab { if(sluggify(entry.category) !== selects.category.selected) return false; } + if(!this.isEntryHonoringMultiselect(multiselects.keywords, entry.keywords)) return false; + return true; } @@ -103,6 +113,29 @@ export class CompendiumBrowserMovesTab extends CompendiumBrowserTab { return order.direction === "asc" ? sorted : sorted.reverse(); } + filterOptionsFromSet(set) { + return [...set].map(value => ({ value, label: value })).sort((a, b) => a.label.localeCompare(b.label)); + } + + /** + * @param multiselectFilter - the `selected` from a filter, e.g. `filterData.multiselects.types` + * @param entrySetToCheck - the set of an entry corresponding to the filter, e.g. `entry.types` + * @return {boolean} - True if the entry honors the filter, i.e. would be valid result + */ + isEntryHonoringMultiselect(multiselectFilter, entrySetToCheck) { + const selected = multiselectFilter.selected.filter(s => !s.not).map(s => s.value); + const notSelected = multiselectFilter.selected.filter(s => s.not).map(s => s.value); + if (selected.length || notSelected.length) { + if (notSelected.some(ns => entrySetToCheck.some(e => sluggify(e) === sluggify(ns)))) return false; + const fulfilled = + multiselectFilter.conjunction === "and" + ? selected.every(s => entrySetToCheck.some(e => sluggify(e) === sluggify(s))) + : selected.some(s => entrySetToCheck.some(e => sluggify(e) === sluggify(s))); + if (!fulfilled) return false; + } + return true; + } + prepareFilterData() { return { checkboxes: { @@ -134,6 +167,14 @@ export class CompendiumBrowserMovesTab extends CompendiumBrowserTab { selected: "" }, }, + multiselects: { + keywords: { + conjunction: "and", + label: "PTU.CompendiumBrowser.FilterOptions.Keywords", + options: [], + selected: [] + } + }, order: { by: "name", direction: "asc", diff --git a/src/module/apps/compendium-browser/tabs/pokeEdges.js b/src/module/apps/compendium-browser/tabs/pokeEdges.js index 29d24e46c..fd1b1a8e0 100644 --- a/src/module/apps/compendium-browser/tabs/pokeEdges.js +++ b/src/module/apps/compendium-browser/tabs/pokeEdges.js @@ -6,9 +6,9 @@ export class CompendiumBrowserPokeEdgesTab extends CompendiumBrowserTab { super(browser); this.searchFields = ["name", "prerequisites.label", "prerequisites.tier"] - this.storeFields = ["name", "uuid", "type", "source", "img", "prerequisites"]; + this.storeFields = ["name", "uuid", "type", "source", "img", "prerequisites", "keywords"]; - this.index = ["img", "system.source.value", "system.prerequisites"]; + this.index = ["img", "system.source.value", "system.prerequisites", "system.keywords"]; this.filterData = this.prepareFilterData(); } @@ -23,9 +23,11 @@ export class CompendiumBrowserPokeEdgesTab extends CompendiumBrowserTab { async loadData() { const abilities = []; - const indexFields = duplicate(this.index); + const indexFields = foundry.utils.duplicate(this.index); const sources = new Set(); + const allKeywordsSeen = new Set(); + for await (const { pack, index } of this.browser.packLoader.loadPacks( "Item", this.browser.loadedPacks(this.tabName), @@ -41,13 +43,18 @@ export class CompendiumBrowserPokeEdgesTab extends CompendiumBrowserTab { const prerequisites = edgeData.system.prerequisites ?? []; + for(const keyword of edgeData.system.keywords) { + allKeywordsSeen.add(keyword); + } + abilities.push({ name: edgeData.name, type: edgeData.type, img: edgeData.img, uuid: `Compendium.${pack.collection}.${edgeData._id}`, source: sourceSlug, - prerequisites: this.#prerequisitesStringToEntries(prerequisites) + prerequisites: this.#prerequisitesStringToEntries(prerequisites), + keywords: edgeData.system.keywords }) } } @@ -56,6 +63,7 @@ export class CompendiumBrowserPokeEdgesTab extends CompendiumBrowserTab { // Set filters if necessary this.filterData.checkboxes.source.options = this.generateSourceCheckboxOptions(sources); + this.filterData.multiselects.keywords.options = this.filterOptionsFromSet(allKeywordsSeen); } #prerequisitesStringToEntries(prerequisites) { @@ -95,12 +103,37 @@ export class CompendiumBrowserPokeEdgesTab extends CompendiumBrowserTab { } filterIndexData(entry) { - const { checkboxes } = this.filterData; + const { checkboxes, multiselects } = this.filterData; if(checkboxes.source.selected.length) { if(!checkboxes.source.selected.includes(entry.source)) return false; } + if(!this.isEntryHonoringMultiselect(multiselects.keywords, entry.keywords)) return false; + + return true; + } + + filterOptionsFromSet(set) { + return [...set].map(value => ({ value, label: value })).sort((a, b) => a.label.localeCompare(b.label)); + } + + /** + * @param multiselectFilter - the `selected` from a filter, e.g. `filterData.multiselects.types` + * @param entrySetToCheck - the set of an entry corresponding to the filter, e.g. `entry.types` + * @return {boolean} - True if the entry honors the filter, i.e. would be valid result + */ + isEntryHonoringMultiselect(multiselectFilter, entrySetToCheck) { + const selected = multiselectFilter.selected.filter(s => !s.not).map(s => s.value); + const notSelected = multiselectFilter.selected.filter(s => s.not).map(s => s.value); + if (selected.length || notSelected.length) { + if (notSelected.some(ns => entrySetToCheck.some(e => sluggify(e) === sluggify(ns)))) return false; + const fulfilled = + multiselectFilter.conjunction === "and" + ? selected.every(s => entrySetToCheck.some(e => sluggify(e) === sluggify(s))) + : selected.some(s => entrySetToCheck.some(e => sluggify(e) === sluggify(s))); + if (!fulfilled) return false; + } return true; } @@ -114,6 +147,14 @@ export class CompendiumBrowserPokeEdgesTab extends CompendiumBrowserTab { selected: [] } }, + multiselects: { + keywords: { + conjunction: "and", + label: "PTU.CompendiumBrowser.FilterOptions.Keywords", + options: [], + selected: [{value: 'Obsolete', label: 'Obsolete', not: true}] + } + }, order: { by: "name", direction: "asc", diff --git a/src/module/apps/compendium-browser/tabs/species.js b/src/module/apps/compendium-browser/tabs/species.js index b765b4436..0f20125f4 100644 --- a/src/module/apps/compendium-browser/tabs/species.js +++ b/src/module/apps/compendium-browser/tabs/species.js @@ -1,14 +1,19 @@ import { sluggify } from "../../../../util/misc.js"; import { CompendiumBrowserTab } from "./base.js"; +const FILTERABLE_CAPABILITIES = ["overland", "sky", "swim", "levitate", "burrow", "highJump", "longJump", "power"] + export class CompendiumBrowserSpeciesTab extends CompendiumBrowserTab { constructor(browser) { super(browser); this.searchFields = ["name"] - this.storeFields = ["name", "uuid", "type", "source", "img", "types", "number"]; + this.storeFields = ["name", "uuid", "type", "source", "img", "types", "number", "moves", "abilities", "capabilities", "keywords"] + + this.index = ["system.source.value", "system.types", "system.number", "system.moves", "system.abilities", "system.capabilities", "system.keywords"]; - this.index = ["system.source.value", "system.types", "system.number"]; + this.capabilitesMinMax = {} + FILTERABLE_CAPABILITIES.forEach(cap => this.capabilitesMinMax[cap] = { "min": 100, "max": -10 }) this.filterData = this.prepareFilterData(); } @@ -23,9 +28,14 @@ export class CompendiumBrowserSpeciesTab extends CompendiumBrowserTab { async loadData() { const species = []; - const indexFields = duplicate(this.index); + const indexFields = foundry.utils.duplicate(this.index); const sources = new Set(); + const allMoveSlugsSeen = new Set() + const allAbilitySlugsSeen = new Set() + const allCapabilitiesSeen = new Set() + const allKeywordsSeen = new Set(); + for await (const { pack, index } of this.browser.packLoader.loadPacks( "Item", this.browser.loadedPacks(this.tabName), @@ -39,13 +49,53 @@ export class CompendiumBrowserSpeciesTab extends CompendiumBrowserTab { const sourceSlug = sluggify(source); if (source) sources.add(source); - if(!Array.isArray(speciesData.system.types) || speciesData.system.types.length === 0) { + if (!Array.isArray(speciesData.system.types) || speciesData.system.types.length === 0) { console.warn(`Species ${speciesData.name} (${speciesData._id}) has no types!`); continue; } const number = Number(speciesData.system.number) + const moveLearnOrigins = ["egg", "tutor", "level", "machine"] + // Looking at species in the compendium, even if they do not have egg moves for example, + // they have an empty array as its value. Let's assume this is supposed to stay this way... + if (moveLearnOrigins.some(o => !Array.isArray(speciesData.system.moves[o])) + ) { + console.warn(`Species ${speciesData.name} (${speciesData._id}) has no valid move structure!`); + continue; + } + + const moves = new Set(moveLearnOrigins.map(o => speciesData.system.moves[o]).flat(1).map(m => { + // The slugs of Natural Tutor moves contain a "-n", we will simplify that for the filter. + return m.slug.substring(m.slug.length - 2) === "-n" ? m.slug.substring(0, m.slug.length - 2) : m.slug + })) + moves.forEach(move => allMoveSlugsSeen.add(move)) + + const abilityRanks = ["basic", "advanced", "high"] + if (abilityRanks.some(o => !Array.isArray(speciesData.system.abilities[o])) + ) { + console.warn(`Species ${speciesData.name} (${speciesData._id}) has no valid ability structure!`); + continue; + } + + const abilities = new Set(abilityRanks.map(r => speciesData.system.abilities[r]).flat(1).map(a => a.slug)).filter(a => a) + abilities.forEach(a => allAbilitySlugsSeen.add(a)) + + if (!speciesData.system.capabilities?.overland) { + console.warn(`Species ${speciesData.name} (${speciesData._id}) seems to have no valid capabilities!`); + continue; + } + + for(const capability of speciesData.system.capabilities.other) { + allCapabilitiesSeen.add(capability.slug); + } + + for (const keyword of speciesData.system.keywords) { + allKeywordsSeen.add(keyword); + } + + this.trackCapabilitiesMinMax(speciesData.system.capabilities); + species.push({ name: speciesData.name, type: speciesData.type, @@ -53,7 +103,10 @@ export class CompendiumBrowserSpeciesTab extends CompendiumBrowserTab { uuid: `Compendium.${pack.collection}.${speciesData._id}`, source: sourceSlug, types: speciesData.system.types, - number: isNaN(number) ? Infinity : number + number: isNaN(number) ? Infinity : number, + moves: moves, + abilities: abilities, + capabilities: speciesData.system.capabilities }) } } @@ -62,10 +115,46 @@ export class CompendiumBrowserSpeciesTab extends CompendiumBrowserTab { // Set filters if necessary this.filterData.checkboxes.source.options = this.generateSourceCheckboxOptions(sources); - if(this.filterData.checkboxes.source.options["ptr-core-dex"]) { + if (this.filterData.checkboxes.source.options["ptr-core-dex"]) { this.filterData.checkboxes.source.options["ptr-core-dex"].selected = true; this.filterData.checkboxes.source.selected.push("ptr-core-dex"); } + + this.filterData.multiselects.moves.options = this.filterOptionsFromSlugList(allMoveSlugsSeen) + this.filterData.multiselects.abilities.options = this.filterOptionsFromSlugList(allAbilitySlugsSeen) + this.filterData.multiselects.capabilities.options = this.filterOptionsFromSlugList(allCapabilitiesSeen) + this.filterData.multiselects.keywords.options = this.filterOptionsFromSet(allKeywordsSeen) + + for (const cap of FILTERABLE_CAPABILITIES) { + this.filterData.sliders[cap].values.max = this.capabilitesMinMax[cap].max + this.filterData.sliders[cap].values.upperLimit = this.capabilitesMinMax[cap].max + this.filterData.sliders[cap].values.min = this.capabilitesMinMax[cap].min + this.filterData.sliders[cap].values.lowerLimit = this.capabilitesMinMax[cap].min + this.filterData.sliders[cap].values.step = 1 + } + } + + /** Updates minima and maxima of all FILTERABLE_CAPABILITIES in this.capabilitesMinMax. + * Using this allows to set the min and max of the sliders properly after loading all + * species. Should be called with the species.system.capabilities of each species. + * @param capabilities species.system.capabilities + */ + trackCapabilitiesMinMax(capabilities) { + if (!capabilities) return; + for (const cap of FILTERABLE_CAPABILITIES) { + const capVal = capabilities[cap] ? capabilities[cap] : 0 + this.capabilitesMinMax[cap].min = Math.min(this.capabilitesMinMax[cap].min, capVal) + this.capabilitesMinMax[cap].max = Math.max(this.capabilitesMinMax[cap].max, capVal) + } + } + + filterOptionsFromSlugList(slugs) { + const nameSlugPairs = [] + for (const slug of slugs) { + const unslugged = slug.split("-").map(p => p[0].toUpperCase() + p.substring(1)).join(" ") + nameSlugPairs.push({ label: unslugged, value: slug }) + } + return nameSlugPairs.sort((a, b) => (`` + a.label).localeCompare(b.label)); } #getImagePath(speciesName, speciesNumber) { @@ -78,7 +167,7 @@ export class CompendiumBrowserSpeciesTab extends CompendiumBrowserTab { generateSourceCheckboxOptions(sources) { return [...sources].sort(this.#sortSources).reduce( - (result,source) => ({ + (result, source) => ({ ...result, [sluggify(source)]: { label: source, @@ -89,35 +178,60 @@ export class CompendiumBrowserSpeciesTab extends CompendiumBrowserTab { ) } - #sortSources(a,b) { + #sortSources(a, b) { // If the source is a PTR Source (starts with PTR), it should be at the top of the list - if(a.startsWith("PTR") && !b.startsWith("PTR")) return -1; - if(b.startsWith("PTR") && !a.startsWith("PTR")) return 1; + if (a.startsWith("PTR") && !b.startsWith("PTR")) return -1; + if (b.startsWith("PTR") && !a.startsWith("PTR")) return 1; // Otherwise sort regularly return a.localeCompare(b, game.i18n.lang); } filterIndexData(entry) { - const { checkboxes, multiselects } = this.filterData; + const { checkboxes, multiselects, sliders } = this.filterData; - if(checkboxes.source.selected.length) { - if(!checkboxes.source.selected.includes(entry.source)) return false; + if (checkboxes.source.selected.length) { + if (!checkboxes.source.selected.includes(entry.source)) return false; } - const selected = multiselects.types.selected.filter(s => !s.not).map(s => s.value); - const notSelected = multiselects.types.selected.filter(s => s.not).map(s => s.value); - if(selected.length || notSelected.length) { - if(notSelected.some(s => entry.types.some(t => sluggify(t) === s))) return false; - const fulfilled = - multiselects.types.conjunction === "and" - ? selected.every(s => entry.types.some(t => sluggify(t) === s)) - : selected.some(s => entry.types.some(t => sluggify(t) === s)); - if(!fulfilled) return false; + if (!this.isEntryHonoringMultiselect(multiselects.types, entry.types)) return false; + if (!this.isEntryHonoringMultiselect(multiselects.moves, entry.moves)) return false; + if (!this.isEntryHonoringMultiselect(multiselects.abilities, entry.abilities)) return false; + if (!this.isEntryHonoringMultiselect(multiselects.capabilities, entry.capabilities.other.map(c => c.slug))) return false; + if (!this.isEntryHonoringMultiselect(multiselects.keywords, entry.keywords, false)) return false; + + for (const cap of FILTERABLE_CAPABILITIES) { + const capVal = entry.capabilities[cap] ? entry.capabilities[cap] : 0 + if (sliders[cap].values.min > capVal || capVal > sliders[cap].values.max) { + return false; + } } return true; } + filterOptionsFromSet(set) { + return [...set].map(value => ({ value, label: value })).sort((a, b) => a.label.localeCompare(b.label)); + } + + /** + @param multiselectFilter - the `selected` from a filter, e.g. `filterData.multiselects.types` + @param entrySetToCheck - the set of an entry corresponding to the filter, e.g. `entry.types` + @return {boolean} - True if the entry honors the filter, i.e. would be valid result + */ + isEntryHonoringMultiselect(multiselectFilter, entrySetToCheck, slug = true) { + const selected = multiselectFilter.selected.filter(s => !s.not).map(s => s.value); + const notSelected = multiselectFilter.selected.filter(s => s.not).map(s => s.value); + if (selected.length || notSelected.length) { + if (notSelected.some(ns => entrySetToCheck.some(e => sluggify(e) === (slug ? ns : sluggify(ns))))) return false; + const fulfilled = + multiselectFilter.conjunction === "and" + ? selected.every(s => entrySetToCheck.some(e => sluggify(e) === (slug ? s : sluggify(s)))) + : selected.some(s => entrySetToCheck.some(e => sluggify(e) === (slug ? s : sluggify(s)))); + if (!fulfilled) return false; + } + return true; + } + prepareFilterData() { return { checkboxes: { @@ -132,9 +246,81 @@ export class CompendiumBrowserSpeciesTab extends CompendiumBrowserTab { types: { conjunction: "and", label: "PTU.CompendiumBrowser.FilterOptions.MoveType", - options: Object.keys(CONFIG.PTU.data.typeEffectiveness).map(type => ({value: sluggify(type), label: type})), + options: Object.keys(CONFIG.PTU.data.typeEffectiveness).map(type => ({ value: sluggify(type), label: type })), + selected: [] + }, + moves: { + conjunction: "and", + label: "PTU.CompendiumBrowser.FilterOptions.LearnableMoves", + options: [], + selected: [] + }, + abilities: { + conjunction: "and", + label: "PTU.CompendiumBrowser.FilterOptions.Abilities", + options: [], + selected: [] + }, + capabilities: { + conjunction: "and", + label: "PTU.CompendiumBrowser.FilterOptions.Capabilities", + options: [], selected: [] }, + keywords: { + conjunction: "and", + label: "PTU.CompendiumBrowser.FilterOptions.Keywords", + options: [], + selected: [] + } + }, + sliders: { + overland: { + isExpanded: false, + label: "overland", + values: { + lowerLimit: 0, + upperLimit: 20, + min: 0, + max: 20, + step: 1, + }, + }, + sky: { + isExpanded: false, + label: "sky", + values: {}, + }, + swim: { + isExpanded: false, + label: "swim", + values: {}, + }, + levitate: { + isExpanded: false, + label: "levitate", + values: {}, + }, + burrow: { + isExpanded: false, + label: "burrow", + values: {}, + }, + highJump: { + isExpanded: false, + label: "highJump", + values: {}, + }, + longJump: { + isExpanded: false, + label: "longJump", + values: {}, + }, + power: { + isExpanded: false, + label: "power", + values: {}, + }, }, // selects: { // form: { @@ -157,4 +343,4 @@ export class CompendiumBrowserSpeciesTab extends CompendiumBrowserTab { } } } -} \ No newline at end of file +} diff --git a/src/module/apps/dex/sheet.js b/src/module/apps/dex/sheet.js index 9023012d8..d5419412b 100644 --- a/src/module/apps/dex/sheet.js +++ b/src/module/apps/dex/sheet.js @@ -21,9 +21,23 @@ class PTUDexSheet extends FormApplication { if (PTUDexSheet.speciesArtCache.size === 0) PTUDexSheet.speciesArtCache = new Map(); if (PTUDexSheet.speciesMap.size === 0) { new Promise( - (resolve, reject) => { - game.packs.get("ptu.species").getDocuments() - .then(docs => resolve(docs)); + async (resolve, reject) => { + const packs = Object.entries(game.settings.get("ptu", "compendiumBrowserPacks")["species"]).reduce((acc, [id, value]) => { + if(id === "ptu.species") { + if(value.load === false) acc = acc.filter(id => id !== "ptu.species") + return acc; + } + if(value?.load) acc.push(id); + return acc; + }, ["ptu.species"]) + if (packs.length === 0) return ui.notifications.error("Please enable at least one species compendium from the Compendium Browser settings."); + const docs = []; + for(const packId of packs) { + const pack = game.packs.get(packId); + if(!pack) continue; + docs.push(...(await pack.getDocuments())); + }; + resolve(docs); } ).then(docs => this.speciesDocs = docs) .then(docs => docs.reduce((map, s) => { @@ -42,7 +56,7 @@ class PTUDexSheet extends FormApplication { } static get defaultOptions() { - return mergeObject(super.defaultOptions, { + return foundry.utils.mergeObject(super.defaultOptions, { title: "PTU.DexSheet.Title", classes: ["ptu", "sheet", "gen8", "dex"], width: 625, @@ -70,7 +84,7 @@ class PTUDexSheet extends FormApplication { data.ballStyle = this.ballStyle; - const dex = duplicate(this.object.system.dex); + const dex = foundry.utils.duplicate(this.object.system.dex); const seen = new Set(dex.seen); const owned = new Set(dex.owned); diff --git a/src/module/apps/level-up-form/document.js b/src/module/apps/level-up-form/document.js index 9e181bd2f..f4e053841 100644 --- a/src/module/apps/level-up-form/document.js +++ b/src/module/apps/level-up-form/document.js @@ -236,7 +236,7 @@ class LevelUpData { } const leftoverLevelUpPoints = (10 + this.level.new + this.pokemon.system.modifiers.statPoints.total ?? 0) - Object.values(this.stats).reduce((a, v) => v.oldLevelUp + v.newLevelUp + a, 0); - const actualLevel = Math.max(1, this.level.new - Math.max(0, Math.clamped(0, leftoverLevelUpPoints, leftoverLevelUpPoints - this.pokemon.system.modifiers.statPoints.total ?? 0))); + const actualLevel = Math.max(1, this.level.new - Math.max(0, Math.clamp(0, leftoverLevelUpPoints, leftoverLevelUpPoints - this.pokemon.system.modifiers.statPoints.total ?? 0))); const evolving = (this.evolution?.slug ?? this.pokemon.species.slug) !== this.pokemon.species.slug; const actualStats = Object.entries(this.stats).reduce((stats, [stat, value]) => { diff --git a/src/module/apps/level-up-form/sheet.js b/src/module/apps/level-up-form/sheet.js index 89ab02a04..88fc1221b 100644 --- a/src/module/apps/level-up-form/sheet.js +++ b/src/module/apps/level-up-form/sheet.js @@ -3,7 +3,7 @@ import { LevelUpData } from "./document.js"; class LevelUpForm extends FormApplication { /** @override */ static get defaultOptions() { - return mergeObject(super.defaultOptions, { + return foundry.utils.mergeObject(super.defaultOptions, { classes: ["ptu", "pokemon", "level-up"], template: "systems/ptu/static/templates/apps/level-up-form.hbs", width: 560, @@ -28,7 +28,7 @@ class LevelUpForm extends FormApplication { } this.resolve = options.resolve; - ui.windows[pokemon.sheet.appId].element.css({"filter": "saturate(0.1) opacity(0.95)", "pointer-events": "none"}) + ui.windows[pokemon.sheet.appId]?.element?.css({"filter": "saturate(0.1) opacity(0.95)", "pointer-events": "none"}) } /** @override */ @@ -126,7 +126,7 @@ class LevelUpForm extends FormApplication { let current; if(this.data.abilities.current[zone]?.uuid) { - current = duplicate(this.data.abilities.current[zone]); + current = foundry.utils.duplicate(this.data.abilities.current[zone]); } this.data.abilities.current[zone] = {uuid: data.uuid, category: data.category, slug: data.slug}; @@ -142,7 +142,7 @@ class LevelUpForm extends FormApplication { async _updateObject(event, formData) { event.preventDefault(); - const data = expandObject(formData); + const data = foundry.utils.expandObject(formData); for(const [stat, value] of Object.entries(data.stats)) { this.data.stats[stat].newLevelUp = value.newLevelUp; @@ -161,7 +161,7 @@ class LevelUpForm extends FormApplication { log: "error", ...options }); - ui.windows[this.data.pokemon.sheet.appId].element.css({"filter": "", "pointer-events": ""}) + ui.windows[this.data.pokemon.sheet.appId]?.element?.css({"filter": "", "pointer-events": ""}) }); return this; } @@ -187,7 +187,7 @@ class LevelUpForm extends FormApplication { } this.resolve(); - ui.windows[this.data.pokemon.sheet.appId].element.css({"filter": "", "pointer-events": ""}) + ui.windows[this.data.pokemon.sheet.appId]?.element?.css({"filter": "", "pointer-events": ""}) return super.close({...options, force: true}); } } diff --git a/src/module/apps/migration-summary.js b/src/module/apps/migration-summary.js index 6c52223cc..62a796ed9 100644 --- a/src/module/apps/migration-summary.js +++ b/src/module/apps/migration-summary.js @@ -1,3 +1,4 @@ + import { MigrationList } from "../migration/index.js"; import { MigrationRunner } from "../migration/runner/index.js" @@ -16,7 +17,7 @@ class MigrationSummary extends Application { (app) => app instanceof MigrationSummary ); if (existing) { - existing.options = mergeObject(existing.options, options); + existing.options = foundry.utils.mergeObject(existing.options, options); return existing; } } diff --git a/src/module/apps/party/sheet.js b/src/module/apps/party/sheet.js index 02cea37d7..3f35ad88a 100644 --- a/src/module/apps/party/sheet.js +++ b/src/module/apps/party/sheet.js @@ -12,7 +12,7 @@ class PTUPartySheet extends FormApplication { } static get defaultOptions() { - return mergeObject(super.defaultOptions, { + return foundry.utils.mergeObject(super.defaultOptions, { title: "PTU.PartySheet.Title", classes: ["ptu", "sheet", "party"], width: 637, @@ -40,11 +40,6 @@ class PTUPartySheet extends FormApplication { getData() { const data = super.getData(); - data.trainer = this.trainer; - data.party = this.party; - data.boxed = this.boxed; - data.available = this.available; - const averageLevelOfMons = monArray => ( monArray.reduce( (a, b) => a + (b.attributes.level.current ?? 0), @@ -52,9 +47,34 @@ class PTUPartySheet extends FormApplication { ) / monArray.length ).toFixed(1); - data.partyApl = this.party?.length > 0 ? averageLevelOfMons(data.party) : undefined - data.boxedApl = this.boxed?.length > 0 ? averageLevelOfMons(data.boxed) : undefined - data.availableApl = this.available?.length > 0 ? averageLevelOfMons(data.available) : undefined + data.trainer = this.trainer; + data.boxes = { + party: { + contents: this.party, + apl: this.party?.length > 0 ? averageLevelOfMons(this.party) : undefined + }, + boxed: { + contents: this.boxed, + apl: this.boxed?.length > 0 ? averageLevelOfMons(this.boxed) : undefined + }, + } + if(this.available?.length > 0) { + data.boxes.available = { + contents: this.available, + apl: averageLevelOfMons(this.available) + } + } + + for (const child of this.trainer.folder.children) { + if ([this.folders.party?.id, this.folders.box?.id].includes(child.folder.id)) continue; + + const slug = CONFIG.PTU.util.sluggify(child.folder.name); + data.boxes[slug] = { + contents: child.entries.filter(actor => actor.type == "pokemon"), + } + data.boxes[slug].apl = data.boxes[slug].contents?.length > 0 ? averageLevelOfMons(data.boxes[slug].contents) : undefined; + this.folders[slug] = child.folder; + } return data; } @@ -116,8 +136,8 @@ class PTUPartySheet extends FormApplication { #loadFolders(strict) { // Get available folders from the trainer's folder - const folder = this.trainer.folder; - if (!folder) { + const trainerFolder = this.trainer.folder; + if (!trainerFolder) { if (strict) { ui.notifications.error("PTU.PartySheet.NoFolder", { localize: true }); throw new Error("PTU.PartySheet.NoFolder"); @@ -125,18 +145,18 @@ class PTUPartySheet extends FormApplication { return; }; - const party = folder.children.find(folder => folder.folder.name == "Party")?.folder; - const box = folder.children.find(folder => folder.folder.name == "Box")?.folder; + const party = trainerFolder.children.find(folder => folder.folder.name == "Party")?.folder ?? game.folders.find(folder => folder.name == "Party" && folder._source.folder == trainerFolder.id); + const box = trainerFolder.children.find(folder => folder.folder.name == "Box")?.folder ?? game.folders.find(folder => folder.name == "Box" && folder._source.folder == trainerFolder.id); this.folders = { - root: folder, + root: trainerFolder, party, box } if (!party) { // Create the party folder - Folder.create({ name: "Party", type: "Actor", folder: folder.id }) + Folder.create({ name: "Party", type: "Actor", folder: trainerFolder.id }) .then(folder => { this.folders.party = folder; }) @@ -148,7 +168,7 @@ class PTUPartySheet extends FormApplication { actor.flags?.ptu?.party?.trainer == this.trainer.id && !actor.flags?.ptu?.party?.boxed); - const available = folder.contents.filter(actor => actor.type == "pokemon" && !actor.flags?.ptu?.party?.trainer) ?? []; + const available = trainerFolder.contents.filter(actor => actor.type == "pokemon" && !actor.flags?.ptu?.party?.trainer) ?? []; for (const mon of available) { if (mon.folder.id == partyFolder.id) continue; await mon.update({ @@ -169,7 +189,7 @@ class PTUPartySheet extends FormApplication { } if (!box) { // Create the box folder - Folder.create({ name: "Box", type: "Actor", folder: folder.id }) + Folder.create({ name: "Box", type: "Actor", folder: trainerFolder.id }) .then(folder => { this.folders.box = folder; }) @@ -260,6 +280,12 @@ class PTUPartySheet extends FormApplication { event.preventDefault(); event.currentTarget.classList.remove("dragover"); }); + + html.find(".party-item[data-actor-uuid]").on('dblclick', async (event) => { + event.preventDefault(); + const actor = await fromUuid(event.currentTarget.dataset.actorUuid); + actor?.sheet?.render(true); + }) } /** @override */ @@ -341,6 +367,18 @@ class PTUPartySheet extends FormApplication { this.handledDrop = false; return this.render(); } + default: { + const folder = this.folders[partyStatus]; + if(!folder) return ui.notifications.error("Invalid folder"); + + if (actor.folder?.id != folder.id) { + await actor.update({ folder: folder.id }); + } + await actor.setFlag("ptu", "party", { trainer: this.trainer.id, boxed: true }); + + this.handledDrop = false; + return this.render(); + } } } @@ -435,6 +473,18 @@ class PTUPartySheet extends FormApplication { this.boxed.push(actor); return this.render(); } + default: { + const folder = this.folders[partyStatus]; + if(!folder) return ui.notifications.error("Invalid folder"); + + if (actor.folder?.id != folder.id) { + await actor.update({ folder: folder.id }); + } + await actor.setFlag("ptu", "party", { trainer: this.trainer.id, boxed: true }); + + this[type]?.splice?.(index, 1); + return this.render(); + } } } @@ -469,7 +519,7 @@ class PTUPartySheet extends FormApplication { systemVersion: game.system.version }; - const filename = ["fvtt", "ptuParty", this.trainer.name?.slugify(), randomID()].filterJoin("-"); + const filename = ["fvtt", "ptuParty", this.trainer.name?.slugify(), foundry.utils.randomID()].filterJoin("-"); saveDataToFile(JSON.stringify(data, null, 2), "text/json", `${filename}.json`); } @@ -537,7 +587,6 @@ class PTUPartySheet extends FormApplication { party: partySheet.party, boxed: partySheet.boxed } - console.log(duplicate(data)); // Delete existing party await existingActor.delete(); diff --git a/src/module/apps/rulebook-journal.js b/src/module/apps/rulebook-journal.js new file mode 100644 index 000000000..ec69ba0a2 --- /dev/null +++ b/src/module/apps/rulebook-journal.js @@ -0,0 +1,10 @@ +/** + * The custom Journal Sheet used for Kingmaker content. + */ +export default class PTURuleBookJournal extends JournalSheet { + constructor(doc, options) { + super(doc, options); + this.options.classes.push("ptu", "rulebook"); + } + } + \ No newline at end of file diff --git a/src/module/apps/species-drag-in/sheet.js b/src/module/apps/species-drag-in/sheet.js index 10bfe9e75..762b27ba3 100644 --- a/src/module/apps/species-drag-in/sheet.js +++ b/src/module/apps/species-drag-in/sheet.js @@ -4,7 +4,7 @@ import { PTUSpecies } from "../../item/index.js"; export class PTUSpeciesDragOptionsPrompt extends FormApplication { /** @override */ static get defaultOptions() { - return mergeObject(super.defaultOptions, { + return foundry.utils.mergeObject(super.defaultOptions, { classes: ["ptu", "pokemon", "drag-in"], template: "systems/ptu/static/templates/apps/species-drag-prompt.hbs", width: 250, diff --git a/src/module/apps/species-mass-generator/document.js b/src/module/apps/species-mass-generator/document.js index d8d013228..a046da0bd 100644 --- a/src/module/apps/species-mass-generator/document.js +++ b/src/module/apps/species-mass-generator/document.js @@ -13,9 +13,9 @@ export class SpeciesGeneratorData { this.tableSelect = { value: undefined, updated: false, - options: game.tables.map(t => ({label: t.name, uuid: t.uuid})) + options: game.tables.map(t => ({ label: t.name, uuid: t.uuid })) } - if(this.tableSelect.options?.length > 0) { + if (this.tableSelect.options?.length > 0) { this.tableSelect.value = this.tableSelect.options[0].uuid; this.tableSelect.updated = true; } @@ -36,20 +36,26 @@ export class SpeciesGeneratorData { } async refresh() { - if(this.speciesField.updated) { - if(this.speciesField.value.includes("Item.")) { + if (this.speciesField.updated) { + if (this.speciesField.value.includes("Item.")) { this.species = await fromUuid(this.speciesField.value); } - else if(!isNaN(Number(this.speciesField.value))) { + else if (!isNaN(Number(this.speciesField.value))) { this.species = (await querySpeciesCompendium((species) => species.system.number == Number(this.speciesField.value))).find(s => !s.system.form); } else { - this.species = await findItemInCompendium({type: "species", name: this.speciesField.value}) + this.species = await (async () => { + const compendiums = Object.entries(game.settings.get("ptu", "compendiumBrowserPacks")?.species ?? { "ptu.species": { load: true } }).filter(([k, v]) => v.load).map(([k, v]) => k); + for (const compendium of compendiums) { + const result = await findItemInCompendium({ type: "species", name: this.speciesField.value, compendium }); + if (result) return result; + } + })() } this.speciesField.updated = false; } - if(this.tableSelect.updated) { - if(!isNaN(Number(this.tableSelect.value))) { + if (this.tableSelect.updated) { + if (!isNaN(Number(this.tableSelect.value))) { this.table = game.tables.get(this.tableSelect.value); } else if (this.tableSelect.value.includes("RollTable.")) { @@ -57,12 +63,12 @@ export class SpeciesGeneratorData { } this.tableSelect.updated = false; } - if(this.folderField.updated) { - if(this.folderField.value.includes("Item.")) { + if (this.folderField.updated) { + if (this.folderField.value.includes("Item.")) { const result = await fromUuid(this.folderField.value); this.folder = result ? result.type == "Actor" ? result : "invalid" : undefined; } - else if(!isNaN(Number(this.folderField.value))) { + else if (!isNaN(Number(this.folderField.value))) { const result = game.folders.get(this.folderField.value); this.folder = result ? result.type == "Actor" ? result : "invalid" : undefined; } @@ -72,37 +78,37 @@ export class SpeciesGeneratorData { this.folderField.updated = false; } - if(this.tableSelect.options?.length == 0) { + if (this.tableSelect.options?.length == 0) { await game.packs.get("ptu.habitats").importAll() - this.tableSelect.options = game.tables.map(t => ({label: t.name, uuid: t.uuid})) - if(this.tableSelect.options?.length > 0) { + this.tableSelect.options = game.tables.map(t => ({ label: t.name, uuid: t.uuid })) + if (this.tableSelect.options?.length > 0) { this.tableSelect.value = this.tableSelect.options[0].uuid; this.tableSelect.updated = true; } } - - if(this.species) { + + if (this.species) { this.helpText.species = `${game.i18n.localize("PTU.MassGenerator.FoundSpecies")} @UUID[${this.species.uuid}]` } - else if(this.speciesField.value) { - this.helpText.species = `${game.i18n.format("PTU.MassGenerator.CouldNotFindSpecies", {species: this.speciesField.value})}` + else if (this.speciesField.value) { + this.helpText.species = `${game.i18n.format("PTU.MassGenerator.CouldNotFindSpecies", { species: this.speciesField.value })}` } - if(this.table) { + if (this.table) { this.helpText.table = `${game.i18n.localize("PTU.MassGenerator.FoundTable")} @UUID[${this.table.uuid}]` } - else if(this.tableSelect.value) { - this.helpText.table = `${game.i18n.format("PTU.MassGenerator.CouldNotFindTable", {table: this.tableSelect.value})}` + else if (this.tableSelect.value) { + this.helpText.table = `${game.i18n.format("PTU.MassGenerator.CouldNotFindTable", { table: this.tableSelect.value })}` } - if(this.folder == "invalid") { + if (this.folder == "invalid") { this.helpText.folder = `${game.i18n.localize("PTU.MassGenerator.InvalidFolder")}` } - else if(this.folder) { + else if (this.folder) { this.helpText.folder = `${game.i18n.localize("PTU.MassGenerator.FoundFolder")} @UUID[${this.folder.uuid}]` } - else if(this.folderField.value) { - this.helpText.folder = `${game.i18n.format("PTU.MassGenerator.CouldNotFindFolder", {folder: this.folderField.value})}` + else if (this.folderField.value) { + this.helpText.folder = `${game.i18n.format("PTU.MassGenerator.CouldNotFindFolder", { folder: this.folderField.value })}` } } @@ -113,7 +119,7 @@ export class SpeciesGeneratorData { amount: this.amount, } - if(this.speciesTab == "species") { + if (this.speciesTab == "species") { data.species = this.species; } else { diff --git a/src/module/apps/species-mass-generator/sheet.js b/src/module/apps/species-mass-generator/sheet.js index aaeb553b0..dc082106f 100644 --- a/src/module/apps/species-mass-generator/sheet.js +++ b/src/module/apps/species-mass-generator/sheet.js @@ -4,7 +4,7 @@ import { SpeciesGeneratorData } from "./document.js"; export class PTUSpeciesMassGenerator extends FormApplication { /** @override */ static get defaultOptions() { - return mergeObject(super.defaultOptions, { + return foundry.utils.mergeObject(super.defaultOptions, { classes: ["ptu", "pokemon", "generator"], template: "systems/ptu/static/templates/apps/species-mass-generator-sheet.hbs", width: 350, diff --git a/src/module/apps/token-panel.js b/src/module/apps/token-panel.js index 54b92a8f3..445d7634c 100644 --- a/src/module/apps/token-panel.js +++ b/src/module/apps/token-panel.js @@ -15,7 +15,7 @@ export class TokenPanel extends Application { /** @override */ static get defaultOptions() { - return mergeObject(super.defaultOptions, { + return foundry.utils.mergeObject(super.defaultOptions, { id: "ptu-token-panel", template: "systems/ptu/static/templates/apps/token-panel.hbs", popOut: false, @@ -42,8 +42,12 @@ export class TokenPanel extends Application { frequency: attack.item?.system.frequency ?? "At-Will", id, rollable: !!attack.roll, - effect: attack.item?.system.effect ?? "", + effect: attack.item?.system.effect ? await TextEditor.enrichHTML(foundry.utils.duplicate(attack.item.system.effect), {async: true}) : "", + range: attack.item?.system.range ?? "", + keywords: attack.item?.system.keywords ?? [], + sort: attack.item?.sort ?? 0, }; + if(attack.item?.system.category) data.category = `/systems/ptu/static/css/images/types2/${attack.item?.system.category}IC_Icon.png`; if (attack.item.system.isStruggle) struggles.push(data); else attacks.push(data); } @@ -63,10 +67,10 @@ export class TokenPanel extends Application { name: feat.name, img: feat.img, id: feat.id, - effect: feat.system.effect, + effect: feat.system.effect ? await TextEditor.enrichHTML(foundry.utils.duplicate(feat.system.effect), {async: true}) : "", frequency: feat.system.frequency, rollable: !!feat.roll, - tags: feat.system.tags, + keywords: feat.system.keywords, }) } @@ -77,12 +81,24 @@ export class TokenPanel extends Application { name: ability.name, img: ability.img, id: ability.id, - effect: ability.system.effect, + effect: ability.system.effect ? await TextEditor.enrichHTML(foundry.utils.duplicate(ability.system.effect), {async: true}) : "", frequency: ability.system.frequency, rollable: !!ability.roll, }) } + const effects = []; + for (const effect of actor.itemTypes.effect?.sort((a, b) => a.sort - b.sort) ?? []) { + if (effect.getFlag("ptu", "showInTokenPanel") === false) continue; + effects.push({ + id: effect.id, + parent: effect.parent.id, + name: effect.name, + img: effect.img, + effect: effect.system.effect ? await TextEditor.enrichHTML(foundry.utils.duplicate(effect.system.effect), {async: true}) : "", + }); + } + let movement = []; movement.push( {name: "Overland", value: actor.system.capabilities?.overland ?? 0, icon: "fas fa-shoe-prints"}, @@ -90,7 +106,8 @@ export class TokenPanel extends Application { {name: "Burrow", value: actor.system.capabilities?.burrow ?? 0, icon: "fas fa-mountain"}, {name: "Levitate", value: actor.system.capabilities?.levitate ?? 0, icon: "fas fa-feather"}, {name: "Sky", value: actor.system.capabilities?.sky ?? 0, icon: "fab fa-fly"}, - {name: "Teleporter", value: actor.system.capabilities?.teleporter ?? 0, icon: "fas fa-people-arrows"} + {name: "Teleporter", value: actor.system.capabilities?.teleporter ?? 0, icon: "fas fa-people-arrows"}, + {name: "Throwing", value: actor.system.capabilities?.throwingRange ?? 0, icon: "fas fa-baseball-ball"}, ); movement = movement.filter(item => item.value !== 0); @@ -110,13 +127,13 @@ export class TokenPanel extends Application { ...(await super.getData(options)), user: { isGM: game.user.isGM }, actor, - attacks, + attacks: attacks.sort((a, b) => a.sort - b.sort), struggles, items, show, party: this.#getPartyInfo(), conditions: actor.itemTypes.condition || [], - effects: actor.itemTypes.effect || [], + effects, feats, abilities, heldItem, @@ -168,7 +185,8 @@ export class TokenPanel extends Application { event, options: msg.context.options ?? [], actor: msg.actor, - targets: msg.targets + targets: msg.targets, + rollResult: msg.context.rollResult ?? null, } const result = await attack.damage?.(params); if (result === null) { @@ -315,6 +333,8 @@ export class TokenPanel extends Application { theme: `tooltipster-shadow ball-themes ${this.actor?.sheet?.ballStyle}`, position: 'top', maxWidth: 500, + contentAsHTML: true, + interactive: true, }); } diff --git a/src/module/apps/type-matrix.js b/src/module/apps/type-matrix.js index ebf88d42e..304a86cdd 100644 --- a/src/module/apps/type-matrix.js +++ b/src/module/apps/type-matrix.js @@ -8,7 +8,7 @@ class TypeMatrix extends FormApplication { const options = super.defaultOptions; options.classes.push("ptu-settings-menu"); - return mergeObject(options, { + return foundry.utils.mergeObject(options, { title: "PTU.TypeMatrix.Title", template: "systems/ptu/static/templates/config/settings/types.hbs", id: "type-matrix", @@ -28,7 +28,7 @@ class TypeMatrix extends FormApplication { this.cache["types"] = types; } - const typeEffectiveness = duplicate(this.cache["types"]); + const typeEffectiveness = foundry.utils.duplicate(this.cache["types"]); delete typeEffectiveness.Untyped; let typeLength = Object.keys(typeEffectiveness).length + 1; diff --git a/src/module/apps/weather.js b/src/module/apps/weather.js new file mode 100644 index 000000000..75ed23ab4 --- /dev/null +++ b/src/module/apps/weather.js @@ -0,0 +1,145 @@ +class Weather extends Application { + /** @type {Map} */ + static globalEffects = new Map(); + + static get defaultOptions() { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["ptu", "weather"], + title: "Weather", + template: "systems/ptu/static/templates/apps/weather.hbs", + width: 450, + height: 300, + resizable: true, + dragDrop: [{ dragSelector: ".window-title", dropSelector: null }], + }); + } + + static _initializeGlobalEffects() { + const effects = game.settings.get("ptu", "weatherEffects"); + for (const effect of effects) { + const item = new CONFIG.PTU.Item.proxy(effect, { temporary: true }); + const updates = {}; + if (item.system.mode === undefined) updates["system.mode"] = 'disabled'; + if (item.system.global === undefined) updates["system.global"] = true; + item.updateSource(updates); + Weather.globalEffects.set(item.id, item); + } + } + + constructor() { + super(); + Weather._initializeGlobalEffects(); + } + + getData() { + const data = super.getData(); + + data.effects = []; + for (const effect of Weather.globalEffects.values()) { + data.effects.push({ + id: effect.id, + name: effect.name, + icon: effect.img, + mode: effect.system.mode, + }); + } + + return data; + } + + activateListeners(html) { + super.activateListeners(html); + + html.find("select[name='mode']").on("change", this._onModeChange.bind(this)); + + html.find(".item-control.effect-to-chat").on("click", (event) => { + const effectId = event.target.closest(".effect").dataset.id; + const effect = Weather.globalEffects.get(effectId); + return effect?.sendToChat?.(); + }); + html.find(".item-control.effect-delete").on("click", (event) => { + const effectId = event.target.closest(".effect").dataset.id; + this.removeEffect(effectId); + }); + } + + async _onModeChange(event) { + const effectId = event.target.closest(".effect").dataset.id; + const effect = Weather.globalEffects.get(effectId); + if (!effect) return; + + effect.updateSource({ "system.mode": event.target.value }); + + await game.settings.set("ptu", "weatherEffects", Array.from(Weather.globalEffects.values(), effect => effect.toObject())); + this.render(true); + } + + async _onDrop(event) { + const data = JSON.parse(event.dataTransfer.getData("text/plain")); + + if (data.type !== "Item") return; + + const item = await fromUuid(data.uuid); + if (!item || !(item instanceof CONFIG.PTU.Item.baseEffect)) return; + + await this.addEffect(item); + } + + /** + * @param {CONFIG.PTU.Item.baseEffect | {system: CONFIG.PTU.Item.baseEffect['system']} | {uuid: string}} effect + */ + async addEffect(effect) { + if (effect instanceof CONFIG.PTU.Item.baseEffect) { + Weather.globalEffects.set(effect.id, new CONFIG.PTU.Item.proxy(effect.toObject(), { temporary: true })); + Weather.globalEffects.get(effect.id).updateSource({ "flags.core.sourceId": effect.uuid }); + } + else if (typeof effect === "object" && "system" in effect) { + Weather.globalEffects.set(effect.id, new CONFIG.PTU.Item.proxy(effect, { temporary: true })); + } + else if ("uuid" in effect) { + const item = await fromUuid(effect.uuid); + if (!item) return; + Weather.globalEffects.set(item.id, new CONFIG.PTU.Item.proxy(item.toObject(), { temporary: true })); + Weather.globalEffects.get(item.id).updateSource({ "flags.core.sourceId": item.uuid }); + } + else { + console.warn("Invalid weather effect", effect); + return; + } + + Weather.globalEffects.get(effect.id).updateSource({ "system.mode": 'disabled', "system.global": true }) + + await game.settings.set("ptu", "weatherEffects", Array.from(Weather.globalEffects.values(), effect => effect.toObject())); + this.render(true); + } + + /** + * @param {string} effectId + */ + async removeEffect(effectId) { + const effect = Weather.globalEffects.get(effectId); + if (!effect) return; + Weather.globalEffects.delete(effectId); + + await game.settings.set("ptu", "weatherEffects", Array.from(Weather.globalEffects.values(), effect => effect.toObject())); + this.render(true); + } + + static async updateGameState() { + Weather._initializeGlobalEffects(); + game.actors.forEach(actor => actor.reset()); + ui.notifications.info("Weather Effects Updated on all actors!"); + } + + static openWeatherMenu() { + for(const apps of Object.values(ui.windows)) { + if(apps instanceof Weather) { + apps.bringToTop(); + return; + } + } + new Weather().render(true); + } +} + +export { Weather }; \ No newline at end of file diff --git a/src/module/canvas/helpers.js b/src/module/canvas/helpers.js index a78623467..cd9673370 100644 --- a/src/module/canvas/helpers.js +++ b/src/module/canvas/helpers.js @@ -14,7 +14,7 @@ function measureDistanceCuboid(r0, r1, { return canvas.grid.measureDistance(r0, r1); } - const gridWidth = canvas.grid.grid.w; + const gridWidth = canvas.grid.grid.sizeX ?? canvas.grid.grid.w; const distance = { dx: 0, @@ -138,6 +138,11 @@ function measureDistanceOnGrid(segment, options = {}) { straight: sortedDistance[2] - sortedDistance[1], }; + // If a move has a burst range it should ignore doubling diagonals + if(options.burst) { + return (squares.doubleDiagonal + squares.diagonal + squares.straight) * gridDistance; + } + // Diagonals in PTU pretty much count as 1.5 times a straight // for diagonals across the x, y, and z axis count it as 1.75 as a best guess const distance = Math.floor(squares.doubleDiagonal * 1.75 + squares.diagonal * 1.5 + squares.straight); diff --git a/src/module/canvas/token/base.js b/src/module/canvas/token/base.js index a42a3d61c..efdbe15c0 100644 --- a/src/module/canvas/token/base.js +++ b/src/module/canvas/token/base.js @@ -14,7 +14,7 @@ class PTUToken extends Token { } const { current, max, temp, injuries } = actor.attributes.health ?? {}; - const healthPercent = Math.clamped(current, 0, max) / max; + const healthPercent = Math.clamp(current, 0, max) / max; // Compute the color based on health percentage, this formula is the one core foundry uses const black = 0x000000; @@ -26,12 +26,12 @@ class PTUToken extends Token { // Bar size logic stolen from core let h = Math.max(canvas.dimensions.size / 12, 8); - const bs = Math.clamped(h / 8, 1, 2); + const bs = Math.clamp(h / 8, 1, 2); if (this.document.height >= 2) h *= 1.6; // Enlarge the bar for large tokens const { is, turns, bars } = actor.system.boss ?? {}; const bossBars = is ? Math.max(turns - 1, bars) : 0; - const brokenBars = is ? Math.clamped(bossBars - bars, 0, bossBars) : 0; + const brokenBars = is ? Math.clamp(bossBars - bars, 0, bossBars) : 0; const numBars = (temp?.value > 0 ? 2 : 1) + bossBars + 1; const barHeight = h / numBars; @@ -51,7 +51,7 @@ class PTUToken extends Token { // Draw temp hp if (temp?.value > 0) { const tempColor = 0x66ccff; - const tempPercent = Math.clamped(temp.value, 0, (temp.max < temp.value ? temp.value : temp.max)) / (temp.max < temp.value ? temp.value : temp.max); + const tempPercent = Math.clamp(temp.value, 0, (temp.max < temp.value ? temp.value : temp.max)) / (temp.max < temp.value ? temp.value : temp.max); const tempWidth = tempPercent * smallBarWidth - 2 * (bs - 1); bar.lineStyle(0).beginFill(black, 0.5).drawRoundedRect(smallWidthOffset, 0, smallBarWidth, barHeight, 3); bar.beginFill(tempColor, 1.0).drawRoundedRect(smallWidthOffset, 0, tempWidth, barHeight, 2); @@ -75,7 +75,7 @@ class PTUToken extends Token { } // Detirmine the width of the health bar due to injuries - const injuryPercent = Math.clamped(injuries, 0, 10) / 10; + const injuryPercent = Math.clamp(injuries, 0, 10) / 10; const injuryWidth = injuryPercent * this.w; const healthWidth = (this.w - injuryWidth) * healthPercent; @@ -272,7 +272,7 @@ class PTUToken extends Token { const maxHP = this.actor?.system?.health?.max; if (!quantity) return null; - const percent = Math.clamped(Math.abs(quantity) / maxHP, 0, 1); + const percent = Math.clamp(Math.abs(quantity) / maxHP, 0, 1); const textColors = { damage: 16711680, // reddish healing: 65280, // greenish @@ -336,7 +336,7 @@ class PTUToken extends Token { const selfElevation = this.document.elevation; const targetElevation = target.document.elevation; if (selfElevation === targetElevation || !this.actor || !target.actor) { - return measureDistanceCuboid(this.bounds, target.bounds, options); + return measureDistanceCuboid(this.bounds, target.bounds, {}, options); } return measureDistanceCuboid(this.bounds, target.bounds, { @@ -404,7 +404,6 @@ class PTUToken extends Token { }; }, {}); - console.log(Object.entries(flankersFlanking).map(([id, count]) => `${canvas.tokens.get(id).name}: ${count}`)); const flankedByCount = Object.values(flankersFlanking).reduce((acc, count) => Math.max(acc, count), 0); return flankedByCount >= flankerRequirement; diff --git a/src/module/canvas/token/document.js b/src/module/canvas/token/document.js index 7d8c9c262..69b494d46 100644 --- a/src/module/canvas/token/document.js +++ b/src/module/canvas/token/document.js @@ -29,7 +29,7 @@ class PTUTokenDocument extends TokenDocument { const autoscaleDefault = game.settings.get("ptu", "tokens.autoscale"); // Autoscaling is a secondary feature of linking to actor size const autoscale = linkToActorSize ? this.flags.ptu.autoscale ?? autoscaleDefault : false; - this.flags.ptu = mergeObject(this.flags.ptu ?? {}, { linkToActorSize, autoscale }); + this.flags.ptu = foundry.utils.mergeObject(this.flags.ptu ?? {}, { linkToActorSize, autoscale }); this.disposition = this.actor.alliance ? { @@ -102,7 +102,7 @@ class PTUTokenDocument extends TokenDocument { const preUpdate = this.toObject(false); this.reset(); const postUpdate = this.toObject(false); - const changes = diffObject(preUpdate, postUpdate); + const changes = foundry.utils.diffObject(preUpdate, postUpdate); if(Object.keys(changes).length > 0) { this._onUpdate(changes, {}, game.user.id); diff --git a/src/module/combat/combatant.js b/src/module/combat/combatant.js index 34ae90ae2..bde91cd56 100644 --- a/src/module/combat/combatant.js +++ b/src/module/combat/combatant.js @@ -91,7 +91,7 @@ class PTUCombatant extends Combatant { hidden, sceneId, tokenId, - ...(index === 0 && turns > 1 ? { flags: { ptu: { isPrimaryBossCombatant: true } } } : {}) + ...(index === 0 ? { flags: { ptu: { isPrimaryBossCombatant: true } } } : {}) })); }); return super.createDocuments(realData, context); @@ -107,6 +107,8 @@ class PTUCombatant extends Combatant { } const paralyzed = actor.conditions.active.find(c => c.slug == "paralysis"); if (paralyzed) await PTUCondition.HandleParalyzed(actor, paralyzed); + const hyperMode = actor.conditions.active.find(c => c.slug == "hyper-mode"); + if (hyperMode) await PTUCondition.HandleHyperMode(actor, hyperMode); if (this.isBoss) { await this.bossTurns.mainTurn.update({ "flags.ptu.roundOfLastTurn": encounter.round }); diff --git a/src/module/combat/config.js b/src/module/combat/config.js index 5b48ec8d5..11c897463 100644 --- a/src/module/combat/config.js +++ b/src/module/combat/config.js @@ -6,7 +6,7 @@ /** @override */ static get defaultOptions() { - return mergeObject(super.defaultOptions, { + return foundry.utils.mergeObject(super.defaultOptions, { template: "systems/ptu/static/templates/config/combat-settings.hbs", title: "PTU Combat Settings" }); diff --git a/src/module/combat/tracker.js b/src/module/combat/tracker.js index dfbc0afae..6cc852d78 100644 --- a/src/module/combat/tracker.js +++ b/src/module/combat/tracker.js @@ -3,7 +3,7 @@ import { PTUCombatant } from "./combatant.js"; class PTUCombatTracker extends CombatTracker { static get defaultOptions() { - return mergeObject(super.defaultOptions, { + return foundry.utils.mergeObject(super.defaultOptions, { template: "systems/ptu/static/templates/sidebar/combat-tracker.hbs", }); } diff --git a/src/module/item/base.js b/src/module/item/base.js index e9b602c38..de564eba4 100644 --- a/src/module/item/base.js +++ b/src/module/item/base.js @@ -1,6 +1,7 @@ import { RuleElements } from "../rules/index.js"; import { processGrantDeletions } from "../rules/rule-element/grant-item/helper.js"; import { sluggify } from "../../util/misc.js" +import { GrantItemRuleElement } from "../rules/rule-element/grant-item/rule-element.js"; class PTUItem extends Item { @@ -64,11 +65,28 @@ class PTUItem extends Item { return this.img.includes("class"); } + get enabled(){ + return !!(this.system.enabled ?? true) + } + + /** Change state of whether items automation should be enabled or disabled. If called + * without argument, toggles between on and off. + * @param newState + * @return {Promise} + */ + async toggleEnableState(newState = !this.enabled){ + await this.update({"system.enabled": newState}) + for(const rule of this.rules) { + if(rule.ignored || !(rule instanceof GrantItemRuleElement)) continue; + return this.actor.update({"system.timestamp": Date.now()}) + } + } + /** @override */ prepareBaseData() { - this.flags.ptu = mergeObject({ rulesSelections: {} }, this.flags.ptu ?? {}); + this.flags.ptu = foundry.utils.mergeObject({ rulesSelections: {} }, this.flags.ptu ?? {}); - this.flags.ptu = mergeObject(this.flags.ptu ?? {}, { + this.flags.ptu = foundry.utils.mergeObject(this.flags.ptu ?? {}, { rollOptions: { all: { [`item:id:${this._id}`]: true, @@ -80,10 +98,15 @@ class PTUItem extends Item { [`item:id:${this._id}`]: true, [`item:slug:${this.slug}`]: true, [`item:type:${this.type}`]: true, - [`${this.type}:${this.slug}`]: true + [`${this.type}:${this.slug}`]: true, } } }); + + if(this.enabled) { + this.flags.ptu.rollOptions.all[`item:enabled`] = true; + this.flags.ptu.rollOptions.item[`item:enabled`] = true; + } } /** @override */ @@ -126,7 +149,7 @@ class PTUItem extends Item { } _updateIcon({ source, update } = { source: undefined, update: false }) { - source ??= duplicate(this); + source ??= foundry.utils.duplicate(this); let required = false; source.img ||= `/systems/ptu/static/css/images/icons/${source.type}_icon.png` @@ -173,9 +196,11 @@ class PTUItem extends Item { } if (!(context.keepId || context.keepEmbeddedIds)) { - source._id = randomID(); + source._id = foundry.utils.randomID(); } + if(source.system.stackSlugs) source.system.slug = sluggify(source.name + foundry.utils.randomID()); + const item = new CONFIG.Item.documentClass(source, { parent: actor }); if (item.type === "condition") { @@ -185,6 +210,7 @@ class PTUItem extends Item { continue; } } + items.push(item); outputSources.push(item._source); } @@ -403,7 +429,7 @@ class PTUItem extends Item { .join(""); })(); - const referenceEffect = this.referenceEffect ? await TextEditor.enrichHTML(`@UUID[${duplicate(this.referenceEffect)}]`, { async: true }) : null; + const referenceEffect = this.referenceEffect ? await TextEditor.enrichHTML(`@UUID[${foundry.utils.duplicate(this.referenceEffect)}]`, { async: true }) : null; const chatData = { user: game.user._id, diff --git a/src/module/item/effect-types/base.js b/src/module/item/effect-types/base.js index 5f03e1323..528fe6593 100644 --- a/src/module/item/effect-types/base.js +++ b/src/module/item/effect-types/base.js @@ -1,6 +1,13 @@ import { sluggify } from "../../../util/misc.js"; import { PTUItem } from "../base.js"; +/** + * @typedef DurationData + * @property {number} value + * @property {"rounds" | "unlimited" | "encounter"} unit + * @property {"turn-start" | "turn-end" | "round-end" | null} expiry + */ + class BaseEffectPTU extends PTUItem { get badge() { @@ -19,6 +26,10 @@ class BaseEffectPTU extends PTUItem { return this.system.expired; } + get isGlobal() { + return !!this.system.global; + } + get totalDuration() { const { duration } = this.system; if(["unlimited", "encounter"].includes(duration.unit)) return Infinity; @@ -159,5 +170,4 @@ class BaseEffectPTU extends PTUItem { } } -export { BaseEffectPTU } -globalThis.BaseEffectPTU = BaseEffectPTU; \ No newline at end of file +export { BaseEffectPTU } \ No newline at end of file diff --git a/src/module/item/effect-types/condition/document.js b/src/module/item/effect-types/condition/document.js index 439dfd57d..bdaa40b8c 100644 --- a/src/module/item/effect-types/condition/document.js +++ b/src/module/item/effect-types/condition/document.js @@ -134,10 +134,19 @@ class PTUCondition extends BaseEffectPTU { const { dc, type, decrease } = this.system.persistent; if (type !== "save") return; + // If this is shadow pokemon's save but you already have the hyper mode condition; skip + if (this.slug === "shadow-pokemon" && this.actor.conditions.bySlug('hyper-mode').length > 0) return; const dcModifiers = (() => { + if(this.slug === "shadow-pokemon") { + const mods = [new PTUModifier({ slug: "dc", label: "DC", modifier: 5 })]; + for(let i = 1; i <= this.actor.attributes.health.injuries; i++) { + mods.push(new PTUModifier({ slug: `injury-${i}`, label: `Injury ${i}`, modifier: 2 })); + } + return mods; + } if (!decrease) return [new PTUModifier({ slug: "dc", label: "DC", modifier: dc })]; - const modifier = 20 - ((this.value - 1) * 6); + const modifier = 20 - ((this.value - 1) * (this.slug === "hyper-mode" ? 2 : 6)); if (modifier <= 0) return [new PTUModifier({ slug: "dc", label: "DC", modifier: -Infinity })]; return [new PTUModifier({ slug: "dc", label: "DC", modifier })]; })(); @@ -174,14 +183,29 @@ class PTUCondition extends BaseEffectPTU { const result = await statistic.roll({ skipDialog: true, targets }); if (statistic.dc.value <= result.total) { + if(this.slug === "shadow-pokemon") { + return; + } await this.delete(); } else { + if(this.slug === "shadow-pokemon") { + return this.#handleHyperMode(); + } await this.increase(); } } } + async #handleHyperMode() { + const hypermode = await fromUuid('Compendium.ptu.effects.Item.NegVrXO5uxnkiyIZ'); + await ChatMessage.create({ + speaker: ChatMessage.getSpeaker({ actor: this.actor }), + content: `

${game.i18n.format("PTU.Action.Hypermode.Create", { name: this.actor.name })}

` + }); + return await this.actor.createEmbeddedDocuments("Item", [hypermode.toObject()]); + } + /** @override */ prepareBaseData() { super.prepareBaseData(); @@ -339,8 +363,11 @@ class PTUCondition extends BaseEffectPTU { const result = await statistic.roll({ skipDialog: true, targets }); const effective = statistic.dc.value <= result.total; - if (effective) { - //TODO: Apply Vulnerable for 1 round + if (!effective) { + const vulnerable = await fromUuid('Compendium.ptu.effects.Item.wqzPWKMbwvOWLVoI'); + const data = vulnerable.toObject(); + data.system.duration = { value: 1, unit: 'rounds', expiry: 'turn-start'}; + await actor.createEmbeddedDocuments("Item", [data]); } return await ChatMessage.create({ @@ -349,6 +376,40 @@ class PTUCondition extends BaseEffectPTU { content: `

${game.i18n.localize(`PTU.Action.Paralyzed.${effective ? "Suppressed" : "Applicable"}.One`)} @UUID[${paralyzed.uuid}] ${game.i18n.localize(`PTU.Action.Paralyzed.${effective ? "Suppressed" : "Applicable"}.Two`)}

`, }) } + + /** + * @param {PTUActor} actor + * @param {PTUCondition} hypermode + */ + static async HandleHyperMode(actor, hypermode) { + + const dcModifiers = [new PTUModifier({ slug: "dc", label: "DC", modifier: 11 })] + const saveModifiers = []; + + const statistic = new Statistic(actor, { + slug: "save-check", + label: game.i18n.format("PTU.SaveCheck", { name: actor.name, save: hypermode.name }), + check: { type: "save-check", domains: ["save-check"], modifiers: saveModifiers }, + dc: { modifiers: dcModifiers, domains: ["save-dc"] }, + domains: ["hypermode"] + }); + + const target = actor.getActiveTokens().shift(); + const targets = target ? [{ + actor: actor.toObject(), + token: target.document.toObject(), + dc: { value: statistic.dc.value, flat: true, slug: hypermode.slug }, + }] : []; + + const result = await statistic.roll({ skipDialog: true, targets }); + const effective = statistic.dc.value <= result.total; + + return await ChatMessage.create({ + speaker: ChatMessage.getSpeaker({ actor }), + flavor: `

${game.i18n.format(`PTU.Action.Hypermode.${effective ? "Success" : "Fail"}`, { name: actor.name })}

`, + content: `

${game.i18n.localize(`PTU.Action.Hypermode.${effective ? "Suppressed" : "Applicable"}.One`)} @UUID[${hypermode.uuid}] ${game.i18n.localize(`PTU.Action.Hypermode.${effective ? "Suppressed" : "Applicable"}.Two`)}

`, + }) + } } export { PTUCondition } \ No newline at end of file diff --git a/src/module/item/effect-types/effect/document.js b/src/module/item/effect-types/effect/document.js index 9ee345c25..0ad45fc21 100644 --- a/src/module/item/effect-types/effect/document.js +++ b/src/module/item/effect-types/effect/document.js @@ -35,7 +35,7 @@ class PTUEffect extends BaseEffectPTU { const badge = this.system.badge; if(badge?.type === "counter") { const max = badge.labels?.length ?? Infinity; - badge.value = Math.clamped(badge.value, 1, max); + badge.value = Math.clamp(badge.value, 1, max); badge.label = badge.labels?.at(badge.value -1)?.trim() || null; } } @@ -54,14 +54,14 @@ class PTUEffect extends BaseEffectPTU { if(badge?.type === "counter" && !this.isExpired) { if(badge.value >= (badge.labels?.length ?? Infinity)) return; - return await this.update({"system.badge.value": duplicate(badge.value)+1}); + return await this.update({"system.badge.value": foundry.utils.duplicate(badge.value)+1}); } } async decrease() { if(this.system.badge?.type !== "counter" || this.system.badge.value === 1 || this.isExpired) return await this.delete(); - return await this.update({"system.badge.value": duplicate(this.system.badge.value)-1}); + return await this.update({"system.badge.value": foundry.utils.duplicate(this.system.badge.value)-1}); } async apply(targets, source = null) { diff --git a/src/module/item/feat/document.js b/src/module/item/feat/document.js index 24b37ed80..630fe75d9 100644 --- a/src/module/item/feat/document.js +++ b/src/module/item/feat/document.js @@ -2,8 +2,7 @@ import { sluggify } from '../../../util/misc.js'; import { PTUItem } from '../index.js'; class PTUFeat extends PTUItem { get category() { - const { tags } = this.system; - if (tags.includes("[Class]")) return "class"; + if(this.system.keywords?.includes("Class")) return "class"; return "feat"; } @@ -24,6 +23,7 @@ class PTUFeat extends PTUItem { if(this.system.class) { const classItem = this.actor?.items.find((item) => item.isClass && item.slug === sluggify(this.system.class)); if(classItem) { + this._source.flags.ptu ??= {}; this._source.flags.ptu.grantedBy = {id: classItem._id, onDelete: "detach"}; await classItem.update({"flags.ptu.itemGrants": {[this._source._id]: {id: this._source._id, onDelete: "detach"}}}); } @@ -38,7 +38,7 @@ class PTUFeat extends PTUItem { if(changed.system?.class !== undefined) { newClass.actor = this.actor?.items.find((item) => item.isClass && item.name === changed.system.class); if(newClass.actor) { - changed.flags = expandObject({"ptu.grantedBy": {id: newClass.actor._id, onDelete: "detach"}}); + changed.flags = foundry.utils.expandObject({"ptu.grantedBy": {id: newClass.actor._id, onDelete: "detach"}}); newClass.update = {"flags.ptu.itemGrants": {[this._id]: {id: this._id, onDelete: "detach"}}}; } if(this.system.class) { @@ -46,7 +46,7 @@ class PTUFeat extends PTUItem { if(oldClass.actor) { if(!changed.flags?.ptu?.grantedBy?.id && this.flags?.ptu?.grantedBy?.id === oldClass.actor._id) { delete changed.flags?.ptu?.grantedBy; - changed.flags= expandObject({"ptu.-=grantedBy": null}); + changed.flags= foundry.utils.expandObject({"ptu.-=grantedBy": null}); }; oldClass.update = {"flags.ptu.itemGrants": {[`-=${this._id}`]: null}}; } diff --git a/src/module/item/item/document.js b/src/module/item/item/document.js index 7967e28fe..3ff0f43ec 100644 --- a/src/module/item/item/document.js +++ b/src/module/item/item/document.js @@ -9,6 +9,15 @@ class PTUItemItem extends PTUItem { return this.system.container; } + prepareBaseData() { + super.prepareBaseData(); + + if(this.enabled) { + this.flags.ptu.rollOptions.all[`item:equipped`] = true; + this.flags.ptu.rollOptions.item[`item:equipped`] = true; + } + } + /** @override */ prepareSiblingData() { const itemGrants = this.flags.ptu.itemGrants; @@ -49,6 +58,37 @@ class PTUItemItem extends PTUItem { if(oldClass?.actor) await oldClass.actor.update(oldClass.update); if(newClass?.actor) await newClass.actor.update(newClass.update); } + + async purchase() { + const actor = (() => { + const character = game.user.character; + if(character) return character; + + const token = canvas.tokens.controlled[0]; + if(token) return token.actor; + + return null; + })(); + if(!actor) return ui.notifications.error("No actor selected"); + + const amount = this.system.cost; + + if ((actor.system.money ?? 0) < amount) return ui.notifications.error(`${actor.name} does not have enough money to pay for ${this.name} (Cost: ${amount} Poké, Current: ${actor.system.money})`); + await actor.update({ + "system.money": actor.system.money - amount, + }); + + // If duplicate item gets added instead increase the quantity + const existingItem = actor.items.getName(this.name); + if (existingItem && existingItem.system.quantity) { + const quantity = foundry.utils.duplicate(existingItem.system.quantity); + await existingItem.update({ "system.quantity": Number(quantity) + (this.system.quantity > 0 ? Number(this.system.quantity) : 1) }); + } + else { + await Item.create(this.toObject(), {parent: actor}); + } + return ui.notifications.info(`${actor.name} Paid ${amount} Poké for ${this.name} (New Total: ${actor.system.money})`); + } } export { PTUItemItem } \ No newline at end of file diff --git a/src/module/item/item/pokeball.js b/src/module/item/item/pokeball.js index 0a4616184..e68dfb83f 100644 --- a/src/module/item/item/pokeball.js +++ b/src/module/item/item/pokeball.js @@ -44,7 +44,7 @@ class PokeballItem extends PTUItemItem { const rollOptions = [...this.getRollOptions(selectors), ...attackRollOptions]; const modifier = new StatisticModifier(this.slug, modifiers); - const action = mergeObject(modifier, { + const action = foundry.utils.mergeObject(modifier, { label: this.name, img: this.img, domains: selectors, @@ -62,8 +62,8 @@ class PokeballItem extends PTUItemItem { params.options ??= []; const target = params.target ?? game.user.targets.first(); - if(!target?.actor) return ui.notifications.warn("PTU.Action.CaptureNoTarget", { localize: true }) - if(target.actor.type === "character") return ui.notifications.warn("PTU.Action.CaptureWrongTarget", { localize: true }) + if (!target?.actor) return ui.notifications.warn("PTU.Action.CaptureNoTarget", { localize: true }) + if (target.actor.type === "character") return ui.notifications.warn("PTU.Action.CaptureWrongTarget", { localize: true }) const context = await this.actor.getCheckContext({ item: this, @@ -105,7 +105,7 @@ class PokeballItem extends PTUItemItem { type: "capture-throw", actor: context.self.actor, token: context.self.token, - targets: [{...context.target, dc: params.dc ?? context.dc, options: context.options ?? []}], + targets: [{ ...context.target, dc: params.dc ?? context.dc, options: context.options ?? [] }], item: context.self.item, domains: selectors, options: context.options, @@ -146,8 +146,8 @@ class PokeballItem extends PTUItemItem { params.options ??= []; const target = params.target ?? game.user.targets.first(); - if(!target?.actor) return ui.notifications.warn("PTU.Action.CaptureNoTarget", { localize: true }) - if(target.actor.type === "character") return ui.notifications.warn("PTU.Action.CaptureWrongTarget", { localize: true }) + if (!target?.actor) return ui.notifications.warn("PTU.Action.CaptureNoTarget", { localize: true }) + if (target.actor.type === "character") return ui.notifications.warn("PTU.Action.CaptureWrongTarget", { localize: true }) const context = await this.actor.getCheckContext({ item: this, @@ -161,12 +161,11 @@ class PokeballItem extends PTUItemItem { const DCModifiers = []; // Capture DC mods { - const levelMod = (-(target.actor.system?.level?.current ?? 0) * 2); // Level mods DCModifiers.push(new PTUModifier({ slug: "level-modifier", label: "Level Modifier", - modifier: game.settings.get("ptu", "variant.trainerRevamp") ? levelMod * 2 : levelMod + modifier: (-(target.actor.system?.level?.current ?? 0) * 2) })); // HP mods @@ -193,19 +192,19 @@ class PokeballItem extends PTUItemItem { // If 1 remaining add +5 // If 0 remaining add +0 const evolutions = target.actor.species?.system?.evolutions ?? []; - if(evolutions.length > 1) { + if (evolutions.length > 1) { const currentEvolution = evolutions.find(e => e.slug == target.actor.species.slug); const remaining = new Set(evolutions.filter(e => e.level > currentEvolution.level).map(x => x.level)).size; const stage = (() => { - const stage =new Set(evolutions.map(x => x.level)).size - remaining; - switch(stage) { + const stage = new Set(evolutions.map(x => x.level)).size - remaining; + switch (stage) { case 1: return "1st Stage"; case 2: return "2nd Stage"; case 3: return "3rd Stage"; default: return `${stage}th Stage` } })(); - if(remaining > 0) { + if (remaining > 0) { DCModifiers.push(new PTUModifier({ slug: "evolution-stage-modifier", label: stage, @@ -216,17 +215,17 @@ class PokeballItem extends PTUItemItem { // Rarity mods // If shiny subtract 10 - if(target.actor.system?.shiny) { + if (target.actor.system?.shiny) { DCModifiers.push(new PTUModifier({ slug: "shiny-modifier", label: "Shiny Modifier", modifier: -10 })); } - + // If legendary subtract 30 // TODO: Add legendary property on pokemon actors - if(target.actor.legendary) { + if (target.actor.legendary) { DCModifiers.push(new PTUModifier({ slug: "legendary-modifier", label: "Legendary Modifier", @@ -238,8 +237,8 @@ class PokeballItem extends PTUItemItem { // For each persistent condition add +15 // For each other condition add +8 const conditions = target.actor.conditions; - for(const condition of conditions?.contents) { - if(condition.persistent) { + for (const condition of conditions?.contents) { + if (condition.persistent) { DCModifiers.push(new PTUModifier({ slug: condition.slug, label: `${condition.name} (persistent condition)`, @@ -258,7 +257,7 @@ class PokeballItem extends PTUItemItem { // Injury mods // For each injury add +5 const injuries = target.actor.system.health?.injuries; - if(injuries) { + if (injuries) { DCModifiers.push(new PTUModifier({ slug: "injury-modifier", label: "Injury Modifier", @@ -269,8 +268,8 @@ class PokeballItem extends PTUItemItem { // Stage mods // For each combat stage in a stat below 0 add +2 to the capture DC // For each combat stage in a stat above 0 add -2 to the capture DC - for(const stat of Object.values(target.actor.system.stats)) { - if(stat.stage?.total < 0) { + for (const stat of Object.values(target.actor.system.stats)) { + if (stat.stage?.total < 0) { DCModifiers.push(new PTUModifier({ slug: stat.slug, label: `${stat.label} Stage Modifier`, @@ -288,7 +287,7 @@ class PokeballItem extends PTUItemItem { } DCModifiers.push( - ...extractModifiers(target.actor.synthetics, ["capture-dc", ...selectors], { injectables: this , test: context.options}) + ...extractModifiers(target.actor.synthetics, ["capture-dc", ...selectors], { injectables: this, test: context.options }) ) const DCCheck = new CheckModifier( @@ -304,7 +303,7 @@ class PokeballItem extends PTUItemItem { rollModifiers.push(new PTUModifier({ slug: "level-modifier", label: "Level Modifier", - modifier: -this.actor.system.level.current + modifier: (["data-revamp", "short-track"].includes(game.settings.get("ptu", "variant.trainerAdvancement")) ? 2 : ["long-track"].includes(game.settings.get("ptu", "variant.trainerAdvancement")) ? 0.5 : 1) * -this.actor.system.level.current })); // Item mods @@ -323,7 +322,7 @@ class PokeballItem extends PTUItemItem { type: "capture-calculation", actor: context.self.actor, token: context.self.token, - targets: [{...context.target, dc: {slug: 'capture-dc', value: 100 + DCCheck.totalModifier}, options: context.options ?? []}], + targets: [{ ...context.target, dc: { slug: 'capture-dc', value: 100 + DCCheck.totalModifier }, options: context.options ?? [] }], item: context.self.item, domains: selectors, options: context.options, @@ -354,7 +353,7 @@ class PokeballItem extends PTUItemItem { return roll; //DC Calculation - + // Roll calc // Roll 1d100 // on nat 100 succeed @@ -377,7 +376,7 @@ class PokeballItem extends PTUItemItem { async requestGmRoll(event, args) { //TODO - if(true) return this.rollCapture(event, args); + if (true) return this.rollCapture(event, args); } async rollCapture(event, args) { @@ -388,7 +387,7 @@ class PokeballItem extends PTUItemItem { async applyCapture(args) { const trainers = game.users.contents.map(c => c.character).filter(c => c?.type === "character"); // If the trainer is not in the list, add them to the front - if(!trainers.includes(this.actor)) trainers.unshift(this.actor); + if (!trainers.includes(this.actor)) trainers.unshift(this.actor); // If the current trainer is in the list, add them to the front else trainers.unshift(trainers.splice(trainers.indexOf(this.actor), 1)[0]); @@ -407,17 +406,17 @@ class PokeballItem extends PTUItemItem { obj[name] = value; return obj; }, {}); - + const trainer = game.actors.get(formData.trainer); - if(!trainer) return ui.notifications.error("PTU.Dialog.CaptureSuccess.TrainerNotFound", { localize: true }); + if (!trainer) return ui.notifications.error("PTU.Dialog.CaptureSuccess.TrainerNotFound", { localize: true }); - const party = new PTUPartySheet({actor: trainer}); + const party = new PTUPartySheet({ actor: trainer }); const location = formData.location; - if(!["party", "box", "available"].includes(location)) return ui.notifications.error("PTU.Dialog.CaptureSuccess.LocationNotFound", { localize: true }); + if (!["party", "box", "available"].includes(location)) return ui.notifications.error("PTU.Dialog.CaptureSuccess.LocationNotFound", { localize: true }); const pokemon = await fromUuid(args.targets[0].actor); - if(!pokemon) return ui.notifications.error("PTU.Dialog.CaptureSuccess.PokemonNotFound", { localize: true }); + if (!pokemon) return ui.notifications.error("PTU.Dialog.CaptureSuccess.PokemonNotFound", { localize: true }); const user = game.users.find(u => u.character?.id === trainer.id); @@ -429,7 +428,7 @@ class PokeballItem extends PTUItemItem { }, "system.pokeball": this.name } - if(location !== "available") { + if (location !== "available") { pokemonUpdateData["flags.ptu.party"] = { trainer: trainer.id, boxed: location === "box", @@ -450,7 +449,7 @@ class PokeballItem extends PTUItemItem { await Actor.updateDocuments([pokemonUpdateData, trainerUpdateData]); await ChatMessage.create({ - content: `${await TextEditor.enrichHTML(game.i18n.format("PTU.Dialog.CaptureSuccess.ChatMessage", { pokemon: pokemon.link, trainer: trainer.link, location: (party.folders?.[location === "available" ? "root" : location]?.link ?? location) }), {async: true})}`, + content: `${await TextEditor.enrichHTML(game.i18n.format("PTU.Dialog.CaptureSuccess.ChatMessage", { pokemon: pokemon.link, trainer: trainer.link, location: (party.folders?.[location === "available" ? "root" : location]?.link ?? location) }), { async: true })}`, speaker: ChatMessage.getSpeaker({ actor: trainer }), }) } diff --git a/src/module/item/move/document.js b/src/module/item/move/document.js index 2b80bd6df..1396336b5 100644 --- a/src/module/item/move/document.js +++ b/src/module/item/move/document.js @@ -12,6 +12,10 @@ class PTUMove extends PTUItem { /** @override */ get rollOptions() { const options = super.rollOptions; + if(this.isDamaging && this.damageBase.isStab) { + options.all['move:is-stab'] = true; + options.item['move:is-stab'] = true; + } if (this.isDamaging && this.damageBase.isStab && !!options.all[`move:damage-base:${this.damageBase.preStab}`]) { delete this.flags.ptu.rollOptions.all[`move:damage-base:${this.damageBase.preStab}`]; delete this.flags.ptu.rollOptions.item[`move:damage-base:${this.damageBase.preStab}`]; @@ -22,6 +26,10 @@ class PTUMove extends PTUItem { options.all[`move:damage-base:${this.damageBase.postStab}`] = true; options.item[`move:damage-base:${this.damageBase.postStab}`] = true; } + for(const keyword of this.system.keywords) { + options.all[`move:${sluggify(keyword)}`] = true; + options.item[`move:${sluggify(keyword)}`] = true; + } return options; } @@ -37,7 +45,7 @@ class PTUMove extends PTUItem { } get isFiveStrike() { - return !!this.rollOptions.item["move:range:five-strike"]; + return (!!this.rollOptions.item["move:range:five-strike"]) || (!!this.rollOptions.item["move:five-strike"]); } get damageBase() { @@ -76,7 +84,7 @@ class PTUMove extends PTUItem { if (!isNaN(Number(this.system.ac))) rollOptions.all[`move:ac:${this.system.ac}`] = true; rollOptions.item = rollOptions.all; - this.flags.ptu = mergeObject(this.flags.ptu, {rollOptions}); + this.flags.ptu = foundry.utils.mergeObject(this.flags.ptu, {rollOptions}); this.flags.ptu.rollOptions.attack = Object.keys(this.flags.ptu.rollOptions.all).reduce((obj, key) => { obj[key.replace("move:", "attack:").replace("item:", "attack:")] = true; return obj; diff --git a/src/module/item/move/sheet.js b/src/module/item/move/sheet.js index edcf6c422..8f97e5dee 100644 --- a/src/module/item/move/sheet.js +++ b/src/module/item/move/sheet.js @@ -2,7 +2,7 @@ import { PTUItemSheet } from "../index.js"; class PTUMoveSheet extends PTUItemSheet { static get defaultOptions() { - return mergeObject(super.defaultOptions, { + return foundry.utils.mergeObject(super.defaultOptions, { template: "systems/ptu/static/templates/item/move-sheet.hbs" }); } diff --git a/src/module/item/sheet/rule-elements/ae-like-form.js b/src/module/item/sheet/rule-elements/ae-like-form.js index ff52ef423..528d73c7c 100644 --- a/src/module/item/sheet/rule-elements/ae-like-form.js +++ b/src/module/item/sheet/rule-elements/ae-like-form.js @@ -1,5 +1,4 @@ import { isObject } from '../../../../util/misc.js'; -import { tagify } from '../../../../util/tags.js'; import { isBracketedValue } from '../../../rules/rule-element/base.js'; import { RuleElementForm } from './base.js' diff --git a/src/module/item/sheet/rule-elements/apply-effect-form.js b/src/module/item/sheet/rule-elements/apply-effect-form.js new file mode 100644 index 000000000..21fa09ea8 --- /dev/null +++ b/src/module/item/sheet/rule-elements/apply-effect-form.js @@ -0,0 +1,80 @@ +import { isObject } from '../../../../util/misc.js'; +import { isBracketedValue } from '../../../rules/rule-element/base.js'; +import { RuleElementForm } from './base.js' + +class ApplyEffectForm extends RuleElementForm { + /** @override */ + get template() { + return "systems/ptu/static/templates/item/rules/apply-effect.hbs"; + } + + /** @override */ + async getData() { + const data = await super.getData(); + const valueMode = this.rule.even ? "even" : "range"; + + const uuid = this.rule.uuid ? String(this.rule.uuid) : null; + const granted = uuid ? await fromUuid(uuid) : null; + + if(this.rule.predicate === undefined) this.updateItem({predicate: []}) + + return { + ...data, + granted, + predicationIsMultiple: Array.isArray(this.rule.predicate) && this.rule.predicate.every(p => typeof p === "string"), + selectorIsArray: Array.isArray(this.rule.selectors), + value: { + mode: valueMode, + data: this.rule.value, + }, + }; + } + + /** @override */ + activateListeners(html) { + // Add events for toggle buttons + html.querySelector("[data-action=toggle-selector]")?.addEventListener("click", () => { + const selector = this.rule.selectors; + const newValue = Array.isArray(selector) ? selector.at(0) ?? "" : [selector ?? ""].filter((s) => !!s); + this.updateItem({ selectors: newValue }); + }); + + // Add events for toggle buttons + html.querySelector("[data-action=toggle-predicate]")?.addEventListener("click", () => { + const predicate = this.rule.predicate; + const newValue = Array.isArray(predicate) ? {"and": predicate.length ? predicate : []} : predicate?.["and"]?.length ? predicate["and"] : []; + this.updateItem({ predicate: newValue }); + }); + + // Add events for toggle buttons + html.querySelector("[data-action=toggle-range]")?.addEventListener("click", () => { + if (this.rule.even) { + this.updateItem({ range: 0, even: false }); + } else { + this.updateItem({ range: "even", even: true }); + } + }); + } + + /** @override */ + _updateObject(formData) { + + formData.value = this.coerceNumber(formData.value ?? ""); + + if(Array.isArray(formData.selectors)) { + formData.selectors = formData.selectors.map(s => s.value).filter(s => !!s) + } + if(Array.isArray(formData.predicate) && formData.predicate.every(p => !!p.value)) { + formData.predicate = formData.predicate.map(s => s.value).filter(s => !!s) + } + + // Remove empty string, null, or falsy values for certain optional parameters + for (const optional of ["label"]) { + if (!formData[optional]) { + delete formData[optional]; + } + } + } +} + +export { ApplyEffectForm }; \ No newline at end of file diff --git a/src/module/item/sheet/rule-elements/base.js b/src/module/item/sheet/rule-elements/base.js index 81d948fdc..0ca0edec0 100644 --- a/src/module/item/sheet/rule-elements/base.js +++ b/src/module/item/sheet/rule-elements/base.js @@ -35,7 +35,7 @@ class RuleElementForm { updateItem(updates) { const rules = this.item.toObject().system.rules; - rules[this.index] = mergeObject(this.rule, updates, { performDeletions: true }); + rules[this.index] = foundry.utils.mergeObject(this.rule, updates, { performDeletions: true }); this.item.update({ [`system.rules`]: rules }); } diff --git a/src/module/item/sheet/rule-elements/effectiveness-form.js b/src/module/item/sheet/rule-elements/effectiveness-form.js index 4899fced0..d789de747 100644 --- a/src/module/item/sheet/rule-elements/effectiveness-form.js +++ b/src/module/item/sheet/rule-elements/effectiveness-form.js @@ -1,4 +1,3 @@ -import { tagify } from '../../../../util/tags.js'; import { RuleElementForm } from './base.js' class EffectivenessForm extends RuleElementForm { diff --git a/src/module/item/sheet/rule-elements/ephemeral-effect-form.js b/src/module/item/sheet/rule-elements/ephemeral-effect-form.js index 22cacc153..0ff8acd9d 100644 --- a/src/module/item/sheet/rule-elements/ephemeral-effect-form.js +++ b/src/module/item/sheet/rule-elements/ephemeral-effect-form.js @@ -1,4 +1,3 @@ -import { tagify } from '../../../../util/tags.js'; import { RuleElementForm } from './index.js'; class EphemeralEffectForm extends RuleElementForm { @@ -18,7 +17,7 @@ class EphemeralEffectForm extends RuleElementForm { return { ...data, granted, - allowDuplicate: !!this.rule.allowDuplicate ?? true, + allowduplicate: !!this.rule.allowduplicate ?? true, selectorIsArray: Array.isArray(this.rule.selectors), predicationIsMultiple: Array.isArray(this.rule.predicate) && this.rule.predicate.every(p => typeof p === "string") }; diff --git a/src/module/item/sheet/rule-elements/flat-modifier-form.js b/src/module/item/sheet/rule-elements/flat-modifier-form.js index f58b0ee05..35a726735 100644 --- a/src/module/item/sheet/rule-elements/flat-modifier-form.js +++ b/src/module/item/sheet/rule-elements/flat-modifier-form.js @@ -1,5 +1,4 @@ import { isObject } from '../../../../util/misc.js'; -import { tagify } from '../../../../util/tags.js'; import { isBracketedValue } from '../../../rules/rule-element/base.js'; import { RuleElementForm } from './base.js' diff --git a/src/module/item/sheet/rule-elements/grant-item-form.js b/src/module/item/sheet/rule-elements/grant-item-form.js index 2d5e349a5..f200ba288 100644 --- a/src/module/item/sheet/rule-elements/grant-item-form.js +++ b/src/module/item/sheet/rule-elements/grant-item-form.js @@ -1,4 +1,3 @@ -import { tagify } from '../../../../util/tags.js'; import { RuleElementForm } from './index.js'; class GrantItemForm extends RuleElementForm { @@ -18,7 +17,7 @@ class GrantItemForm extends RuleElementForm { return { ...data, granted, - allowDuplicate: !!this.rule.allowDuplicate ?? true, + allowduplicate: !!this.rule.allowduplicate ?? true, predicationIsMultiple: Array.isArray(this.rule.predicate) && this.rule.predicate.every(p => typeof p === "string") }; } @@ -50,7 +49,7 @@ class GrantItemForm extends RuleElementForm { if (!formData.reevaluateOnUpdate) delete formData.reevaluateOnUpdate; // Optional but defaults to true - if (formData.allowDuplicate) delete formData.allowDuplicate; + if (formData.allowduplicate) delete formData.allowduplicate; if(Array.isArray(formData.predicate) && formData.predicate.every(p => !!p.value)) { formData.predicate = formData.predicate.map(s => s.value).filter(s => !!s) diff --git a/src/module/item/sheet/rule-elements/index.js b/src/module/item/sheet/rule-elements/index.js index f459196d6..966806121 100644 --- a/src/module/item/sheet/rule-elements/index.js +++ b/src/module/item/sheet/rule-elements/index.js @@ -1,4 +1,5 @@ import { AELikeForm } from "./ae-like-form.js" +import { ApplyEffectForm } from "./apply-effect-form.js" import { RuleElementForm } from "./base.js" import { EffectivenessForm } from "./effectiveness-form.js" import { EphemeralEffectForm } from "./ephemeral-effect-form.js" @@ -12,7 +13,8 @@ const RULE_ELEMENT_FORMS = { RollOption: RollOptionForm, ActiveEffectLike: AELikeForm, Effectiveness: EffectivenessForm, - EphemeralEffect: EphemeralEffectForm + EphemeralEffect: EphemeralEffectForm, + ApplyEffect: ApplyEffectForm } export { RULE_ELEMENT_FORMS, RuleElementForm} \ No newline at end of file diff --git a/src/module/item/sheet/sheet.js b/src/module/item/sheet/sheet.js index 7dc118dd6..a0549cb02 100644 --- a/src/module/item/sheet/sheet.js +++ b/src/module/item/sheet/sheet.js @@ -1,12 +1,11 @@ import { sluggify, sortStringRecord } from "../../../util/misc.js"; -import { tagify } from "../../../util/tags.js"; import { RuleElements } from "../../rules/index.js"; import { RULE_ELEMENT_FORMS, RuleElementForm } from "./rule-elements/index.js"; class PTUItemSheet extends ItemSheet { /** @override */ static get defaultOptions() { - return mergeObject(super.defaultOptions, { + return foundry.utils.mergeObject(super.defaultOptions, { classes: ["ptu", "sheet", "item"], width: 650, height: 510, @@ -15,6 +14,11 @@ class PTUItemSheet extends ItemSheet { }); } + /** @override */ + _canDragDrop(selector) { + return this.isEditable; + } + /** @override */ get template() { return `systems/ptu/static/templates/item/${this.object.type}-sheet.hbs`; @@ -28,8 +32,14 @@ class PTUItemSheet extends ItemSheet { this.object._updateIcon({update: true}); - data.referenceEffect = this.item.referenceEffect ? await TextEditor.enrichHTML(`@UUID[${duplicate(this.item.referenceEffect)}]`, {async: true}) : null; - data.itemEffect = this.item.system.effect ? await TextEditor.enrichHTML(duplicate(this.item.system.effect), {async: true}) : this.item.system.effect; + data.referenceEffect = this.item.referenceEffect ? await TextEditor.enrichHTML(`@UUID[${foundry.utils.duplicate(this.item.referenceEffect)}]`, {async: true}) : null; + data.itemEffect = this.item.system.effect ? await TextEditor.enrichHTML(foundry.utils.duplicate(this.item.system.effect), {async: true}) : this.item.system.effect; + data.itemCost = await (async () => { + const cost = parseInt(this.item.system.cost); + if(!cost) return this.item.system.cost || "-"; + + return TextEditor.enrichHTML(`@Poke[${this.item.uuid} noname]`, {async: true}) + })(); const rules = this.item.toObject().system.rules ?? []; this.ruleElementForms = {}; @@ -54,7 +64,7 @@ class PTUItemSheet extends ItemSheet { selected: this.selectedRuleElementType, types: sortStringRecord( Object.keys(RuleElements.all).reduce( - (result, key) => mergeObject(result, {[key]: `RULES.Types.${key}`}), + (result, key) => foundry.utils.mergeObject(result, {[key]: `RULES.Types.${key}`}), {} ) ) @@ -70,7 +80,7 @@ class PTUItemSheet extends ItemSheet { if(this.item.flags.ptu?.showInTokenPanel === undefined) { if(this.item.type === "item" && this.item.roll) data.item.flags.ptu.showInTokenPanel = true; - if (["move", "ability", "feat"].includes(this.item.type)) data.item.flags.ptu.showInTokenPanel = true; + if (["move", "ability", "feat", "effect"].includes(this.item.type)) data.item.flags.ptu.showInTokenPanel = true; } return data; @@ -79,6 +89,13 @@ class PTUItemSheet extends ItemSheet { async _onDrop(event) { const data = JSON.parse(event.dataTransfer.getData('text/plain')); + if(data.type === "pokedollar" && this.item.type === "item") { + const amount = parseInt(data.data.amount); + if(!amount) return; + + this.object.update({"system.cost": amount}); + } + if(data.type === "Item" && data.uuid) { const item = await fromUuid(data.uuid); if(!["effect", "condition".includes(item.type)]) return; @@ -105,10 +122,6 @@ class PTUItemSheet extends ItemSheet { activateListeners(html) { super.activateListeners(html); - for(const taggifyElement of html.find(".ptu-tagify")) { - tagify(taggifyElement); - } - html.find('select[data-action=select-rule-element]').on('change', (event) => { this.selectedRuleElementType = event.target.value; }); @@ -189,11 +202,14 @@ class PTUItemSheet extends ItemSheet { /** @override */ async _updateObject(event, formData) { - const expanded = expandObject(formData); + const expanded = foundry.utils.expandObject(formData); if(Array.isArray(expanded.system.prerequisites)) { expanded.system.prerequisites = expanded.system.prerequisites.map(s => s.value).filter(s => !!s) } + if(Array.isArray(expanded.system.keywords)) { + expanded.system.keywords = expanded.system.keywords.map(s => s.value).filter(s => !!s) + } if(expanded.system?.rules) { const rules = this.item.toObject().system.rules ?? []; @@ -221,7 +237,7 @@ class PTUItemSheet extends ItemSheet { if(!value) continue; - rules[idx] = mergeObject(rules[idx] ?? {}, value); + rules[idx] = foundry.utils.mergeObject(rules[idx] ?? {}, value); // Call specific subhandlers this.ruleElementForms[idx]?._updateObject(rules[idx]) @@ -246,7 +262,7 @@ class PTUItemSheet extends ItemSheet { expanded.system.rules = rules; } - return super._updateObject(event, flattenObject(expanded)); + return super._updateObject(event, foundry.utils.flattenObject(expanded)); } } diff --git a/src/module/item/species/sheet.js b/src/module/item/species/sheet.js index d83983095..8a701c612 100644 --- a/src/module/item/species/sheet.js +++ b/src/module/item/species/sheet.js @@ -151,14 +151,15 @@ class PTUSpeciesSheet extends PTUItemSheet { /** @override */ _onDragStart(event) { const li = event.currentTarget; - const { itemUuid, itemSlug, itemType, itemSubtype, itemIndex } = li.dataset; + const { itemUuid, itemSlug, itemType, itemSubtype, itemIndex, itemLevel } = li.dataset; event.dataTransfer.setData('text/plain', JSON.stringify({ uuid: itemUuid, slug: itemSlug, type: itemType, subtype: itemSubtype, - index: itemIndex + index: itemIndex, + level: itemLevel })); } @@ -222,7 +223,7 @@ class PTUSpeciesSheet extends PTUItemSheet { this.dragMove = null; return; } - moves.level.unshift({uuid: item.uuid, slug: item.slug, level: 1}); + moves.level.unshift({uuid: item.uuid, slug: item.slug, level: Number(data.level) || 1}); } return this.item.update({"system.moves": moves}); @@ -295,6 +296,22 @@ class PTUSpeciesSheet extends PTUItemSheet { const {itemType, itemIndex, zone, subZone} = event.currentTarget.dataset; let { itemSubtype } = event.currentTarget.dataset; + const localItem = this.item.system.moves[subtype][index]; + const realItem = await fromUuid(data.uuid); + if(localItem?.slug != realItem?.slug) { + if(!realItem || realItem.type != "move") return; + + const moves = this.item.system.moves; + if(itemSubtype == "level") { + moves[itemSubtype].unshift({slug: realItem.slug, uuid: realItem.uuid, level: Number(data.level) || 1}); + moves[itemSubtype] = moves[itemSubtype].sort((a, b) => a.level - b.level); + } + else { + moves[itemSubtype].push({slug: realItem.slug, uuid: realItem.uuid}); + } + return this.item.update({"system.moves": moves}); + } + if(itemType != "move") { if(zone != "move") return; @@ -331,7 +348,7 @@ class PTUSpeciesSheet extends PTUItemSheet { /** @override */ _updateObject(event, formData) { - const expanded = expandObject(formData); + const expanded = foundry.utils.expandObject(formData); const types = [...new Set(Object.values(expanded.system.type))].filter(type => type != "Untyped" && type != ""); if(types.length == 0) types.push("Untyped"); @@ -396,7 +413,7 @@ class PTUSpeciesSheet extends PTUItemSheet { expanded.system.evolutions = evolutions.sort((a, b) => a.level - b.level); } - return super._updateObject(event, flattenObject(expanded)); + return super._updateObject(event, foundry.utils.flattenObject(expanded)); } /** @override */ diff --git a/src/module/message/attack.js b/src/module/message/attack.js index db11c9bf4..0b355e764 100644 --- a/src/module/message/attack.js +++ b/src/module/message/attack.js @@ -78,6 +78,7 @@ class AttackMessagePTU extends ChatMessagePTU { const params = { event, options: this.context.options ?? [], + rollResult: this.context.rollResult ?? null, actor: this.actor, targets: this.targets, callback: () => { diff --git a/src/module/message/base.js b/src/module/message/base.js index 3a88c66f3..09df2b122 100644 --- a/src/module/message/base.js +++ b/src/module/message/base.js @@ -1,6 +1,6 @@ class ChatMessagePTU extends ChatMessage { constructor(data = {}, context = {}) { - data.flags = mergeObject(expandObject(data.flags ?? {}), { core: {}, ptu: {} }); + data.flags = foundry.utils.mergeObject(foundry.utils.expandObject(data.flags ?? {}), { core: {}, ptu: {} }); super(data, context); } @@ -128,7 +128,7 @@ class ChatMessagePTU extends ChatMessage { const options = actor.getRollOptions(["all", "attack-roll"]); - const rollArgs = { event, options }; + const rollArgs = { event, options, rollResult: this.context.rollResult ?? null, }; return attack.damage?.(rollArgs); } @@ -207,6 +207,7 @@ class ChatMessagePTU extends ChatMessage { activateListeners($html) { $html.find("button.use").on("click", async event => { event.preventDefault(); + event.stopImmediatePropagation(); const item = await fromUuid(this.flags.ptu.origin.item); if (!item) return; @@ -214,7 +215,7 @@ class ChatMessagePTU extends ChatMessage { }); $html.find("button.apply-capture").on("click", async event => { event.preventDefault(); - event.stopPropagation(); + event.stopImmediatePropagation(); const item = await fromUuid(this.flags.ptu.origin.uuid); if (!item) return; @@ -222,7 +223,7 @@ class ChatMessagePTU extends ChatMessage { }); $html.find("button.contested-check").on("click", async event => { event.preventDefault(); - event.stopPropagation(); + event.stopImmediatePropagation(); const total = this.rolls[0]?.total; const target = this.targets.find(t => t.actor.isOwner); diff --git a/src/module/message/damage.js b/src/module/message/damage.js index ac38c17a8..9c1893eb7 100644 --- a/src/module/message/damage.js +++ b/src/module/message/damage.js @@ -1,4 +1,5 @@ -import { extractEphemeralEffects } from "../rules/helpers.js"; +import { sluggify } from "../../util/misc.js"; +import { extractApplyEffects, extractEphemeralEffects } from "../rules/helpers.js"; import { DamageRoll } from "../system/damage/roll.js"; import { ChatMessagePTU } from "./base.js"; @@ -186,6 +187,23 @@ async function applyDamageFromMessage({ message, targets, mode = "full", addend const [effectiveness, multiplier] = getMAFromMode(mode); + const originAttackOptions = message.flags.ptu.attack ?? {}; + const originItem = (await fromUuid(originAttackOptions.actor))?.items.get(originAttackOptions.id) ?? null; + const itemDomains = []; + if (originItem) { + itemDomains.push( + `${originItem.id}-damage-received`, + `${originItem.slug}-damage-received`, + ) + if(originItem.type === "move") { + itemDomains.push( + `${originItem.system.category.toLocaleLowerCase(game.i18n.lang)}-damage-received`, + `${originItem.system.type.toLocaleLowerCase(game.i18n.lang)}-damage-received`, + `${sluggify(originItem.system.frequency)}-damage-received`, + ) + } + } + const messageRollOptions = message.flags.ptu.context?.options ?? []; const originRollOptions = messageRollOptions .filter(o => o.startsWith("self:")) @@ -204,13 +222,15 @@ async function applyDamageFromMessage({ message, targets, mode = "full", addend totalCritImmune: (multiplier * roll.critImmuneTotal) + addend, } + const domains = ["damage-received", ...itemDomains]; + const ephemeralEffects = [ ...await extractEphemeralEffects({ affects: "target", origin: message.actor, target: token.actor, item: message.item, - domains: ["damage-received"], + domains, options: messageRollOptions }), // Ephemeral Effects on the target that it wishes to apply to itself @@ -220,11 +240,25 @@ async function applyDamageFromMessage({ message, targets, mode = "full", addend origin: message.actor, target: token.actor, item: message.item, - domains: ["damage-received"], + domains, options: messageRollOptions }) ]; + const applyEffectsTarget = Object.values([ + ...await extractApplyEffects({ + affects: "target", + origin: message.actor, + target: token.actor, + item: message.item, + domains, + options: messageRollOptions, + roll: Number(message.flags.ptu.context.accuracyRollResult ?? 0) + }), + ].reduce((a, b) => { + if (!a[b.slug]) a[b.slug] = b; + return a; + }, {})); const contextClone = token.actor.getContextualClone(originRollOptions, ephemeralEffects); const applicationRollOptions = new Set([ @@ -241,6 +275,41 @@ async function applyDamageFromMessage({ message, targets, mode = "full", addend rollOptions: applicationRollOptions, skipIWR }); + + if (applyEffectsTarget.length > 0) { + const newItems = await contextClone.createEmbeddedDocuments("Item", applyEffectsTarget); + if (newItems.length > 0) + await ChatMessage.create({ + content: await renderTemplate("systems/ptu/static/templates/chat/damage/effects-applied.hbs", { target: contextClone, effects: newItems }), + speaker: ChatMessage.getSpeaker({ actor: contextClone }), + whisper: ChatMessage.getWhisperRecipients("GM") + }) + } + } + + const applyEffectsOrigin = Object.values([ + ...await extractApplyEffects({ + affects: "origin", + origin: message.actor, + target: message.actor, + item: message.item, + domains: ["damage-dealt", ...itemDomains.map(d => d.replace(/-received$/, "-dealt"))], + options: messageRollOptions, + roll: Number(message.flags.ptu.context.accuracyRollResult ?? 0) + }), + ].reduce((a, b) => { + if (!a[b.slug]) a[b.slug] = b; + return a; + }, {})); + + if (applyEffectsOrigin.length > 0) { + const newItems = await message.actor.createEmbeddedDocuments("Item", applyEffectsOrigin); + if (newItems.length > 0) + await ChatMessage.create({ + content: await renderTemplate("systems/ptu/static/templates/chat/damage/effects-applied.hbs", { target: message.actor, effects: newItems }), + speaker: ChatMessage.getSpeaker({ actor: message.actor }), + whisper: ChatMessage.getWhisperRecipients("GM") + }) } } diff --git a/src/module/migration/migrations/105-custom-species-migration.js b/src/module/migration/migrations/105-custom-species-migration.js index bdb50109c..da264d864 100644 --- a/src/module/migration/migrations/105-custom-species-migration.js +++ b/src/module/migration/migrations/105-custom-species-migration.js @@ -18,11 +18,14 @@ export class Migration105CustomSpeciesMigration extends MigrationBase { const species = []; for (const data of oldCS.data) { const specie = await CONFIG.PTU.Item.documentClasses.species.convertToPTUSpecies(data, { prepareOnly: true }); + specie._id = foundry.utils.randomID(); + const ownEvolution = specie.system.evolutions.at(1); + if(!!ownEvolution.uuid) ownEvolution.uuid = specie._id; specie.folder = folder.id; species.push(specie); } - const customSpecies = await Item.createDocuments(species); + const customSpecies = await Item.createDocuments(species, { keepId: true}); if (customSpecies.length > 0) { await game.settings.set("ptu", "customSpeciesData", { flags: { schemaVersion: this.version } }); } diff --git a/src/module/migration/migrations/112-agility-reference.js b/src/module/migration/migrations/112-agility-reference.js new file mode 100644 index 000000000..6ddd9fa7b --- /dev/null +++ b/src/module/migration/migrations/112-agility-reference.js @@ -0,0 +1,23 @@ +import { sluggify } from "../../../util/misc.js"; +import { MigrationBase } from "../base.js"; + +export class Migration112AgilityReference extends MigrationBase { + static version = 0.112; + + /** + * @type {MigrationBase['updateItem']} + */ + async updateItem(item, actor) { + if (item.type !== "species") return; + + for (const type of Object.keys(item.system.moves)) { + for (const move of item.system.moves[type]) { + if (move.slug === "agility") { move.uuid = "Compendium.ptu.moves.Item.aS33KlxhRCP0s5Zd"; continue; } + if (item.slug === "palafin" && move.slug === "wave-crash") { move.uuid = "Compendium.ptu.moves.Item.XNmCCgclMJf0MP6C"; continue; } + if (item.slug === "dipplin" && move.slug === "withdraw") { move.uuid = "Compendium.ptu.moves.Item.PLNrSnH8aJCbQSAq"; continue; } + if (item.slug === "dipplin" && move.slug === "astonish") { move.uuid = "Compendium.ptu.moves.Item.VtkRa05N67wR9RcG"; continue; } + if (item.slug === "dipplin" && move.slug === "syrupy-bomb") { move.uuid = "Compendium.ptu.moves.Item.oKd3bholSLtezIEv"; continue; } + } + } + } +} \ No newline at end of file diff --git a/src/module/migration/migrations/113-keywords.js b/src/module/migration/migrations/113-keywords.js new file mode 100644 index 000000000..e0b535297 --- /dev/null +++ b/src/module/migration/migrations/113-keywords.js @@ -0,0 +1,33 @@ +import { sluggify } from "../../../util/misc.js"; +import { MigrationBase } from "../base.js"; + +export class Migration113Keywords extends MigrationBase { + static version = 0.113; + + items = { + feat: undefined, + move: undefined, + item: undefined + } + + /** + * @type {MigrationBase['updateItem']} + */ + async updateItem(item, actor) { + if(!["feat", "move", "item"].includes(item.type)) return; + + const items = this.items[item.type] ??= await game.packs.get(`ptu.${item.type}s`).getDocuments(); + + const entry = items.find(entry => entry.slug === (item.slug || sluggify(item.name))); + if(!entry) return; + + item.system.keywords = entry.system.keywords; + if(item.type === 'feat') { + delete item.system.tags; + item.system["-=tags"] = null; + } + if(item.type === "move") { + item.system.range = entry.system.range; + } + } +} \ No newline at end of file diff --git a/src/module/migration/migrations/114-hardened.js b/src/module/migration/migrations/114-hardened.js new file mode 100644 index 000000000..38eeb1829 --- /dev/null +++ b/src/module/migration/migrations/114-hardened.js @@ -0,0 +1,19 @@ +import { sluggify } from "../../../util/misc.js"; +import { MigrationBase } from "../base.js"; + +export class Migration114Hardened extends MigrationBase { + static version = 0.114; + + /** + * @type {MigrationBase['updateItem']} + */ + async updateItem(item, actor) { + const rules = item.system.rules; + if(!rules) return; + + const rule = rules.find(rule => rule.path === "system.modifiers.resistanceSteps.mod"); + if(!rule) return; + + if(rule.value === 0.5) rule.value = 1; + } +} \ No newline at end of file diff --git a/src/module/migration/migrations/115-rules-automation.js b/src/module/migration/migrations/115-rules-automation.js new file mode 100644 index 000000000..d4703aaca --- /dev/null +++ b/src/module/migration/migrations/115-rules-automation.js @@ -0,0 +1,55 @@ +import { sluggify } from "../../../util/misc.js"; +import { MigrationBase } from "../base.js"; + +export class Migration115RulesAutomation extends MigrationBase { + static version = 0.115; + requiresFlush = true; + + /** + * @type {MigrationBase['updateItem']} + */ + async updateItem(item, actor) { + if(item.type !== "move") return; + const moves = this.moves ??= await game.packs.get("ptu.moves").getDocuments(); + const slug = item.system.slug || sluggify(item.name); + + const move = (() => { + // Try to look up by source ID + const sourceId = item.flags?.core?.sourceId; + if(sourceId) { + const idPart = sourceId.split(".").at(-1); + const move = moves.find(move => move.id === idPart); + if(move) return move; + } + + // Try to look up by slug + return moves.find(move => move.slug === slug); + })(); + if(!move) return; + + if(move.system.referenceEffect && !item.system.referenceEffect) { + item.system.referenceEffect = move.system.referenceEffect; + } + + if(move.system.rules?.length == 0) return; + + const oldRules = item.system.rules ?? []; + const newRules = []; + for(const rule of move.system.rules) { + const anyMatch = oldRules.some(oldRule => foundry.utils.objectsEqual(oldRule, rule)); + if(anyMatch) continue; + + newRules.push(rule); + } + + item.system.rules = oldRules.concat(newRules); + + if(!this.once) { + this.once = true; + await ChatMessage.create({ + speaker: { alias: "System" }, + content: "

Move Rules Automation Updated!

All moves have been updated to include newly created rules automation.

If you had manually created rules automation before, they have been added in addition, therefore you may now find duplicate automation on your moves.

Please double check this in case you added custom automation to core moves!

" + }) + } + } +} \ No newline at end of file diff --git a/src/module/migration/migrations/index.js b/src/module/migration/migrations/index.js index 7a1ab780c..cb5c838b7 100644 --- a/src/module/migration/migrations/index.js +++ b/src/module/migration/migrations/index.js @@ -8,4 +8,8 @@ export { Migration107ActiveEffectsToEffectItem} from './107-active-effects-to-ef export { Migration108SpritesFix} from './108-sprites-fix.js'; export { Migration109PokeballFix} from './109-pokeball-fix.js'; export { Migration110ActorLink} from './110-actor-link.js'; -export { Migration111PrereqItems} from './111-prereq-items.js'; \ No newline at end of file +export { Migration111PrereqItems} from './111-prereq-items.js'; +export { Migration112AgilityReference} from './112-agility-reference.js'; +export { Migration113Keywords} from './113-keywords.js'; +export { Migration114Hardened} from './114-hardened.js'; +export { Migration115RulesAutomation} from './115-rules-automation.js'; \ No newline at end of file diff --git a/src/module/migration/runner/base.js b/src/module/migration/runner/base.js index a1fd8b99d..767765d82 100644 --- a/src/module/migration/runner/base.js +++ b/src/module/migration/runner/base.js @@ -11,7 +11,7 @@ class MigrationRunnerBase { /** @type {MigrationBase[]} */ migrations = [] - static LATEST_SCHEMA_VERSION = 0.111; + static LATEST_SCHEMA_VERSION = 0.115; static MINIMUM_SAFE_VERSION = 0.100; static RECOMMENDED_SAFE_VERSION = 0.101; @@ -73,7 +73,7 @@ class MigrationRunnerBase { * @param {MigrationBase[]} migrations */ async getUpdatedActor(actorSource, migrations) { - const currentActor = deepClone(actorSource); + const currentActor = foundry.utils.deepClone(actorSource); for (const migration of migrations) { for (const currentItem of currentActor.items) { @@ -106,7 +106,7 @@ class MigrationRunnerBase { * @param {MigrationBase[]} migrations */ async getUpdatedItem(itemSource, migrations) { - const current = deepClone(itemSource); + const current = foundry.utils.deepClone(itemSource); for (const migration of migrations) { await migration.preUpdateItem?.(current); @@ -127,7 +127,7 @@ class MigrationRunnerBase { * @param {MigrationBase[]} migrations */ async getUpdatedTable(tableSource, migrations) { - const current = deepClone(tableSource); + const current = foundry.utils.deepClone(tableSource); for (const migration of migrations) { try { @@ -145,7 +145,7 @@ class MigrationRunnerBase { * @param {MigrationBase[]} migrations */ async getUpdatedMacro(macroSource, migrations) { - const current = deepClone(macroSource); + const current = foundry.utils.deepClone(macroSource); for (const migration of migrations) { try { @@ -163,7 +163,7 @@ class MigrationRunnerBase { * @param {MigrationBase[]} migrations */ async getUpdatedJournalEntry(journalSource, migrations) { - const clone = deepClone(journalSource); + const clone = foundry.utils.deepClone(journalSource); for (const migration of migrations) { try { @@ -194,7 +194,7 @@ class MigrationRunnerBase { * @param {MigrationBase[]} migrations */ async getUpdatedUser(userData, migrations) { - const current = deepClone(userData); + const current = foundry.utils.deepClone(userData); for (const migration of migrations) { try { await migration.updateUser?.(current); diff --git a/src/module/migration/runner/index.js b/src/module/migration/runner/index.js index 74055a392..548cf897e 100644 --- a/src/module/migration/runner/index.js +++ b/src/module/migration/runner/index.js @@ -184,7 +184,7 @@ class MigrationRunner extends MigrationRunnerBase { try { const updated = await this.getUpdatedJournalEntry(journalEntry.toObject(), migrations); - const changes = diffObject(journalEntry.toObject(), updated); + const changes = foundry.utils.diffObject(journalEntry.toObject(), updated); if (Object.keys(changes).length > 0) { await journalEntry.update(changes, { noHook: true }); } @@ -204,7 +204,7 @@ class MigrationRunner extends MigrationRunnerBase { try { const updatedMacro = await this.getUpdatedMacro(macro.toObject(), migrations); - const changes = diffObject(macro.toObject(), updatedMacro); + const changes = foundry.utils.diffObject(macro.toObject(), updatedMacro); if (Object.keys(changes).length > 0) { await macro.update(changes, { noHook: true }); } @@ -224,7 +224,7 @@ class MigrationRunner extends MigrationRunnerBase { try { const updatedMacro = await this.getUpdatedTable(table.toObject(), migrations); - const changes = diffObject(table.toObject(), updatedMacro); + const changes = foundry.utils.diffObject(table.toObject(), updatedMacro); if (Object.keys(changes).length > 0) { table.update(changes, { noHook: true }); } @@ -244,7 +244,7 @@ class MigrationRunner extends MigrationRunnerBase { try { const updatedToken = await this.getUpdatedToken(token, migrations); - const changes = diffObject(token.toObject(), updatedToken); + const changes = foundry.utils.diffObject(token.toObject(), updatedToken); if (Object.keys(changes).length > 0) { try { @@ -272,7 +272,7 @@ class MigrationRunner extends MigrationRunnerBase { try { const baseUser = user.toObject(); const updatedUser = await this.getUpdatedUser(baseUser, migrations); - const changes = diffObject(user.toObject(), updatedUser); + const changes = foundry.utils.diffObject(user.toObject(), updatedUser); if (Object.keys(changes).length > 0) { await user.update(changes, { noHook: true }); } diff --git a/src/module/rules/helpers.js b/src/module/rules/helpers.js index cb1ace2c7..11df6e8fa 100644 --- a/src/module/rules/helpers.js +++ b/src/module/rules/helpers.js @@ -44,18 +44,33 @@ async function extractEphemeralEffects({ affects, origin, target, item, domains, ).flatMap(e => e ?? []) } +async function extractApplyEffects({ affects, origin, target, item, domains, options, roll }) { + if (!(origin && target)) return []; + + const [effectsFrom, effectsTo] = affects === "target" ? [origin, target] : [target, origin]; + const fullOptions = [...options, ...effectsTo.getSelfRollOptions(affects)]; + const resolvables = item?.type == "move" ? { move: item } : {}; + return ( + await Promise.all( + domains + .flatMap(s => effectsFrom.synthetics.applyEffects[s]?.[affects] ?? []) + .map(d => d({ test: fullOptions, resolvables, roll })) + ) + ).flatMap(e => e ?? []) +} + function extractRollSubstitutions(substitutions, domains, rollOptions) { return domains - .flatMap((d) => deepClone(substitutions?.[d] ?? [])) + .flatMap((d) => foundry.utils.deepClone(substitutions?.[d] ?? [])) .filter((s) => s.predicate?.test(rollOptions) ?? true); } -async function processPreUpdateActorHooks(changed,{ pack }){ +async function processPreUpdateActorHooks(changed, { pack }) { const actorId = String(changed._id); const actor = pack ? await game.packs.get(pack)?.getDocument(actorId) : game.actors.get(actorId); if (!(actor instanceof CONFIG.PTU.Actor.documentClass)) return; - if(actor.prototypeToken.actorLink !== true) { + if (actor.prototypeToken.actorLink !== true) { changed.prototypeToken ??= {}; changed.prototypeToken.actorLink = true; } @@ -69,8 +84,7 @@ async function processPreUpdateActorHooks(changed,{ pack }){ await Promise.all( rules.map( (r) => - actor.items.has(r.item.id) ? r.preUpdateActor() : new Promise(() => ({ create: [], delete: [] })) - ) + actor.items.has(r.item.id) ? r.preUpdateActor() : { create: [], delete: [] }) ) ).reduce( (combined, cd) => ({ @@ -85,7 +99,7 @@ async function processPreUpdateActorHooks(changed,{ pack }){ await actor.deleteEmbeddedDocuments("Item", createDeletes.delete, { render: false }); } -export { extractEphemeralEffects, extractDamageDice, extractNotes, extractModifierAdjustments, extractRollSubstitutions, extractModifiers, processPreUpdateActorHooks} +export { extractEphemeralEffects, extractApplyEffects, extractDamageDice, extractNotes, extractModifierAdjustments, extractRollSubstitutions, extractModifiers, processPreUpdateActorHooks } globalThis.extractEphemeralEffects = extractEphemeralEffects; globalThis.extractDamageDice = extractDamageDice; diff --git a/src/module/rules/index.js b/src/module/rules/index.js index 0dde39d71..f8f7998b2 100644 --- a/src/module/rules/index.js +++ b/src/module/rules/index.js @@ -13,6 +13,7 @@ import { TempHPRuleElement } from "./rule-element/temp-hp.js"; import { EffectivenessRuleElement } from "./rule-element/effectiveness.js"; import { EphemeralEffectRuleElement } from "./rule-element/ephemeral-effect.js"; import { ActionPointsRuleElement } from "./rule-element/ap.js"; +import { ApplyEffectRuleElement } from "./rule-element/apply-effect.js"; class RuleElements { static builtin = { @@ -29,7 +30,8 @@ class RuleElements { "TempHP": TempHPRuleElement, "Effectiveness": EffectivenessRuleElement, "EphemeralEffect": EphemeralEffectRuleElement, - "ActionPoint": ActionPointsRuleElement + "ActionPoint": ActionPointsRuleElement, + "ApplyEffect": ApplyEffectRuleElement, } static custom = {} diff --git a/src/module/rules/rule-element/ae-like.js b/src/module/rules/rule-element/ae-like.js index 4769b1ef8..cbb8501e7 100644 --- a/src/module/rules/rule-element/ae-like.js +++ b/src/module/rules/rule-element/ae-like.js @@ -17,8 +17,9 @@ class AELikeRuleElement extends RuleElementPTU { ...super.defineSchema(), mode: new foundry.data.fields.StringField({ type: String, required: true, choices: Object.keys(AELikeRuleElement.CHANGE_MODES), initial: undefined }), path: new foundry.data.fields.StringField({ type: String, required: true, nullable: false, blank: false, initial: undefined }), - phase: new foundry.data.fields.StringField({ type: String, required: false, nullable: false, choices: deepClone(AELikeRuleElement.PHASES), initial: "applyAEs" }), - value: new ResolvableValueField({ required: true, nullable: true, initial: undefined }) + phase: new foundry.data.fields.StringField({ type: String, required: false, nullable: false, choices: foundry.utils.deepClone(AELikeRuleElement.PHASES), initial: "applyAEs" }), + value: new ResolvableValueField({ required: true, nullable: true, initial: undefined }), + priority: new foundry.data.fields.NumberField({ required: false, nullable: true, initial: undefined }), } } @@ -40,7 +41,7 @@ class AELikeRuleElement extends RuleElementPTU { typeof this.path === "string" && this.path.length > 0 && [this.path, this.path.replace(/\.\w+$/, ""), this.path.replace(/\.?\w+\.\w+$/, "")].some( - (path) => typeof getProperty(actor, path) !== undefined + (path) => typeof foundry.utils.getProperty(actor, path) !== undefined ); if (!pathIsValid) return this._warn("path"); } @@ -101,7 +102,7 @@ class AELikeRuleElement extends RuleElementPTU { apply(rollOptions) { this.validateData(); - if (!this.test(rollOptions)) return; + if (!this.test(rollOptions ?? this.actor.getRollOptions())) return; const path = this.resolveInjectedProperties(this.path); @@ -109,7 +110,7 @@ class AELikeRuleElement extends RuleElementPTU { if (/\bundefined\b/.test(path)) return; const { actor } = this; - const current = getProperty(actor, path); + const current = foundry.utils.getProperty(actor, path); const change = this.resolveValue(this.value) const newValue = this.getNewValue(current, change); if (this.ignored) return; @@ -125,7 +126,7 @@ class AELikeRuleElement extends RuleElementPTU { return; } try { - setProperty(actor, path, newValue); + foundry.utils.setProperty(actor, path, newValue); this._logChange(change); } catch (error) { console.warn(error); @@ -212,7 +213,7 @@ class AELikeRuleElement extends RuleElementPTU { const { changes } = this.actor.system; const realPath = this.resolveInjectedProperties(this.path); const entries = (changes[realPath] ??= {}); - entries[randomID()] = { mode: this.mode, value, sourceId: this.item.uuid, source: this.item.name.includes(":") ? this.item.name.split(":")[1].trim() : this.item.name }; + entries[foundry.utils.randomID()] = { mode: this.mode, value, sourceId: this.item.isGlobal ? (this.item.flags?.core?.sourceId ?? this.item.uuid) : this.item.uuid, source: this.item.name.includes(":") ? this.item.name.split(":")[1].trim() : this.item.name}; } _warn(property) { diff --git a/src/module/rules/rule-element/ap.js b/src/module/rules/rule-element/ap.js index f509f24fa..235c30d6c 100644 --- a/src/module/rules/rule-element/ap.js +++ b/src/module/rules/rule-element/ap.js @@ -24,8 +24,8 @@ export class ActionPointsRuleElement extends RuleElementPTU { // drained and bound values are already included. Therefore, the maxAp before this rule can only be calced by adding // this rules values up to it again. const maxApBeforeRule = (Number(this.actor.system.ap.max) || 0) + this.drainedValue + this.boundValue; - const currentApAfterRule = Math.clamped(currentApBeforeRule - this.drainedValue - this.boundValue, 0, maxApBeforeRule - this.drainedValue - this.boundValue); - mergeObject(actorUpdates, { + const currentApAfterRule = Math.clamp(currentApBeforeRule - this.drainedValue - this.boundValue, 0, maxApBeforeRule - this.drainedValue - this.boundValue); + foundry.utils.mergeObject(actorUpdates, { "system.ap.value": currentApAfterRule }); } @@ -42,9 +42,9 @@ export class ActionPointsRuleElement extends RuleElementPTU { const maxApBeforeDeletion = Number(this.actor.system.ap.max) || 0; // Regain previously Bound AP - const currentApAfterDeletion = Math.clamped(currentApBeforeDeletion + this.boundValue, 0, maxApBeforeDeletion + this.drainedValue + this.boundValue); + const currentApAfterDeletion = Math.clamp(currentApBeforeDeletion + this.boundValue, 0, maxApBeforeDeletion + this.drainedValue + this.boundValue); - mergeObject(actorUpdates, { + foundry.utils.mergeObject(actorUpdates, { "system.ap.value": currentApAfterDeletion }); } diff --git a/src/module/rules/rule-element/apply-effect.js b/src/module/rules/rule-element/apply-effect.js new file mode 100644 index 000000000..b73333a54 --- /dev/null +++ b/src/module/rules/rule-element/apply-effect.js @@ -0,0 +1,105 @@ +import { sluggify } from "../../../util/misc.js"; +import { PTUModifier } from "../../actor/modifiers.js"; +import { ResolvableValueField } from "../../system/schema-data-fields.js"; +import { RuleElementPTU } from "./base.js"; + +class ApplyEffectRuleElement extends RuleElementPTU { + constructor(source, item, options) { + super(source, item, options); + } + + /** @override */ + static defineSchema() { + const { fields } = foundry.data; + + return { + ...super.defineSchema(), + uuid: new foundry.data.fields.StringField({ required: true, nullable: false, blank: false, initial: undefined }), + affects: new fields.StringField({ required: true, choices: ["target", "origin"], initial: "target" }), + selectors: new fields.ArrayField( + new fields.StringField({ required: true, blank: false, initial: undefined }), + ), + range: new ResolvableValueField({ required: false, nullable: false, initial: undefined }), + even: new fields.BooleanField({ required: false, nullable: false, initial: false }), + }; + } + + /** @override */ + beforePrepareData() { + if (this.ignored) return; + + const uuid = this.resolveInjectedProperties(this.uuid); + + const selectors = this.selectors.map(s => this.resolveInjectedProperties(s)).filter(s => !!s); + if (selectors.length === 0) { + return this.failValidation("must have at least one selector"); + } + + for (const selector of selectors) { + const construct = async (options = {}) => { + if (!this.test(options.test ?? this.actor.getRollOptions())) { + return null; + } + + const grantedItem = await (async () => { + try { + return (await fromUuid(uuid))?.clone(this.overwrites ?? {}) ?? null; + } + catch (error) { + console.error(error); + return null; + } + })(); + if (!(grantedItem instanceof CONFIG.PTU.Item.documentClass)) return null; + + if (this.even && Number(options.roll) % 2 !== 0) { + return null; + } + + const effectRange = (() => { + const modifiers = [ + new PTUModifier({ + slug: "effect-range", + label: "Effect Range", + modifier: this.actor?.system?.modifiers?.effectRange?.total ?? 0, + }) + ] + if (this.actor?.synthetics) { + modifiers.push( + ...extractModifiers(this.actor?.synthetics, [ + "effect-range", + this.item?.id ? `${this.item.id}-effect-range` : [], + this.item?.slug ? `${this.item.slug}-effect-range` : [], + this.item?.system?.category ? `${sluggify(this.item.system.category)}-effect-range` : [], + this.item?.system?.type ? `${sluggify(this.item.system.type)}-effect-range` : [], + this.item?.system?.frequency ? `${sluggify(this.item.system.frequency)}-effect-range` : [], + ].flat(), { test: options.test ?? this.actor.getRollOptions() }) + ) + } + + return Number(Object.values( + modifiers.reduce((acc, mod) => { + if (!mod.ignored && !acc[mod.slug]) acc[mod.slug] = mod.modifier; + return acc; + }, {}) + ).reduce((acc, mod) => acc + mod, 0)) || 0; + })(); + + if (this.range && (Number(options.roll) + effectRange) < Number(this.range)) { + return null; + } + + const itemObject = grantedItem.toObject(); + itemObject.system.effect ??= ""; + itemObject.system.effect += `
Applied by ${this.label ?? this.item.name} from ${this.actor.name}
`; + + return itemObject; + } + + const synthetics = ((this.actor.synthetics.applyEffects[selector] ??= { target: [], origin: [] })); + synthetics[this.affects].push(construct); + } + } +} + +export { ApplyEffectRuleElement } \ No newline at end of file diff --git a/src/module/rules/rule-element/base.js b/src/module/rules/rule-element/base.js index 67bc15e27..88e343afa 100644 --- a/src/module/rules/rule-element/base.js +++ b/src/module/rules/rule-element/base.js @@ -62,11 +62,15 @@ class RuleElementPTU extends foundry.abstract.DataModel { this.ignored = true; } else if(item instanceof CONFIG.PTU.Item.documentClass) { - this.ignored ??= false; + this.ignored ??= false } else { this.ignored = true; } + + if(this.item?.enabled !== undefined) { + this.ignored = !this.item.enabled; + } } /** @override */ @@ -196,6 +200,7 @@ class RuleElementPTU extends foundry.abstract.DataModel { test(options) { if(this.ignored) return false; + if(!this.item.enabled) return false; if(this.predicate.length === 0) return true; const optionSet = new Set([ @@ -242,7 +247,7 @@ class RuleElementPTU extends foundry.abstract.DataModel { const property = prop.replace(regex, replaceFunc.bind(this)); - const value = getProperty(data, property); + const value = foundry.utils.getProperty(data, property); if (value === undefined) { this.failValidation(`Failed to resolve injected property "${source}"`); } @@ -291,9 +296,9 @@ class RuleElementPTU extends foundry.abstract.DataModel { const {actor, item} = this; switch(source) { - case "actor": return Number(getProperty(actor, field.substring(seperator + 1))) || 0; - case "item": return Number(getProperty(item, field.substring(seperator + 1))) || 0; - case "rule": return Number(getProperty(this, field.substring(seperator + 1))) || 0; + case "actor": return Number(foundry.utils.getProperty(actor, field.substring(seperator + 1))) || 0; + case "item": return Number(foundry.utils.getProperty(item, field.substring(seperator + 1))) || 0; + case "rule": return Number(foundry.utils.getProperty(this, field.substring(seperator + 1))) || 0; default: return 0; } })(); @@ -331,7 +336,7 @@ class RuleElementPTU extends foundry.abstract.DataModel { }; return value instanceof Object && defaultValue instanceof Object - ? mergeObject(defaultValue, value, {inplace: false}) + ? foundry.utils.mergeObject(defaultValue, value, {inplace: false}) : typeof value === "string" && evaluate ? saferEval(Roll.replaceFormulaData(value, {actor: this.actor, item: this.item, ...injectables})) : value; diff --git a/src/module/rules/rule-element/choice-set/rule-element.js b/src/module/rules/rule-element/choice-set/rule-element.js index b771dccbb..48a0d5926 100644 --- a/src/module/rules/rule-element/choice-set/rule-element.js +++ b/src/module/rules/rule-element/choice-set/rule-element.js @@ -23,6 +23,8 @@ class ChoiceSetRuleElement extends RuleElementPTU { this.choices.predicate = new PTUPredicate(this.choices.predicate ?? []); } + + this.prompt = typeof data.prompt === "string" ? this.resolveInjectedProperties(data.prompt) : data.prompt; // Assign the selection to a flag on the parent item if (this.selection !== null) { const resolvedFlag = this.resolveInjectedProperties(this.flag); @@ -169,7 +171,7 @@ class ChoiceSetRuleElement extends RuleElementPTU { } #choicesFromPath(path) { - const choiceObject = getProperty(CONFIG.PTU, path) ?? getProperty(this.actor, path) ?? {}; + const choiceObject = foundry.utils.getProperty(CONFIG.PTU, path) ?? foundry.utils.getProperty(this.actor, path) ?? {}; if(Array.isArray(choiceObject) && choiceObject.every((c) => isObject(c) && typeof c.value === "string")) { return choiceObject; } diff --git a/src/module/rules/rule-element/ephemeral-effect.js b/src/module/rules/rule-element/ephemeral-effect.js index 2ddb78eba..1d7fc007a 100644 --- a/src/module/rules/rule-element/ephemeral-effect.js +++ b/src/module/rules/rule-element/ephemeral-effect.js @@ -41,7 +41,7 @@ class EphemeralEffectRuleElement extends RuleElementPTU { return null; } const effect = (await fromUuid(uuid)); - if (!(effect instanceof BaseEffectPTU && ["condition", "effect"].includes(effect.type))) { + if (!(effect instanceof CONFIG.PTU.Item.baseEffect && ["condition", "effect"].includes(effect.type))) { this.failValidation(`unable to find effect or condition item with uuid "${uuid}"`); return null; } diff --git a/src/module/rules/rule-element/flat-modifier.js b/src/module/rules/rule-element/flat-modifier.js index 783f87a0e..d503b5f86 100644 --- a/src/module/rules/rule-element/flat-modifier.js +++ b/src/module/rules/rule-element/flat-modifier.js @@ -15,32 +15,39 @@ class FlatModifierRuleElement extends RuleElementPTU { return { ...super.defineSchema(), selectors: new fields.ArrayField( - new fields.StringField({required: true, blank: false, initial: undefined}), + new fields.StringField({ required: true, blank: false, initial: undefined }), ), min: new fields.NumberField({ required: false, nullable: false, initial: undefined }), max: new fields.NumberField({ required: false, nullable: false, initial: undefined }), force: new fields.BooleanField(), - hideIfDisabled: new fields.BooleanField({required: false, initial: true}), - value: new ResolvableValueField({required: false, nullable: false, initial: undefined}) + hideIfDisabled: new fields.BooleanField({ required: false, initial: true }), + value: new ResolvableValueField({ required: false, nullable: false, initial: undefined }) }; } /** @override */ beforePrepareData() { - if(this.ignored) return; + if (this.ignored) return; const slug = this.slug ?? sluggify(this.reducedLabel); const selectors = this.selectors.map(s => this.resolveInjectedProperties(s)).filter(s => !!s); - if(selectors.length === 0) { + if (selectors.length === 0) { return this.failValidation("must have at least one selector"); } - for(const selector of selectors) { + for (const selector of selectors) { const construct = (options = {}) => { - if(this.ignored) return null; - const resolvedValue = Number(this.resolveValue(this.value, 0, options)) || 0; - const finalValue = Math.clamped(resolvedValue, this.min ?? resolvedValue, this.max ?? resolvedValue); + const finalValue = (() => { + if (selector.includes("damage-dice")) { + options.evaluate = false; + return this.resolveValue(this.value, "", options); + } + + const resolvedValue = Number(this.resolveValue(this.value, 0, options)) || 0; + const finalValue = Math.clamp(resolvedValue, this.min ?? resolvedValue, this.max ?? resolvedValue); + return finalValue; + })(); const modifier = new PTUModifier({ slug, @@ -52,9 +59,9 @@ class FlatModifierRuleElement extends RuleElementPTU { source: this.item.uuid, hideIfDisabled: this.hideIfDisabled }) - if(options.test) modifier.test(options.test); + if (options.test) modifier.test(options.test); return modifier; - } + } const modifiers = (this.actor.synthetics.statisticsModifiers[selector] ??= []); modifiers.push(construct); diff --git a/src/module/rules/rule-element/grant-item/rule-element.js b/src/module/rules/rule-element/grant-item/rule-element.js index 1e0a65b1b..0e77cd023 100644 --- a/src/module/rules/rule-element/grant-item/rule-element.js +++ b/src/module/rules/rule-element/grant-item/rule-element.js @@ -7,14 +7,12 @@ class GrantItemRuleElement extends RuleElementPTU { constructor(source, item, options = {}) { super(source, item, options); - if(this.reevaluateOnUpdate) { + if (this.reevaluateOnUpdate) { this.replaceSelf = false; - this.allowDuplicate = false; + this.allowduplicate = false; } this.onDeleteActions = this.#getOnDeleteActions(source); - - this.grantedId = this.item.flags.ptu?.itemGrants?.[this.flag ?? ""]?.id ?? null; } /** @override */ @@ -25,7 +23,7 @@ class GrantItemRuleElement extends RuleElementPTU { flag: new foundry.data.fields.StringField({ required: true, nullable: true, initial: null }), reevaluateOnUpdate: new foundry.data.fields.BooleanField({ required: false, nullable: false, initial: false }), replaceSelf: new foundry.data.fields.BooleanField({ required: false, nullable: false, initial: false }), - allowDuplicate: new foundry.data.fields.BooleanField({ required: false, nullable: false, initial: true }), + allowduplicate: new foundry.data.fields.BooleanField({ required: false, nullable: false, initial: true }), onDeleteActions: new foundry.data.fields.ObjectField({ required: false, nullable: false, initial: undefined }), overwrites: new foundry.data.fields.ObjectField({ required: false, nullable: false, initial: undefined }) }; @@ -33,11 +31,15 @@ class GrantItemRuleElement extends RuleElementPTU { static ON_DELETE_ACTIONS = ["cascade", "detach", "restrict"]; + get grantedId() { + return this.item.flags.ptu?.itemGrants?.[this.flag]?.id ?? this.item.flags.ptu?.itemGrants?.[this.flag?.replace(/\d{1,2}$/, '')]?.id; + } + /** @override */ async preCreate(args) { - const { itemSource, pendingItems, context, ruleSource} = args; - - if(this.reevaluateOnUpdate && this.predicate.length === 0) { + const { itemSource, pendingItems, context, ruleSource } = args; + + if (this.reevaluateOnUpdate && this.predicate.length === 0) { ruleSource.ignored = true; return this.failValidation("`reevaluateOnUpdate` may only be used with a predicate."); } @@ -52,9 +54,9 @@ class GrantItemRuleElement extends RuleElementPTU { return null; } })(); - if(!(grantedItem instanceof PTUItem)) return; + if (!(grantedItem instanceof PTUItem)) return; - ruleSource.flag = + ruleSource.flag = typeof ruleSource.flag === "string" && ruleSource.flag.length > 0 ? ruleSource.flag : (() => { @@ -62,62 +64,66 @@ class GrantItemRuleElement extends RuleElementPTU { const flagPattern = new RegExp(`^${defaultFlag}\\d*$`); const itemGrants = itemSource.flags?.ptu?.itemGrants ?? {}; const nthGrant = Object.keys(itemGrants).filter((g => flagPattern.test(g))).length; - return nthGrant > 0 ? `${defaultFlag}${nthGrant+1}` : defaultFlag; + return nthGrant > 0 ? `${defaultFlag}${nthGrant + 1}` : defaultFlag; })(); this.flag = String(ruleSource.flag); - if(!this.test()) return; + if (!this.test()) return; const migrations = MigrationList.constructFromVersion(grantedItem.schemaVersion); - if(migrations.length) { + if (migrations.length) { await MigrationRunner.ensureSchemaVersion(grantedItem, migrations); } const existingItem = this.actor.items.find((i) => i.sourceId === uuid || (grantedItem.type === "condition" && i.slug === grantedItem.slug)); - if(!this.allowDuplicate && existingItem) { - if(this.replaceSelf) { + if (!this.allowduplicate && existingItem) { + if (this.replaceSelf) { pendingItems.splice(pendingItems.indexOf(existingItem), 1); } //this.#setGrantFlags(itemSource, existingItem); - return ui.notifications.warn(`Item ${grantedItem.name} is already granted to ${this.actor.name}.`); + return args.reevaluation ? null : ui.notifications.warn(`Item ${grantedItem.name} is already granted to ${this.actor.name}.`); } - itemSource._id ??= randomID(); + if (!this.actor?.allowedItemTypes.includes(grantedItem.type)) { + ui.notifications.error(`PTU | ${source.type.capitalize()}s cannot be added to ${actor.name}`); + return []; + } + + itemSource._id ??= foundry.utils.randomID(); const grantedSource = grantedItem.toObject(); - grantedSource._id = randomID(); + grantedSource._id = foundry.utils.randomID(); - if(["feat", "edge"].includes(grantedSource.type)) { + if (["feat", "edge"].includes(grantedSource.type) && this.item?.slug !== 'grant-training-feature') { grantedSource.system.free = true; } // Guarantee future alreadyGranted checks pass in all cases by re-assigning sourceId - grantedSource.flags = mergeObject(grantedSource.flags, { core: { sourceId: uuid } }); + grantedSource.flags = foundry.utils.mergeObject(grantedSource.flags, { core: { sourceId: uuid } }); // Create a temporary owned item and run its actor-data preparation and early-stage rule-element callbacks - const tempGranted = new PTUItem(deepClone(grantedSource), { parent: this.actor }); + const tempGranted = new PTUItem(foundry.utils.deepClone(grantedSource), { parent: this.actor }); tempGranted.prepareActorData?.(); - for(const rule of tempGranted.prepareRuleElements()) { + for (const rule of tempGranted.prepareRuleElements()) { rule.onApplyActiveEffects?.(); } - if(this.ignored) return; + if (this.ignored) return; // If the granted item is replacing the granting item, swap it out and return early - if(this.replaceSelf) { + if (this.replaceSelf) { pendingItems.findSplice((i) => i === itemSource, grantedSource); await this.#runGrantedItemPreCreates(args, tempGranted, grantedSource, context); return; } - this.grantedId = grantedSource._id; context.keepId = true; this.#setGrantFlags(itemSource, grantedSource); // Run the granted item's preCreate callbacks unless this is a pre-actor-update reevaluation - if(!args.reevaluation) { + if (!args.reevaluation) { await this.#runGrantedItemPreCreates(args, tempGranted, grantedSource, context); } @@ -126,19 +132,25 @@ class GrantItemRuleElement extends RuleElementPTU { /** @override */ async preUpdateActor() { - const noAction = { create: [], delete: []}; - if(!this.reevaluateOnUpdate) return noAction; - - if(this.grantedId && this.actor.items.has(this.grantedId)) { - if(!this.test()) { - return { create: [], delete: [this.grantedId] }; + const noAction = { create: [], delete: [] }; + if (!this.reevaluateOnUpdate) return noAction; + + let noId = false; + if (this.grantedId) { + if (this.actor.items.has(this.grantedId)) { + if (!this.test()) { + return { create: [], delete: [this.grantedId] }; + } + return noAction; } - return noAction; + } + else { + noId = true; } const itemSource = this.item.toObject(); const ruleSource = itemSource.system.rules[this.sourceIndex ?? -1]; - if(!ruleSource) return noAction; + if (!ruleSource) return noAction; const pendingItems = []; const context = { @@ -148,9 +160,18 @@ class GrantItemRuleElement extends RuleElementPTU { await this.preCreate({ itemSource, pendingItems, context, ruleSource, reevaluation: true }); - if(pendingItems.length > 0) { + if (noId) { + if (this.grantedId && this.actor.items.has(this.grantedId)) { + if (!this.test()) { + return { create: [], delete: [this.grantedId] }; + } + return noAction; + } + } + + if (pendingItems.length > 0) { const updatedGrants = itemSource.flags.ptu.itemGrants ?? {}; - await this.item.update({"flags.ptu.itemGrants": updatedGrants}, { render: false }); + await this.item.update({ "flags.ptu.itemGrants": updatedGrants }, { render: false }); return { create: pendingItems, delete: [] }; } return noAction; @@ -158,22 +179,22 @@ class GrantItemRuleElement extends RuleElementPTU { #getOnDeleteActions(source) { const actions = source.onDeleteActions; - if(typeof actions === "object") { + if (typeof actions === "object") { const ACTIONS = GrantItemRuleElement.ON_DELETE_ACTIONS; - return ACTIONS.includes(actions.granter) || ACTIONS.includes(actions.grantee) - ? actions : null; + return ACTIONS.includes(actions.granter) || ACTIONS.includes(actions.grantee) + ? actions : null; } } #setGrantFlags(granter, grantee) { - const flags = mergeObject(granter.flags ?? {}, { ptu: { itemGrants: { } } }); - if(!this.flag) throw new Error("GrantItemRuleElement#flag must be set before calling #setGrantFlags"); + const flags = foundry.utils.mergeObject(granter.flags ?? {}, { ptu: { itemGrants: {} } }); + if (!this.flag) throw new Error("GrantItemRuleElement#flag must be set before calling #setGrantFlags"); flags.ptu.itemGrants[this.flag] = { id: grantee instanceof PTUItem ? grantee.id : grantee._id, // The on-delete action determines what will happen to the granter item when the granted item is deleted: // Default to "detach" (do nothing). onDelete: this.onDeleteActions?.grantee ?? "detach" - } + } // The granted item records its granting item's ID at `flags.ptu.grantedBy` const grantedBy = { @@ -183,19 +204,19 @@ class GrantItemRuleElement extends RuleElementPTU { onDelete: this.onDeleteActions?.granter ?? "cascade" } - if(grantee instanceof PTUItem) { + if (grantee instanceof PTUItem) { // This is a previously granted item: update its grantedBy flag - grantee.update({"flags.ptu.grantedBy": grantedBy}, { render: false }); + grantee.update({ "flags.ptu.grantedBy": grantedBy }, { render: false }); } else { - grantee.flags = mergeObject(grantee.flags ?? {}, { ptu: { grantedBy } }); + grantee.flags = foundry.utils.mergeObject(grantee.flags ?? {}, { ptu: { grantedBy } }); } } async #runGrantedItemPreCreates(args, grantedItem, grantedSource, context) { - for(const rule of grantedItem.rules) { + for (const rule of grantedItem.rules) { const ruleSource = grantedSource.system.rules[grantedItem.rules.indexOf(rule)]; - await rule.preCreate?.({ ...args, itemSource: grantedSource, context, ruleSource}); + await rule.preCreate?.({ ...args, itemSource: grantedSource, context, ruleSource }); } } } diff --git a/src/module/rules/rule-element/roll-option.js b/src/module/rules/rule-element/roll-option.js index 2baa15894..b02fe9847 100644 --- a/src/module/rules/rule-element/roll-option.js +++ b/src/module/rules/rule-element/roll-option.js @@ -38,7 +38,7 @@ class RollOptionRuleElement extends RuleElementPTU { #resolveOption() { return this.resolveInjectedProperties(this.option) - .replace(/[^-:\w]/g, "") + ?.replace(/[^-:\w]/g, "") .replace(/:+/g, ":") .replace(/-+/g, "-") .trim(); diff --git a/src/module/rules/rule-element/temp-hp.js b/src/module/rules/rule-element/temp-hp.js index 8471292d1..5f87c4a35 100644 --- a/src/module/rules/rule-element/temp-hp.js +++ b/src/module/rules/rule-element/temp-hp.js @@ -13,7 +13,7 @@ export class TempHPRuleElement extends RuleElementPTU { onCreate(actorUpdates) { if(this.ignored) return; - const updatedActorData = mergeObject(this.actor._source, actorUpdates, {inplace: false}); + const updatedActorData = foundry.utils.mergeObject(this.actor._source, actorUpdates, {inplace: false}); const value = this.resolveValue(this.value) const rollOptions = Array.from(new Set(this.actor.getRollOptions())); @@ -21,9 +21,9 @@ export class TempHPRuleElement extends RuleElementPTU { if(!this.predicate.test(rollOptions)) return; if(typeof value !== "number") return this.failValidation("Temporary HP requires a non-zero value field"); - const currentTempHP = Number(getProperty(updatedActorData, "system.tempHp.value")) || 0; + const currentTempHP = Number(foundry.utils.getProperty(updatedActorData, "system.tempHp.value")) || 0; if(value > currentTempHP) { - mergeObject(actorUpdates, { + foundry.utils.mergeObject(actorUpdates, { "system.tempHp.value": value, "system.tempHp.max": value, "system.tempHp.source": this.item.uuid @@ -34,11 +34,11 @@ export class TempHPRuleElement extends RuleElementPTU { /** @override */ onDelete(actorUpdates) { - const updatedActorData = mergeObject(this.actor._source, actorUpdates, {inplace: false}); + const updatedActorData = foundry.utils.mergeObject(this.actor._source, actorUpdates, {inplace: false}); if(!this.removeOnDelete) return; - if(getProperty(updatedActorData, "system.tempHp.source") === this.item.uuid) { - mergeObject(actorUpdates, { + if(foundry.utils.getProperty(updatedActorData, "system.tempHp.source") === this.item.uuid) { + foundry.utils.mergeObject(actorUpdates, { "system.tempHp.value": 0, "system.tempHp.-=source": null }); diff --git a/src/module/rules/rule-element/token-image.js b/src/module/rules/rule-element/token-image.js index d626d1cfe..83bef1f31 100644 --- a/src/module/rules/rule-element/token-image.js +++ b/src/module/rules/rule-element/token-image.js @@ -27,7 +27,7 @@ export class TokenImageRuleElement extends RuleElementPTU { /** @override */ afterPrepareData() { let src = this.value; - if (!this.#srcIsValid(src)) src = this.resolveValue(this.value); + if (!this.#srcIsValid(src)) src = this.resolveValue(this.value, 0, {evaluate: false}); if (!this.test()) return; @@ -53,6 +53,7 @@ export class TokenImageRuleElement extends RuleElementPTU { #srcIsValid(src) { if (typeof src !== "string") return false; + if (src.includes("{")) return false; const extension = /(?<=\.)([a-z0-9]{3,4})(\?[a-zA-Z0-9]+)?$/i.exec(src)?.at(1); return !!extension && (extension in CONST.IMAGE_FILE_EXTENSIONS || extension in CONST.VIDEO_FILE_EXTENSIONS); } diff --git a/src/module/rules/rule-element/token-light.js b/src/module/rules/rule-element/token-light.js index 1a0ba2e75..662dcfad4 100644 --- a/src/module/rules/rule-element/token-light.js +++ b/src/module/rules/rule-element/token-light.js @@ -33,6 +33,6 @@ export class TokenLightRuleElement extends RuleElementPTU { /** @override */ afterPrepareData(){ if (!this.test()) return; - this.actor.synthetics.tokenOverrides.light = deepClone(this.value); + this.actor.synthetics.tokenOverrides.light = foundry.utils.deepClone(this.value); } } \ No newline at end of file diff --git a/src/module/system/check/attack.js b/src/module/system/check/attack.js index f8e47aceb..d35c3c507 100644 --- a/src/module/system/check/attack.js +++ b/src/module/system/check/attack.js @@ -62,6 +62,25 @@ class PTUAttackCheck extends PTUDiceCheck { this.modifiers = modifiers; + const critRangeModifiers = [ + new PTUModifier({ + slug: "crit-range", + label: "Crit Range", + modifier: this.actor.system.modifiers.critRange.total ?? 0 + }), + ]; + + critRangeModifiers.push(...extractModifiers(this.actor.synthetics, [ + "crit-range", + `${this.item.id}-crit-range`, + `${this.item.slug}-crit-range`, + `${sluggify(this.item.system.category)}-crit-range`, + `${sluggify(this.item.system.type)}-crit-range`, + `${sluggify(this.item.system.frequency)}-crit-range` + ], { test: this.options })); + + this.critRangeModifiers = critRangeModifiers; + return this; } @@ -71,6 +90,16 @@ class PTUAttackCheck extends PTUDiceCheck { */ prepareStatistic() { super.prepareStatistic(sluggify(game.i18n.format("PTU.Action.AttackRoll", { move: this.item.name }))); + + this.critMod = Math.max( + 0, + Object.values( + this.critRangeModifiers.reduce((acc, mod) => { + if(!mod.ignored && !acc[mod.slug]) acc[mod.slug] = mod.modifier; + return acc; + }, {}) + ).reduce((acc, mod) => acc + mod, 0) + ) return this; } @@ -112,8 +141,7 @@ class PTUAttackCheck extends PTUDiceCheck { /** @type {DcCollection} */ const dcs = (() => { const targets = new Map(); - const critMod = this.actor.system.modifiers?.critRange?.total ?? 0; - const critRange = Array.fromRange(1 + Math.max(critMod, 0), 20 - Math.max(critMod, 0)); + const critRange = Array.fromRange(1 + Math.max(this.critMod, 0), 20 - Math.max(this.critMod, 0)); /** @type {TargetContext[]} */ const contexts = this._contexts.size > 0 ? this._contexts : [{ actor: this.actor, options: this.options, token: this.token }] @@ -190,7 +218,7 @@ class PTUAttackCheck extends PTUDiceCheck { } } - for(const modifier of extractModifiers(context.actor.synthetics, ["evasion"], { test: context.options })) { + for (const modifier of extractModifiers(context.actor.synthetics, ["evasion"], { test: context.options })) { target.statistic.push(modifier); } @@ -344,7 +372,7 @@ class PTUAttackCheck extends PTUDiceCheck { ui.notifications.warn("PTU.Action.StatusAttackWhileRaging", { localize: true }); return false; } - if (this.conditionOptions.has("condition:disabled") && this.options.includes(`condition:disabled:${move.slug}`)) { + if (this.conditionOptions.has("condition:disabled") && this.options.has(`condition:disabled:${this.item.slug}`)) { ui.notifications.warn("PTU.Action.DisabledMove", { localize: true }); return false; } diff --git a/src/module/system/check/check.js b/src/module/system/check/check.js index a50d84d78..ec1937d32 100644 --- a/src/module/system/check/check.js +++ b/src/module/system/check/check.js @@ -214,6 +214,9 @@ class PTUDiceCheck { ? rollResult.terms.find(t => t instanceof NumericTerm) : rollResult.dice.find(d => d instanceof Die && d.faces === diceSize ))?.total ?? 1; + + options.rollResult = result; + const total = rollResult.total; const targets = []; if (options.dcs?.targets.size > 0) { @@ -276,12 +279,13 @@ class PTUDiceCheck { title, type, skipDialog, - isReroll + isReroll, + rollResult: result }, modifierName: this.statistic.slug, modifiers: this.statistic.modifiers.map(m => m.toObject()), origin: options.origin, - resolved: targets.length > 0 ? game.settings.get("ptu", "autoRollDamage") : false + resolved: targets.length > 0 ? game.settings.get("ptu", "autoRollDamage") : false, } } if (attack) flags.ptu.attack = attack; @@ -300,6 +304,7 @@ class PTUDiceCheck { return { rolls: this.rolls, targets, + rollResult: result, } } @@ -408,7 +413,7 @@ class PTUDiceCheck { */ class PTUCheck { static async roll(check, context, event, callback, diceStatistic = null) { - if (event) mergeObject(context, eventToRollParams(event)); + if (event) foundry.utils.mergeObject(context, eventToRollParams(event)); context.skipDialog ??= game.settings.get("ptu", "skipRollDialog"); context.createMessage ??= true; diff --git a/src/module/system/check/damage.js b/src/module/system/check/damage.js index 977cfa0c6..d42851fd8 100644 --- a/src/module/system/check/damage.js +++ b/src/module/system/check/damage.js @@ -7,10 +7,11 @@ import { CheckDialog } from "./dialogs/dialog.js"; class PTUDamageCheck extends PTUDiceCheck { - constructor({ source, targets, selectors, event, outcomes }) { + constructor({ source, targets, selectors, event, outcomes, accuracyRollResult }) { super({ source, targets, selectors, event }); this.outcomes = outcomes; + this.accuracyRollResult = accuracyRollResult; } get isSelfAttack() { @@ -66,7 +67,7 @@ class PTUDamageCheck extends PTUDiceCheck { modifier: this.item.damageBase.preStab, }) ] - if(this.item.damageBase.isStab) { + if (this.item.damageBase.isStab) { damageBaseModifiers.push( new PTUModifier({ slug: "stab", @@ -77,6 +78,7 @@ class PTUDamageCheck extends PTUDiceCheck { } const modifiers = [] + const diceModifiers = [] const damageBonus = isNaN(Number(this.item.system.damageBonus)) ? 0 : Number(this.item.system.damageBonus); if (damageBonus != 0) { @@ -141,9 +143,20 @@ class PTUDamageCheck extends PTUDiceCheck { `${sluggify(this.item.system.frequency)}-damage-base` ], { injectables: { move: this.item, item: this.item, actor: this.actor }, test: this.targetOptions }) ) + diceModifiers.push( + ...extractModifiers(this.actor.synthetics, [ + "damage-dice", + `${this.item.id}-damage-dice`, + `${this.item.slug}-damage-dice`, + `${sluggify(this.item.system.category)}-damage-dice`, + `${sluggify(this.item.system.type)}-damage-dice`, + `${sluggify(this.item.system.frequency)}-damage-dice` + ], { injectables: { move: this.item, item: this.item, actor: this.actor }, test: this.targetOptions }) + ) this.modifiers = modifiers; this.damageBaseModifiers = damageBaseModifiers; + this.diceModifiers = diceModifiers; return this; } @@ -155,10 +168,13 @@ class PTUDamageCheck extends PTUDiceCheck { prepareStatistic() { super.prepareStatistic(sluggify(game.i18n.format("PTU.Action.DamageRoll", { move: this.item.name }))); - this.damageBase = Object.values(this.damageBaseModifiers.reduce((a, b) => { - if (!b.ignored && !a[b.slug]) a[b.slug] = b.modifier; - return a; - }, {})).reduce((a, b) => a + b, 0); + this.damageBase = Math.max( + Object.values(this.damageBaseModifiers.reduce((a, b) => { + if (!b.ignored && !a[b.slug]) a[b.slug] = b.modifier; + return a; + }, {})).reduce((a, b) => a + b, 0), + 1 + ); this.targetOptions.add(`damage-base:${this.damageBase}`) return this; @@ -314,7 +330,20 @@ class PTUDamageCheck extends PTUDiceCheck { const totalModifiersPart = this.statistic.totalModifier?.signedString() ?? ""; options.modifierPart = totalModifiersPart; - const roll = new this.rollCls(`${diceString}${totalModifiersPart}`, {}, options); + // Add the dice modifier to the total modifier + const diceModifierParts = this.diceModifiers.reduce((a, b) => { + if (!b.ignored && !a[b.slug]) a[b.slug] = b.modifier; + return a; + }, {}); + + if (this.options.has("charge:bonus") && this.options.has("move:type:electric")) { + diceModifierParts["charge"] = `${diceString}+${diceModifier}`; + } + + const diceModifiers = Object.values(diceModifierParts).reduce((a, b) => `${a} + ${b}`, ""); + options.diceModifiers = diceModifiers; + + const roll = new this.rollCls(`${diceString}${totalModifiersPart}${diceModifiers}`, {}, options); const rollResult = await roll.evaluate({ async: true }); const critDice = `${diceString}+${diceString}`; @@ -325,7 +354,7 @@ class PTUDamageCheck extends PTUDiceCheck { } const hasCrit = Object.values(this.outcomes).some(o => o == "crit-hit") - const critRoll = new this.rollCls(`${critDice}${totalModifiersPartCrit}`, {}, { ...options, crit: { hit: true, show: hasCrit, nonCritValue: rollResult.total }, fudges }); + const critRoll = new this.rollCls(`${critDice}${totalModifiersPartCrit}${diceModifiers}`, {}, { ...options, crit: { hit: true, show: hasCrit, nonCritValue: rollResult.total }, fudges }); const critRollResult = await critRoll.evaluate({ async: true }); const flags = { @@ -346,6 +375,7 @@ class PTUDamageCheck extends PTUDiceCheck { skipDialog, isReroll, outcomes: this.outcomes, + accuracyRollResult: this.accuracyRollResult }, modifierName: this.statistic.slug, modifiers: this.statistic.modifiers.map(m => m.toObject()), @@ -355,6 +385,7 @@ class PTUDamageCheck extends PTUDiceCheck { } const extraTags = this.fiveStrikeResult > 0 ? [`Five Strike x${this.fiveStrikeResult}`] : []; + extraTags.push(...Object.entries(diceModifierParts).flatMap(([slug, mod]) => `${Handlebars.helpers.formatSlug(slug)} +${mod}`)) const message = await this.createMessage({ roll, rollMode, flags, inverse: false, critRoll, type, extraTags }); @@ -379,19 +410,22 @@ class PTUDamageCheck extends PTUDiceCheck { * @returns {Promise} */ async executeDamage(callback = null, attackStatistic = null) { + if (!Number.isNumeric(this.item?.damageBase?.preStab) || this.item.damageBase.preStab < 1) return; + await this.prepareContexts(attackStatistic); this.prepareModifiers(); this.prepareStatistic(); await this.beforeRoll(); + const roll = await this.execute({ isReroll: false, title: game.i18n.format("PTU.Action.DamageRoll", { move: this.item.name }), type: "damage-roll" }, callback); - await this.afterRoll(); + await this.afterRoll(); return roll; } } diff --git a/src/module/system/check/skill.js b/src/module/system/check/skill.js index 809ce9aa0..7b924aa04 100644 --- a/src/module/system/check/skill.js +++ b/src/module/system/check/skill.js @@ -35,7 +35,7 @@ class PTUSkillCheck extends PTUDiceCheck { /** @type {PTUDiceModifier[]} */ const diceModifiers = [ new PTUDiceModifier({ - diceNumber: Math.clamped(this.actor.system.skills[this.skill]?.value?.total ?? 1, 1, 6), + diceNumber: Math.clamp(this.actor.system.skills[this.skill]?.value?.total ?? 1, 1, 6), dieSize: 6, label: game.i18n.format("PTU.Check.SkillDice", { skill: this.skillLabel }) }) diff --git a/src/module/system/predication.js b/src/module/system/predication.js index 72f345bc7..6ab3a6aa6 100644 --- a/src/module/system/predication.js +++ b/src/module/system/predication.js @@ -35,7 +35,7 @@ class PTUPredicate extends Array { } toObject() { - return deepClone([...this]); + return foundry.utils.deepClone([...this]); } clone() { diff --git a/src/module/system/settings/base.js b/src/module/system/settings/base.js index 1fe8e1282..ff65852ab 100644 --- a/src/module/system/settings/base.js +++ b/src/module/system/settings/base.js @@ -12,7 +12,7 @@ class PTUSettingsMenu extends FormApplication { const options = super.defaultOptions; options.classes.push("ptu-settings-menu"); - return mergeObject(options, { + return foundry.utils.mergeObject(options, { title: `PTU.Settings.${this.namespace.titleCase()}.Name`, id: `${this.namespace}-settings`, template: `systems/ptu/static/templates/config/settings/menu.hbs`, @@ -58,7 +58,7 @@ class PTUSettingsMenu extends FormApplication { async getData() { const settings = this.constructor.settings; const templateData = settingsToSheetData(settings, this.cache, this.prefix) - return mergeObject(await super.getData(), { + return foundry.utils.mergeObject(await super.getData(), { settings: templateData, instructions: `PTU.Settings.${this.namespace.titleCase()}.Hint` }) diff --git a/src/module/system/settings/generation.js b/src/module/system/settings/generation.js index 000aab0bb..b2e4bfc40 100644 --- a/src/module/system/settings/generation.js +++ b/src/module/system/settings/generation.js @@ -14,6 +14,12 @@ const GenerationSettingsConfig = { type: String, default: ".webp" }, + "defaultTokenImageExtension": { + name: "PTU.Settings.Generation.DefaultTokenImageExtension.Name", + hint: "PTU.Settings.Generation.DefaultTokenImageExtension.Hint", + type: String, + default: ".webp" + }, "defaultPokemonImageNameType": { name: "PTU.Settings.Generation.DefaultPokemonImageNameType.Name", hint: "PTU.Settings.Generation.DefaultPokemonImageNameType.Hint", diff --git a/src/module/system/settings/homebrew.js b/src/module/system/settings/homebrew.js index 594287ca0..3561ef672 100644 --- a/src/module/system/settings/homebrew.js +++ b/src/module/system/settings/homebrew.js @@ -13,8 +13,7 @@ const HomebrewSettingsConfig = { hint: "PTU.Settings.Homebrew.ShadowType.Hint", type: Boolean, default: false, - requiresReload: true, - hide: true, + requiresReload: true } } diff --git a/src/module/system/settings/settings.js b/src/module/system/settings/settings.js index 24f78a808..cdf8fef7d 100644 --- a/src/module/system/settings/settings.js +++ b/src/module/system/settings/settings.js @@ -82,20 +82,6 @@ export function registerSettings() { }) TypeSettings.registerSettings(); - game.settings.register("ptu", "gameLanguage", { - name: "Localization", - hint: "Changes the name of Pokemon in the system. Would you like your language here? Get in contact on how to translate!", - scope: "world", - config: true, - type: String, - default: "en", - choices: { - "en": "English", - "de": "German", - }, - requiresReload: true - }); - game.settings.register("ptu", "skipRollDialog", { name: "Skip roll dialog", hint: "Skip the roll dialog and automatically roll the dice.", @@ -212,6 +198,18 @@ export function registerSettings() { config: true, default: MigrationRunner.MINIMUM_SAFE_VERSION, type: Number, + requiresReload: true + }); + + game.settings.register("ptu", "weatherEffects", { + name: "Weather & Global Effects", + scope: "world", + config: false, + default: [], + type: Object, + onChange: () => { + game.ptu.weather.updateGameState(); + } }); diff --git a/src/module/system/settings/types.js b/src/module/system/settings/types.js index f7062905c..d612ea09d 100644 --- a/src/module/system/settings/types.js +++ b/src/module/system/settings/types.js @@ -22,7 +22,7 @@ export class TypeSettings extends PTUSettingsMenu { } static get defaultOptions() { - return mergeObject(super.defaultOptions, { + return foundry.utils.mergeObject(super.defaultOptions, { width: 820, height: 920, template: "systems/ptu/static/templates/config/settings/types.hbs", @@ -38,7 +38,7 @@ export class TypeSettings extends PTUSettingsMenu { this.cache["types"] = types; } - const typeEffectiveness = duplicate(this.cache["types"]); + const typeEffectiveness = foundry.utils.duplicate(this.cache["types"]); delete typeEffectiveness.Untyped; let typeLength = Object.keys(typeEffectiveness).length + 1; @@ -77,7 +77,7 @@ export class TypeSettings extends PTUSettingsMenu { const { offensive, defensive } = event.currentTarget.dataset; if (!offensive || !defensive) return; - const types = duplicate(this.cache["types"]); + const types = foundry.utils.duplicate(this.cache["types"]); const newValue = (() => { switch (types[defensive].effectiveness[offensive]) { case 0: @@ -102,7 +102,7 @@ export class TypeSettings extends PTUSettingsMenu { const { offensive, defensive } = event.currentTarget.dataset; if (!offensive || !defensive) return; - const types = duplicate(this.cache["types"]); + const types = foundry.utils.duplicate(this.cache["types"]); const newValue = (() => { switch (types[defensive].effectiveness[offensive]) { case 0: @@ -183,7 +183,7 @@ export class TypeSettings extends PTUSettingsMenu { return obj; }, {}); - const types = duplicate(this.cache["types"]); + const types = foundry.utils.duplicate(this.cache["types"]); if (exists) { types[typeData.name].images = { bar: formData.barImg, @@ -196,12 +196,12 @@ export class TypeSettings extends PTUSettingsMenu { bar: formData.barImg, icon: formData.iconImg }, - effectiveness: duplicate(types.Untyped.effectiveness) + effectiveness: foundry.utils.duplicate(types.Untyped.effectiveness) }; for (const type of Object.keys(types)) { types[type].effectiveness[formData.name.titleCase()] = 1; } - const Untyped = duplicate(types.Untyped); + const Untyped = foundry.utils.duplicate(types.Untyped); delete types.Untyped; types.Untyped = Untyped; } @@ -221,7 +221,7 @@ export class TypeSettings extends PTUSettingsMenu { title: game.i18n.localize("PTU.Settings.Type.Delete.Title"), content: game.i18n.localize("PTU.Settings.Type.Delete.Content"), yes: () => { - const types = duplicate(this.cache["types"]); + const types = foundry.utils.duplicate(this.cache["types"]); delete types[typeData.name]; for (const type of Object.keys(types)) { delete types[type].effectiveness[typeData.name]; diff --git a/src/module/system/settings/variant.js b/src/module/system/settings/variant.js index 0d649547f..96504e8ef 100644 --- a/src/module/system/settings/variant.js +++ b/src/module/system/settings/variant.js @@ -14,17 +14,11 @@ const VariantSettingsConfig = { default: false, requiresReload: true }, - "trainerRevamp": { - name: "PTU.Settings.Variant.TrainerRevamp.Name", - hint: "PTU.Settings.Variant.TrainerRevamp.Hint", - type: Boolean, - default: false - }, "spiritPlaytest": { name: "PTU.Settings.Variant.SpiritPlaytest.Name", hint: "PTU.Settings.Variant.SpiritPlaytest.Hint", type: Boolean, - default: false, + default: true, requiresReload: true }, "improvedStatsRework": { @@ -41,7 +35,21 @@ const VariantSettingsConfig = { default: false, requiresReload: true, hide: true - } + }, + "trainerAdvancement": { + name: "PTU.Settings.Variant.trainerAdvancement.Name", + hint: "PTU.Settings.Variant.trainerAdvancement.Hint", + type: String, + choices: { + "original": "PTU.Settings.Variant.trainerAdvancement.Original", + "data-revamp": "PTU.Settings.Variant.trainerAdvancement.DataRevamp", + "short-track": "PTU.Settings.Variant.trainerAdvancement.ShortTrack", + "ptr-update": "PTU.Settings.Variant.trainerAdvancement.PTRUpdate", + "long-track": "PTU.Settings.Variant.trainerAdvancement.LongTrack", + }, + default: "original", + requiresReload: true, + }, } export class VariantSettings extends PTUSettingsMenu { @@ -59,7 +67,7 @@ export class VariantSettings extends PTUSettingsMenu { async _updateObject(event, data) { await super._updateObject(event, data); - if(game.settings.get("ptu", "variant.trainerRevamp") && game.settings.get("ptu", "variant.useDexExp")) { + if(game.settings.get("ptu", "variant.trainerAdvancement") === "data-revamp" && game.settings.get("ptu", "variant.useDexExp")) { return game.settings.set("ptu", "variant.useDexExp", false); } } diff --git a/src/module/system/species.js b/src/module/system/species.js index 049c3ed20..fad3e07ad 100644 --- a/src/module/system/species.js +++ b/src/module/system/species.js @@ -41,7 +41,7 @@ export function getSpeciesData(species) { } } } - const toReturn = mergeObject(JSON.parse(JSON.stringify(preJson)), extra); + const toReturn = foundry.utils.mergeObject(JSON.parse(JSON.stringify(preJson)), extra); if (toReturn.Type.indexOf("null") === 1) toReturn.Type.splice(1, 1); return toReturn; } diff --git a/src/module/system/statistic/index.js b/src/module/system/statistic/index.js index 881f65bd2..3792c8626 100644 --- a/src/module/system/statistic/index.js +++ b/src/module/system/statistic/index.js @@ -114,8 +114,8 @@ class Statistic extends SimpleStatistic { * @param {RollOptionsParameters | null} options */ withRollOptions(options) { - const newOptions = mergeObject(this.options ?? {}, options ?? {}, { inplace: false }); - return new Statistic(this.actor, deepClone(this.data), newOptions); + const newOptions = foundry.utils.mergeObject(this.options ?? {}, options ?? {}, { inplace: false }); + return new Statistic(this.actor, foundry.utils.deepClone(this.data), newOptions); } /** @@ -128,7 +128,7 @@ class Statistic extends SimpleStatistic { return [...new Set([arr1 ?? [], arr2 ?? []].flat())]; } - const result = mergeObject(deepClone(this.data), data); + const result = foundry.utils.mergeObject(foundry.utils.deepClone(this.data), data); result.domains = maybeMergeArrays(this.domains, data.domains); result.modifiers = maybeMergeArrays(this.data.modifiers, data.modifiers); result.rollOptions = maybeMergeArrays(this.data.rollOptions, data.rollOptions); @@ -251,7 +251,7 @@ class StatisticCheck { const event = args.event?.originalEvent ?? args.event; if(event instanceof MouseEvent) { const { rollMode, skipDialog } = args; - return mergeObject({rollMode, skipDialog}, eventToRollParams(event)); + return foundry.utils.mergeObject({rollMode, skipDialog}, eventToRollParams(event)); } } return args; @@ -309,7 +309,7 @@ class StatisticCheck { context, null, args.callback, - new StatisticDiceModifier(args.label || this.label, this.diceModifiers, options) + (args?.slug || this.slug || this.parent?.slug) === 'save-check' ? null : new StatisticDiceModifier(args.label || this.label, this.diceModifiers, options) ) for(const rule of actor.rules.filter(r => !r.ignored)) { diff --git a/src/module/user.js b/src/module/user.js index b21f93209..ceb888715 100644 --- a/src/module/user.js +++ b/src/module/user.js @@ -10,7 +10,7 @@ export class PTUUser extends User { /** @override */ prepareBaseData() { super.prepareBaseData(); - this.flags = mergeObject( + this.flags = foundry.utils.mergeObject( { ptu: { settings: { diff --git a/src/ptr.js b/src/ptr.js index 8b9fff966..9e0668c01 100644 --- a/src/ptr.js +++ b/src/ptr.js @@ -1,3 +1,6 @@ import { PtuHooks } from "./scripts/hooks/index.js"; +// V11 - V12 compatability +if(Math.clamp === undefined) Math.clamp = Math.clamped; + PtuHooks.listen(); \ No newline at end of file diff --git a/src/scripts/config/index.js b/src/scripts/config/index.js index 2ad17ed06..b232d3084 100644 --- a/src/scripts/config/index.js +++ b/src/scripts/config/index.js @@ -41,6 +41,8 @@ import { PTUContestMoveSheet } from '../../module/item/contestmove/sheet.js'; import { PTUHotBar } from '../../module/apps/hotbar.js'; import { PTUTokenConfig } from '../../module/canvas/token/sheet.js'; import { PackLoader } from '../../module/apps/compendium-browser/index.js'; +import PTURuleBookJournal from '../../module/apps/rulebook-journal.js'; +import { BaseEffectPTU } from '../../module/item/effect-types/base.js'; const data = { skills: { @@ -222,7 +224,7 @@ export const PTUCONFIG = { species: PTUSpecies, condition: PTUCondition, reference: PTUItem, - spiritaction: PTUItem + spiritaction: PTUItem, }, sheetClasses: { item: PTUItemSheet, @@ -232,6 +234,12 @@ export const PTUCONFIG = { contestmove: PTUContestMoveSheet, effect: PTUEffectSheet, species: PTUSpeciesSheet, + }, + baseEffect: BaseEffectPTU + }, + Journal: { + Rulebook: { + journalClass: PTURuleBookJournal } }, rule: { @@ -270,7 +278,7 @@ export const PTUCONFIG = { documentClass: PTUUser }, Capabilities: { - numericNonMovement: ["highJump", "longJump", "power", "weightClass"], + numericNonMovement: ["highJump", "longJump", "power", "weightClass", "throwingRange"], stringArray: ["naturewalk", "other"], } } \ No newline at end of file diff --git a/src/scripts/game-ptu.js b/src/scripts/game-ptu.js index c6d0a8eb7..710b67783 100644 --- a/src/scripts/game-ptu.js +++ b/src/scripts/game-ptu.js @@ -13,6 +13,7 @@ import { dexSync } from "./macros/dex-sync.js" import { pokedex } from "./macros/pokedex.js" import { changeRotomForm } from "./macros/rotom-form-change.js" import { TypeMatrix } from "../module/apps/type-matrix.js"; +import { Weather } from "../module/apps/weather.js"; const GamePTU = { onInit() { @@ -39,6 +40,7 @@ const GamePTU = { MigrationRunner, MigrationSummary }, + weather: Weather, macros: { changeRotomForm, pokedex, @@ -55,11 +57,13 @@ const GamePTU = { tokenPanel: new TokenPanel() } - game.ptu = mergeObject(game.ptu ?? {}, initData) + Weather._initializeGlobalEffects(); + + game.ptu = foundry.utils.mergeObject(game.ptu ?? {}, initData) CONFIG.PTU.data.typeEffectiveness = game.settings.get("ptu", "type.typeEffectiveness") ?? CONFIG.PTU.data.typeEffectiveness; }, - onSetup() { }, + onSetup() {}, onReady() { game.ptu.compendiumBrowser = new CompendiumBrowser(); game.ptu.typeMatrix = new TypeMatrix() diff --git a/src/scripts/handlebars.js b/src/scripts/handlebars.js index 12d3198a6..d103e81a7 100644 --- a/src/scripts/handlebars.js +++ b/src/scripts/handlebars.js @@ -9,7 +9,7 @@ export function registerHandlebarsHelpers() { function _registerPTUHelpers() { Handlebars.registerHelper("getGameSetting", function (key) { return game.settings.get("ptu", key) }); - Handlebars.registerHelper("getProperty", getProperty); // native foundry function + Handlebars.registerHelper("getProperty", foundry.utils.getProperty); // native foundry function Handlebars.registerHelper("json", function (context) { return JSON.stringify(context); }); @@ -37,7 +37,7 @@ function _registerPTUHelpers() { }) Handlebars.registerHelper("calcHeight", function (percent) { - return Math.round((100 - percent) / 100 * 48); + return Math.clamp(Math.round((100 - percent) / 100 * 48), 0, 48); }); // //pokeball themed background for pokemon @@ -122,7 +122,7 @@ function _registerPTUHelpers() { const dbNumber = Number(item.damageBase.postStab ?? item); if (isNaN(dbNumber)) return "Not a valid DB"; - const realDb = Math.clamped(dbNumber, 0, 28); + const realDb = Math.clamp(dbNumber, 0, 28); const dbString = CONFIG.PTU.data.dbData[realDb]; if (!actor) return dbString; @@ -347,6 +347,7 @@ function _registerBasicHelpers() { Handlebars.registerHelper("capitalizeFirst", (e) => { return "string" != typeof e ? e : e.charAt(0).toUpperCase() + e.slice(1) }); const capitalize = function (input) { + if(!!input == false) return ""; var i, j, str, lowers, uppers; str = input.replace(/([^\W_]+[^\s-]*) */g, function (txt) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); diff --git a/src/scripts/hooks/actor-tab-buttons.js b/src/scripts/hooks/actor-tab-buttons.js index ece06eb1d..3ac568fc7 100644 --- a/src/scripts/hooks/actor-tab-buttons.js +++ b/src/scripts/hooks/actor-tab-buttons.js @@ -1,10 +1,10 @@ export const ActorButtons = { listen() { Hooks.on("renderSidebarTab", () => { - if(!game.user.isGM) return; + if (!game.user.isGM) return; const sidebarButtons = $("#sidebar #actors .directory-header .action-buttons"); - if(sidebarButtons.find(".import-party").length > 0) return; + if (sidebarButtons.find(".import-party").length > 0) return; sidebarButtons.append(``) sidebarButtons.append(``) @@ -18,12 +18,57 @@ export const ActorButtons = { Hooks.on("renderSidebarTab", () => { const footer = $(".compendium-sidebar .directory-footer"); - if(footer.find(".compendium-browser-btn").length > 0) return; + if (footer.find(".compendium-browser-btn").length > 0) return; footer.append(``); footer.find(".compendium-browser-btn").on("click", () => { game.ptu.compendiumBrowser.render(true) }); }); + + Hooks.on("renderSidebarTab", () => { + if (!game.user.isGM) return; + const sidebarButtons = $("#sidebar #items .directory-header .action-buttons"); + + if (sidebarButtons.find(".mass-import").length > 0) return; + if(!window.showOpenFilePicker) return; + sidebarButtons.append(``) + + $("#sidebar #items .directory-header .action-buttons .mass-import").on("click", async (event) => { + + const fileHandlers = await window.showOpenFilePicker({ + types: [ + { + description: "JSON Files", + accept: { + "text/json": [".json"] + } + } + ], + excludeAcceptAllOption: true, + multiple: true + }); + + const items = []; + const folder = await (async () => { + const folder = game.folders.getName("Mass Item Import"); + if (folder) return folder; + return await Folder.create({ + type: "Item", + name: "Mass Item Import" + }); + })(); + + for (const fileHandler of fileHandlers) { + const file = await fileHandler.getFile(); + const text = await file.text(); + const json = JSON.parse(text); + json.folder = folder.id; + items.push(json); + } + + return await CONFIG.Item.documentClass.createDocuments(items); + }); + }); } } \ No newline at end of file diff --git a/src/scripts/hooks/compendium-browser-inline-enricher.js b/src/scripts/hooks/compendium-browser-inline-enricher.js new file mode 100644 index 000000000..1ef6369e4 --- /dev/null +++ b/src/scripts/hooks/compendium-browser-inline-enricher.js @@ -0,0 +1,284 @@ +/** + * @param {Array} values ["athlete", "ace-trainer-cr"] + * @param {string} paramName Parameter name as named in compendium browser filter data, e.g. "types" + * @param {Object} filterData Whole filterData of CompendiumBrowserTab. Will not get modified. + * + * @return {Object} New Object for filter data, merged with provided + */ +function checkboxes(values, paramName, filterData) { + const fd = deepClone(filterData) + fd.checkboxes[paramName].selected = [] + for (const optionName of Object.keys(fd.checkboxes[paramName].options)) { + fd.checkboxes[paramName].options[optionName].selected = false; + } + for (const value of values) { + if (fd.checkboxes[paramName].options[value] === undefined) continue; + fd.checkboxes[paramName].options[value].selected = true; + fd.checkboxes[paramName].selected.push(value) + } + return fd +} + +/** + * @param {Array} positives + * @param {Array} negatives + * @param {string|null} conjunction "and"|"or"|null + * @param {string} paramName Parameter name as named in compendium browser filter data, e.g. "types" + * @param {Object} filterData Whole filterData of CompendiumBrowserTab. Will not get modified. + * + * @return {Object} New Object for filter data, merged with provided + */ +function multiselects(positives, negatives, conjunction, paramName, filterData) { + const fd = deepClone(filterData) + + if (conjunction) fd.multiselects[paramName].conjunction = conjunction; + + const negs = negatives + .map(value => { + return { + value: value, + not: true, + label: filterData.multiselects[paramName].options.find(o => o.value === value).label + } + }) + const poss = positives + .filter(v => !v.startsWith("not-")) + .map(value => { + return { + value: value, + label: filterData.multiselects[paramName].options.find(o => o.value === value).label + } + }) + + fd.multiselects[paramName].selected = poss.concat(negs) + return fd +} + +/** + * @param {Number|null} min + * @param {string} paramName Parameter name as named in compendium browser filter data, e.g. "types" + * @param {Object} filterData Whole filterData of CompendiumBrowserTab. Will not get modified. + * + * @return {Object} New Object for filter data, merged with provided + */ +function sliders(min, max, paramName, filterData) { + const fd = deepClone(filterData) + + if (min === 0 || min) fd.sliders[paramName].values.min = min + if (max === 0 || max) fd.sliders[paramName].values.max = max + + return fd; +} + +/** + * @param {string} value + * @param {string} paramName Parameter name as named in compendium browser filter data, e.g. "types" + * @param {Object} filterData Whole filterData of CompendiumBrowserTab. Will not get modified. + * + * @return {Object} New Object for filter data, merged with provided + */ +function selects(value, paramName, filterData) { + const fd = deepClone(filterData); + const legalOptionNames = Object.keys(fd.selects[paramName].options); + if (legalOptionNames.includes(value)) fd.selects[paramName].selected = value; + return fd; +} + +/** + * @param {string|null|undefined} by + * @param {string|null|undefined} dir + * @param {Object} filterData Whole filterData of CompendiumBrowserTab. Will not get modified. + * + * @return {Object} New Object for filter data, merged with provided + */ +function order(by, dir, filterData) { + const fd = deepClone(filterData) + if (by) fd.order.by = by + if (dir) fd.order.direction = by + return fd +} + +/** + * @param {Array.|null|undefined} values Words to be searched for + * @param {Object} filterData Whole filterData of CompendiumBrowserTab. Will not get modified. + * + * @return {Object} New Object for filter data, merged with provided + */ +function search(values, filterData) { + const fd = deepClone(filterData) + if (values) fd.search.text = values.join(" ") + return fd +} + +export const CompendiumBrowserInlineEnricher = { + listen: () => { + + // Set up a single Enricher that goes through all(?) content and subs the hand written + // stuff with an anchor https://discord.com/channels/170995199584108546/722559135371231352/1192834854614737068 + Hooks.on('setup', () => { + CONFIG.TextEditor.enrichers.push({ + pattern: /@CompSearch\[([A-Za-z]+) ?([0-9a-zA-Z\-= ]*)\](:?{(:?[^\[\]\{\}@]*)?})?/gim, + enricher: async (match, enrichmentOptions) => { + const [tabName, paramString, displayText] = match.slice(1, 4) + const tabNameSlug = CONFIG.PTU.util.sluggify(tabName, { camel: "dromedary" }) + + const broken = game?.ptu?.compendiumBrowser + ? game.ptu.compendiumBrowser.tabs[tabNameSlug] === undefined + : false; + + const a = document.createElement("a"); + a.classList.add("content-link", "compendium-link") + + a.setAttribute("compendium-link-tab", tabNameSlug) + + if (broken) { + a.classList.add("broken") + a.innerHTML = `` + a.insertAdjacentText("beforeend", displayText?.replace("{","")?.replace("}","") || `${tabName} Search` + ` (Broken)`) + } + else { + const pValues = {} + pValues["search"] = paramString.split(" ").filter(p => !p.includes("=")) + const params = paramString.split(" ").filter(p => p.includes("=")) + for (const param of params) { + const [pName, pValue] = param.split(/=(.*)/s).splice(0, 2) + if (!pValues[pName]) pValues[pName] = new Set() + pValues[pName].add(pValue) + } + for (const pName of Object.keys(pValues)) { + a.setAttribute(`compendium-filter-setting-${pName}`, Array.from(pValues[pName]).join(" ")) + } + a.innerHTML = `` + a.insertAdjacentText("beforeend", displayText?.replace("{","")?.replace("}","") || `${tabName} Search`) + } + return a; + } + }); + }) + + // This function adds Event Listeners to the given htmlElement for the compendium-link anchors + // that the previous enricher will have added to the content + const activateCompendiumEnricherListener = (htmlElement) => { + if(!htmlElement) return; + htmlElement.querySelectorAll(`.compendium-link`).forEach(el => { + el.addEventListener("click", async (event) => { + let tabKey = el.getAttribute("compendium-link-tab") + if (tabKey === "pokeedges") tabKey = "pokeEdges"; + /** @type {CompendiumBrowserTab} */ + const tab = game.ptu.compendiumBrowser.tabs[tabKey] + + if (!tab) { + if(!event.currentTarget.classList.contains("broken")) { + event.currentTarget.classList.add("broken") + event.currentTarget.firstChild.outerHTML = `` + }; + ui.notifications.warn(game.i18n.format("PTU.CompendiumBrowser.Enrichment.UnknownTab", { tabName: tabKey })); + return; + } + + const prefix = "compendium-filter-setting-" + + const allElAttributes = el.getAttributeNames() + const params = allElAttributes.filter(pName => pName.startsWith(prefix)).map(s => { + return { + name: s.substring(26), + rawString: el.getAttribute(s), + used: false + } + }) + const usedParams = [] + + let filterData = await tab.getFilterData() + + const searchWords = el.getAttribute(prefix + "search") + usedParams.push("search") + filterData = search(searchWords.split(" "), filterData); + + const orderBy = el.getAttribute(prefix + "order-by") + const orderDir = el.getAttribute(prefix + "order-dir") + usedParams.push("order-by") + usedParams.push("order-dir") + + filterData = order(orderBy, orderDir, filterData) + + if (filterData.checkboxes) { + for (const checkboxName of Object.keys(filterData.checkboxes)) { + const checkboxString = el.getAttribute(prefix + checkboxName) + usedParams.push(checkboxName) + if (checkboxString) { + filterData = checkboxes(checkboxString.split(" "), checkboxName, filterData) + } + } + } + if (filterData.selects) { + for (const selectName of Object.keys(filterData.selects)) { + const selectString = el.getAttribute(prefix + selectName) + usedParams.push(selectName) + if (selectString) { + filterData = selects(selectString, selectName, filterData) + } + } + } + if (filterData.sliders) { + for (const sliderName of Object.keys(filterData.sliders)) { + const min = el.getAttribute(prefix + `${sliderName}-min`) + const max = el.getAttribute(prefix + `${sliderName}-max`) + usedParams.push(`${sliderName}-min`) + usedParams.push(`${sliderName}-max`) + if (min || max) { + filterData = sliders(min, max, sliderName, filterData) + } + } + } + if (filterData.multiselects) { + for (const multiselectName of Object.keys(filterData.multiselects)) { + const multString = el.getAttribute(prefix + multiselectName) + usedParams.push(multiselectName) + if (multString) { + const positives = multString.split(" ").filter(s => !s.startsWith("not-")) + const negatives = multString.split(" ").filter(s => s.startsWith("not-")).map(s => s.substring(4)) + const conjunction = el.getAttribute(prefix + `${multiselectName}-logic`) + usedParams.push(`${multiselectName}-logic`) + filterData = multiselects(positives, negatives, conjunction, multiselectName, filterData) + } + } + } + + params.filter(p => !usedParams.includes(p.name)).forEach(param => ui.notifications.warn(game.i18n.format("PTU.CompendiumBrowser.Enrichment.UnknownFilterSetting", { + filterName: param.name, + tabName: tabKey + }))) + + try { + await tab.open(filterData) + } catch (e) { + ui.notifications.error(game.i18n.format("PTU.CompendiumBrowser.Enrichment.LikelyMalformedExpressionBrowserCrashed")); + throw e; + } + }) + }) + } + + // These Sheets should be interactive, such that clicking on the anchor actually does something + // Due to some performance stutters during development, those are relatively restrictive. + // It the anchor appears somehwere where it should also work, add an appropriate hook here + Hooks.on("renderJournalTextPageSheet", (journal, $html) => { + // maybe related to why this does do need a filter? https://github.com/foundryvtt/foundryvtt/issues/3088 + const journalHtmlElement = $html.filter(".journal-page-content").get(0); + activateCompendiumEnricherListener(journalHtmlElement) + + }); + Hooks.on("renderChatMessage", (message, $html) => { + // maybe related to why this does not need a filter? https://github.com/foundryvtt/foundryvtt/issues/3088 + const messageHtmlElement = $html.get(0); + activateCompendiumEnricherListener(messageHtmlElement) + + message.activateListeners($html) + }); + Hooks.on("renderPTUItemSheet", (itemSheet, $html) => { + // maybe related to why this does not need a filter? https://github.com/foundryvtt/foundryvtt/issues/3088 + const itemHtmlElement = $html.get(0) + activateCompendiumEnricherListener(itemHtmlElement) + }); + } +} \ No newline at end of file diff --git a/src/scripts/hooks/embed-enricher.js b/src/scripts/hooks/embed-enricher.js new file mode 100644 index 000000000..945309016 --- /dev/null +++ b/src/scripts/hooks/embed-enricher.js @@ -0,0 +1,165 @@ +export const V11EmbedCompatability = { + listen: () => { + // V11 - V12 compatability + if (TextEditor._enrichEmbeds === undefined) { + Hooks.on('setup', () => { + CONFIG.TextEditor.enrichers.push({ + pattern: /@Embed\[(?[^\]]+)](?:{(?