Compare commits
448 Commits
70d8ac8038
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| e3b6196349 | |||
| baed51259e | |||
| ba77d1b787 | |||
| 85aafd8a02 | |||
| 71a2146c4b | |||
| 509ac11749 | |||
| 5ee7f02b42 | |||
| a7f1ff712a | |||
| 2385f1d088 | |||
| c247c2d998 | |||
| 35f45513aa | |||
| a7b0e68197 | |||
| f74e21e7d9 | |||
| cb26cec7bc | |||
| 8249c68266 | |||
| 338a7e4c0e | |||
| d099d878a0 | |||
| 339e413f82 | |||
| e888bbf8a8 | |||
| a5bf6639e8 | |||
| 9dd5c7e627 | |||
| 9a38c6d98f | |||
| 8f1689574c | |||
| 9c1e27d473 | |||
| bd933ca671 | |||
| 70d549f929 | |||
| c674e2c27a | |||
| a3998d6c1d | |||
| 77176a6d4f | |||
| fa25c1bef3 | |||
| 0e21554773 | |||
| 0cbd03da69 | |||
| c06b53ed6b | |||
| 2d043320e0 | |||
| 1e0c9ece0f | |||
| 65d1b64e0b | |||
| ddb58622d6 | |||
| d63b125175 | |||
| b66b653982 | |||
| ccb06960a2 | |||
| ccc9fadef9 | |||
| a3dace36de | |||
| a3d1a139e9 | |||
| 2af18cbf1d | |||
| 044202cb3a | |||
| b3ac787360 | |||
| 60a26ba81d | |||
| edc6415ad5 | |||
| e2a1fafaa9 | |||
| 51b0245cfe | |||
| 605f4b99f7 | |||
| 92b619a407 | |||
| 3a775f47e0 | |||
|
|
2a963cea0a | ||
|
|
ab81917eb2 | ||
|
|
48f71d1a3a | ||
|
|
c4bb775af4 | ||
|
|
d1e3c3f5f3 | ||
| addda6f54f | |||
|
|
019c2fea60 | ||
|
|
938f0fb758 | ||
| 2da820b980 | |||
| 43004a3a0a | |||
|
|
1ebf6e5348 | ||
|
|
4396336cae | ||
|
|
dc543660be | ||
|
|
833131c123 | ||
|
|
f665bf86f1 | ||
|
|
85e61dbb14 | ||
|
|
a57c43a7e4 | ||
|
|
0fd2cb4e9a | ||
| e8358bfb59 | |||
| 163cbe7bb3 | |||
| 750bb2543d | |||
| c7f4b4c137 | |||
| 8558cacc83 | |||
| 739dea8db7 | |||
| 563df6ca28 | |||
| ebf01e2bb0 | |||
| 5ba59bbdf3 | |||
| 346e3c1957 | |||
| 5304c66b0b | |||
| d3a1878656 | |||
| e741d4fb33 | |||
| 9ff561dd9d | |||
| cc963f547f | |||
| 090e6d5263 | |||
| f04932899a | |||
| f31e89aacb | |||
| 52f37b494e | |||
| d212808ee1 | |||
| c7fdb7ecc5 | |||
| 0f187fdbe2 | |||
| e7ccc0e1ab | |||
| 8006ee4b66 | |||
| 0ac780c188 | |||
| 0d77e79b0f | |||
| ab218fbe34 | |||
| ef05ac1c5a | |||
| a43d2f11ea | |||
| e11bf1455d | |||
| b519b80209 | |||
| 885b0e08f7 | |||
| f9647eaf97 | |||
| c55b6e8f0d | |||
| 2e6b8bb377 | |||
| 96a967f0d9 | |||
| c09c332758 | |||
| 6a5c7b4384 | |||
| cd2be8490f | |||
| 1b0f2ee0df | |||
| 6eca7efb0c | |||
| f8b6c1c45c | |||
| 9076f5896d | |||
| 7022739c55 | |||
| 94a3ceb9e0 | |||
| 072feb5c52 | |||
| 46682e1de7 | |||
| a7c068fd45 | |||
| 68a6051dca | |||
| f3544a581b | |||
| 568485a62e | |||
| 027fdbd53e | |||
| 7d6bf6ec34 | |||
| 8002147b91 | |||
| 4506e76a1a | |||
| 98b59ff445 | |||
| ef75c7d0ce | |||
| dd4fd92393 | |||
| 5b6b185da4 | |||
| aaba70761b | |||
| 66a4eab5a1 | |||
| 85810a1ebb | |||
| 25377a495e | |||
| 8676bc8e82 | |||
| 7bcfc7898c | |||
| fafdc952ac | |||
| 5e48cb976b | |||
| 4bd749ce9c | |||
| 8ffa6231c2 | |||
| 781e6cfb9e | |||
| 59bbec7952 | |||
| 27893bff24 | |||
| 3d68d93789 | |||
| 46e4b9a1f6 | |||
| 6625039561 | |||
| f70390d585 | |||
| 0fdb054234 | |||
| 9d7add9774 | |||
| 0a335b372a | |||
| c686582184 | |||
| b5dc107766 | |||
| 59d2b9769a | |||
| ebd60fbb2e | |||
| f62641bd21 | |||
| 45b9959ef4 | |||
| 6b30b8d12e | |||
| 55e0ef65fd | |||
| 71a90a8851 | |||
| 8d9908369e | |||
| 5aede274c8 | |||
| a9ad7a7fa8 | |||
| 429eb7aca8 | |||
| 9fd4c1f71d | |||
| 89376e85a1 | |||
| cd559abbb0 | |||
| 2e46676ecd | |||
| d6286006b8 | |||
| 6d95a5f094 | |||
| c970dfc4ed | |||
| 4426ed52eb | |||
| d408edfdeb | |||
| ba7a680cfd | |||
| 9fd5f55e9d | |||
| 4ea630ecca | |||
| 33e2b52dc3 | |||
| 70e5f5edf0 | |||
| 627f02409c | |||
| 5e0b998b70 | |||
| 07f59788a3 | |||
| af1cd55bc9 | |||
| 3ec62789c6 | |||
| 5c1a524875 | |||
| 596186deaa | |||
| 935ab63899 | |||
| 055a8b28cc | |||
| baf28534e1 | |||
| 3fa4ae0bdb | |||
| 7ddf4932f8 | |||
| 9e3b36da69 | |||
| 7de324e922 | |||
| ed44cd9947 | |||
| ab6dfea689 | |||
| 0c3821dbe6 | |||
| fc2c908043 | |||
| 06069e89ba | |||
| 3599d8b2c1 | |||
| 08fb3745c8 | |||
| 96eddbbaa0 | |||
| af7e3666de | |||
| a549e908be | |||
| d27a7ca5c5 | |||
| 6459ad0125 | |||
| 749da2151e | |||
| 23a33f7ffe | |||
| 85e2529289 | |||
| 0f6454ea41 | |||
| d4e93a92a7 | |||
| 5f4902305c | |||
| 2b6741b5dc | |||
| 423ee475f4 | |||
| f90227bc29 | |||
| 79ef9a50d1 | |||
| 09a16c85b7 | |||
| ae287d0551 | |||
| c8f18a00dd | |||
| 69b207177c | |||
| e23b35f75e | |||
| 67c3ec480d | |||
| 2cd9e6d92d | |||
| a59571aae8 | |||
| f5c3ce57de | |||
| 3439dbda42 | |||
| 0299213dfa | |||
| eb7199d5a9 | |||
| 7073e3d952 | |||
| bad8a374af | |||
| 44b585aab8 | |||
| e2a54c7b89 | |||
| 106b41d2ee | |||
| 6565d0a82d | |||
| 861bae24d6 | |||
| 99298d3f28 | |||
| e25e37c871 | |||
| 3a7125ee88 | |||
| 21eae233f3 | |||
| 5dee63fe54 | |||
| 7bef443ead | |||
| 5a9a1e87d3 | |||
| ecfdf7ae58 | |||
| 920c3ca15e | |||
| d90c32691a | |||
| c1bf39aba7 | |||
| b83a88c1f2 | |||
| c204a6baaf | |||
| 9f0896943f | |||
| c3feaf4f64 | |||
| a6ad738d48 | |||
| 15e3cd3810 | |||
| 0e551ce01b | |||
| cd813285d1 | |||
| 46372268d4 | |||
| 30b32592ed | |||
| cee4f24e93 | |||
| 137d6f7c43 | |||
| da74b68a9c | |||
| a5344abeb0 | |||
| fe431475a2 | |||
| 3bb52bc368 | |||
| 9d185d27a5 | |||
| 518ff49907 | |||
| f960df1424 | |||
| b5cb02937b | |||
| f6ef6a7f3d | |||
| 8be1479357 | |||
| 3d54342687 | |||
| b79cb36335 | |||
| 5da0ee2549 | |||
| 65afc41197 | |||
| d0bd26c7e0 | |||
| ec921aa705 | |||
| 692bfdd943 | |||
| e057f6f12e | |||
| 0e800dfd1c | |||
| 9ced8e3407 | |||
| 3c3ed3c94b | |||
| 757f75657f | |||
| 23b840f3ff | |||
| fae10b17ed | |||
| f4c981ab36 | |||
| 15f012a65a | |||
| 2a9b936d0d | |||
| 308cb755fe | |||
| 0912ac0fd8 | |||
| b2c21e0542 | |||
| 57bf1f2e3a | |||
| 828d65ad44 | |||
| ec7f827123 | |||
| b8eb935d9a | |||
| caa1be50cf | |||
| 533677baff | |||
| d041105e1e | |||
| 7bbe86c901 | |||
| 868bc86143 | |||
| f23fe12e7b | |||
| 5e00722c3b | |||
| 6341081fde | |||
| b2eed1ffc7 | |||
| 5a2e98d35b | |||
| 82e5545904 | |||
| 16aa24150e | |||
| 29f5ef991e | |||
| 44354f7c8f | |||
| edb9449fbc | |||
| f94c7863b7 | |||
| 28c1e271ab | |||
| 93399a8bda | |||
| a6c33fb803 | |||
| 1ecd4a4327 | |||
| 71cd2572a2 | |||
| 800e7ef6f1 | |||
| f973a618c6 | |||
| 84b0d246b5 | |||
| 893559baa6 | |||
| e21c755e62 | |||
| 3dc1707f3c | |||
| da6a03144a | |||
| 544c19c959 | |||
| b41b138f93 | |||
| d1a3a1d247 | |||
| c58f0909bb | |||
| 7514237a84 | |||
| c224c51f84 | |||
| 74745f6d54 | |||
| 8f52940d98 | |||
| 81726e9554 | |||
| 555efac0c4 | |||
| a4774c2401 | |||
| 9bf1595f29 | |||
| c9b15a21a5 | |||
| 4a1145fd28 | |||
| 2ef6c06e32 | |||
| 82625d4548 | |||
| e821656871 | |||
| bc23b55429 | |||
| 0fe24d767c | |||
| 42baec2a38 | |||
| 6586b429b7 | |||
| 8029fb7540 | |||
| a78b6e03c8 | |||
| 4ff6a28c67 | |||
| 88927c9ddc | |||
| 3e93aa59b4 | |||
| 700724ce5b | |||
| bead4f8ee5 | |||
| e309e15af4 | |||
| 5aa88932f7 | |||
| f8d3a893ca | |||
| a6f048cc2d | |||
| d66e5140e7 | |||
| e541fdcae3 | |||
| efaf492e41 | |||
| a26031a04f | |||
| dc8299ade6 | |||
| 05b3ad1280 | |||
| aca029955e | |||
| 26f92e43f6 | |||
| 58ce77e4df | |||
| d3dd60fb2c | |||
| c14b10a032 | |||
| 91a99a5df3 | |||
| 52a7ba871a | |||
| 2fd50a9f68 | |||
| 2aaca29741 | |||
| 513e2c3511 | |||
| 7643b98c01 | |||
| 0c1deecb5b | |||
| 91df63802a | |||
| 42ddfbd52f | |||
| 9396502b3d | |||
| ee9994c2c8 | |||
| edb1c672eb | |||
| dd05b39ea9 | |||
| fa553a447a | |||
| 68e2f84e21 | |||
| c7d821e72f | |||
| 8efb17a56c | |||
| e0fce50e1a | |||
| fade50eef5 | |||
| 1e79bc5500 | |||
| 2e70466891 | |||
| f61fdc0aba | |||
| fb6418cc80 | |||
| dd7f46f580 | |||
| f0e804ce59 | |||
| a8c88feb1f | |||
| 0e1a07972b | |||
| e3384538a5 | |||
| 25b9d06935 | |||
| 5c18e815dd | |||
| 5f58e9e41d | |||
| b1fbc169dc | |||
| 651a8e30d4 | |||
| 314906f74d | |||
| 327a640b83 | |||
| 2a20e7262d | |||
| 15a89785c6 | |||
| 0cff85092b | |||
| 437e18d2cc | |||
| 49cb07704b | |||
| e18f44a136 | |||
| 6e76767c26 | |||
| 48792ceebe | |||
| 1342c55a1f | |||
| 0ff71193d6 | |||
| 32025eb4e7 | |||
| d338d42912 | |||
| 2f57c10a5a | |||
| 3db7334ba4 | |||
| 84ce8ba80c | |||
| 757f0ded69 | |||
| 555ba4c007 | |||
| d6f12b302a | |||
| c3031fbc39 | |||
| bab5a55c3b | |||
| af47156557 | |||
| f688898d96 | |||
| 5c5a0bb4f1 | |||
| 8a758448a0 | |||
| 70ddeeb5f1 | |||
| bfcef65da3 | |||
| e4f33e553a | |||
| 937aba27b1 | |||
| d8ddde6e7a | |||
| 90c28b6629 | |||
| 473fa15f7c | |||
| 06c1e1b880 | |||
| 1e1abba82f | |||
| f12465222f | |||
| 8871a57a2c | |||
| 55ed75e36a | |||
| 1e5e7b4cca | |||
| b8db6daa94 | |||
| e98443a5dd | |||
| 1d47e65b1e | |||
| fbbecce6e6 | |||
| 687c215e73 | |||
| bc947f5c10 | |||
| 9a3bae2f5c | |||
| ea2539e21c | |||
| 3b4808a360 | |||
| c7202763ec | |||
| 4263645966 | |||
| afdbe20f80 | |||
| e017b2859d | |||
| d3f1e8dae2 | |||
| 3af7380f37 | |||
| 58b30d59e9 |
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# OS / System
|
||||||
|
.DS_Store
|
||||||
|
._*
|
||||||
|
thumbs.db
|
||||||
|
|
||||||
|
# Build & Tooling
|
||||||
|
.artifacts/
|
||||||
|
.worktrees/
|
||||||
|
.build/
|
||||||
|
|
||||||
18
.idea/.gitignore
generated
vendored
Normal file
18
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# User-specific and local IDE state files
|
||||||
|
/workspace.xml
|
||||||
|
/shelf/
|
||||||
|
/caches/
|
||||||
|
/deviceManager.xml
|
||||||
|
/deploymentTargetSelector.xml
|
||||||
|
/appInsightsSettings.xml
|
||||||
|
/planningMode.xml
|
||||||
|
/studiobot.xml
|
||||||
|
/markdown.xml
|
||||||
|
/assetWizardSettings.xml
|
||||||
|
/usage.statistics.xml
|
||||||
|
/tasks.xml
|
||||||
|
|
||||||
|
# IntelliJ module files generated by Gradle
|
||||||
|
*.iml
|
||||||
|
*.iws
|
||||||
|
/modules.xml
|
||||||
1
.idea/.name
generated
Normal file
1
.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
MonsterCards
|
||||||
6
.idea/AndroidProjectSystem.xml
generated
Normal file
6
.idea/AndroidProjectSystem.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AndroidProjectSystem">
|
||||||
|
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/compiler.xml
generated
Normal file
6
.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<bytecodeTargetLevel target="21" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
18
.idea/gradle.xml
generated
Normal file
18
.idea/gradle.xml
generated
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||||
|
<component name="GradleSettings">
|
||||||
|
<option name="linkedExternalProjectsSettings">
|
||||||
|
<GradleProjectSettings>
|
||||||
|
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$/Android" />
|
||||||
|
<option name="modules">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$/Android" />
|
||||||
|
<option value="$PROJECT_DIR$/Android/app" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</GradleProjectSettings>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
60
.idea/misc.xml
generated
Normal file
60
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="NullableNotNullManager">
|
||||||
|
<option name="myDefaultNullable" value="androidx.annotation.Nullable" />
|
||||||
|
<option name="myDefaultNotNull" value="androidx.annotation.NonNull" />
|
||||||
|
<option name="myOrdered" value="true" />
|
||||||
|
<option name="myNullables">
|
||||||
|
<value>
|
||||||
|
<list size="17">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
|
||||||
|
<item index="1" class="java.lang.String" itemvalue="io.reactivex.annotations.Nullable" />
|
||||||
|
<item index="2" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.Nullable" />
|
||||||
|
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
||||||
|
<item index="4" class="java.lang.String" itemvalue="android.annotation.Nullable" />
|
||||||
|
<item index="5" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
|
||||||
|
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
|
||||||
|
<item index="7" class="java.lang.String" itemvalue="org.jspecify.annotations.Nullable" />
|
||||||
|
<item index="8" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
||||||
|
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
|
||||||
|
<item index="10" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
|
||||||
|
<item index="11" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
|
||||||
|
<item index="12" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" />
|
||||||
|
<item index="13" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
||||||
|
<item index="14" class="java.lang.String" itemvalue="jakarta.annotation.Nullable" />
|
||||||
|
<item index="15" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
||||||
|
<item index="16" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="myNotNulls">
|
||||||
|
<value>
|
||||||
|
<list size="16">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
|
||||||
|
<item index="1" class="java.lang.String" itemvalue="io.reactivex.annotations.NonNull" />
|
||||||
|
<item index="2" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.NonNull" />
|
||||||
|
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
|
||||||
|
<item index="4" class="java.lang.String" itemvalue="android.annotation.NonNull" />
|
||||||
|
<item index="5" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
|
||||||
|
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
|
||||||
|
<item index="7" class="java.lang.String" itemvalue="org.jspecify.annotations.NonNull" />
|
||||||
|
<item index="8" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
|
||||||
|
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
|
||||||
|
<item index="10" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
|
||||||
|
<item index="11" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
|
||||||
|
<item index="12" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" />
|
||||||
|
<item index="13" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
|
||||||
|
<item index="14" class="java.lang.String" itemvalue="jakarta.annotation.Nonnull" />
|
||||||
|
<item index="15" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectType">
|
||||||
|
<option name="id" value="Android" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
17
.idea/runConfigurations.xml
generated
Normal file
17
.idea/runConfigurations.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RunConfigurationProducerService">
|
||||||
|
<option name="ignoredProducers">
|
||||||
|
<set>
|
||||||
|
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
68
.idea/runConfigurations/app.xml
generated
Normal file
68
.idea/runConfigurations/app.xml
generated
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="app" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false" focusToolWindowBeforeRun="true">
|
||||||
|
<module name="MonsterCards.app" />
|
||||||
|
<option name="ANDROID_RUN_CONFIGURATION_SCHEMA_VERSION" value="1" />
|
||||||
|
<option name="DEPLOY" value="true" />
|
||||||
|
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
|
||||||
|
<option name="DEPLOY_AS_INSTANT" value="false" />
|
||||||
|
<option name="ARTIFACT_NAME" value="" />
|
||||||
|
<option name="PM_INSTALL_OPTIONS" value="" />
|
||||||
|
<option name="ALL_USERS" value="false" />
|
||||||
|
<option name="ALWAYS_INSTALL_WITH_PM" value="false" />
|
||||||
|
<option name="ALLOW_ASSUME_VERIFIED" value="false" />
|
||||||
|
<option name="CLEAR_APP_STORAGE" value="false" />
|
||||||
|
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
|
||||||
|
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
|
||||||
|
<option name="MODE" value="default_activity" />
|
||||||
|
<option name="RESTORE_ENABLED" value="false" />
|
||||||
|
<option name="RESTORE_FILE" value="" />
|
||||||
|
<option name="RESTORE_FRESH_INSTALL_ONLY" value="false" />
|
||||||
|
<option name="CLEAR_LOGCAT" value="true" />
|
||||||
|
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="true" />
|
||||||
|
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
|
||||||
|
<option name="DEBUGGER_TYPE" value="Auto" />
|
||||||
|
<Auto>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="true" />
|
||||||
|
</Auto>
|
||||||
|
<Hybrid>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
</Hybrid>
|
||||||
|
<Java>
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
</Java>
|
||||||
|
<Native>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
</Native>
|
||||||
|
<Profilers>
|
||||||
|
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="LeakCanary" />
|
||||||
|
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
|
||||||
|
</Profilers>
|
||||||
|
<option name="DEEP_LINK" value="" />
|
||||||
|
<option name="ACTIVITY" value="" />
|
||||||
|
<option name="ACTIVITY_CLASS" value="" />
|
||||||
|
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
|
||||||
|
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
15
Android/.gitignore
vendored
Normal file
15
Android/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Build outputs
|
||||||
|
build/
|
||||||
|
app/build/
|
||||||
|
.externalNativeBuild/
|
||||||
|
.cxx/
|
||||||
|
captures/
|
||||||
|
local.properties
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
# Android / Gradle caches
|
||||||
|
.gradle/
|
||||||
|
.artifacts/
|
||||||
|
|
||||||
|
# System
|
||||||
|
.DS_Store
|
||||||
18
Android/.idea/.gitignore
generated
vendored
Normal file
18
Android/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# User-specific and local IDE state files
|
||||||
|
/workspace.xml
|
||||||
|
/shelf/
|
||||||
|
/caches/
|
||||||
|
/deviceManager.xml
|
||||||
|
/deploymentTargetSelector.xml
|
||||||
|
/appInsightsSettings.xml
|
||||||
|
/planningMode.xml
|
||||||
|
/studiobot.xml
|
||||||
|
/markdown.xml
|
||||||
|
/assetWizardSettings.xml
|
||||||
|
/usage.statistics.xml
|
||||||
|
/tasks.xml
|
||||||
|
|
||||||
|
# IntelliJ module files generated by Gradle
|
||||||
|
*.iml
|
||||||
|
*.iws
|
||||||
|
/modules.xml
|
||||||
1
Android/.idea/.name
generated
Normal file
1
Android/.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
MonsterCards
|
||||||
6
Android/.idea/AndroidProjectSystem.xml
generated
Normal file
6
Android/.idea/AndroidProjectSystem.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AndroidProjectSystem">
|
||||||
|
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
23
Android/.idea/appInsightsSettings.xml
generated
Normal file
23
Android/.idea/appInsightsSettings.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AppInsightsSettings">
|
||||||
|
<option name="tabSettings">
|
||||||
|
<map>
|
||||||
|
<entry key="Android vitals">
|
||||||
|
<value>
|
||||||
|
<InsightsFilterSettings>
|
||||||
|
<option name="connection">
|
||||||
|
<ConnectionSetting>
|
||||||
|
<option name="appId" value="com.planbgamestudios.downtosize" />
|
||||||
|
</ConnectionSetting>
|
||||||
|
</option>
|
||||||
|
<option name="signal" value="SIGNAL_UNSPECIFIED" />
|
||||||
|
<option name="timeIntervalDays" value="SEVEN_DAYS" />
|
||||||
|
<option name="visibilityType" value="ALL" />
|
||||||
|
</InsightsFilterSettings>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
116
Android/.idea/codeStyles/Project.xml
generated
Normal file
116
Android/.idea/codeStyles/Project.xml
generated
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<codeStyleSettings language="XML">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
|
<arrangement>
|
||||||
|
<rules>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:android</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:id</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>style</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
</rules>
|
||||||
|
</arrangement>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
||||||
6
Android/.idea/compiler.xml
generated
Normal file
6
Android/.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<bytecodeTargetLevel target="21" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
18
Android/.idea/gradle.xml
generated
Normal file
18
Android/.idea/gradle.xml
generated
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||||
|
<component name="GradleSettings">
|
||||||
|
<option name="linkedExternalProjectsSettings">
|
||||||
|
<GradleProjectSettings>
|
||||||
|
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="modules">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
<option value="$PROJECT_DIR$/app" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</GradleProjectSettings>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
7
Android/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
7
Android/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="FieldCanBeLocal" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="TrivialIf" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
30
Android/.idea/jarRepositories.xml
generated
Normal file
30
Android/.idea/jarRepositories.xml
generated
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RemoteRepositoriesConfiguration">
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Maven Central repository" />
|
||||||
|
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="jboss.community" />
|
||||||
|
<option name="name" value="JBoss Community repository" />
|
||||||
|
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="BintrayJCenter" />
|
||||||
|
<option name="name" value="BintrayJCenter" />
|
||||||
|
<option name="url" value="https://jcenter.bintray.com/" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="Google" />
|
||||||
|
<option name="name" value="Google" />
|
||||||
|
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="MavenRepo" />
|
||||||
|
<option name="name" value="MavenRepo" />
|
||||||
|
<option name="url" value="https://repo.maven.apache.org/maven2/" />
|
||||||
|
</remote-repository>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
10
Android/.idea/migrations.xml
generated
Normal file
10
Android/.idea/migrations.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectMigrations">
|
||||||
|
<option name="MigrateToGradleLocalJavaHome">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
60
Android/.idea/misc.xml
generated
Normal file
60
Android/.idea/misc.xml
generated
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="NullableNotNullManager">
|
||||||
|
<option name="myDefaultNullable" value="androidx.annotation.Nullable" />
|
||||||
|
<option name="myDefaultNotNull" value="androidx.annotation.NonNull" />
|
||||||
|
<option name="myOrdered" value="true" />
|
||||||
|
<option name="myNullables">
|
||||||
|
<value>
|
||||||
|
<list size="17">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
|
||||||
|
<item index="1" class="java.lang.String" itemvalue="io.reactivex.annotations.Nullable" />
|
||||||
|
<item index="2" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.Nullable" />
|
||||||
|
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
||||||
|
<item index="4" class="java.lang.String" itemvalue="android.annotation.Nullable" />
|
||||||
|
<item index="5" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
|
||||||
|
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
|
||||||
|
<item index="7" class="java.lang.String" itemvalue="org.jspecify.annotations.Nullable" />
|
||||||
|
<item index="8" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
||||||
|
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
|
||||||
|
<item index="10" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
|
||||||
|
<item index="11" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
|
||||||
|
<item index="12" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" />
|
||||||
|
<item index="13" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
||||||
|
<item index="14" class="java.lang.String" itemvalue="jakarta.annotation.Nullable" />
|
||||||
|
<item index="15" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
||||||
|
<item index="16" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="myNotNulls">
|
||||||
|
<value>
|
||||||
|
<list size="16">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
|
||||||
|
<item index="1" class="java.lang.String" itemvalue="io.reactivex.annotations.NonNull" />
|
||||||
|
<item index="2" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.NonNull" />
|
||||||
|
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
|
||||||
|
<item index="4" class="java.lang.String" itemvalue="android.annotation.NonNull" />
|
||||||
|
<item index="5" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
|
||||||
|
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
|
||||||
|
<item index="7" class="java.lang.String" itemvalue="org.jspecify.annotations.NonNull" />
|
||||||
|
<item index="8" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
|
||||||
|
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
|
||||||
|
<item index="10" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
|
||||||
|
<item index="11" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
|
||||||
|
<item index="12" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" />
|
||||||
|
<item index="13" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
|
||||||
|
<item index="14" class="java.lang.String" itemvalue="jakarta.annotation.Nonnull" />
|
||||||
|
<item index="15" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectType">
|
||||||
|
<option name="id" value="Android" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
20
Android/.idea/runConfigurations.xml
generated
Normal file
20
Android/.idea/runConfigurations.xml
generated
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RunConfigurationProducerService">
|
||||||
|
<option name="ignoredProducers">
|
||||||
|
<set>
|
||||||
|
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
68
Android/.idea/runConfigurations/app.xml
generated
Normal file
68
Android/.idea/runConfigurations/app.xml
generated
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="app" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false" focusToolWindowBeforeRun="true">
|
||||||
|
<module name="MonsterCards.app" />
|
||||||
|
<option name="ANDROID_RUN_CONFIGURATION_SCHEMA_VERSION" value="1" />
|
||||||
|
<option name="DEPLOY" value="true" />
|
||||||
|
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
|
||||||
|
<option name="DEPLOY_AS_INSTANT" value="false" />
|
||||||
|
<option name="ARTIFACT_NAME" value="" />
|
||||||
|
<option name="PM_INSTALL_OPTIONS" value="" />
|
||||||
|
<option name="ALL_USERS" value="false" />
|
||||||
|
<option name="ALWAYS_INSTALL_WITH_PM" value="false" />
|
||||||
|
<option name="ALLOW_ASSUME_VERIFIED" value="false" />
|
||||||
|
<option name="CLEAR_APP_STORAGE" value="false" />
|
||||||
|
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
|
||||||
|
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
|
||||||
|
<option name="MODE" value="default_activity" />
|
||||||
|
<option name="RESTORE_ENABLED" value="false" />
|
||||||
|
<option name="RESTORE_FILE" value="" />
|
||||||
|
<option name="RESTORE_FRESH_INSTALL_ONLY" value="false" />
|
||||||
|
<option name="CLEAR_LOGCAT" value="true" />
|
||||||
|
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="true" />
|
||||||
|
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
|
||||||
|
<option name="DEBUGGER_TYPE" value="Auto" />
|
||||||
|
<Auto>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="true" />
|
||||||
|
</Auto>
|
||||||
|
<Hybrid>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
</Hybrid>
|
||||||
|
<Java>
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
</Java>
|
||||||
|
<Native>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
</Native>
|
||||||
|
<Profilers>
|
||||||
|
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="LeakCanary" />
|
||||||
|
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
|
||||||
|
</Profilers>
|
||||||
|
<option name="DEEP_LINK" value="" />
|
||||||
|
<option name="ACTIVITY" value="" />
|
||||||
|
<option name="ACTIVITY_CLASS" value="" />
|
||||||
|
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
|
||||||
|
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
17
Android/.idea/saveactions_settings.xml
generated
Normal file
17
Android/.idea/saveactions_settings.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="SaveActionSettings">
|
||||||
|
<option name="actions">
|
||||||
|
<set>
|
||||||
|
<option value="activate" />
|
||||||
|
<option value="activateOnShortcut" />
|
||||||
|
<option value="activateOnBatch" />
|
||||||
|
<option value="noActionIfCompileErrors" />
|
||||||
|
<option value="organizeImports" />
|
||||||
|
<option value="reformat" />
|
||||||
|
<option value="rearrange" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
<option name="configurationPath" value="" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
Android/.idea/vcs.xml
generated
Normal file
6
Android/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
1
Android/app/.gitignore
vendored
Normal file
1
Android/app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
105
Android/app/build.gradle
Normal file
105
Android/app/build.gradle
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
plugins {
|
||||||
|
id 'com.android.application'
|
||||||
|
id 'androidx.navigation.safeargs'
|
||||||
|
}
|
||||||
|
|
||||||
|
Properties properties = new Properties()
|
||||||
|
def propertiesFile = project.rootProject.file('local.properties')
|
||||||
|
if (propertiesFile.exists()) {
|
||||||
|
properties.load(propertiesFile.newDataInputStream())
|
||||||
|
}
|
||||||
|
def appCenterLocalSecret = properties.getProperty('appCenter.localSecret')
|
||||||
|
def appCenterEnvSecret = System.getenv('APPCENTER_SECRET')
|
||||||
|
def appCenterSecret = appCenterLocalSecret != null ? appCenterLocalSecret : appCenterEnvSecret != null ? appCenterEnvSecret : ""
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace "com.majinnaibu.monstercards"
|
||||||
|
compileSdk 37
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "com.majinnaibu.monstercards"
|
||||||
|
minSdkVersion 24
|
||||||
|
//noinspection OldTargetApi
|
||||||
|
targetSdk 36
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
buildConfigField "String", "APPCENTER_SECRET", "\"${appCenterSecret}\""
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
javaCompileOptions {
|
||||||
|
annotationProcessorOptions {
|
||||||
|
arguments += ["room.schemaLocation": "$projectDir/schemas".toString()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
// Enables code shrinking, obfuscation, and optimization for only
|
||||||
|
// your project's release build type.
|
||||||
|
minifyEnabled true
|
||||||
|
|
||||||
|
// Enables resource shrinking, which is performed by the
|
||||||
|
// Android Gradle plugin.
|
||||||
|
shrinkResources true
|
||||||
|
|
||||||
|
// Includes the default ProGuard rules files that are packaged with
|
||||||
|
// the Android Gradle plugin. To learn more, go to the section about
|
||||||
|
// R8 configuration files.
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_21
|
||||||
|
targetCompatibility JavaVersion.VERSION_21
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding true
|
||||||
|
buildConfig true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// Included libs
|
||||||
|
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||||
|
|
||||||
|
// Google
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.8.0'
|
||||||
|
implementation 'com.google.android.material:material:1.14.0'
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:2.2.2'
|
||||||
|
implementation "androidx.navigation:navigation-fragment:2.10.1"
|
||||||
|
implementation "androidx.navigation:navigation-ui:2.10.1"
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||||
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
|
implementation 'androidx.recyclerview:recyclerview:1.4.0'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.11.0'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.11.0'
|
||||||
|
|
||||||
|
// Testing
|
||||||
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
androidTestImplementation 'androidx.test.ext:junit:1.3.0'
|
||||||
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0'
|
||||||
|
|
||||||
|
// Room DB
|
||||||
|
implementation "io.reactivex.rxjava3:rxjava:3.1.12"
|
||||||
|
implementation "io.reactivex.rxjava3:rxandroid:3.0.2"
|
||||||
|
implementation "androidx.room:room-runtime:2.8.5"
|
||||||
|
annotationProcessor "androidx.room:room-compiler:2.8.5"
|
||||||
|
implementation "androidx.room:room-rxjava3:2.8.5"
|
||||||
|
//testImplementation "androidx.room:room-testing:2.8.5"
|
||||||
|
|
||||||
|
// AppCenter
|
||||||
|
debugImplementation "com.microsoft.appcenter:appcenter-analytics:5.0.6"
|
||||||
|
debugImplementation "com.microsoft.appcenter:appcenter-crashes:5.0.6"
|
||||||
|
|
||||||
|
// Other 3rd Party
|
||||||
|
implementation 'com.atlassian.commonmark:commonmark:0.17.0'
|
||||||
|
implementation "com.google.code.gson:gson:2.14.0"
|
||||||
|
}
|
||||||
32
Android/app/proguard-rules.pro
vendored
Normal file
32
Android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
|
|
||||||
|
-keep enum com.majinnaibu.monstercards.data.enums.AbilityScore
|
||||||
|
-keep enum com.majinnaibu.monstercards.data.enums.ProficiencyType
|
||||||
|
-keep enum com.majinnaibu.monstercards.data.enums.AdvantageType
|
||||||
|
-keep enum com.majinnaibu.monstercards.data.enums.TraitType
|
||||||
|
-keep enum com.majinnaibu.monstercards.data.enums.StringType
|
||||||
|
-keepclassmembers,allowoptimization enum * {
|
||||||
|
<fields>;
|
||||||
|
public static **[] values();
|
||||||
|
public static ** valueOf(java.lang.String);
|
||||||
|
}
|
||||||
@@ -0,0 +1,440 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 1,
|
||||||
|
"identityHash": "db1293d2f490940b55ca1f4f56b21b1a",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "Monster",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `size` TEXT NOT NULL DEFAULT '', `type` TEXT NOT NULL DEFAULT '', `subtype` TEXT NOT NULL DEFAULT '', `alignment` TEXT NOT NULL DEFAULT '', `strength_score` INTEGER NOT NULL DEFAULT 10, `strength_saving_throw_advantage` TEXT DEFAULT 'none', `strength_saving_throw_proficiency` TEXT DEFAULT 'none', `dexterity_score` INTEGER NOT NULL DEFAULT 10, `dexterity_saving_throw_advantage` TEXT DEFAULT 'none', `dexterity_saving_throw_proficiency` TEXT DEFAULT 'none', `constitution_score` INTEGER NOT NULL DEFAULT 10, `constitution_saving_throw_advantage` TEXT DEFAULT 'none', `constitution_saving_throw_proficiency` TEXT DEFAULT 'none', `intelligence_score` INTEGER NOT NULL DEFAULT 10, `intelligence_saving_throw_advantage` TEXT DEFAULT 'none', `intelligence_saving_throw_proficiency` TEXT DEFAULT 'none', `wisdom_score` INTEGER NOT NULL DEFAULT 10, `wisdom_saving_throw_advantage` TEXT DEFAULT 'none', `wisdom_saving_throw_proficiency` TEXT DEFAULT 'none', `charisma_score` INTEGER NOT NULL DEFAULT 10, `charisma_saving_throw_advantage` TEXT DEFAULT 'none', `charisma_saving_throw_proficiency` TEXT DEFAULT 'none', `armor_type` TEXT DEFAULT 'none', `shield_bonus` INTEGER NOT NULL DEFAULT 0, `natural_armor_bonus` INTEGER NOT NULL DEFAULT 0, `other_armor_description` TEXT DEFAULT '', `hit_dice` INTEGER NOT NULL DEFAULT 1, `has_custom_hit_points` INTEGER NOT NULL, `custom_hit_points_description` TEXT DEFAULT '', `walk_speed` INTEGER NOT NULL DEFAULT 0, `burrow_speed` INTEGER NOT NULL DEFAULT 0, `climb_speed` INTEGER NOT NULL DEFAULT 0, `fly_speed` INTEGER NOT NULL DEFAULT 0, `can_hover` INTEGER NOT NULL DEFAULT false, `swim_speed` INTEGER NOT NULL DEFAULT 0, `has_custom_speed` INTEGER NOT NULL DEFAULT false, `custom_speed_description` TEXT, `challenge_rating` TEXT DEFAULT '1', `custom_challenge_rating_description` TEXT DEFAULT '', `custom_proficiency_bonus` INTEGER NOT NULL DEFAULT 0, `blindsight_range` INTEGER NOT NULL DEFAULT 0, `is_blind_beyond_blindsight_range` INTEGER NOT NULL DEFAULT false, `darkvision_range` INTEGER NOT NULL DEFAULT 0, `tremorsense_range` INTEGER NOT NULL DEFAULT 0, `truesight_range` INTEGER NOT NULL DEFAULT 0, `telepathy_range` INTEGER NOT NULL DEFAULT 0, `understands_but_description` TEXT DEFAULT '', `skills` TEXT, `damage_immunities` TEXT, `damage_resistances` TEXT, `damage_vulnerabilities` TEXT, `condition_immunities` TEXT, `languages` TEXT, `abilities` TEXT, `actions` TEXT, `reactions` TEXT, `lair_actions` TEXT, `legendary_actions` TEXT, `regional_actions` TEXT, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subtype",
|
||||||
|
"columnName": "subtype",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alignment",
|
||||||
|
"columnName": "alignment",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthScore",
|
||||||
|
"columnName": "strength_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthSavingThrowAdvantage",
|
||||||
|
"columnName": "strength_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthSavingThrowProficiency",
|
||||||
|
"columnName": "strength_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexterityScore",
|
||||||
|
"columnName": "dexterity_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexteritySavingThrowAdvantage",
|
||||||
|
"columnName": "dexterity_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexteritySavingThrowProficiency",
|
||||||
|
"columnName": "dexterity_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionScore",
|
||||||
|
"columnName": "constitution_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionSavingThrowAdvantage",
|
||||||
|
"columnName": "constitution_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionSavingThrowProficiency",
|
||||||
|
"columnName": "constitution_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceScore",
|
||||||
|
"columnName": "intelligence_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceSavingThrowAdvantage",
|
||||||
|
"columnName": "intelligence_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceSavingThrowProficiency",
|
||||||
|
"columnName": "intelligence_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomScore",
|
||||||
|
"columnName": "wisdom_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomSavingThrowAdvantage",
|
||||||
|
"columnName": "wisdom_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomSavingThrowProficiency",
|
||||||
|
"columnName": "wisdom_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaScore",
|
||||||
|
"columnName": "charisma_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaSavingThrowAdvantage",
|
||||||
|
"columnName": "charisma_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaSavingThrowProficiency",
|
||||||
|
"columnName": "charisma_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "armorType",
|
||||||
|
"columnName": "armor_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "shieldBonus",
|
||||||
|
"columnName": "shield_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "naturalArmorBonus",
|
||||||
|
"columnName": "natural_armor_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "otherArmorDescription",
|
||||||
|
"columnName": "other_armor_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hitDice",
|
||||||
|
"columnName": "hit_dice",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hasCustomHP",
|
||||||
|
"columnName": "has_custom_hit_points",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customHPDescription",
|
||||||
|
"columnName": "custom_hit_points_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "walkSpeed",
|
||||||
|
"columnName": "walk_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "burrowSpeed",
|
||||||
|
"columnName": "burrow_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "climbSpeed",
|
||||||
|
"columnName": "climb_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "flySpeed",
|
||||||
|
"columnName": "fly_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "canHover",
|
||||||
|
"columnName": "can_hover",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "swimSpeed",
|
||||||
|
"columnName": "swim_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hasCustomSpeed",
|
||||||
|
"columnName": "has_custom_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customSpeedDescription",
|
||||||
|
"columnName": "custom_speed_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "challengeRating",
|
||||||
|
"columnName": "challenge_rating",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'1'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customChallengeRatingDescription",
|
||||||
|
"columnName": "custom_challenge_rating_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customProficiencyBonus",
|
||||||
|
"columnName": "custom_proficiency_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "blindsightRange",
|
||||||
|
"columnName": "blindsight_range",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isBlindBeyondBlindsightRange",
|
||||||
|
"columnName": "is_blind_beyond_blindsight_range",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "darkvisionRange",
|
||||||
|
"columnName": "darkvision_range",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tremorsenseRange",
|
||||||
|
"columnName": "tremorsense_range",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "truesightRange",
|
||||||
|
"columnName": "truesight_range",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "telepathyRange",
|
||||||
|
"columnName": "telepathy_range",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "understandsButDescription",
|
||||||
|
"columnName": "understands_but_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "skills",
|
||||||
|
"columnName": "skills",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageImmunities",
|
||||||
|
"columnName": "damage_immunities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageResistances",
|
||||||
|
"columnName": "damage_resistances",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageVulnerabilities",
|
||||||
|
"columnName": "damage_vulnerabilities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "conditionImmunities",
|
||||||
|
"columnName": "condition_immunities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "languages",
|
||||||
|
"columnName": "languages",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "abilities",
|
||||||
|
"columnName": "abilities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "actions",
|
||||||
|
"columnName": "actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "reactions",
|
||||||
|
"columnName": "reactions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lairActions",
|
||||||
|
"columnName": "lair_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "legendaryActions",
|
||||||
|
"columnName": "legendary_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "regionalActions",
|
||||||
|
"columnName": "regional_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'db1293d2f490940b55ca1f4f56b21b1a')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,499 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 2,
|
||||||
|
"identityHash": "6f1e7a2b2ab96fc4be4da1657a7a0138",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "monsters",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `size` TEXT NOT NULL DEFAULT '', `type` TEXT NOT NULL DEFAULT '', `subtype` TEXT NOT NULL DEFAULT '', `alignment` TEXT NOT NULL DEFAULT '', `strength_score` INTEGER NOT NULL DEFAULT 10, `strength_saving_throw_advantage` TEXT DEFAULT 'none', `strength_saving_throw_proficiency` TEXT DEFAULT 'none', `dexterity_score` INTEGER NOT NULL DEFAULT 10, `dexterity_saving_throw_advantage` TEXT DEFAULT 'none', `dexterity_saving_throw_proficiency` TEXT DEFAULT 'none', `constitution_score` INTEGER NOT NULL DEFAULT 10, `constitution_saving_throw_advantage` TEXT DEFAULT 'none', `constitution_saving_throw_proficiency` TEXT DEFAULT 'none', `intelligence_score` INTEGER NOT NULL DEFAULT 10, `intelligence_saving_throw_advantage` TEXT DEFAULT 'none', `intelligence_saving_throw_proficiency` TEXT DEFAULT 'none', `wisdom_score` INTEGER NOT NULL DEFAULT 10, `wisdom_saving_throw_advantage` TEXT DEFAULT 'none', `wisdom_saving_throw_proficiency` TEXT DEFAULT 'none', `charisma_score` INTEGER NOT NULL DEFAULT 10, `charisma_saving_throw_advantage` TEXT DEFAULT 'none', `charisma_saving_throw_proficiency` TEXT DEFAULT 'none', `armor_type` TEXT DEFAULT 'none', `shield_bonus` INTEGER NOT NULL DEFAULT 0, `natural_armor_bonus` INTEGER NOT NULL DEFAULT 0, `other_armor_description` TEXT DEFAULT '', `hit_dice` INTEGER NOT NULL DEFAULT 1, `has_custom_hit_points` INTEGER NOT NULL, `custom_hit_points_description` TEXT DEFAULT '', `walk_speed` INTEGER NOT NULL DEFAULT 0, `burrow_speed` INTEGER NOT NULL DEFAULT 0, `climb_speed` INTEGER NOT NULL DEFAULT 0, `fly_speed` INTEGER NOT NULL DEFAULT 0, `can_hover` INTEGER NOT NULL DEFAULT false, `swim_speed` INTEGER NOT NULL DEFAULT 0, `has_custom_speed` INTEGER NOT NULL DEFAULT false, `custom_speed_description` TEXT, `challenge_rating` TEXT DEFAULT '1', `custom_challenge_rating_description` TEXT DEFAULT '', `custom_proficiency_bonus` INTEGER NOT NULL DEFAULT 0, `blindsight_range` INTEGER NOT NULL DEFAULT 0, `is_blind_beyond_blindsight_range` INTEGER NOT NULL DEFAULT false, `darkvision_range` INTEGER NOT NULL DEFAULT 0, `tremorsense_range` INTEGER NOT NULL DEFAULT 0, `truesight_range` INTEGER NOT NULL DEFAULT 0, `telepathy_range` INTEGER NOT NULL DEFAULT 0, `understands_but_description` TEXT DEFAULT '', `skills` TEXT, `damage_immunities` TEXT, `damage_resistances` TEXT, `damage_vulnerabilities` TEXT, `condition_immunities` TEXT, `languages` TEXT, `abilities` TEXT, `actions` TEXT, `reactions` TEXT, `lair_actions` TEXT, `legendary_actions` TEXT, `regional_actions` TEXT, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subtype",
|
||||||
|
"columnName": "subtype",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alignment",
|
||||||
|
"columnName": "alignment",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthScore",
|
||||||
|
"columnName": "strength_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthSavingThrowAdvantage",
|
||||||
|
"columnName": "strength_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthSavingThrowProficiency",
|
||||||
|
"columnName": "strength_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexterityScore",
|
||||||
|
"columnName": "dexterity_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexteritySavingThrowAdvantage",
|
||||||
|
"columnName": "dexterity_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexteritySavingThrowProficiency",
|
||||||
|
"columnName": "dexterity_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionScore",
|
||||||
|
"columnName": "constitution_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionSavingThrowAdvantage",
|
||||||
|
"columnName": "constitution_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionSavingThrowProficiency",
|
||||||
|
"columnName": "constitution_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceScore",
|
||||||
|
"columnName": "intelligence_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceSavingThrowAdvantage",
|
||||||
|
"columnName": "intelligence_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceSavingThrowProficiency",
|
||||||
|
"columnName": "intelligence_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomScore",
|
||||||
|
"columnName": "wisdom_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomSavingThrowAdvantage",
|
||||||
|
"columnName": "wisdom_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomSavingThrowProficiency",
|
||||||
|
"columnName": "wisdom_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaScore",
|
||||||
|
"columnName": "charisma_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaSavingThrowAdvantage",
|
||||||
|
"columnName": "charisma_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaSavingThrowProficiency",
|
||||||
|
"columnName": "charisma_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "armorType",
|
||||||
|
"columnName": "armor_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "shieldBonus",
|
||||||
|
"columnName": "shield_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "naturalArmorBonus",
|
||||||
|
"columnName": "natural_armor_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "otherArmorDescription",
|
||||||
|
"columnName": "other_armor_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hitDice",
|
||||||
|
"columnName": "hit_dice",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hasCustomHP",
|
||||||
|
"columnName": "has_custom_hit_points",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customHPDescription",
|
||||||
|
"columnName": "custom_hit_points_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "walkSpeed",
|
||||||
|
"columnName": "walk_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "burrowSpeed",
|
||||||
|
"columnName": "burrow_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "climbSpeed",
|
||||||
|
"columnName": "climb_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "flySpeed",
|
||||||
|
"columnName": "fly_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "canHover",
|
||||||
|
"columnName": "can_hover",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "swimSpeed",
|
||||||
|
"columnName": "swim_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hasCustomSpeed",
|
||||||
|
"columnName": "has_custom_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customSpeedDescription",
|
||||||
|
"columnName": "custom_speed_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "challengeRating",
|
||||||
|
"columnName": "challenge_rating",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'1'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customChallengeRatingDescription",
|
||||||
|
"columnName": "custom_challenge_rating_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customProficiencyBonus",
|
||||||
|
"columnName": "custom_proficiency_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "blindsightRange",
|
||||||
|
"columnName": "blindsight_range",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isBlindBeyondBlindsightRange",
|
||||||
|
"columnName": "is_blind_beyond_blindsight_range",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "darkvisionRange",
|
||||||
|
"columnName": "darkvision_range",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tremorsenseRange",
|
||||||
|
"columnName": "tremorsense_range",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "truesightRange",
|
||||||
|
"columnName": "truesight_range",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "telepathyRange",
|
||||||
|
"columnName": "telepathy_range",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "understandsButDescription",
|
||||||
|
"columnName": "understands_but_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "skills",
|
||||||
|
"columnName": "skills",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageImmunities",
|
||||||
|
"columnName": "damage_immunities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageResistances",
|
||||||
|
"columnName": "damage_resistances",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageVulnerabilities",
|
||||||
|
"columnName": "damage_vulnerabilities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "conditionImmunities",
|
||||||
|
"columnName": "condition_immunities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "languages",
|
||||||
|
"columnName": "languages",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "abilities",
|
||||||
|
"columnName": "abilities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "actions",
|
||||||
|
"columnName": "actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "reactions",
|
||||||
|
"columnName": "reactions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lairActions",
|
||||||
|
"columnName": "lair_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "legendaryActions",
|
||||||
|
"columnName": "legendary_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "regionalActions",
|
||||||
|
"columnName": "regional_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ftsVersion": "FTS4",
|
||||||
|
"ftsOptions": {
|
||||||
|
"tokenizer": "simple",
|
||||||
|
"tokenizerArgs": [],
|
||||||
|
"contentTable": "monsters",
|
||||||
|
"languageIdColumnName": "",
|
||||||
|
"matchInfo": "FTS4",
|
||||||
|
"notIndexedColumns": [],
|
||||||
|
"prefixSizes": [],
|
||||||
|
"preferredOrder": "ASC"
|
||||||
|
},
|
||||||
|
"contentSyncTriggers": [
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_UPDATE BEFORE UPDATE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_DELETE BEFORE DELETE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_UPDATE AFTER UPDATE ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_INSERT AFTER INSERT ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END"
|
||||||
|
],
|
||||||
|
"tableName": "monsters_fts",
|
||||||
|
"createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`name` TEXT, `size` TEXT, `type` TEXT, `subtype` TEXT, `alignment` TEXT, content=`monsters`)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subtype",
|
||||||
|
"columnName": "subtype",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alignment",
|
||||||
|
"columnName": "alignment",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6f1e7a2b2ab96fc4be4da1657a7a0138')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,483 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 3,
|
||||||
|
"identityHash": "7c3c3ed79c7002102e7af7cfd21c23e0",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "monsters",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `size` TEXT NOT NULL DEFAULT '', `type` TEXT NOT NULL DEFAULT '', `subtype` TEXT NOT NULL DEFAULT '', `alignment` TEXT NOT NULL DEFAULT '', `strength_score` INTEGER NOT NULL DEFAULT 10, `strength_saving_throw_advantage` TEXT DEFAULT 'none', `strength_saving_throw_proficiency` TEXT DEFAULT 'none', `dexterity_score` INTEGER NOT NULL DEFAULT 10, `dexterity_saving_throw_advantage` TEXT DEFAULT 'none', `dexterity_saving_throw_proficiency` TEXT DEFAULT 'none', `constitution_score` INTEGER NOT NULL DEFAULT 10, `constitution_saving_throw_advantage` TEXT DEFAULT 'none', `constitution_saving_throw_proficiency` TEXT DEFAULT 'none', `intelligence_score` INTEGER NOT NULL DEFAULT 10, `intelligence_saving_throw_advantage` TEXT DEFAULT 'none', `intelligence_saving_throw_proficiency` TEXT DEFAULT 'none', `wisdom_score` INTEGER NOT NULL DEFAULT 10, `wisdom_saving_throw_advantage` TEXT DEFAULT 'none', `wisdom_saving_throw_proficiency` TEXT DEFAULT 'none', `charisma_score` INTEGER NOT NULL DEFAULT 10, `charisma_saving_throw_advantage` TEXT DEFAULT 'none', `charisma_saving_throw_proficiency` TEXT DEFAULT 'none', `armor_type` TEXT DEFAULT 'none', `shield_bonus` INTEGER NOT NULL DEFAULT 0, `natural_armor_bonus` INTEGER NOT NULL DEFAULT 0, `other_armor_description` TEXT DEFAULT '', `hit_dice` INTEGER NOT NULL DEFAULT 1, `has_custom_hit_points` INTEGER NOT NULL, `custom_hit_points_description` TEXT DEFAULT '', `walk_speed` INTEGER NOT NULL DEFAULT 0, `burrow_speed` INTEGER NOT NULL DEFAULT 0, `climb_speed` INTEGER NOT NULL DEFAULT 0, `fly_speed` INTEGER NOT NULL DEFAULT 0, `can_hover` INTEGER NOT NULL DEFAULT false, `swim_speed` INTEGER NOT NULL DEFAULT 0, `has_custom_speed` INTEGER NOT NULL DEFAULT false, `custom_speed_description` TEXT, `challenge_rating` TEXT DEFAULT '1', `custom_challenge_rating_description` TEXT DEFAULT '', `custom_proficiency_bonus` INTEGER NOT NULL DEFAULT 0, `telepathy_range` INTEGER NOT NULL DEFAULT 0, `understands_but_description` TEXT DEFAULT '', `senses` TEXT DEFAULT '[]', `skills` TEXT DEFAULT '[]', `damage_immunities` TEXT DEFAULT '[]', `damage_resistances` TEXT DEFAULT '[]', `damage_vulnerabilities` TEXT DEFAULT '[]', `condition_immunities` TEXT DEFAULT '[]', `languages` TEXT DEFAULT '[]', `abilities` TEXT DEFAULT '[]', `actions` TEXT DEFAULT '[]', `reactions` TEXT DEFAULT '[]', `lair_actions` TEXT DEFAULT '[]', `legendary_actions` TEXT DEFAULT '[]', `regional_actions` TEXT DEFAULT '[]', PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subtype",
|
||||||
|
"columnName": "subtype",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alignment",
|
||||||
|
"columnName": "alignment",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthScore",
|
||||||
|
"columnName": "strength_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthSavingThrowAdvantage",
|
||||||
|
"columnName": "strength_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthSavingThrowProficiency",
|
||||||
|
"columnName": "strength_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexterityScore",
|
||||||
|
"columnName": "dexterity_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexteritySavingThrowAdvantage",
|
||||||
|
"columnName": "dexterity_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexteritySavingThrowProficiency",
|
||||||
|
"columnName": "dexterity_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionScore",
|
||||||
|
"columnName": "constitution_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionSavingThrowAdvantage",
|
||||||
|
"columnName": "constitution_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionSavingThrowProficiency",
|
||||||
|
"columnName": "constitution_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceScore",
|
||||||
|
"columnName": "intelligence_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceSavingThrowAdvantage",
|
||||||
|
"columnName": "intelligence_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceSavingThrowProficiency",
|
||||||
|
"columnName": "intelligence_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomScore",
|
||||||
|
"columnName": "wisdom_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomSavingThrowAdvantage",
|
||||||
|
"columnName": "wisdom_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomSavingThrowProficiency",
|
||||||
|
"columnName": "wisdom_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaScore",
|
||||||
|
"columnName": "charisma_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaSavingThrowAdvantage",
|
||||||
|
"columnName": "charisma_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaSavingThrowProficiency",
|
||||||
|
"columnName": "charisma_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "armorType",
|
||||||
|
"columnName": "armor_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "shieldBonus",
|
||||||
|
"columnName": "shield_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "naturalArmorBonus",
|
||||||
|
"columnName": "natural_armor_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "otherArmorDescription",
|
||||||
|
"columnName": "other_armor_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hitDice",
|
||||||
|
"columnName": "hit_dice",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hasCustomHP",
|
||||||
|
"columnName": "has_custom_hit_points",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customHPDescription",
|
||||||
|
"columnName": "custom_hit_points_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "walkSpeed",
|
||||||
|
"columnName": "walk_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "burrowSpeed",
|
||||||
|
"columnName": "burrow_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "climbSpeed",
|
||||||
|
"columnName": "climb_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "flySpeed",
|
||||||
|
"columnName": "fly_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "canHover",
|
||||||
|
"columnName": "can_hover",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "swimSpeed",
|
||||||
|
"columnName": "swim_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hasCustomSpeed",
|
||||||
|
"columnName": "has_custom_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customSpeedDescription",
|
||||||
|
"columnName": "custom_speed_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "challengeRating",
|
||||||
|
"columnName": "challenge_rating",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'1'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customChallengeRatingDescription",
|
||||||
|
"columnName": "custom_challenge_rating_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customProficiencyBonus",
|
||||||
|
"columnName": "custom_proficiency_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "telepathyRange",
|
||||||
|
"columnName": "telepathy_range",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "understandsButDescription",
|
||||||
|
"columnName": "understands_but_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "senses",
|
||||||
|
"columnName": "senses",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "skills",
|
||||||
|
"columnName": "skills",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageImmunities",
|
||||||
|
"columnName": "damage_immunities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageResistances",
|
||||||
|
"columnName": "damage_resistances",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageVulnerabilities",
|
||||||
|
"columnName": "damage_vulnerabilities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "conditionImmunities",
|
||||||
|
"columnName": "condition_immunities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "languages",
|
||||||
|
"columnName": "languages",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "abilities",
|
||||||
|
"columnName": "abilities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "actions",
|
||||||
|
"columnName": "actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "reactions",
|
||||||
|
"columnName": "reactions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lairActions",
|
||||||
|
"columnName": "lair_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "legendaryActions",
|
||||||
|
"columnName": "legendary_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "regionalActions",
|
||||||
|
"columnName": "regional_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ftsVersion": "FTS4",
|
||||||
|
"ftsOptions": {
|
||||||
|
"tokenizer": "simple",
|
||||||
|
"tokenizerArgs": [],
|
||||||
|
"contentTable": "monsters",
|
||||||
|
"languageIdColumnName": "",
|
||||||
|
"matchInfo": "FTS4",
|
||||||
|
"notIndexedColumns": [],
|
||||||
|
"prefixSizes": [],
|
||||||
|
"preferredOrder": "ASC"
|
||||||
|
},
|
||||||
|
"contentSyncTriggers": [
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_UPDATE BEFORE UPDATE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_DELETE BEFORE DELETE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_UPDATE AFTER UPDATE ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_INSERT AFTER INSERT ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END"
|
||||||
|
],
|
||||||
|
"tableName": "monsters_fts",
|
||||||
|
"createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`name` TEXT, `size` TEXT, `type` TEXT, `subtype` TEXT, `alignment` TEXT, content=`monsters`)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subtype",
|
||||||
|
"columnName": "subtype",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alignment",
|
||||||
|
"columnName": "alignment",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7c3c3ed79c7002102e7af7cfd21c23e0')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,554 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 4,
|
||||||
|
"identityHash": "ca0ad042eed557daa5ff0ada5de6596c",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "monsters",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `size` TEXT NOT NULL DEFAULT '', `type` TEXT NOT NULL DEFAULT '', `subtype` TEXT NOT NULL DEFAULT '', `alignment` TEXT NOT NULL DEFAULT '', `strength_score` INTEGER NOT NULL DEFAULT 10, `strength_saving_throw_advantage` TEXT DEFAULT 'none', `strength_saving_throw_proficiency` TEXT DEFAULT 'none', `dexterity_score` INTEGER NOT NULL DEFAULT 10, `dexterity_saving_throw_advantage` TEXT DEFAULT 'none', `dexterity_saving_throw_proficiency` TEXT DEFAULT 'none', `constitution_score` INTEGER NOT NULL DEFAULT 10, `constitution_saving_throw_advantage` TEXT DEFAULT 'none', `constitution_saving_throw_proficiency` TEXT DEFAULT 'none', `intelligence_score` INTEGER NOT NULL DEFAULT 10, `intelligence_saving_throw_advantage` TEXT DEFAULT 'none', `intelligence_saving_throw_proficiency` TEXT DEFAULT 'none', `wisdom_score` INTEGER NOT NULL DEFAULT 10, `wisdom_saving_throw_advantage` TEXT DEFAULT 'none', `wisdom_saving_throw_proficiency` TEXT DEFAULT 'none', `charisma_score` INTEGER NOT NULL DEFAULT 10, `charisma_saving_throw_advantage` TEXT DEFAULT 'none', `charisma_saving_throw_proficiency` TEXT DEFAULT 'none', `armor_type` TEXT DEFAULT 'none', `shield_bonus` INTEGER NOT NULL DEFAULT 0, `natural_armor_bonus` INTEGER NOT NULL DEFAULT 0, `other_armor_description` TEXT DEFAULT '', `hit_dice` INTEGER NOT NULL DEFAULT 1, `has_custom_hit_points` INTEGER NOT NULL, `custom_hit_points_description` TEXT DEFAULT '', `walk_speed` INTEGER NOT NULL DEFAULT 0, `burrow_speed` INTEGER NOT NULL DEFAULT 0, `climb_speed` INTEGER NOT NULL DEFAULT 0, `fly_speed` INTEGER NOT NULL DEFAULT 0, `can_hover` INTEGER NOT NULL DEFAULT false, `swim_speed` INTEGER NOT NULL DEFAULT 0, `has_custom_speed` INTEGER NOT NULL DEFAULT false, `custom_speed_description` TEXT, `challenge_rating` TEXT DEFAULT '1', `custom_challenge_rating_description` TEXT DEFAULT '', `custom_proficiency_bonus` INTEGER NOT NULL DEFAULT 0, `telepathy_range` INTEGER NOT NULL DEFAULT 0, `understands_but_description` TEXT DEFAULT '', `senses` TEXT DEFAULT '[]', `skills` TEXT DEFAULT '[]', `damage_immunities` TEXT DEFAULT '[]', `damage_resistances` TEXT DEFAULT '[]', `damage_vulnerabilities` TEXT DEFAULT '[]', `condition_immunities` TEXT DEFAULT '[]', `languages` TEXT DEFAULT '[]', `abilities` TEXT DEFAULT '[]', `actions` TEXT DEFAULT '[]', `reactions` TEXT DEFAULT '[]', `lair_actions` TEXT DEFAULT '[]', `legendary_actions` TEXT DEFAULT '[]', `regional_actions` TEXT DEFAULT '[]', PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subtype",
|
||||||
|
"columnName": "subtype",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alignment",
|
||||||
|
"columnName": "alignment",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthScore",
|
||||||
|
"columnName": "strength_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthSavingThrowAdvantage",
|
||||||
|
"columnName": "strength_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthSavingThrowProficiency",
|
||||||
|
"columnName": "strength_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexterityScore",
|
||||||
|
"columnName": "dexterity_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexteritySavingThrowAdvantage",
|
||||||
|
"columnName": "dexterity_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexteritySavingThrowProficiency",
|
||||||
|
"columnName": "dexterity_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionScore",
|
||||||
|
"columnName": "constitution_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionSavingThrowAdvantage",
|
||||||
|
"columnName": "constitution_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionSavingThrowProficiency",
|
||||||
|
"columnName": "constitution_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceScore",
|
||||||
|
"columnName": "intelligence_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceSavingThrowAdvantage",
|
||||||
|
"columnName": "intelligence_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceSavingThrowProficiency",
|
||||||
|
"columnName": "intelligence_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomScore",
|
||||||
|
"columnName": "wisdom_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomSavingThrowAdvantage",
|
||||||
|
"columnName": "wisdom_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomSavingThrowProficiency",
|
||||||
|
"columnName": "wisdom_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaScore",
|
||||||
|
"columnName": "charisma_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaSavingThrowAdvantage",
|
||||||
|
"columnName": "charisma_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaSavingThrowProficiency",
|
||||||
|
"columnName": "charisma_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "armorType",
|
||||||
|
"columnName": "armor_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "shieldBonus",
|
||||||
|
"columnName": "shield_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "naturalArmorBonus",
|
||||||
|
"columnName": "natural_armor_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "otherArmorDescription",
|
||||||
|
"columnName": "other_armor_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hitDice",
|
||||||
|
"columnName": "hit_dice",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hasCustomHP",
|
||||||
|
"columnName": "has_custom_hit_points",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customHPDescription",
|
||||||
|
"columnName": "custom_hit_points_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "walkSpeed",
|
||||||
|
"columnName": "walk_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "burrowSpeed",
|
||||||
|
"columnName": "burrow_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "climbSpeed",
|
||||||
|
"columnName": "climb_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "flySpeed",
|
||||||
|
"columnName": "fly_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "canHover",
|
||||||
|
"columnName": "can_hover",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "swimSpeed",
|
||||||
|
"columnName": "swim_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hasCustomSpeed",
|
||||||
|
"columnName": "has_custom_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customSpeedDescription",
|
||||||
|
"columnName": "custom_speed_description",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "challengeRating",
|
||||||
|
"columnName": "challenge_rating",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'1'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customChallengeRatingDescription",
|
||||||
|
"columnName": "custom_challenge_rating_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customProficiencyBonus",
|
||||||
|
"columnName": "custom_proficiency_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "telepathyRange",
|
||||||
|
"columnName": "telepathy_range",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "understandsButDescription",
|
||||||
|
"columnName": "understands_but_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "senses",
|
||||||
|
"columnName": "senses",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "skills",
|
||||||
|
"columnName": "skills",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageImmunities",
|
||||||
|
"columnName": "damage_immunities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageResistances",
|
||||||
|
"columnName": "damage_resistances",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageVulnerabilities",
|
||||||
|
"columnName": "damage_vulnerabilities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "conditionImmunities",
|
||||||
|
"columnName": "condition_immunities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "languages",
|
||||||
|
"columnName": "languages",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "abilities",
|
||||||
|
"columnName": "abilities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "actions",
|
||||||
|
"columnName": "actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "reactions",
|
||||||
|
"columnName": "reactions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lairActions",
|
||||||
|
"columnName": "lair_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "legendaryActions",
|
||||||
|
"columnName": "legendary_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "regionalActions",
|
||||||
|
"columnName": "regional_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "monsters_fts",
|
||||||
|
"createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`name` TEXT, `size` TEXT, `type` TEXT, `subtype` TEXT, `alignment` TEXT, content=`monsters`)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subtype",
|
||||||
|
"columnName": "subtype",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alignment",
|
||||||
|
"columnName": "alignment",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": []
|
||||||
|
},
|
||||||
|
"ftsVersion": "FTS4",
|
||||||
|
"ftsOptions": {
|
||||||
|
"tokenizer": "simple",
|
||||||
|
"tokenizerArgs": [],
|
||||||
|
"contentTable": "monsters",
|
||||||
|
"languageIdColumnName": "",
|
||||||
|
"matchInfo": "FTS4",
|
||||||
|
"notIndexedColumns": [],
|
||||||
|
"prefixSizes": [],
|
||||||
|
"preferredOrder": "ASC"
|
||||||
|
},
|
||||||
|
"contentSyncTriggers": [
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_UPDATE BEFORE UPDATE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_DELETE BEFORE DELETE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_UPDATE AFTER UPDATE ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_INSERT AFTER INSERT ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "collections",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `description` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "description",
|
||||||
|
"columnName": "description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "collection_monsters",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `collection_id` TEXT NOT NULL, `monster_id` TEXT NOT NULL, `ordinal` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`collection_id`) REFERENCES `collections`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`monster_id`) REFERENCES `monsters`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "collectionId",
|
||||||
|
"columnName": "collection_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "monsterId",
|
||||||
|
"columnName": "monster_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "ordinal",
|
||||||
|
"columnName": "ordinal",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_collection_monsters_collection_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"collection_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_monsters_collection_id` ON `${TABLE_NAME}` (`collection_id`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_collection_monsters_monster_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"monster_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_monsters_monster_id` ON `${TABLE_NAME}` (`monster_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "collections",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"collection_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "monsters",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"monster_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ca0ad042eed557daa5ff0ada5de6596c')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,609 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 5,
|
||||||
|
"identityHash": "6d497d0f0530c0f46bed1d57492e940e",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "monsters",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `size` TEXT NOT NULL DEFAULT '', `type` TEXT NOT NULL DEFAULT '', `subtype` TEXT NOT NULL DEFAULT '', `alignment` TEXT NOT NULL DEFAULT '', `strength_score` INTEGER NOT NULL DEFAULT 10, `strength_saving_throw_advantage` TEXT DEFAULT 'none', `strength_saving_throw_proficiency` TEXT DEFAULT 'none', `dexterity_score` INTEGER NOT NULL DEFAULT 10, `dexterity_saving_throw_advantage` TEXT DEFAULT 'none', `dexterity_saving_throw_proficiency` TEXT DEFAULT 'none', `constitution_score` INTEGER NOT NULL DEFAULT 10, `constitution_saving_throw_advantage` TEXT DEFAULT 'none', `constitution_saving_throw_proficiency` TEXT DEFAULT 'none', `intelligence_score` INTEGER NOT NULL DEFAULT 10, `intelligence_saving_throw_advantage` TEXT DEFAULT 'none', `intelligence_saving_throw_proficiency` TEXT DEFAULT 'none', `wisdom_score` INTEGER NOT NULL DEFAULT 10, `wisdom_saving_throw_advantage` TEXT DEFAULT 'none', `wisdom_saving_throw_proficiency` TEXT DEFAULT 'none', `charisma_score` INTEGER NOT NULL DEFAULT 10, `charisma_saving_throw_advantage` TEXT DEFAULT 'none', `charisma_saving_throw_proficiency` TEXT DEFAULT 'none', `armor_type` TEXT DEFAULT 'none', `shield_bonus` INTEGER NOT NULL DEFAULT 0, `natural_armor_bonus` INTEGER NOT NULL DEFAULT 0, `other_armor_description` TEXT DEFAULT '', `hit_dice` INTEGER NOT NULL DEFAULT 1, `has_custom_hit_points` INTEGER NOT NULL, `custom_hit_points_description` TEXT DEFAULT '', `walk_speed` INTEGER NOT NULL DEFAULT 0, `burrow_speed` INTEGER NOT NULL DEFAULT 0, `climb_speed` INTEGER NOT NULL DEFAULT 0, `fly_speed` INTEGER NOT NULL DEFAULT 0, `can_hover` INTEGER NOT NULL DEFAULT false, `swim_speed` INTEGER NOT NULL DEFAULT 0, `has_custom_speed` INTEGER NOT NULL DEFAULT false, `custom_speed_description` TEXT, `challenge_rating` TEXT DEFAULT '1', `custom_challenge_rating_description` TEXT DEFAULT '', `custom_proficiency_bonus` INTEGER NOT NULL DEFAULT 0, `telepathy_range` INTEGER NOT NULL DEFAULT 0, `understands_but_description` TEXT DEFAULT '', `senses` TEXT DEFAULT '[]', `skills` TEXT DEFAULT '[]', `damage_immunities` TEXT DEFAULT '[]', `damage_resistances` TEXT DEFAULT '[]', `damage_vulnerabilities` TEXT DEFAULT '[]', `condition_immunities` TEXT DEFAULT '[]', `languages` TEXT DEFAULT '[]', `abilities` TEXT DEFAULT '[]', `actions` TEXT DEFAULT '[]', `reactions` TEXT DEFAULT '[]', `lair_actions` TEXT DEFAULT '[]', `legendary_actions` TEXT DEFAULT '[]', `regional_actions` TEXT DEFAULT '[]', PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subtype",
|
||||||
|
"columnName": "subtype",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alignment",
|
||||||
|
"columnName": "alignment",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthScore",
|
||||||
|
"columnName": "strength_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthSavingThrowAdvantage",
|
||||||
|
"columnName": "strength_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthSavingThrowProficiency",
|
||||||
|
"columnName": "strength_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexterityScore",
|
||||||
|
"columnName": "dexterity_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexteritySavingThrowAdvantage",
|
||||||
|
"columnName": "dexterity_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexteritySavingThrowProficiency",
|
||||||
|
"columnName": "dexterity_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionScore",
|
||||||
|
"columnName": "constitution_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionSavingThrowAdvantage",
|
||||||
|
"columnName": "constitution_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionSavingThrowProficiency",
|
||||||
|
"columnName": "constitution_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceScore",
|
||||||
|
"columnName": "intelligence_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceSavingThrowAdvantage",
|
||||||
|
"columnName": "intelligence_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceSavingThrowProficiency",
|
||||||
|
"columnName": "intelligence_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomScore",
|
||||||
|
"columnName": "wisdom_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomSavingThrowAdvantage",
|
||||||
|
"columnName": "wisdom_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomSavingThrowProficiency",
|
||||||
|
"columnName": "wisdom_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaScore",
|
||||||
|
"columnName": "charisma_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaSavingThrowAdvantage",
|
||||||
|
"columnName": "charisma_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaSavingThrowProficiency",
|
||||||
|
"columnName": "charisma_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "armorType",
|
||||||
|
"columnName": "armor_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "shieldBonus",
|
||||||
|
"columnName": "shield_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "naturalArmorBonus",
|
||||||
|
"columnName": "natural_armor_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "otherArmorDescription",
|
||||||
|
"columnName": "other_armor_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hitDice",
|
||||||
|
"columnName": "hit_dice",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hasCustomHP",
|
||||||
|
"columnName": "has_custom_hit_points",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customHPDescription",
|
||||||
|
"columnName": "custom_hit_points_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "walkSpeed",
|
||||||
|
"columnName": "walk_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "burrowSpeed",
|
||||||
|
"columnName": "burrow_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "climbSpeed",
|
||||||
|
"columnName": "climb_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "flySpeed",
|
||||||
|
"columnName": "fly_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "canHover",
|
||||||
|
"columnName": "can_hover",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "swimSpeed",
|
||||||
|
"columnName": "swim_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hasCustomSpeed",
|
||||||
|
"columnName": "has_custom_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customSpeedDescription",
|
||||||
|
"columnName": "custom_speed_description",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "challengeRating",
|
||||||
|
"columnName": "challenge_rating",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'1'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customChallengeRatingDescription",
|
||||||
|
"columnName": "custom_challenge_rating_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customProficiencyBonus",
|
||||||
|
"columnName": "custom_proficiency_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "telepathyRange",
|
||||||
|
"columnName": "telepathy_range",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "understandsButDescription",
|
||||||
|
"columnName": "understands_but_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "senses",
|
||||||
|
"columnName": "senses",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "skills",
|
||||||
|
"columnName": "skills",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageImmunities",
|
||||||
|
"columnName": "damage_immunities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageResistances",
|
||||||
|
"columnName": "damage_resistances",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageVulnerabilities",
|
||||||
|
"columnName": "damage_vulnerabilities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "conditionImmunities",
|
||||||
|
"columnName": "condition_immunities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "languages",
|
||||||
|
"columnName": "languages",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "abilities",
|
||||||
|
"columnName": "abilities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "actions",
|
||||||
|
"columnName": "actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "reactions",
|
||||||
|
"columnName": "reactions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lairActions",
|
||||||
|
"columnName": "lair_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "legendaryActions",
|
||||||
|
"columnName": "legendary_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "regionalActions",
|
||||||
|
"columnName": "regional_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "monsters_fts",
|
||||||
|
"createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`name` TEXT, `size` TEXT, `type` TEXT, `subtype` TEXT, `alignment` TEXT, content=`monsters`)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subtype",
|
||||||
|
"columnName": "subtype",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alignment",
|
||||||
|
"columnName": "alignment",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": []
|
||||||
|
},
|
||||||
|
"ftsVersion": "FTS4",
|
||||||
|
"ftsOptions": {
|
||||||
|
"tokenizer": "simple",
|
||||||
|
"tokenizerArgs": [],
|
||||||
|
"contentTable": "monsters",
|
||||||
|
"languageIdColumnName": "",
|
||||||
|
"matchInfo": "FTS4",
|
||||||
|
"notIndexedColumns": [],
|
||||||
|
"prefixSizes": [],
|
||||||
|
"preferredOrder": "ASC"
|
||||||
|
},
|
||||||
|
"contentSyncTriggers": [
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_UPDATE BEFORE UPDATE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_DELETE BEFORE DELETE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_UPDATE AFTER UPDATE ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_INSERT AFTER INSERT ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "collections",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `description` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "description",
|
||||||
|
"columnName": "description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "collection_monsters",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `collection_id` TEXT NOT NULL, `monster_id` TEXT NOT NULL, `ordinal` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`collection_id`) REFERENCES `collections`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`monster_id`) REFERENCES `monsters`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "collectionId",
|
||||||
|
"columnName": "collection_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "monsterId",
|
||||||
|
"columnName": "monster_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "ordinal",
|
||||||
|
"columnName": "ordinal",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_collection_monsters_collection_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"collection_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_monsters_collection_id` ON `${TABLE_NAME}` (`collection_id`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_collection_monsters_monster_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"monster_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_monsters_monster_id` ON `${TABLE_NAME}` (`monster_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "collections",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"collection_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "monsters",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"monster_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "dashboard_monsters",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `monster_id` TEXT NOT NULL, `ordinal` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`monster_id`) REFERENCES `monsters`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "monsterId",
|
||||||
|
"columnName": "monster_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "ordinal",
|
||||||
|
"columnName": "ordinal",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_dashboard_monsters_monster_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"monster_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_dashboard_monsters_monster_id` ON `${TABLE_NAME}` (`monster_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "monsters",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"monster_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6d497d0f0530c0f46bed1d57492e940e')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,663 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 6,
|
||||||
|
"identityHash": "0ab331e246de46304f847d7ed2d10124",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "monsters",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `size` TEXT NOT NULL DEFAULT '', `type` TEXT NOT NULL DEFAULT '', `subtype` TEXT NOT NULL DEFAULT '', `alignment` TEXT NOT NULL DEFAULT '', `strength_score` INTEGER NOT NULL DEFAULT 10, `strength_saving_throw_advantage` TEXT DEFAULT 'none', `strength_saving_throw_proficiency` TEXT DEFAULT 'none', `dexterity_score` INTEGER NOT NULL DEFAULT 10, `dexterity_saving_throw_advantage` TEXT DEFAULT 'none', `dexterity_saving_throw_proficiency` TEXT DEFAULT 'none', `constitution_score` INTEGER NOT NULL DEFAULT 10, `constitution_saving_throw_advantage` TEXT DEFAULT 'none', `constitution_saving_throw_proficiency` TEXT DEFAULT 'none', `intelligence_score` INTEGER NOT NULL DEFAULT 10, `intelligence_saving_throw_advantage` TEXT DEFAULT 'none', `intelligence_saving_throw_proficiency` TEXT DEFAULT 'none', `wisdom_score` INTEGER NOT NULL DEFAULT 10, `wisdom_saving_throw_advantage` TEXT DEFAULT 'none', `wisdom_saving_throw_proficiency` TEXT DEFAULT 'none', `charisma_score` INTEGER NOT NULL DEFAULT 10, `charisma_saving_throw_advantage` TEXT DEFAULT 'none', `charisma_saving_throw_proficiency` TEXT DEFAULT 'none', `armor_type` TEXT DEFAULT 'none', `shield_bonus` INTEGER NOT NULL DEFAULT 0, `natural_armor_bonus` INTEGER NOT NULL DEFAULT 0, `other_armor_description` TEXT DEFAULT '', `hit_dice` INTEGER NOT NULL DEFAULT 1, `has_custom_hit_points` INTEGER NOT NULL, `custom_hit_points_description` TEXT DEFAULT '', `walk_speed` INTEGER NOT NULL DEFAULT 0, `burrow_speed` INTEGER NOT NULL DEFAULT 0, `climb_speed` INTEGER NOT NULL DEFAULT 0, `fly_speed` INTEGER NOT NULL DEFAULT 0, `can_hover` INTEGER NOT NULL DEFAULT false, `swim_speed` INTEGER NOT NULL DEFAULT 0, `has_custom_speed` INTEGER NOT NULL DEFAULT false, `custom_speed_description` TEXT, `challenge_rating` TEXT DEFAULT '1', `custom_challenge_rating_description` TEXT DEFAULT '', `custom_proficiency_bonus` INTEGER NOT NULL DEFAULT 0, `telepathy_range` INTEGER NOT NULL DEFAULT 0, `understands_but_description` TEXT DEFAULT '', `senses` TEXT DEFAULT '[]', `skills` TEXT DEFAULT '[]', `damage_immunities` TEXT DEFAULT '[]', `damage_resistances` TEXT DEFAULT '[]', `damage_vulnerabilities` TEXT DEFAULT '[]', `condition_immunities` TEXT DEFAULT '[]', `languages` TEXT DEFAULT '[]', `abilities` TEXT DEFAULT '[]', `actions` TEXT DEFAULT '[]', `reactions` TEXT DEFAULT '[]', `lair_actions` TEXT DEFAULT '[]', `legendary_actions` TEXT DEFAULT '[]', `regional_actions` TEXT DEFAULT '[]', `source_url` TEXT DEFAULT '', `bonus_actions` TEXT DEFAULT '[]', `mythic_actions` TEXT DEFAULT '[]', `legendary_actions_description` TEXT DEFAULT '', `lair_actions_description` TEXT DEFAULT '', `lair_actions_end_note` TEXT DEFAULT '', `regional_actions_description` TEXT DEFAULT '', `regional_actions_end_note` TEXT DEFAULT '', `mythic_actions_description` TEXT DEFAULT '', PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subtype",
|
||||||
|
"columnName": "subtype",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alignment",
|
||||||
|
"columnName": "alignment",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthScore",
|
||||||
|
"columnName": "strength_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthSavingThrowAdvantage",
|
||||||
|
"columnName": "strength_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthSavingThrowProficiency",
|
||||||
|
"columnName": "strength_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexterityScore",
|
||||||
|
"columnName": "dexterity_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexteritySavingThrowAdvantage",
|
||||||
|
"columnName": "dexterity_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexteritySavingThrowProficiency",
|
||||||
|
"columnName": "dexterity_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionScore",
|
||||||
|
"columnName": "constitution_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionSavingThrowAdvantage",
|
||||||
|
"columnName": "constitution_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionSavingThrowProficiency",
|
||||||
|
"columnName": "constitution_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceScore",
|
||||||
|
"columnName": "intelligence_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceSavingThrowAdvantage",
|
||||||
|
"columnName": "intelligence_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceSavingThrowProficiency",
|
||||||
|
"columnName": "intelligence_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomScore",
|
||||||
|
"columnName": "wisdom_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomSavingThrowAdvantage",
|
||||||
|
"columnName": "wisdom_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomSavingThrowProficiency",
|
||||||
|
"columnName": "wisdom_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaScore",
|
||||||
|
"columnName": "charisma_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaSavingThrowAdvantage",
|
||||||
|
"columnName": "charisma_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaSavingThrowProficiency",
|
||||||
|
"columnName": "charisma_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "armorType",
|
||||||
|
"columnName": "armor_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "shieldBonus",
|
||||||
|
"columnName": "shield_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "naturalArmorBonus",
|
||||||
|
"columnName": "natural_armor_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "otherArmorDescription",
|
||||||
|
"columnName": "other_armor_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hitDice",
|
||||||
|
"columnName": "hit_dice",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hasCustomHP",
|
||||||
|
"columnName": "has_custom_hit_points",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customHPDescription",
|
||||||
|
"columnName": "custom_hit_points_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "walkSpeed",
|
||||||
|
"columnName": "walk_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "burrowSpeed",
|
||||||
|
"columnName": "burrow_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "climbSpeed",
|
||||||
|
"columnName": "climb_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "flySpeed",
|
||||||
|
"columnName": "fly_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "canHover",
|
||||||
|
"columnName": "can_hover",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "swimSpeed",
|
||||||
|
"columnName": "swim_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hasCustomSpeed",
|
||||||
|
"columnName": "has_custom_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customSpeedDescription",
|
||||||
|
"columnName": "custom_speed_description",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "challengeRating",
|
||||||
|
"columnName": "challenge_rating",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'1'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customChallengeRatingDescription",
|
||||||
|
"columnName": "custom_challenge_rating_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customProficiencyBonus",
|
||||||
|
"columnName": "custom_proficiency_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "telepathyRange",
|
||||||
|
"columnName": "telepathy_range",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "understandsButDescription",
|
||||||
|
"columnName": "understands_but_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "senses",
|
||||||
|
"columnName": "senses",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "skills",
|
||||||
|
"columnName": "skills",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageImmunities",
|
||||||
|
"columnName": "damage_immunities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageResistances",
|
||||||
|
"columnName": "damage_resistances",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageVulnerabilities",
|
||||||
|
"columnName": "damage_vulnerabilities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "conditionImmunities",
|
||||||
|
"columnName": "condition_immunities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "languages",
|
||||||
|
"columnName": "languages",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "abilities",
|
||||||
|
"columnName": "abilities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "actions",
|
||||||
|
"columnName": "actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "reactions",
|
||||||
|
"columnName": "reactions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lairActions",
|
||||||
|
"columnName": "lair_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "legendaryActions",
|
||||||
|
"columnName": "legendary_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "regionalActions",
|
||||||
|
"columnName": "regional_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "sourceUrl",
|
||||||
|
"columnName": "source_url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bonusActions",
|
||||||
|
"columnName": "bonus_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "mythicActions",
|
||||||
|
"columnName": "mythic_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "legendaryActionsDescription",
|
||||||
|
"columnName": "legendary_actions_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lairActionsDescription",
|
||||||
|
"columnName": "lair_actions_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lairActionsEndNote",
|
||||||
|
"columnName": "lair_actions_end_note",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "regionalActionsDescription",
|
||||||
|
"columnName": "regional_actions_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "regionalActionsEndNote",
|
||||||
|
"columnName": "regional_actions_end_note",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "mythicActionsDescription",
|
||||||
|
"columnName": "mythic_actions_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "monsters_fts",
|
||||||
|
"createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`name` TEXT, `size` TEXT, `type` TEXT, `subtype` TEXT, `alignment` TEXT, content=`monsters`)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subtype",
|
||||||
|
"columnName": "subtype",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alignment",
|
||||||
|
"columnName": "alignment",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": []
|
||||||
|
},
|
||||||
|
"ftsVersion": "FTS4",
|
||||||
|
"ftsOptions": {
|
||||||
|
"tokenizer": "simple",
|
||||||
|
"tokenizerArgs": [],
|
||||||
|
"contentTable": "monsters",
|
||||||
|
"languageIdColumnName": "",
|
||||||
|
"matchInfo": "FTS4",
|
||||||
|
"notIndexedColumns": [],
|
||||||
|
"prefixSizes": [],
|
||||||
|
"preferredOrder": "ASC"
|
||||||
|
},
|
||||||
|
"contentSyncTriggers": [
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_UPDATE BEFORE UPDATE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_DELETE BEFORE DELETE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_UPDATE AFTER UPDATE ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_INSERT AFTER INSERT ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "collections",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `description` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "description",
|
||||||
|
"columnName": "description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "collection_monsters",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `collection_id` TEXT NOT NULL, `monster_id` TEXT NOT NULL, `ordinal` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`collection_id`) REFERENCES `collections`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`monster_id`) REFERENCES `monsters`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "collectionId",
|
||||||
|
"columnName": "collection_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "monsterId",
|
||||||
|
"columnName": "monster_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "ordinal",
|
||||||
|
"columnName": "ordinal",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_collection_monsters_collection_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"collection_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_monsters_collection_id` ON `${TABLE_NAME}` (`collection_id`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_collection_monsters_monster_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"monster_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_monsters_monster_id` ON `${TABLE_NAME}` (`monster_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "collections",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"collection_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "monsters",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"monster_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "dashboard_monsters",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `monster_id` TEXT NOT NULL, `ordinal` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`monster_id`) REFERENCES `monsters`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "monsterId",
|
||||||
|
"columnName": "monster_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "ordinal",
|
||||||
|
"columnName": "ordinal",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_dashboard_monsters_monster_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"monster_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_dashboard_monsters_monster_id` ON `${TABLE_NAME}` (`monster_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "monsters",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"monster_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0ab331e246de46304f847d7ed2d10124')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,768 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 7,
|
||||||
|
"identityHash": "38f6bbd6f1e298b20e17b3a08fbc7f70",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "monsters",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `size` TEXT NOT NULL DEFAULT '', `type` TEXT NOT NULL DEFAULT '', `subtype` TEXT NOT NULL DEFAULT '', `alignment` TEXT NOT NULL DEFAULT '', `strength_score` INTEGER NOT NULL DEFAULT 10, `strength_saving_throw_advantage` TEXT DEFAULT 'none', `strength_saving_throw_proficiency` TEXT DEFAULT 'none', `dexterity_score` INTEGER NOT NULL DEFAULT 10, `dexterity_saving_throw_advantage` TEXT DEFAULT 'none', `dexterity_saving_throw_proficiency` TEXT DEFAULT 'none', `constitution_score` INTEGER NOT NULL DEFAULT 10, `constitution_saving_throw_advantage` TEXT DEFAULT 'none', `constitution_saving_throw_proficiency` TEXT DEFAULT 'none', `intelligence_score` INTEGER NOT NULL DEFAULT 10, `intelligence_saving_throw_advantage` TEXT DEFAULT 'none', `intelligence_saving_throw_proficiency` TEXT DEFAULT 'none', `wisdom_score` INTEGER NOT NULL DEFAULT 10, `wisdom_saving_throw_advantage` TEXT DEFAULT 'none', `wisdom_saving_throw_proficiency` TEXT DEFAULT 'none', `charisma_score` INTEGER NOT NULL DEFAULT 10, `charisma_saving_throw_advantage` TEXT DEFAULT 'none', `charisma_saving_throw_proficiency` TEXT DEFAULT 'none', `armor_type` TEXT DEFAULT 'none', `shield_bonus` INTEGER NOT NULL DEFAULT 0, `natural_armor_bonus` INTEGER NOT NULL DEFAULT 0, `other_armor_description` TEXT DEFAULT '', `hit_dice` INTEGER NOT NULL DEFAULT 1, `has_custom_hit_points` INTEGER NOT NULL, `custom_hit_points_description` TEXT DEFAULT '', `walk_speed` INTEGER NOT NULL DEFAULT 0, `burrow_speed` INTEGER NOT NULL DEFAULT 0, `climb_speed` INTEGER NOT NULL DEFAULT 0, `fly_speed` INTEGER NOT NULL DEFAULT 0, `can_hover` INTEGER NOT NULL DEFAULT false, `swim_speed` INTEGER NOT NULL DEFAULT 0, `has_custom_speed` INTEGER NOT NULL DEFAULT false, `custom_speed_description` TEXT, `challenge_rating` TEXT DEFAULT '1', `custom_challenge_rating_description` TEXT DEFAULT '', `custom_proficiency_bonus` INTEGER NOT NULL DEFAULT 0, `telepathy_range` INTEGER NOT NULL DEFAULT 0, `understands_but_description` TEXT DEFAULT '', `senses` TEXT DEFAULT '[]', `skills` TEXT DEFAULT '[]', `damage_immunities` TEXT DEFAULT '[]', `damage_resistances` TEXT DEFAULT '[]', `damage_vulnerabilities` TEXT DEFAULT '[]', `condition_immunities` TEXT DEFAULT '[]', `languages` TEXT DEFAULT '[]', `abilities` TEXT DEFAULT '[]', `actions` TEXT DEFAULT '[]', `reactions` TEXT DEFAULT '[]', `lair_actions` TEXT DEFAULT '[]', `legendary_actions` TEXT DEFAULT '[]', `regional_actions` TEXT DEFAULT '[]', `source_url` TEXT DEFAULT '', `bonus_actions` TEXT DEFAULT '[]', `mythic_actions` TEXT DEFAULT '[]', `legendary_actions_description` TEXT DEFAULT '', `lair_actions_description` TEXT DEFAULT '', `lair_actions_end_note` TEXT DEFAULT '', `regional_actions_description` TEXT DEFAULT '', `regional_actions_end_note` TEXT DEFAULT '', `mythic_actions_description` TEXT DEFAULT '', `player_name` TEXT NOT NULL DEFAULT '', `background` TEXT NOT NULL DEFAULT '', `personality_traits` TEXT NOT NULL DEFAULT '', `ideals` TEXT NOT NULL DEFAULT '', `bonds` TEXT NOT NULL DEFAULT '', `flaws` TEXT NOT NULL DEFAULT '', `age` TEXT NOT NULL DEFAULT '', `height` TEXT NOT NULL DEFAULT '', `weight` TEXT NOT NULL DEFAULT '', `eyes` TEXT NOT NULL DEFAULT '', `skin` TEXT NOT NULL DEFAULT '', `hair` TEXT NOT NULL DEFAULT '', `appearance` TEXT NOT NULL DEFAULT '', `backstory` TEXT NOT NULL DEFAULT '', `allies_and_organizations` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subtype",
|
||||||
|
"columnName": "subtype",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alignment",
|
||||||
|
"columnName": "alignment",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthScore",
|
||||||
|
"columnName": "strength_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthSavingThrowAdvantage",
|
||||||
|
"columnName": "strength_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthSavingThrowProficiency",
|
||||||
|
"columnName": "strength_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexterityScore",
|
||||||
|
"columnName": "dexterity_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexteritySavingThrowAdvantage",
|
||||||
|
"columnName": "dexterity_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexteritySavingThrowProficiency",
|
||||||
|
"columnName": "dexterity_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionScore",
|
||||||
|
"columnName": "constitution_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionSavingThrowAdvantage",
|
||||||
|
"columnName": "constitution_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionSavingThrowProficiency",
|
||||||
|
"columnName": "constitution_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceScore",
|
||||||
|
"columnName": "intelligence_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceSavingThrowAdvantage",
|
||||||
|
"columnName": "intelligence_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceSavingThrowProficiency",
|
||||||
|
"columnName": "intelligence_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomScore",
|
||||||
|
"columnName": "wisdom_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomSavingThrowAdvantage",
|
||||||
|
"columnName": "wisdom_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomSavingThrowProficiency",
|
||||||
|
"columnName": "wisdom_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaScore",
|
||||||
|
"columnName": "charisma_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaSavingThrowAdvantage",
|
||||||
|
"columnName": "charisma_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaSavingThrowProficiency",
|
||||||
|
"columnName": "charisma_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "armorType",
|
||||||
|
"columnName": "armor_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "shieldBonus",
|
||||||
|
"columnName": "shield_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "naturalArmorBonus",
|
||||||
|
"columnName": "natural_armor_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "otherArmorDescription",
|
||||||
|
"columnName": "other_armor_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hitDice",
|
||||||
|
"columnName": "hit_dice",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hasCustomHP",
|
||||||
|
"columnName": "has_custom_hit_points",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customHPDescription",
|
||||||
|
"columnName": "custom_hit_points_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "walkSpeed",
|
||||||
|
"columnName": "walk_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "burrowSpeed",
|
||||||
|
"columnName": "burrow_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "climbSpeed",
|
||||||
|
"columnName": "climb_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "flySpeed",
|
||||||
|
"columnName": "fly_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "canHover",
|
||||||
|
"columnName": "can_hover",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "swimSpeed",
|
||||||
|
"columnName": "swim_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hasCustomSpeed",
|
||||||
|
"columnName": "has_custom_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customSpeedDescription",
|
||||||
|
"columnName": "custom_speed_description",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "challengeRating",
|
||||||
|
"columnName": "challenge_rating",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'1'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customChallengeRatingDescription",
|
||||||
|
"columnName": "custom_challenge_rating_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customProficiencyBonus",
|
||||||
|
"columnName": "custom_proficiency_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "telepathyRange",
|
||||||
|
"columnName": "telepathy_range",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "understandsButDescription",
|
||||||
|
"columnName": "understands_but_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "senses",
|
||||||
|
"columnName": "senses",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "skills",
|
||||||
|
"columnName": "skills",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageImmunities",
|
||||||
|
"columnName": "damage_immunities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageResistances",
|
||||||
|
"columnName": "damage_resistances",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageVulnerabilities",
|
||||||
|
"columnName": "damage_vulnerabilities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "conditionImmunities",
|
||||||
|
"columnName": "condition_immunities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "languages",
|
||||||
|
"columnName": "languages",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "abilities",
|
||||||
|
"columnName": "abilities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "actions",
|
||||||
|
"columnName": "actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "reactions",
|
||||||
|
"columnName": "reactions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lairActions",
|
||||||
|
"columnName": "lair_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "legendaryActions",
|
||||||
|
"columnName": "legendary_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "regionalActions",
|
||||||
|
"columnName": "regional_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "sourceUrl",
|
||||||
|
"columnName": "source_url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bonusActions",
|
||||||
|
"columnName": "bonus_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "mythicActions",
|
||||||
|
"columnName": "mythic_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "legendaryActionsDescription",
|
||||||
|
"columnName": "legendary_actions_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lairActionsDescription",
|
||||||
|
"columnName": "lair_actions_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lairActionsEndNote",
|
||||||
|
"columnName": "lair_actions_end_note",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "regionalActionsDescription",
|
||||||
|
"columnName": "regional_actions_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "regionalActionsEndNote",
|
||||||
|
"columnName": "regional_actions_end_note",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "mythicActionsDescription",
|
||||||
|
"columnName": "mythic_actions_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playerName",
|
||||||
|
"columnName": "player_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "background",
|
||||||
|
"columnName": "background",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "personalityTraits",
|
||||||
|
"columnName": "personality_traits",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "ideals",
|
||||||
|
"columnName": "ideals",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bonds",
|
||||||
|
"columnName": "bonds",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "flaws",
|
||||||
|
"columnName": "flaws",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "age",
|
||||||
|
"columnName": "age",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "height",
|
||||||
|
"columnName": "height",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "weight",
|
||||||
|
"columnName": "weight",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "eyes",
|
||||||
|
"columnName": "eyes",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "skin",
|
||||||
|
"columnName": "skin",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hair",
|
||||||
|
"columnName": "hair",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "appearance",
|
||||||
|
"columnName": "appearance",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "backstory",
|
||||||
|
"columnName": "backstory",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alliesAndOrganizations",
|
||||||
|
"columnName": "allies_and_organizations",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "monsters_fts",
|
||||||
|
"createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`name` TEXT, `size` TEXT, `type` TEXT, `subtype` TEXT, `alignment` TEXT, content=`monsters`)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subtype",
|
||||||
|
"columnName": "subtype",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alignment",
|
||||||
|
"columnName": "alignment",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": []
|
||||||
|
},
|
||||||
|
"ftsVersion": "FTS4",
|
||||||
|
"ftsOptions": {
|
||||||
|
"tokenizer": "simple",
|
||||||
|
"tokenizerArgs": [],
|
||||||
|
"contentTable": "monsters",
|
||||||
|
"languageIdColumnName": "",
|
||||||
|
"matchInfo": "FTS4",
|
||||||
|
"notIndexedColumns": [],
|
||||||
|
"prefixSizes": [],
|
||||||
|
"preferredOrder": "ASC"
|
||||||
|
},
|
||||||
|
"contentSyncTriggers": [
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_UPDATE BEFORE UPDATE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_DELETE BEFORE DELETE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_UPDATE AFTER UPDATE ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_INSERT AFTER INSERT ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "collections",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `description` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "description",
|
||||||
|
"columnName": "description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "collection_monsters",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `collection_id` TEXT NOT NULL, `monster_id` TEXT NOT NULL, `ordinal` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`collection_id`) REFERENCES `collections`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`monster_id`) REFERENCES `monsters`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "collectionId",
|
||||||
|
"columnName": "collection_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "monsterId",
|
||||||
|
"columnName": "monster_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "ordinal",
|
||||||
|
"columnName": "ordinal",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_collection_monsters_collection_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"collection_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_monsters_collection_id` ON `${TABLE_NAME}` (`collection_id`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_collection_monsters_monster_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"monster_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_monsters_monster_id` ON `${TABLE_NAME}` (`monster_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "collections",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"collection_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "monsters",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"monster_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "dashboard_monsters",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `monster_id` TEXT NOT NULL, `ordinal` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`monster_id`) REFERENCES `monsters`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "monsterId",
|
||||||
|
"columnName": "monster_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "ordinal",
|
||||||
|
"columnName": "ordinal",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_dashboard_monsters_monster_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"monster_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_dashboard_monsters_monster_id` ON `${TABLE_NAME}` (`monster_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "monsters",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"monster_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '38f6bbd6f1e298b20e17b3a08fbc7f70')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,768 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 8,
|
||||||
|
"identityHash": "38f6bbd6f1e298b20e17b3a08fbc7f70",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "monsters",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `size` TEXT NOT NULL DEFAULT '', `type` TEXT NOT NULL DEFAULT '', `subtype` TEXT NOT NULL DEFAULT '', `alignment` TEXT NOT NULL DEFAULT '', `strength_score` INTEGER NOT NULL DEFAULT 10, `strength_saving_throw_advantage` TEXT DEFAULT 'none', `strength_saving_throw_proficiency` TEXT DEFAULT 'none', `dexterity_score` INTEGER NOT NULL DEFAULT 10, `dexterity_saving_throw_advantage` TEXT DEFAULT 'none', `dexterity_saving_throw_proficiency` TEXT DEFAULT 'none', `constitution_score` INTEGER NOT NULL DEFAULT 10, `constitution_saving_throw_advantage` TEXT DEFAULT 'none', `constitution_saving_throw_proficiency` TEXT DEFAULT 'none', `intelligence_score` INTEGER NOT NULL DEFAULT 10, `intelligence_saving_throw_advantage` TEXT DEFAULT 'none', `intelligence_saving_throw_proficiency` TEXT DEFAULT 'none', `wisdom_score` INTEGER NOT NULL DEFAULT 10, `wisdom_saving_throw_advantage` TEXT DEFAULT 'none', `wisdom_saving_throw_proficiency` TEXT DEFAULT 'none', `charisma_score` INTEGER NOT NULL DEFAULT 10, `charisma_saving_throw_advantage` TEXT DEFAULT 'none', `charisma_saving_throw_proficiency` TEXT DEFAULT 'none', `armor_type` TEXT DEFAULT 'none', `shield_bonus` INTEGER NOT NULL DEFAULT 0, `natural_armor_bonus` INTEGER NOT NULL DEFAULT 0, `other_armor_description` TEXT DEFAULT '', `hit_dice` INTEGER NOT NULL DEFAULT 1, `has_custom_hit_points` INTEGER NOT NULL, `custom_hit_points_description` TEXT DEFAULT '', `walk_speed` INTEGER NOT NULL DEFAULT 0, `burrow_speed` INTEGER NOT NULL DEFAULT 0, `climb_speed` INTEGER NOT NULL DEFAULT 0, `fly_speed` INTEGER NOT NULL DEFAULT 0, `can_hover` INTEGER NOT NULL DEFAULT false, `swim_speed` INTEGER NOT NULL DEFAULT 0, `has_custom_speed` INTEGER NOT NULL DEFAULT false, `custom_speed_description` TEXT, `challenge_rating` TEXT DEFAULT '1', `custom_challenge_rating_description` TEXT DEFAULT '', `custom_proficiency_bonus` INTEGER NOT NULL DEFAULT 0, `telepathy_range` INTEGER NOT NULL DEFAULT 0, `understands_but_description` TEXT DEFAULT '', `senses` TEXT DEFAULT '[]', `skills` TEXT DEFAULT '[]', `damage_immunities` TEXT DEFAULT '[]', `damage_resistances` TEXT DEFAULT '[]', `damage_vulnerabilities` TEXT DEFAULT '[]', `condition_immunities` TEXT DEFAULT '[]', `languages` TEXT DEFAULT '[]', `abilities` TEXT DEFAULT '[]', `actions` TEXT DEFAULT '[]', `reactions` TEXT DEFAULT '[]', `lair_actions` TEXT DEFAULT '[]', `legendary_actions` TEXT DEFAULT '[]', `regional_actions` TEXT DEFAULT '[]', `source_url` TEXT DEFAULT '', `bonus_actions` TEXT DEFAULT '[]', `mythic_actions` TEXT DEFAULT '[]', `legendary_actions_description` TEXT DEFAULT '', `lair_actions_description` TEXT DEFAULT '', `lair_actions_end_note` TEXT DEFAULT '', `regional_actions_description` TEXT DEFAULT '', `regional_actions_end_note` TEXT DEFAULT '', `mythic_actions_description` TEXT DEFAULT '', `player_name` TEXT NOT NULL DEFAULT '', `background` TEXT NOT NULL DEFAULT '', `personality_traits` TEXT NOT NULL DEFAULT '', `ideals` TEXT NOT NULL DEFAULT '', `bonds` TEXT NOT NULL DEFAULT '', `flaws` TEXT NOT NULL DEFAULT '', `age` TEXT NOT NULL DEFAULT '', `height` TEXT NOT NULL DEFAULT '', `weight` TEXT NOT NULL DEFAULT '', `eyes` TEXT NOT NULL DEFAULT '', `skin` TEXT NOT NULL DEFAULT '', `hair` TEXT NOT NULL DEFAULT '', `appearance` TEXT NOT NULL DEFAULT '', `backstory` TEXT NOT NULL DEFAULT '', `allies_and_organizations` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subtype",
|
||||||
|
"columnName": "subtype",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alignment",
|
||||||
|
"columnName": "alignment",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthScore",
|
||||||
|
"columnName": "strength_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthSavingThrowAdvantage",
|
||||||
|
"columnName": "strength_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "strengthSavingThrowProficiency",
|
||||||
|
"columnName": "strength_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexterityScore",
|
||||||
|
"columnName": "dexterity_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexteritySavingThrowAdvantage",
|
||||||
|
"columnName": "dexterity_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dexteritySavingThrowProficiency",
|
||||||
|
"columnName": "dexterity_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionScore",
|
||||||
|
"columnName": "constitution_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionSavingThrowAdvantage",
|
||||||
|
"columnName": "constitution_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "constitutionSavingThrowProficiency",
|
||||||
|
"columnName": "constitution_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceScore",
|
||||||
|
"columnName": "intelligence_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceSavingThrowAdvantage",
|
||||||
|
"columnName": "intelligence_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "intelligenceSavingThrowProficiency",
|
||||||
|
"columnName": "intelligence_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomScore",
|
||||||
|
"columnName": "wisdom_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomSavingThrowAdvantage",
|
||||||
|
"columnName": "wisdom_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wisdomSavingThrowProficiency",
|
||||||
|
"columnName": "wisdom_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaScore",
|
||||||
|
"columnName": "charisma_score",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaSavingThrowAdvantage",
|
||||||
|
"columnName": "charisma_saving_throw_advantage",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "charismaSavingThrowProficiency",
|
||||||
|
"columnName": "charisma_saving_throw_proficiency",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "armorType",
|
||||||
|
"columnName": "armor_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'none'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "shieldBonus",
|
||||||
|
"columnName": "shield_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "naturalArmorBonus",
|
||||||
|
"columnName": "natural_armor_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "otherArmorDescription",
|
||||||
|
"columnName": "other_armor_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hitDice",
|
||||||
|
"columnName": "hit_dice",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hasCustomHP",
|
||||||
|
"columnName": "has_custom_hit_points",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customHPDescription",
|
||||||
|
"columnName": "custom_hit_points_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "walkSpeed",
|
||||||
|
"columnName": "walk_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "burrowSpeed",
|
||||||
|
"columnName": "burrow_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "climbSpeed",
|
||||||
|
"columnName": "climb_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "flySpeed",
|
||||||
|
"columnName": "fly_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "canHover",
|
||||||
|
"columnName": "can_hover",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "swimSpeed",
|
||||||
|
"columnName": "swim_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hasCustomSpeed",
|
||||||
|
"columnName": "has_custom_speed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customSpeedDescription",
|
||||||
|
"columnName": "custom_speed_description",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "challengeRating",
|
||||||
|
"columnName": "challenge_rating",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'1'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customChallengeRatingDescription",
|
||||||
|
"columnName": "custom_challenge_rating_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "customProficiencyBonus",
|
||||||
|
"columnName": "custom_proficiency_bonus",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "telepathyRange",
|
||||||
|
"columnName": "telepathy_range",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "understandsButDescription",
|
||||||
|
"columnName": "understands_but_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "senses",
|
||||||
|
"columnName": "senses",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "skills",
|
||||||
|
"columnName": "skills",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageImmunities",
|
||||||
|
"columnName": "damage_immunities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageResistances",
|
||||||
|
"columnName": "damage_resistances",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "damageVulnerabilities",
|
||||||
|
"columnName": "damage_vulnerabilities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "conditionImmunities",
|
||||||
|
"columnName": "condition_immunities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "languages",
|
||||||
|
"columnName": "languages",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "abilities",
|
||||||
|
"columnName": "abilities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "actions",
|
||||||
|
"columnName": "actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "reactions",
|
||||||
|
"columnName": "reactions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lairActions",
|
||||||
|
"columnName": "lair_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "legendaryActions",
|
||||||
|
"columnName": "legendary_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "regionalActions",
|
||||||
|
"columnName": "regional_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "sourceUrl",
|
||||||
|
"columnName": "source_url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bonusActions",
|
||||||
|
"columnName": "bonus_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "mythicActions",
|
||||||
|
"columnName": "mythic_actions",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "legendaryActionsDescription",
|
||||||
|
"columnName": "legendary_actions_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lairActionsDescription",
|
||||||
|
"columnName": "lair_actions_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lairActionsEndNote",
|
||||||
|
"columnName": "lair_actions_end_note",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "regionalActionsDescription",
|
||||||
|
"columnName": "regional_actions_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "regionalActionsEndNote",
|
||||||
|
"columnName": "regional_actions_end_note",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "mythicActionsDescription",
|
||||||
|
"columnName": "mythic_actions_description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playerName",
|
||||||
|
"columnName": "player_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "background",
|
||||||
|
"columnName": "background",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "personalityTraits",
|
||||||
|
"columnName": "personality_traits",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "ideals",
|
||||||
|
"columnName": "ideals",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bonds",
|
||||||
|
"columnName": "bonds",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "flaws",
|
||||||
|
"columnName": "flaws",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "age",
|
||||||
|
"columnName": "age",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "height",
|
||||||
|
"columnName": "height",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "weight",
|
||||||
|
"columnName": "weight",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "eyes",
|
||||||
|
"columnName": "eyes",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "skin",
|
||||||
|
"columnName": "skin",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hair",
|
||||||
|
"columnName": "hair",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "appearance",
|
||||||
|
"columnName": "appearance",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "backstory",
|
||||||
|
"columnName": "backstory",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alliesAndOrganizations",
|
||||||
|
"columnName": "allies_and_organizations",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "monsters_fts",
|
||||||
|
"createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`name` TEXT, `size` TEXT, `type` TEXT, `subtype` TEXT, `alignment` TEXT, content=`monsters`)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subtype",
|
||||||
|
"columnName": "subtype",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alignment",
|
||||||
|
"columnName": "alignment",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": []
|
||||||
|
},
|
||||||
|
"ftsVersion": "FTS4",
|
||||||
|
"ftsOptions": {
|
||||||
|
"tokenizer": "simple",
|
||||||
|
"tokenizerArgs": [],
|
||||||
|
"contentTable": "monsters",
|
||||||
|
"languageIdColumnName": "",
|
||||||
|
"matchInfo": "FTS4",
|
||||||
|
"notIndexedColumns": [],
|
||||||
|
"prefixSizes": [],
|
||||||
|
"preferredOrder": "ASC"
|
||||||
|
},
|
||||||
|
"contentSyncTriggers": [
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_UPDATE BEFORE UPDATE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_DELETE BEFORE DELETE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_UPDATE AFTER UPDATE ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_INSERT AFTER INSERT ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "collections",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `description` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "description",
|
||||||
|
"columnName": "description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "collection_monsters",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `collection_id` TEXT NOT NULL, `monster_id` TEXT NOT NULL, `ordinal` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`collection_id`) REFERENCES `collections`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`monster_id`) REFERENCES `monsters`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "collectionId",
|
||||||
|
"columnName": "collection_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "monsterId",
|
||||||
|
"columnName": "monster_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "ordinal",
|
||||||
|
"columnName": "ordinal",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_collection_monsters_collection_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"collection_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_monsters_collection_id` ON `${TABLE_NAME}` (`collection_id`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_collection_monsters_monster_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"monster_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_monsters_monster_id` ON `${TABLE_NAME}` (`monster_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "collections",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"collection_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "monsters",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"monster_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "dashboard_monsters",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `monster_id` TEXT NOT NULL, `ordinal` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`monster_id`) REFERENCES `monsters`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "monsterId",
|
||||||
|
"columnName": "monster_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "ordinal",
|
||||||
|
"columnName": "ordinal",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_dashboard_monsters_monster_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"monster_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_dashboard_monsters_monster_id` ON `${TABLE_NAME}` (`monster_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "monsters",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"monster_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '38f6bbd6f1e298b20e17b3a08fbc7f70')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
## Monster
|
||||||
|
id: UUID as TEXT // doesn't exist in the iOS model
|
||||||
|
abilities: Set<Trait> converted to JSON as TEXT
|
||||||
|
actions: Set<Trait> converted to JSON as TEXT
|
||||||
|
alignment: String as TEXT
|
||||||
|
armor_type: Enum<String> as TEXT
|
||||||
|
blindsight_range: int as INTEGER
|
||||||
|
burrow_speed: int as INTEGER
|
||||||
|
can_hover: boolean as INTEGER
|
||||||
|
challenge_rating: Enum<String> as TEXT
|
||||||
|
charisma_saving_throw_advantage
|
||||||
|
charisma_saving_throw_proficiency
|
||||||
|
charisma_score: int as INTEGER
|
||||||
|
climb_speed: int as INTEGER
|
||||||
|
condition_immunities: Set<String> converted to JSON as TEXT
|
||||||
|
constitution_saving_throw_advantage
|
||||||
|
constitution_saving_throw_proficiency
|
||||||
|
constitution_score: int as INTEGER
|
||||||
|
//other_armor_description: String as TEXT
|
||||||
|
custom_challenge_rating_description: String as TEXT
|
||||||
|
custom_hit_points_description: String
|
||||||
|
custom_proficiency_bonus: int as INTEGER
|
||||||
|
custom_speed_description: String as TEXT
|
||||||
|
damage_immunities: Set<String> converted to JSON as TEXT
|
||||||
|
damage_resistances: Set<String> converted to JSON as TEXT
|
||||||
|
damage_vulnerabilities: Set<String> converted to JSON as TEXT
|
||||||
|
darkvision_range: int as INTEGER
|
||||||
|
dexterity_saving_throw_advantage
|
||||||
|
dexterity_saving_throw_proficiency
|
||||||
|
dexterity_score: int as INTEGER
|
||||||
|
fly_speed: int as INTEGER
|
||||||
|
has_custom_hit_points: boolean as INTEGER
|
||||||
|
has_custom_speed: boolean as INTEGER
|
||||||
|
// has_shield
|
||||||
|
hit_dice: int as INTEGER
|
||||||
|
intelligence_saving_throw_advantage
|
||||||
|
intelligence_saving_throw_proficiency
|
||||||
|
intelligence_score: int as INTEGER
|
||||||
|
is_blind_beyond_blindsight_range: boolean as INTEGER
|
||||||
|
lair_actions
|
||||||
|
languages: Set<Language> converted to JSON as TEXT
|
||||||
|
legendary_actions
|
||||||
|
name: String as TEXT
|
||||||
|
natural_armor_bonus: int as INTEGER
|
||||||
|
other_armor_description: String as TEXT
|
||||||
|
reactions
|
||||||
|
regional_actions
|
||||||
|
// senses
|
||||||
|
shield_bonus: int as INTEGER
|
||||||
|
size: String as TEXT
|
||||||
|
strength_saving_throw_advantage
|
||||||
|
strength_saving_throw_proficiency
|
||||||
|
strength_score: int as INTEGER
|
||||||
|
tag: String as TEXT // subtype || tag
|
||||||
|
swim_speed: int as INTEGER
|
||||||
|
telepathy_range: int as INTEGER
|
||||||
|
tremorsense_range: int as INTEGER
|
||||||
|
truesight_range: int as INTEGER
|
||||||
|
type: String as TEXT
|
||||||
|
understands_but_description: String as TEXT
|
||||||
|
walk_speed: int as INTEGER
|
||||||
|
wisdom_saving_throw_advantage
|
||||||
|
wisdom_saving_throw_proficiency
|
||||||
|
wisdom_score: int as INTEGER
|
||||||
|
|
||||||
|
// tracked as relationship (don't do this)
|
||||||
|
skills: Set<Skill> converted to JSON as TEXT
|
||||||
|
|
||||||
|
## Skill
|
||||||
|
// ability_score_name String defaults to "strength"
|
||||||
|
// advantage String defaults to "none"
|
||||||
|
// name String defaults to ""
|
||||||
|
// proficiency String defaults to "none"
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.majinnaibu.monstercards;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
public void useAppContext() {
|
||||||
|
// Context of the app under test.
|
||||||
|
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||||
|
assertEquals("com.majinnaibu.monstercards", appContext.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.majinnaibu.monstercards.init;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
|
||||||
|
import com.majinnaibu.monstercards.BuildConfig;
|
||||||
|
import com.microsoft.appcenter.AppCenter;
|
||||||
|
import com.microsoft.appcenter.analytics.Analytics;
|
||||||
|
import com.microsoft.appcenter.crashes.Crashes;
|
||||||
|
|
||||||
|
public class AppCenterInitializer {
|
||||||
|
|
||||||
|
public static void init(Application app) {
|
||||||
|
if (BuildConfig.APPCENTER_SECRET != null && !"".equals(BuildConfig.APPCENTER_SECRET)) {
|
||||||
|
AppCenter.start(
|
||||||
|
app,
|
||||||
|
BuildConfig.APPCENTER_SECRET,
|
||||||
|
Analytics.class,
|
||||||
|
Crashes.class
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
99
Android/app/src/main/AndroidManifest.xml
Normal file
99
Android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:name=".MonsterCardsApplication"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:intentMatchingFlags="enforceIntentFilter"
|
||||||
|
android:theme="@style/AppTheme">
|
||||||
|
<activity
|
||||||
|
android:name=".SplashActivity"
|
||||||
|
android:theme="@style/Theme.App.Starting"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:windowSoftInputMode="adjustResize"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
<!-- Intent filter for viewing/opening content:// URIs with binary/octet-stream MIME type (.monster) -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<action android:name="android.intent.action.EDIT" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="content" />
|
||||||
|
<data android:mimeType="application/octet-stream" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<!-- Intent filter for file:// URIs ending in .monster, .monster.txt, .card, or .card.txt -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<action android:name="android.intent.action.EDIT" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="file" android:host="*" />
|
||||||
|
<data android:pathPattern=".*\\.monster" />
|
||||||
|
<data android:pathPattern=".*\\.monster\\.txt" />
|
||||||
|
<data android:pathPattern=".*\\.card" />
|
||||||
|
<data android:pathPattern=".*\\.card\\.txt" />
|
||||||
|
<data android:pathPattern=".*\\.binder" />
|
||||||
|
<data android:pathPattern=".*\\.binder\\.txt" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<!-- Intent filter for explicitly sharing files/text to Monster Cards -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|
||||||
|
<data android:mimeType="text/plain" />
|
||||||
|
<data android:mimeType="application/octet-stream" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<!-- Intent filter for viewing dndbeyond.com character URLs directly in Monster Cards -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="http" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
<data android:host="www.dndbeyond.com" />
|
||||||
|
<data android:host="dndbeyond.com" />
|
||||||
|
<data android:pathPrefix="/characters/" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<nav-graph android:value="@navigation/mobile_navigation" />
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="com.majinnaibu.monstercards.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths" />
|
||||||
|
</provider>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.majinnaibu.monstercards;
|
||||||
|
|
||||||
|
import androidx.room.Database;
|
||||||
|
import androidx.room.RoomDatabase;
|
||||||
|
import androidx.room.TypeConverters;
|
||||||
|
|
||||||
|
import com.majinnaibu.monstercards.data.CollectionDAO;
|
||||||
|
import com.majinnaibu.monstercards.data.DashboardDAO;
|
||||||
|
import com.majinnaibu.monstercards.data.MonsterDAO;
|
||||||
|
import com.majinnaibu.monstercards.data.converters.ArmorTypeConverter;
|
||||||
|
import com.majinnaibu.monstercards.data.converters.ChallengeRatingConverter;
|
||||||
|
import com.majinnaibu.monstercards.data.converters.ListOfTraitsConverter;
|
||||||
|
import com.majinnaibu.monstercards.data.converters.SetOfLanguageConverter;
|
||||||
|
import com.majinnaibu.monstercards.data.converters.SetOfSkillConverter;
|
||||||
|
import com.majinnaibu.monstercards.data.converters.SetOfStringConverter;
|
||||||
|
import com.majinnaibu.monstercards.data.converters.UUIDConverter;
|
||||||
|
import com.majinnaibu.monstercards.models.Collection;
|
||||||
|
import com.majinnaibu.monstercards.models.CollectionMonster;
|
||||||
|
import com.majinnaibu.monstercards.models.DashboardMonster;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
import com.majinnaibu.monstercards.models.MonsterFTS;
|
||||||
|
|
||||||
|
@Database(entities = {Monster.class, MonsterFTS.class, Collection.class, CollectionMonster.class, DashboardMonster.class}, version = 8)
|
||||||
|
@TypeConverters({
|
||||||
|
ArmorTypeConverter.class,
|
||||||
|
ChallengeRatingConverter.class,
|
||||||
|
ListOfTraitsConverter.class,
|
||||||
|
SetOfLanguageConverter.class,
|
||||||
|
SetOfSkillConverter.class,
|
||||||
|
SetOfStringConverter.class,
|
||||||
|
UUIDConverter.class,
|
||||||
|
})
|
||||||
|
public abstract class AppDatabase extends RoomDatabase {
|
||||||
|
public abstract MonsterDAO monsterDAO();
|
||||||
|
public abstract CollectionDAO collectionDAO();
|
||||||
|
public abstract DashboardDAO dashboardDAO();
|
||||||
|
}
|
||||||
@@ -0,0 +1,480 @@
|
|||||||
|
package com.majinnaibu.monstercards;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.provider.OpenableColumns;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.core.graphics.Insets;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
import androidx.core.view.WindowCompat;
|
||||||
|
import androidx.core.view.WindowInsetsCompat;
|
||||||
|
import androidx.navigation.NavController;
|
||||||
|
import androidx.navigation.NavDirections;
|
||||||
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
|
import androidx.navigation.ui.AppBarConfiguration;
|
||||||
|
import androidx.navigation.ui.NavigationUI;
|
||||||
|
|
||||||
|
import com.google.android.material.appbar.MaterialToolbar;
|
||||||
|
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.majinnaibu.monstercards.helpers.MonsterImportHelper;
|
||||||
|
import com.majinnaibu.monstercards.helpers.StringHelper;
|
||||||
|
import com.majinnaibu.monstercards.importers.BinderImporter;
|
||||||
|
import com.majinnaibu.monstercards.importers.DnDBeyondImporter;
|
||||||
|
import com.majinnaibu.monstercards.importers.GitRepoImporterService;
|
||||||
|
import com.majinnaibu.monstercards.importers.Open5eApiWrapper;
|
||||||
|
import com.majinnaibu.monstercards.init.AppCenterInitializer;
|
||||||
|
import com.majinnaibu.monstercards.models.BinderExport;
|
||||||
|
import com.majinnaibu.monstercards.models.ImportSource;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
import com.majinnaibu.monstercards.utils.Logger;
|
||||||
|
import com.majinnaibu.monstercards.utils.SnackbarHelper;
|
||||||
|
import com.majinnaibu.monstercards.utils.ToastHelper;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.rxjava3.core.Single;
|
||||||
|
import io.reactivex.rxjava3.disposables.Disposable;
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private Disposable mImportDisposable;
|
||||||
|
private ImportSource mCurrentImportSource;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||||
|
if (item.getItemId() == android.R.id.home) {
|
||||||
|
getOnBackPressedDispatcher().onBackPressed();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
setTheme(R.style.AppTheme);
|
||||||
|
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
AppCenterInitializer.init(getApplication());
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
|
MaterialToolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
if (toolbar != null) {
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
}
|
||||||
|
|
||||||
|
View appBarLayout = findViewById(R.id.app_bar_layout);
|
||||||
|
BottomNavigationView navView = findViewById(R.id.nav_view);
|
||||||
|
|
||||||
|
View container = findViewById(R.id.container);
|
||||||
|
if (container != null) {
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(container, (v, windowInsets) -> {
|
||||||
|
Insets systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||||
|
if (appBarLayout != null) {
|
||||||
|
appBarLayout.setPadding(0, systemBars.top, 0, 0);
|
||||||
|
}
|
||||||
|
if (navView != null) {
|
||||||
|
navView.setPadding(0, 0, 0, systemBars.bottom);
|
||||||
|
}
|
||||||
|
return windowInsets;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Passing each menu ID as a set of Ids because each
|
||||||
|
// menu should be considered as top level destinations.
|
||||||
|
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
|
||||||
|
R.id.navigation_search,
|
||||||
|
R.id.navigation_dashboard,
|
||||||
|
R.id.navigation_collections,
|
||||||
|
R.id.navigation_library)
|
||||||
|
.build();
|
||||||
|
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
|
||||||
|
NavController navController = navHostFragment.getNavController();
|
||||||
|
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
|
||||||
|
NavigationUI.setupWithNavController(navView, navController);
|
||||||
|
onNewIntent(getIntent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onNewIntent(Intent intent) {
|
||||||
|
super.onNewIntent(intent);
|
||||||
|
|
||||||
|
if (intent == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String action = intent.getAction();
|
||||||
|
Bundle extras = intent.getExtras();
|
||||||
|
|
||||||
|
if ("android.intent.action.SEND".equals(action) && extras != null) {
|
||||||
|
CharSequence sharedText = extras.getCharSequence(Intent.EXTRA_TEXT);
|
||||||
|
if (sharedText != null) {
|
||||||
|
String textStr = sharedText.toString().trim();
|
||||||
|
DnDBeyondImporter dndImporter = new DnDBeyondImporter();
|
||||||
|
if (dndImporter.canImport(textStr)) {
|
||||||
|
importMonsterFromInputAndNavigate(textStr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ("android.intent.action.VIEW".equals(action)) {
|
||||||
|
Uri dataUri = intent.getData();
|
||||||
|
if (dataUri != null) {
|
||||||
|
String dataStr = dataUri.toString().trim();
|
||||||
|
DnDBeyondImporter dndImporter = new DnDBeyondImporter();
|
||||||
|
if (dndImporter.canImport(dataStr)) {
|
||||||
|
importMonsterFromInputAndNavigate(dataStr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri intentUri = getUriFromIntent(intent);
|
||||||
|
String fileName = intentUri != null ? getFileNameFromUri(intentUri) : null;
|
||||||
|
String json = readMonsterJSONFromIntent(intent);
|
||||||
|
if (!StringHelper.isNullOrEmpty(json)) {
|
||||||
|
importMonsterFromInputAndNavigate(json, fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Uri getUriFromIntent(@NonNull Intent intent) {
|
||||||
|
String action = intent.getAction();
|
||||||
|
Bundle extras = intent.getExtras();
|
||||||
|
if ("android.intent.action.SEND".equals(action)) {
|
||||||
|
if (extras != null) {
|
||||||
|
return extras.getParcelable(Intent.EXTRA_STREAM);
|
||||||
|
}
|
||||||
|
} else if ("android.intent.action.VIEW".equals(action) || "android.intent.action.EDIT".equals(action)) {
|
||||||
|
return intent.getData();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void importMonsterFromInputAndNavigate(@NonNull String input) {
|
||||||
|
importMonsterFromInputAndNavigate(input, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void importMonsterFromInputAndNavigate(@NonNull String input, @Nullable String fileName) {
|
||||||
|
try {
|
||||||
|
BinderImporter binderImporter = new BinderImporter();
|
||||||
|
if (binderImporter.canImport(input)) {
|
||||||
|
ToastHelper.showShort(this, R.string.toast_importing_url);
|
||||||
|
Single.fromCallable(() -> binderImporter.parse(input))
|
||||||
|
.flatMapCompletable(binder -> ((MonsterCardsApplication) getApplication()).getMonsterRepository().importBinder(binder))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(() -> {
|
||||||
|
ToastHelper.showLong(this, R.string.snackbar_import_binder_success);
|
||||||
|
NavHostFragment navHostFragment = Objects.requireNonNull((NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment));
|
||||||
|
NavController navController = navHostFragment.getNavController();
|
||||||
|
navController.navigate(R.id.navigation_library);
|
||||||
|
}, throwable -> {
|
||||||
|
Logger.logError("Failed to import binder from input: " + fileName, throwable);
|
||||||
|
ToastHelper.showLong(this, R.string.failed_to_import_url);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ToastHelper.showShort(this, R.string.toast_importing_url);
|
||||||
|
Single.fromCallable(() -> MonsterImportHelper.fromJSON(input, fileName))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(monster -> {
|
||||||
|
String serializedJson = new Gson().toJson(monster);
|
||||||
|
NavHostFragment navHostFragment = Objects.requireNonNull((NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment));
|
||||||
|
NavController navController = navHostFragment.getNavController();
|
||||||
|
NavDirections navAction = MobileNavigationDirections.actionGlobalMonsterImportFragment(serializedJson);
|
||||||
|
navController.navigate(navAction);
|
||||||
|
}, throwable -> {
|
||||||
|
Logger.logError("Failed to import monster from input: " + fileName, throwable);
|
||||||
|
ToastHelper.showLong(this, R.string.failed_to_import_url);
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.logError("Failed to process import input: " + fileName, e);
|
||||||
|
ToastHelper.showLong(this, R.string.failed_to_import_url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startImportFromSource(ImportSource source) {
|
||||||
|
if (mImportDisposable != null && !mImportDisposable.isDisposed()) {
|
||||||
|
new AlertDialog.Builder(this)
|
||||||
|
.setTitle(R.string.dialog_import_already_running_title)
|
||||||
|
.setMessage(getString(R.string.dialog_import_already_running_message, mCurrentImportSource != null ? mCurrentImportSource.projectName : ""))
|
||||||
|
.setPositiveButton(R.string.action_cancel_current, (dialog, which) -> {
|
||||||
|
mImportDisposable.dispose();
|
||||||
|
executeImportFromSource(source);
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.dialog_cancel, null)
|
||||||
|
.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
executeImportFromSource(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeImportFromSource(ImportSource source) {
|
||||||
|
mCurrentImportSource = source;
|
||||||
|
View rootView = findViewById(android.R.id.content);
|
||||||
|
Snackbar snackbar = SnackbarHelper.makeIndefinite(rootView, getString(R.string.snackbar_importing_source, source.projectName));
|
||||||
|
snackbar.setAction(R.string.action_cancel, v -> {
|
||||||
|
if (mImportDisposable != null && !mImportDisposable.isDisposed()) {
|
||||||
|
mImportDisposable.dispose();
|
||||||
|
SnackbarHelper.showLong(rootView, getString(R.string.snackbar_import_source_cancelled, source.projectName));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
snackbar.show();
|
||||||
|
|
||||||
|
Callable<Integer> importTask;
|
||||||
|
if (source.importType == ImportSource.ImportType.OPEN5E_API) {
|
||||||
|
importTask = () -> {
|
||||||
|
int totalImported = 0;
|
||||||
|
String nextUrl = null;
|
||||||
|
do {
|
||||||
|
if (mImportDisposable != null && mImportDisposable.isDisposed()) {
|
||||||
|
throw new InterruptedException("Open5e import was cancelled");
|
||||||
|
}
|
||||||
|
Open5eApiWrapper.Open5ePageResult page = Open5eApiWrapper.fetchPage(nextUrl, () -> mImportDisposable != null && mImportDisposable.isDisposed());
|
||||||
|
for (Monster monster : page.monsters) {
|
||||||
|
if (mImportDisposable != null && mImportDisposable.isDisposed()) {
|
||||||
|
throw new InterruptedException("Open5e import was cancelled");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
((MonsterCardsApplication) getApplication()).getMonsterRepository()
|
||||||
|
.saveMonster(monster)
|
||||||
|
.blockingAwait();
|
||||||
|
totalImported++;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.logError("Failed to save monster to database", e);
|
||||||
|
runOnUiThread(() -> ToastHelper.showShort(MainActivity.this, "An error occurred while saving a monster."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nextUrl = page.nextUrl;
|
||||||
|
} while (nextUrl != null && !nextUrl.isEmpty());
|
||||||
|
return totalImported;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
importTask = () -> {
|
||||||
|
try {
|
||||||
|
return GitRepoImporterService.importFromGitRepository(getApplicationContext(), source, () -> mImportDisposable != null && mImportDisposable.isDisposed());
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Logger.logError("Import task was interrupted", e);
|
||||||
|
return 0; // Return gracefully on interruption
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mImportDisposable = Single.fromCallable(importTask)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(resultCount -> {
|
||||||
|
snackbar.dismiss();
|
||||||
|
SnackbarHelper.showLong(rootView, getString(R.string.snackbar_import_source_complete, resultCount, source.projectName));
|
||||||
|
}, throwable -> {
|
||||||
|
snackbar.dismiss();
|
||||||
|
if (throwable instanceof InterruptedException) {
|
||||||
|
Logger.logError("Import task was cancelled: " + source.projectName, throwable);
|
||||||
|
// The user cancelled via UI, message handled in the snackbar action.
|
||||||
|
} else {
|
||||||
|
Logger.logError("Failed to import from source: " + source.projectName, throwable);
|
||||||
|
SnackbarHelper.showLong(rootView, getString(R.string.snackbar_import_source_failed, source.projectName));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void importMultipleFilesFromUris(@NonNull List<Uri> uris) {
|
||||||
|
if (uris.isEmpty()) return;
|
||||||
|
if (uris.size() == 1) {
|
||||||
|
Uri singleUri = uris.get(0);
|
||||||
|
String fileName = getFileNameFromUri(singleUri);
|
||||||
|
String content = readContentsOfUri(singleUri);
|
||||||
|
if (content != null && !content.trim().isEmpty()) {
|
||||||
|
importMonsterFromInputAndNavigate(content, fileName);
|
||||||
|
} else {
|
||||||
|
ToastHelper.showLong(this, R.string.failed_to_import_url);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ToastHelper.showShort(this, R.string.toast_importing_url);
|
||||||
|
Single.fromCallable(() -> {
|
||||||
|
int monstersImported = 0;
|
||||||
|
int bindersImported = 0;
|
||||||
|
|
||||||
|
for (Uri uri : uris) {
|
||||||
|
try {
|
||||||
|
String fileName = getFileNameFromUri(uri);
|
||||||
|
String content = readContentsOfUri(uri);
|
||||||
|
if (content == null || content.trim().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
BinderImporter binderImporter = new BinderImporter();
|
||||||
|
if (binderImporter.canImport(content)) {
|
||||||
|
BinderExport binder = binderImporter.parse(content);
|
||||||
|
((MonsterCardsApplication) getApplication()).getMonsterRepository()
|
||||||
|
.importBinder(binder)
|
||||||
|
.blockingAwait();
|
||||||
|
bindersImported++;
|
||||||
|
} else {
|
||||||
|
Monster monster = MonsterImportHelper.fromJSON(content, fileName);
|
||||||
|
((MonsterCardsApplication) getApplication()).getMonsterRepository()
|
||||||
|
.saveMonster(monster)
|
||||||
|
.blockingAwait();
|
||||||
|
monstersImported++;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.logError("Failed to import file URI: " + uri, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BatchImportResult(monstersImported, bindersImported);
|
||||||
|
})
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(result -> {
|
||||||
|
if (result.bindersImported > 0 || result.monstersImported > 0) {
|
||||||
|
String message = buildImportSummaryMessage(result.monstersImported, result.bindersImported);
|
||||||
|
ToastHelper.showLong(this, message);
|
||||||
|
NavHostFragment navHostFragment = Objects.requireNonNull((NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment));
|
||||||
|
NavController navController = navHostFragment.getNavController();
|
||||||
|
navController.navigate(R.id.navigation_library);
|
||||||
|
} else {
|
||||||
|
ToastHelper.showLong(this, R.string.failed_to_import_url);
|
||||||
|
}
|
||||||
|
}, throwable -> {
|
||||||
|
Logger.logError("Failed to execute batch file import", throwable);
|
||||||
|
ToastHelper.showLong(this, R.string.failed_to_import_url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildImportSummaryMessage(int monstersCount, int bindersCount) {
|
||||||
|
StringBuilder sb = new StringBuilder("Successfully imported ");
|
||||||
|
if (bindersCount > 0 && monstersCount > 0) {
|
||||||
|
sb.append(bindersCount).append(bindersCount == 1 ? " collection" : " collections")
|
||||||
|
.append(" and ")
|
||||||
|
.append(monstersCount).append(monstersCount == 1 ? " monster" : " monsters");
|
||||||
|
} else if (bindersCount > 0) {
|
||||||
|
sb.append(bindersCount).append(bindersCount == 1 ? " collection" : " collections");
|
||||||
|
} else {
|
||||||
|
sb.append(monstersCount).append(monstersCount == 1 ? " monster" : " monsters");
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class BatchImportResult {
|
||||||
|
final int monstersImported;
|
||||||
|
final int bindersImported;
|
||||||
|
|
||||||
|
BatchImportResult(int monstersImported, int bindersImported) {
|
||||||
|
this.monstersImported = monstersImported;
|
||||||
|
this.bindersImported = bindersImported;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String readMonsterJSONFromIntent(@NonNull Intent intent) {
|
||||||
|
String action = intent.getAction();
|
||||||
|
Bundle extras = intent.getExtras();
|
||||||
|
Uri uri = null;
|
||||||
|
if ("android.intent.action.SEND".equals(action)) {
|
||||||
|
if (extras != null) {
|
||||||
|
uri = extras.getParcelable(Intent.EXTRA_STREAM);
|
||||||
|
}
|
||||||
|
} else if ("android.intent.action.VIEW".equals(action) || "android.intent.action.EDIT".equals(action)) {
|
||||||
|
uri = intent.getData();
|
||||||
|
} else {
|
||||||
|
Logger.logError(String.format("unexpected launch configuration action: %s", action));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri == null || !isMonsterFile(uri)) {
|
||||||
|
if (uri != null) {
|
||||||
|
Logger.logError("Ignored file because extension is not supported (.monster, .card, .binder): " + uri);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String json = readContentsOfUri(uri);
|
||||||
|
if (StringHelper.isNullOrEmpty(json)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isMonsterFile(@NonNull Uri uri) {
|
||||||
|
String fileName = getFileNameFromUri(uri);
|
||||||
|
if (fileName == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String lowerName = fileName.toLowerCase(Locale.ROOT);
|
||||||
|
return lowerName.endsWith(".monster") || lowerName.endsWith(".monster.txt")
|
||||||
|
|| lowerName.endsWith(".card") || lowerName.endsWith(".card.txt")
|
||||||
|
|| lowerName.endsWith(".binder") || lowerName.endsWith(".binder.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String getFileNameFromUri(@NonNull Uri uri) {
|
||||||
|
String displayName = null;
|
||||||
|
if ("content".equals(uri.getScheme())) {
|
||||||
|
try (Cursor cursor = getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null)) {
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
|
||||||
|
if (nameIndex != -1) {
|
||||||
|
displayName = cursor.getString(nameIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.logError("Error querying display name from content URI", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (displayName == null) {
|
||||||
|
displayName = uri.getPath();
|
||||||
|
}
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String readContentsOfUri(Uri uri) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
try (InputStream inputStream =
|
||||||
|
getContentResolver().openInputStream(uri);
|
||||||
|
BufferedReader reader = new BufferedReader(
|
||||||
|
new InputStreamReader(Objects.requireNonNull(inputStream)))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
builder.append(line);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Logger.logError("error reading file", e);
|
||||||
|
ToastHelper.showLong(this, "An error occurred while reading the file.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
if (mImportDisposable != null && !mImportDisposable.isDisposed()) {
|
||||||
|
mImportDisposable.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
package com.majinnaibu.monstercards;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.room.Room;
|
||||||
|
import androidx.room.migration.Migration;
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||||
|
|
||||||
|
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.exceptions.UndeliverableException;
|
||||||
|
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
|
||||||
|
import com.majinnaibu.monstercards.utils.Logger;
|
||||||
|
|
||||||
|
import java.io.InterruptedIOException;
|
||||||
|
|
||||||
|
public class MonsterCardsApplication extends Application {
|
||||||
|
|
||||||
|
private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
|
||||||
|
@Override
|
||||||
|
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||||
|
// rename table monster to monsters
|
||||||
|
database.execSQL("ALTER TABLE monster RENAME TO monsters");
|
||||||
|
// create the fts view
|
||||||
|
database.execSQL("CREATE VIRTUAL TABLE IF NOT EXISTS `monsters_fts` USING FTS4(`name` TEXT, `size` TEXT, `type` TEXT, `subtype` TEXT, `alignment` TEXT, content=`monsters`)");
|
||||||
|
// build the initial full text search index
|
||||||
|
database.execSQL("INSERT INTO monsters_fts(monsters_fts) VALUES('rebuild')");
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final Migration MIGRATION_2_3 = new Migration(2, 3) {
|
||||||
|
@Override
|
||||||
|
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||||
|
// Add the senses column
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN 'senses' TEXT DEFAULT '[]'");
|
||||||
|
database.execSQL("CREATE TABLE new_monsters (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `size` TEXT NOT NULL DEFAULT '', `type` TEXT NOT NULL DEFAULT '', `subtype` TEXT NOT NULL DEFAULT '', `alignment` TEXT NOT NULL DEFAULT '', `strength_score` INTEGER NOT NULL DEFAULT 10, `strength_saving_throw_advantage` TEXT DEFAULT 'none', `strength_saving_throw_proficiency` TEXT DEFAULT 'none', `dexterity_score` INTEGER NOT NULL DEFAULT 10, `dexterity_saving_throw_advantage` TEXT DEFAULT 'none', `dexterity_saving_throw_proficiency` TEXT DEFAULT 'none', `constitution_score` INTEGER NOT NULL DEFAULT 10, `constitution_saving_throw_advantage` TEXT DEFAULT 'none', `constitution_saving_throw_proficiency` TEXT DEFAULT 'none', `intelligence_score` INTEGER NOT NULL DEFAULT 10, `intelligence_saving_throw_advantage` TEXT DEFAULT 'none', `intelligence_saving_throw_proficiency` TEXT DEFAULT 'none', `wisdom_score` INTEGER NOT NULL DEFAULT 10, `wisdom_saving_throw_advantage` TEXT DEFAULT 'none', `wisdom_saving_throw_proficiency` TEXT DEFAULT 'none', `charisma_score` INTEGER NOT NULL DEFAULT 10, `charisma_saving_throw_advantage` TEXT DEFAULT 'none', `charisma_saving_throw_proficiency` TEXT DEFAULT 'none', `armor_type` TEXT DEFAULT 'none', `shield_bonus` INTEGER NOT NULL DEFAULT 0, `natural_armor_bonus` INTEGER NOT NULL DEFAULT 0, `other_armor_description` TEXT DEFAULT '', `hit_dice` INTEGER NOT NULL DEFAULT 1, `has_custom_hit_points` INTEGER NOT NULL, `custom_hit_points_description` TEXT DEFAULT '', `walk_speed` INTEGER NOT NULL DEFAULT 0, `burrow_speed` INTEGER NOT NULL DEFAULT 0, `climb_speed` INTEGER NOT NULL DEFAULT 0, `fly_speed` INTEGER NOT NULL DEFAULT 0, `can_hover` INTEGER NOT NULL DEFAULT false, `swim_speed` INTEGER NOT NULL DEFAULT 0, `has_custom_speed` INTEGER NOT NULL DEFAULT false, `custom_speed_description` TEXT, `challenge_rating` TEXT DEFAULT '1', `custom_challenge_rating_description` TEXT DEFAULT '', `custom_proficiency_bonus` INTEGER NOT NULL DEFAULT 0, `telepathy_range` INTEGER NOT NULL DEFAULT 0, `understands_but_description` TEXT DEFAULT '', `senses` TEXT DEFAULT '[]', `skills` TEXT DEFAULT '[]', `damage_immunities` TEXT DEFAULT '[]', `damage_resistances` TEXT DEFAULT '[]', `damage_vulnerabilities` TEXT DEFAULT '[]', `condition_immunities` TEXT DEFAULT '[]', `languages` TEXT DEFAULT '[]', `abilities` TEXT DEFAULT '[]', `actions` TEXT DEFAULT '[]', `reactions` TEXT DEFAULT '[]', `lair_actions` TEXT DEFAULT '[]', `legendary_actions` TEXT DEFAULT '[]', `regional_actions` TEXT DEFAULT '[]', PRIMARY KEY(`id`))");
|
||||||
|
database.execSQL("INSERT INTO new_monsters(id, name, size, type, subtype, alignment, strength_score, strength_saving_throw_advantage, strength_saving_throw_proficiency, dexterity_score, dexterity_saving_throw_advantage, dexterity_saving_throw_proficiency, constitution_score, constitution_saving_throw_advantage, constitution_saving_throw_proficiency, intelligence_score, intelligence_saving_throw_advantage, intelligence_saving_throw_proficiency, wisdom_score, wisdom_saving_throw_advantage, wisdom_saving_throw_proficiency, charisma_score, charisma_saving_throw_advantage, charisma_saving_throw_proficiency, armor_type, shield_bonus, natural_armor_bonus, other_armor_description, hit_dice, has_custom_hit_points, custom_hit_points_description, walk_speed, burrow_speed, climb_speed, fly_speed, can_hover, swim_speed, has_custom_speed, custom_speed_description, challenge_rating, custom_challenge_rating_description, custom_proficiency_bonus, telepathy_range, understands_but_description, senses, skills, damage_immunities, damage_resistances, damage_vulnerabilities, condition_immunities, languages, abilities, actions, reactions, lair_actions, legendary_actions, regional_actions) SELECT id, name, size, type, subtype, alignment, strength_score, strength_saving_throw_advantage, strength_saving_throw_proficiency, dexterity_score, dexterity_saving_throw_advantage, dexterity_saving_throw_proficiency, constitution_score, constitution_saving_throw_advantage, constitution_saving_throw_proficiency, intelligence_score, intelligence_saving_throw_advantage, intelligence_saving_throw_proficiency, wisdom_score, wisdom_saving_throw_advantage, wisdom_saving_throw_proficiency, charisma_score, charisma_saving_throw_advantage, charisma_saving_throw_proficiency, armor_type, shield_bonus, natural_armor_bonus, other_armor_description, hit_dice, has_custom_hit_points, custom_hit_points_description, walk_speed, burrow_speed, climb_speed, fly_speed, can_hover, swim_speed, has_custom_speed, custom_speed_description, challenge_rating, custom_challenge_rating_description, custom_proficiency_bonus, telepathy_range, understands_but_description, senses, skills, damage_immunities, damage_resistances, damage_vulnerabilities, condition_immunities, languages, abilities, actions, reactions, lair_actions, legendary_actions, regional_actions FROM monsters");
|
||||||
|
database.execSQL("DROP TABLE monsters");
|
||||||
|
database.execSQL("ALTER TABLE new_monsters RENAME TO monsters");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final Migration MIGRATION_3_4 = new Migration(3, 4) {
|
||||||
|
@Override
|
||||||
|
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||||
|
database.execSQL("CREATE TABLE IF NOT EXISTS `collections` (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `description` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))");
|
||||||
|
database.execSQL("CREATE TABLE IF NOT EXISTS `collection_monsters` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `collection_id` TEXT NOT NULL, `monster_id` TEXT NOT NULL, `ordinal` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`collection_id`) REFERENCES `collections`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY(`monster_id`) REFERENCES `monsters`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE)");
|
||||||
|
database.execSQL("CREATE INDEX IF NOT EXISTS `index_collection_monsters_collection_id` ON `collection_monsters` (`collection_id`)");
|
||||||
|
database.execSQL("CREATE INDEX IF NOT EXISTS `index_collection_monsters_monster_id` ON `collection_monsters` (`monster_id`)");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final Migration MIGRATION_4_5 = new Migration(4, 5) {
|
||||||
|
@Override
|
||||||
|
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||||
|
database.execSQL("CREATE TABLE IF NOT EXISTS `dashboard_monsters` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `monster_id` TEXT NOT NULL, `ordinal` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`monster_id`) REFERENCES `monsters`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE)");
|
||||||
|
database.execSQL("CREATE INDEX IF NOT EXISTS `index_dashboard_monsters_monster_id` ON `dashboard_monsters` (`monster_id`)");
|
||||||
|
database.execSQL("INSERT INTO dashboard_monsters(monster_id, ordinal) SELECT id, 0 FROM monsters");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final Migration MIGRATION_5_6 = new Migration(5, 6) {
|
||||||
|
@Override
|
||||||
|
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `source_url` TEXT DEFAULT ''");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `bonus_actions` TEXT DEFAULT '[]'");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `mythic_actions` TEXT DEFAULT '[]'");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `legendary_actions_description` TEXT DEFAULT ''");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `lair_actions_description` TEXT DEFAULT ''");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `lair_actions_end_note` TEXT DEFAULT ''");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `regional_actions_description` TEXT DEFAULT ''");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `regional_actions_end_note` TEXT DEFAULT ''");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `mythic_actions_description` TEXT DEFAULT ''");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final Migration MIGRATION_6_7 = new Migration(6, 7) {
|
||||||
|
@Override
|
||||||
|
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `player_name` TEXT NOT NULL DEFAULT ''");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `background` TEXT NOT NULL DEFAULT ''");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `personality_traits` TEXT NOT NULL DEFAULT ''");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `ideals` TEXT NOT NULL DEFAULT ''");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `bonds` TEXT NOT NULL DEFAULT ''");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `flaws` TEXT NOT NULL DEFAULT ''");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `age` TEXT NOT NULL DEFAULT ''");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `height` TEXT NOT NULL DEFAULT ''");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `weight` TEXT NOT NULL DEFAULT ''");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `eyes` TEXT NOT NULL DEFAULT ''");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `skin` TEXT NOT NULL DEFAULT ''");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `hair` TEXT NOT NULL DEFAULT ''");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `appearance` TEXT NOT NULL DEFAULT ''");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `backstory` TEXT NOT NULL DEFAULT ''");
|
||||||
|
database.execSQL("ALTER TABLE monsters ADD COLUMN `allies_and_organizations` TEXT NOT NULL DEFAULT ''");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final Migration MIGRATION_7_8 = new Migration(7, 8) {
|
||||||
|
@Override
|
||||||
|
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||||
|
database.execSQL("CREATE TABLE IF NOT EXISTS `new_monsters` (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `size` TEXT NOT NULL DEFAULT '', `type` TEXT NOT NULL DEFAULT '', `subtype` TEXT NOT NULL DEFAULT '', `alignment` TEXT NOT NULL DEFAULT '', `strength_score` INTEGER NOT NULL DEFAULT 10, `strength_saving_throw_advantage` TEXT DEFAULT 'none', `strength_saving_throw_proficiency` TEXT DEFAULT 'none', `dexterity_score` INTEGER NOT NULL DEFAULT 10, `dexterity_saving_throw_advantage` TEXT DEFAULT 'none', `dexterity_saving_throw_proficiency` TEXT DEFAULT 'none', `constitution_score` INTEGER NOT NULL DEFAULT 10, `constitution_saving_throw_advantage` TEXT DEFAULT 'none', `constitution_saving_throw_proficiency` TEXT DEFAULT 'none', `intelligence_score` INTEGER NOT NULL DEFAULT 10, `intelligence_saving_throw_advantage` TEXT DEFAULT 'none', `intelligence_saving_throw_proficiency` TEXT DEFAULT 'none', `wisdom_score` INTEGER NOT NULL DEFAULT 10, `wisdom_saving_throw_advantage` TEXT DEFAULT 'none', `wisdom_saving_throw_proficiency` TEXT DEFAULT 'none', `charisma_score` INTEGER NOT NULL DEFAULT 10, `charisma_saving_throw_advantage` TEXT DEFAULT 'none', `charisma_saving_throw_proficiency` TEXT DEFAULT 'none', `armor_type` TEXT DEFAULT 'none', `shield_bonus` INTEGER NOT NULL DEFAULT 0, `natural_armor_bonus` INTEGER NOT NULL DEFAULT 0, `other_armor_description` TEXT DEFAULT '', `hit_dice` INTEGER NOT NULL DEFAULT 1, `has_custom_hit_points` INTEGER NOT NULL, `custom_hit_points_description` TEXT DEFAULT '', `walk_speed` INTEGER NOT NULL DEFAULT 0, `burrow_speed` INTEGER NOT NULL DEFAULT 0, `climb_speed` INTEGER NOT NULL DEFAULT 0, `fly_speed` INTEGER NOT NULL DEFAULT 0, `can_hover` INTEGER NOT NULL DEFAULT false, `swim_speed` INTEGER NOT NULL DEFAULT 0, `has_custom_speed` INTEGER NOT NULL DEFAULT false, `custom_speed_description` TEXT, `challenge_rating` TEXT DEFAULT '1', `custom_challenge_rating_description` TEXT DEFAULT '', `custom_proficiency_bonus` INTEGER NOT NULL DEFAULT 0, `telepathy_range` INTEGER NOT NULL DEFAULT 0, `understands_but_description` TEXT DEFAULT '', `senses` TEXT DEFAULT '[]', `skills` TEXT DEFAULT '[]', `damage_immunities` TEXT DEFAULT '[]', `damage_resistances` TEXT DEFAULT '[]', `damage_vulnerabilities` TEXT DEFAULT '[]', `condition_immunities` TEXT DEFAULT '[]', `languages` TEXT DEFAULT '[]', `abilities` TEXT DEFAULT '[]', `actions` TEXT DEFAULT '[]', `reactions` TEXT DEFAULT '[]', `lair_actions` TEXT DEFAULT '[]', `legendary_actions` TEXT DEFAULT '[]', `regional_actions` TEXT DEFAULT '[]', `source_url` TEXT DEFAULT '', `bonus_actions` TEXT DEFAULT '[]', `mythic_actions` TEXT DEFAULT '[]', `legendary_actions_description` TEXT DEFAULT '', `lair_actions_description` TEXT DEFAULT '', `lair_actions_end_note` TEXT DEFAULT '', `regional_actions_description` TEXT DEFAULT '', `regional_actions_end_note` TEXT DEFAULT '', `mythic_actions_description` TEXT DEFAULT '', `player_name` TEXT NOT NULL DEFAULT '', `background` TEXT NOT NULL DEFAULT '', `personality_traits` TEXT NOT NULL DEFAULT '', `ideals` TEXT NOT NULL DEFAULT '', `bonds` TEXT NOT NULL DEFAULT '', `flaws` TEXT NOT NULL DEFAULT '', `age` TEXT NOT NULL DEFAULT '', `height` TEXT NOT NULL DEFAULT '', `weight` TEXT NOT NULL DEFAULT '', `eyes` TEXT NOT NULL DEFAULT '', `skin` TEXT NOT NULL DEFAULT '', `hair` TEXT NOT NULL DEFAULT '', `appearance` TEXT NOT NULL DEFAULT '', `backstory` TEXT NOT NULL DEFAULT '', `allies_and_organizations` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))");
|
||||||
|
database.execSQL("INSERT INTO `new_monsters` SELECT id, name, size, type, subtype, alignment, strength_score, strength_saving_throw_advantage, strength_saving_throw_proficiency, dexterity_score, dexterity_saving_throw_advantage, dexterity_saving_throw_proficiency, constitution_score, constitution_saving_throw_advantage, constitution_saving_throw_proficiency, intelligence_score, intelligence_saving_throw_advantage, intelligence_saving_throw_proficiency, wisdom_score, wisdom_saving_throw_advantage, wisdom_saving_throw_proficiency, charisma_score, charisma_saving_throw_advantage, charisma_saving_throw_proficiency, armor_type, shield_bonus, natural_armor_bonus, other_armor_description, hit_dice, has_custom_hit_points, custom_hit_points_description, walk_speed, burrow_speed, climb_speed, fly_speed, can_hover, swim_speed, has_custom_speed, custom_speed_description, challenge_rating, custom_challenge_rating_description, custom_proficiency_bonus, telepathy_range, understands_but_description, senses, skills, damage_immunities, damage_resistances, damage_vulnerabilities, condition_immunities, languages, abilities, actions, reactions, lair_actions, legendary_actions, regional_actions, source_url, bonus_actions, mythic_actions, legendary_actions_description, lair_actions_description, lair_actions_end_note, regional_actions_description, regional_actions_end_note, mythic_actions_description, COALESCE(player_name, ''), COALESCE(background, ''), COALESCE(personality_traits, ''), COALESCE(ideals, ''), COALESCE(bonds, ''), COALESCE(flaws, ''), COALESCE(age, ''), COALESCE(height, ''), COALESCE(weight, ''), COALESCE(eyes, ''), COALESCE(skin, ''), COALESCE(hair, ''), COALESCE(appearance, ''), COALESCE(backstory, ''), COALESCE(allies_and_organizations, '') FROM `monsters`");
|
||||||
|
database.execSQL("DROP TABLE `monsters`");
|
||||||
|
database.execSQL("ALTER TABLE `new_monsters` RENAME TO `monsters`");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private MonsterRepository m_monsterLibraryRepository;
|
||||||
|
|
||||||
|
|
||||||
|
public MonsterCardsApplication() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public MonsterRepository getMonsterRepository() {
|
||||||
|
return m_monsterLibraryRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when the application is starting, before any other application objects have been created.
|
||||||
|
// Overriding this method is totally optional!
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
|
||||||
|
RxJavaPlugins.setErrorHandler(e -> {
|
||||||
|
if (e instanceof UndeliverableException) {
|
||||||
|
e = e.getCause();
|
||||||
|
}
|
||||||
|
if (e instanceof InterruptedException || e instanceof InterruptedIOException) {
|
||||||
|
// Expected when a blocking task is cancelled; swallow silently.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.logError("Undeliverable exception received by RxJava global handler", e);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Required initialization logic here!
|
||||||
|
|
||||||
|
// .fallbackToDestructiveMigration()
|
||||||
|
AppDatabase m_db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "monsters")
|
||||||
|
.addMigrations(MIGRATION_1_2)
|
||||||
|
.addMigrations(MIGRATION_2_3)
|
||||||
|
.addMigrations(MIGRATION_3_4)
|
||||||
|
.addMigrations(MIGRATION_4_5)
|
||||||
|
.addMigrations(MIGRATION_5_6)
|
||||||
|
.addMigrations(MIGRATION_6_7)
|
||||||
|
.addMigrations(MIGRATION_7_8)
|
||||||
|
.fallbackToDestructiveMigrationOnDowngrade()
|
||||||
|
// .fallbackToDestructiveMigration()
|
||||||
|
.build();
|
||||||
|
m_monsterLibraryRepository = new MonsterRepository(m_db);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by the system when the device configuration changes while your component is running.
|
||||||
|
// Overriding this method is totally optional!
|
||||||
|
@Override
|
||||||
|
public void onConfigurationChanged(Configuration newConfig) {
|
||||||
|
super.onConfigurationChanged(newConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is called when the overall system is running low on memory,
|
||||||
|
// and would like actively running processes to tighten their belts.
|
||||||
|
// Overriding this method is totally optional!
|
||||||
|
@Override
|
||||||
|
public void onLowMemory() {
|
||||||
|
super.onLowMemory();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.majinnaibu.monstercards;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
public class SplashActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
setTheme(R.style.AppTheme);
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_splash);
|
||||||
|
|
||||||
|
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||||
|
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
|
||||||
|
if (getIntent() != null) {
|
||||||
|
if (getIntent().getExtras() != null) {
|
||||||
|
intent.putExtras(getIntent().getExtras());
|
||||||
|
}
|
||||||
|
if (getIntent().getAction() != null) {
|
||||||
|
intent.setAction(getIntent().getAction());
|
||||||
|
}
|
||||||
|
if (getIntent().getData() != null) {
|
||||||
|
intent.setData(getIntent().getData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||||
|
}, 1200);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.majinnaibu.monstercards.data;
|
||||||
|
|
||||||
|
import androidx.room.Dao;
|
||||||
|
import androidx.room.Delete;
|
||||||
|
import androidx.room.Insert;
|
||||||
|
import androidx.room.OnConflictStrategy;
|
||||||
|
import androidx.room.Query;
|
||||||
|
import androidx.room.Update;
|
||||||
|
|
||||||
|
import com.majinnaibu.monstercards.models.Collection;
|
||||||
|
import com.majinnaibu.monstercards.models.CollectionMonster;
|
||||||
|
import com.majinnaibu.monstercards.models.CollectionWithCount;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.core.Completable;
|
||||||
|
import io.reactivex.rxjava3.core.Flowable;
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
public interface CollectionDAO {
|
||||||
|
|
||||||
|
@Query("SELECT * FROM collections")
|
||||||
|
Flowable<List<Collection>> getAll();
|
||||||
|
|
||||||
|
@Query("SELECT collections.*, COUNT(collection_monsters.id) as monsterCount FROM collections LEFT JOIN collection_monsters ON collections.id = collection_monsters.collection_id GROUP BY collections.id")
|
||||||
|
Flowable<List<CollectionWithCount>> getCollectionsWithCount();
|
||||||
|
|
||||||
|
@Query("SELECT * FROM collections WHERE id = :id LIMIT 1")
|
||||||
|
Flowable<Collection> getById(String id);
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
Completable save(Collection... collections);
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
Completable delete(Collection collection);
|
||||||
|
|
||||||
|
@Query("SELECT monsters.* FROM monsters INNER JOIN collection_monsters ON monsters.id = collection_monsters.monster_id WHERE collection_monsters.collection_id = :collectionId ORDER BY collection_monsters.ordinal ASC, collection_monsters.id ASC")
|
||||||
|
Flowable<List<Monster>> getMonstersForCollection(String collectionId);
|
||||||
|
|
||||||
|
@Query("SELECT * FROM collection_monsters WHERE collection_id = :collectionId ORDER BY ordinal ASC, id ASC")
|
||||||
|
Flowable<List<CollectionMonster>> getCollectionMonstersForCollection(String collectionId);
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
Completable addMonsterToCollection(CollectionMonster... collectionMonsters);
|
||||||
|
|
||||||
|
@Query("DELETE FROM collection_monsters WHERE collection_id = :collectionId AND monster_id = :monsterId")
|
||||||
|
Completable removeMonsterFromCollection(String collectionId, String monsterId);
|
||||||
|
|
||||||
|
@Query("DELETE FROM collection_monsters WHERE collection_id = :collectionId")
|
||||||
|
Completable removeAllMonstersFromCollection(String collectionId);
|
||||||
|
|
||||||
|
@Query("DELETE FROM collection_monsters WHERE id = :id")
|
||||||
|
Completable removeCollectionMonsterById(long id);
|
||||||
|
|
||||||
|
@Query("DELETE FROM collection_monsters")
|
||||||
|
Completable deleteAllCollectionMonsters();
|
||||||
|
|
||||||
|
@Query("DELETE FROM collections")
|
||||||
|
Completable deleteAllCollections();
|
||||||
|
|
||||||
|
@Update
|
||||||
|
Completable updateCollectionMonsters(List<CollectionMonster> collectionMonsters);
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.majinnaibu.monstercards.data;
|
||||||
|
|
||||||
|
import androidx.room.Dao;
|
||||||
|
import androidx.room.Insert;
|
||||||
|
import androidx.room.Query;
|
||||||
|
import androidx.room.Transaction;
|
||||||
|
import androidx.room.Update;
|
||||||
|
|
||||||
|
import com.majinnaibu.monstercards.models.DashboardMonster;
|
||||||
|
import com.majinnaibu.monstercards.models.DashboardMonsterWithMonster;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.core.Completable;
|
||||||
|
import io.reactivex.rxjava3.core.Flowable;
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
public interface DashboardDAO {
|
||||||
|
|
||||||
|
@Query("SELECT monsters.* FROM monsters INNER JOIN dashboard_monsters ON monsters.id = dashboard_monsters.monster_id ORDER BY dashboard_monsters.ordinal ASC, dashboard_monsters.id ASC")
|
||||||
|
Flowable<List<Monster>> getDashboardMonsters();
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
@Query("SELECT * FROM dashboard_monsters ORDER BY ordinal ASC, id ASC")
|
||||||
|
Flowable<List<DashboardMonsterWithMonster>> getDashboardMonstersWithMonster();
|
||||||
|
|
||||||
|
@Query("SELECT * FROM dashboard_monsters ORDER BY ordinal ASC, id ASC")
|
||||||
|
Flowable<List<DashboardMonster>> getDashboardMonsterEntries();
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
Completable addMonsterToDashboard(DashboardMonster... dashboardMonsters);
|
||||||
|
|
||||||
|
@Query("DELETE FROM dashboard_monsters WHERE id = :id")
|
||||||
|
Completable removeDashboardMonsterById(long id);
|
||||||
|
|
||||||
|
@Query("DELETE FROM dashboard_monsters WHERE id = (SELECT id FROM dashboard_monsters WHERE monster_id = :monsterId LIMIT 1)")
|
||||||
|
Completable removeMonsterFromDashboard(String monsterId);
|
||||||
|
|
||||||
|
@Query("DELETE FROM dashboard_monsters WHERE monster_id = :monsterId")
|
||||||
|
Completable removeAllMonstersFromDashboard(String monsterId);
|
||||||
|
|
||||||
|
@Query("DELETE FROM dashboard_monsters")
|
||||||
|
Completable clearDashboard();
|
||||||
|
|
||||||
|
@Update
|
||||||
|
Completable updateDashboardMonsters(List<DashboardMonster> dashboardMonsters);
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package com.majinnaibu.monstercards.data;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.ArmorType;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.ChallengeRating;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||||
|
import com.majinnaibu.monstercards.models.Language;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
import com.majinnaibu.monstercards.models.Skill;
|
||||||
|
import com.majinnaibu.monstercards.models.Trait;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class DevContent {
|
||||||
|
@NonNull
|
||||||
|
public static Monster createSampleMonster() {
|
||||||
|
Monster monster = new Monster();
|
||||||
|
// Name
|
||||||
|
monster.name = "Pixie";
|
||||||
|
// Meta
|
||||||
|
monster.size = "tiny";
|
||||||
|
monster.type = "fey";
|
||||||
|
monster.subtype = "";
|
||||||
|
monster.alignment = "neutral good";
|
||||||
|
monster.armorType = ArmorType.NONE;
|
||||||
|
// Armor & Armor Class
|
||||||
|
monster.shieldBonus = 0;
|
||||||
|
monster.naturalArmorBonus = 7;
|
||||||
|
monster.otherArmorDescription = "14";
|
||||||
|
// Hit Points
|
||||||
|
monster.hitDice = 1;
|
||||||
|
monster.hasCustomHP = false;
|
||||||
|
monster.customHPDescription = "11 (2d8 + 2)";
|
||||||
|
monster.walkSpeed = 10;
|
||||||
|
monster.burrowSpeed = 0;
|
||||||
|
monster.climbSpeed = 0;
|
||||||
|
monster.flySpeed = 30;
|
||||||
|
monster.canHover = false;
|
||||||
|
monster.swimSpeed = 0;
|
||||||
|
monster.hasCustomSpeed = false;
|
||||||
|
monster.customSpeedDescription = "30 ft., swim 30 ft.";
|
||||||
|
// Ability Scores
|
||||||
|
monster.strengthScore = Integer.parseInt("2");
|
||||||
|
monster.dexterityScore = Integer.parseInt("20");
|
||||||
|
monster.constitutionScore = Integer.parseInt("8");
|
||||||
|
monster.intelligenceScore = Integer.parseInt("10");
|
||||||
|
monster.wisdomScore = Integer.parseInt("14");
|
||||||
|
monster.charismaScore = Integer.parseInt("15");
|
||||||
|
// monster.strengthScore = 10;
|
||||||
|
// monster.dexterityScore = 10;
|
||||||
|
// monster.constitutionScore = 10;
|
||||||
|
// monster.intelligenceScore = 10;
|
||||||
|
// monster.wisdomScore = 10;
|
||||||
|
// monster.charismaScore = 10;
|
||||||
|
|
||||||
|
// Saving Throws
|
||||||
|
monster.strengthSavingThrowAdvantage = AdvantageType.NONE;
|
||||||
|
monster.strengthSavingThrowProficiency = ProficiencyType.NONE;
|
||||||
|
monster.dexteritySavingThrowAdvantage = AdvantageType.ADVANTAGE;
|
||||||
|
monster.dexteritySavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
monster.constitutionSavingThrowAdvantage = AdvantageType.DISADVANTAGE;
|
||||||
|
monster.constitutionSavingThrowProficiency = ProficiencyType.EXPERTISE;
|
||||||
|
monster.intelligenceSavingThrowAdvantage = AdvantageType.NONE;
|
||||||
|
monster.intelligenceSavingThrowProficiency = ProficiencyType.EXPERTISE;
|
||||||
|
monster.wisdomSavingThrowAdvantage = AdvantageType.ADVANTAGE;
|
||||||
|
monster.wisdomSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
monster.charismaSavingThrowAdvantage = AdvantageType.DISADVANTAGE;
|
||||||
|
monster.charismaSavingThrowProficiency = ProficiencyType.NONE;
|
||||||
|
//Skills
|
||||||
|
monster.skills.add(new Skill("perception", AbilityScore.WISDOM));
|
||||||
|
monster.skills.add(new Skill("stealth", AbilityScore.DEXTERITY));
|
||||||
|
// Damage Types
|
||||||
|
monster.damageImmunities.add("force");
|
||||||
|
monster.damageImmunities.add("lightning");
|
||||||
|
monster.damageImmunities.add("poison");
|
||||||
|
monster.damageResistances.add("cold");
|
||||||
|
monster.damageResistances.add("fire");
|
||||||
|
monster.damageResistances.add("piercing");
|
||||||
|
monster.damageVulnerabilities.add("acid");
|
||||||
|
monster.damageVulnerabilities.add("bludgeoning");
|
||||||
|
monster.damageVulnerabilities.add("necrotic");
|
||||||
|
// Condition Immunities
|
||||||
|
monster.conditionImmunities.add("blinded");
|
||||||
|
// Senses
|
||||||
|
monster.senses.add("blindsight 10 ft. (blind beyond this range)");
|
||||||
|
monster.senses.add("darkvision 20 ft.");
|
||||||
|
monster.senses.add("tremorsense 30 ft.");
|
||||||
|
monster.senses.add("truesight 40 ft.");
|
||||||
|
monster.telepathyRange = 20;
|
||||||
|
monster.understandsButDescription = "doesn't care";
|
||||||
|
// Languages
|
||||||
|
monster.languages.add(new Language("English", true));
|
||||||
|
monster.languages.add(new Language("Steve", false));
|
||||||
|
monster.languages.add(new Language("Spanish", true));
|
||||||
|
monster.languages.add(new Language("French", true));
|
||||||
|
monster.languages.add(new Language("Mermataur", false));
|
||||||
|
monster.languages.add(new Language("Goldfish", false));
|
||||||
|
// Challenge Rating
|
||||||
|
monster.challengeRating = ChallengeRating.CUSTOM;
|
||||||
|
monster.customChallengeRatingDescription = "Infinite (0XP)";
|
||||||
|
monster.customProficiencyBonus = 4;
|
||||||
|
// Abilities
|
||||||
|
monster.abilities.add(new Trait("Spellcasting", "The acolyte is a 1st-level spellcaster. Its spellcasting ability is Wisdom (spell save DC [WIS SAVE], [WIS ATK] to hit with spell attacks). The acolyte has following cleric spells prepared:\n\n\n> Cantrips (at will): _light, sacred flame, thaumaturgy_\n> 1st level (3 slots): _bless, cure wounds, sanctuary_"));
|
||||||
|
monster.abilities.add(new Trait("Amphibious", "The dragon can breathe air and water."));
|
||||||
|
monster.abilities.add(new Trait("Legendary Resistance (3/Day)", "If the dragon fails a saving throw, it can choose to succeed instead."));
|
||||||
|
// Actions
|
||||||
|
monster.actions.add(new Trait("Club", "_Melee Weapon Attack:_ [STR ATK] to hit, reach 5 ft., one target. _Hit:_ 2 (1d4) bludgeoning damage."));
|
||||||
|
|
||||||
|
return monster;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.majinnaibu.monstercards.data;
|
||||||
|
|
||||||
|
import com.majinnaibu.monstercards.models.ImportSource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ImportConfig {
|
||||||
|
public static final List<ImportSource> SOURCES = new ArrayList<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
SOURCES.add(new ImportSource(
|
||||||
|
"open5e",
|
||||||
|
"Open5e.com System Reference Document",
|
||||||
|
"Open5e",
|
||||||
|
"https://open5e.com",
|
||||||
|
ImportSource.ImportType.OPEN5E_API,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
));
|
||||||
|
|
||||||
|
SOURCES.add(new ImportSource(
|
||||||
|
"pf2e_foundry",
|
||||||
|
"Pathfinder 2e Foundry VTT",
|
||||||
|
"foundryvtt",
|
||||||
|
"https://github.com/foundryvtt/pf2e",
|
||||||
|
ImportSource.ImportType.GIT_ARCHIVE,
|
||||||
|
"https://github.com/foundryvtt/pf2e/archive/refs/heads/v14-dev.zip",
|
||||||
|
"com.majinnaibu.monstercards.importers.Pf2eImporter",
|
||||||
|
".json",
|
||||||
|
"pf2e-14-dev/packs/pf2e"
|
||||||
|
));
|
||||||
|
|
||||||
|
SOURCES.add(new ImportSource(
|
||||||
|
"sf2e_foundry",
|
||||||
|
"Starfinder 2e Foundry VTT",
|
||||||
|
"foundryvtt",
|
||||||
|
"https://github.com/foundryvtt/pf2e",
|
||||||
|
ImportSource.ImportType.GIT_ARCHIVE,
|
||||||
|
"https://github.com/foundryvtt/pf2e/archive/refs/heads/v14-dev.zip",
|
||||||
|
"com.majinnaibu.monstercards.importers.Pf2eImporter",
|
||||||
|
".json",
|
||||||
|
"pf2e-14-dev/packs/sf2e"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.majinnaibu.monstercards.data;
|
||||||
|
|
||||||
|
|
||||||
|
import androidx.room.Dao;
|
||||||
|
import androidx.room.Delete;
|
||||||
|
import androidx.room.Insert;
|
||||||
|
import androidx.room.OnConflictStrategy;
|
||||||
|
import androidx.room.Query;
|
||||||
|
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.core.Completable;
|
||||||
|
import io.reactivex.rxjava3.core.Flowable;
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
public interface MonsterDAO {
|
||||||
|
@Query("SELECT * FROM monsters")
|
||||||
|
Flowable<List<Monster>> getAll();
|
||||||
|
|
||||||
|
@Query("SELECT * FROM monsters WHERE id IN (:monsterIds)")
|
||||||
|
Flowable<List<Monster>> loadAllByIds(String[] monsterIds);
|
||||||
|
|
||||||
|
@Query("SELECT * FROM monsters WHERE name LIKE :name LIMIT 1")
|
||||||
|
Flowable<Monster> findByName(String name);
|
||||||
|
|
||||||
|
@Query("SELECT monsters.* FROM monsters JOIN monsters_fts ON monsters.oid = monsters_fts.docid WHERE monsters_fts MATCH :searchText")
|
||||||
|
Flowable<List<Monster>> search(String searchText);
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
Completable insertAll(Monster... monsters);
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
Completable save(Monster... monsters);
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
Completable delete(Monster monster);
|
||||||
|
|
||||||
|
@Query("DELETE FROM monsters")
|
||||||
|
Completable deleteAllMonsters();
|
||||||
|
}
|
||||||
@@ -0,0 +1,551 @@
|
|||||||
|
package com.majinnaibu.monstercards.data;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.majinnaibu.monstercards.AppDatabase;
|
||||||
|
import com.majinnaibu.monstercards.helpers.StringHelper;
|
||||||
|
import com.majinnaibu.monstercards.models.BinderExport;
|
||||||
|
import com.majinnaibu.monstercards.models.Collection;
|
||||||
|
import com.majinnaibu.monstercards.models.CollectionMonster;
|
||||||
|
import com.majinnaibu.monstercards.models.CollectionWithCount;
|
||||||
|
import com.majinnaibu.monstercards.models.DashboardMonster;
|
||||||
|
import com.majinnaibu.monstercards.models.DashboardMonsterWithMonster;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
import com.majinnaibu.monstercards.models.SearchResultItem;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.rxjava3.core.Completable;
|
||||||
|
import io.reactivex.rxjava3.core.Flowable;
|
||||||
|
import io.reactivex.rxjava3.core.Single;
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
|
||||||
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
|
public class MonsterRepository {
|
||||||
|
|
||||||
|
private final AppDatabase m_db;
|
||||||
|
|
||||||
|
public MonsterRepository(@NonNull AppDatabase db) {
|
||||||
|
m_db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Flowable<List<Monster>> getMonsters() {
|
||||||
|
return m_db.monsterDAO()
|
||||||
|
.getAll()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Flowable<List<Monster>> searchMonsters(String searchText) {
|
||||||
|
return m_db.monsterDAO()
|
||||||
|
.getAll()
|
||||||
|
.map(monsters -> {
|
||||||
|
ArrayList<Monster> filteredMonsters = new ArrayList<>();
|
||||||
|
for (Monster monster : monsters) {
|
||||||
|
if (Helpers.monsterMatchesSearch(monster, searchText)) {
|
||||||
|
filteredMonsters.add(monster);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (List<Monster>) filteredMonsters;
|
||||||
|
})
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Flowable<List<SearchResultItem>> searchAll(String searchText) {
|
||||||
|
Flowable<List<Monster>> monstersFlowable = m_db.monsterDAO()
|
||||||
|
.getAll()
|
||||||
|
.map(monsters -> {
|
||||||
|
ArrayList<Monster> filteredMonsters = new ArrayList<>();
|
||||||
|
for (Monster monster : monsters) {
|
||||||
|
if (Helpers.monsterMatchesSearch(monster, searchText)) {
|
||||||
|
filteredMonsters.add(monster);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (List<Monster>) filteredMonsters;
|
||||||
|
});
|
||||||
|
|
||||||
|
Flowable<List<Collection>> collectionsFlowable = m_db.collectionDAO()
|
||||||
|
.getAll()
|
||||||
|
.map(collections -> {
|
||||||
|
ArrayList<Collection> filteredCollections = new ArrayList<>();
|
||||||
|
for (Collection collection : collections) {
|
||||||
|
if (StringHelper.isNullOrEmpty(searchText) || StringHelper.containsCaseInsensitive(collection.name, searchText)) {
|
||||||
|
filteredCollections.add(collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (List<Collection>) filteredCollections;
|
||||||
|
});
|
||||||
|
|
||||||
|
return Flowable.combineLatest(monstersFlowable, collectionsFlowable, (monsters, collections) -> {
|
||||||
|
ArrayList<SearchResultItem> results = new ArrayList<>();
|
||||||
|
for (Monster monster : monsters) {
|
||||||
|
results.add(new SearchResultItem(monster));
|
||||||
|
}
|
||||||
|
for (Collection collection : collections) {
|
||||||
|
results.add(new SearchResultItem(collection));
|
||||||
|
}
|
||||||
|
return (List<SearchResultItem>) results;
|
||||||
|
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Flowable<Monster> getMonster(@NonNull UUID monsterId) {
|
||||||
|
return m_db.monsterDAO()
|
||||||
|
.loadAllByIds(new String[]{monsterId.toString()})
|
||||||
|
.map(
|
||||||
|
monsters -> {
|
||||||
|
if (monsters.size() > 0) {
|
||||||
|
return monsters.get(0);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable addMonster(Monster monster) {
|
||||||
|
Completable result = m_db.monsterDAO().insertAll(monster);
|
||||||
|
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable deleteMonster(Monster monster) {
|
||||||
|
Completable result = m_db.monsterDAO().delete(monster);
|
||||||
|
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable saveMonster(Monster monster) {
|
||||||
|
Completable result = m_db.monsterDAO().save(monster);
|
||||||
|
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Flowable<List<Collection>> getCollections() {
|
||||||
|
return m_db.collectionDAO()
|
||||||
|
.getAll()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Flowable<List<CollectionWithCount>> getCollectionsWithCount() {
|
||||||
|
return m_db.collectionDAO()
|
||||||
|
.getCollectionsWithCount()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Flowable<Collection> getCollection(@NonNull UUID collectionId) {
|
||||||
|
return m_db.collectionDAO()
|
||||||
|
.getById(collectionId.toString())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable saveCollection(Collection collection) {
|
||||||
|
Completable result = m_db.collectionDAO().save(collection);
|
||||||
|
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable deleteCollection(Collection collection) {
|
||||||
|
Completable result = m_db.collectionDAO().delete(collection);
|
||||||
|
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Flowable<List<Monster>> getMonstersForCollection(@NonNull UUID collectionId) {
|
||||||
|
return m_db.collectionDAO()
|
||||||
|
.getMonstersForCollection(collectionId.toString())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Flowable<List<CollectionMonster>> getCollectionMonstersForCollection(@NonNull UUID collectionId) {
|
||||||
|
return m_db.collectionDAO()
|
||||||
|
.getCollectionMonstersForCollection(collectionId.toString())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable addMonsterToCollection(@NonNull UUID collectionId, @NonNull UUID monsterId) {
|
||||||
|
return addMonsterToCollection(collectionId, monsterId, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable addMonsterToCollection(@NonNull UUID collectionId, @NonNull UUID monsterId, int ordinal) {
|
||||||
|
CollectionMonster collectionMonster = new CollectionMonster(collectionId, monsterId, ordinal);
|
||||||
|
Completable result = m_db.collectionDAO().addMonsterToCollection(collectionMonster);
|
||||||
|
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable removeMonsterFromCollection(@NonNull UUID collectionId, @NonNull UUID monsterId) {
|
||||||
|
Completable result = m_db.collectionDAO().removeMonsterFromCollection(collectionId.toString(), monsterId.toString());
|
||||||
|
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable removeCollectionMonsterById(long junctionId) {
|
||||||
|
Completable result = m_db.collectionDAO().removeCollectionMonsterById(junctionId);
|
||||||
|
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable updateCollectionMonsters(List<CollectionMonster> collectionMonsters) {
|
||||||
|
Completable result = m_db.collectionDAO().updateCollectionMonsters(collectionMonsters);
|
||||||
|
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Flowable<List<Monster>> getDashboardMonsters() {
|
||||||
|
return m_db.dashboardDAO()
|
||||||
|
.getDashboardMonsters()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Flowable<List<com.majinnaibu.monstercards.models.DashboardMonsterWithMonster>> getDashboardMonstersWithMonster() {
|
||||||
|
return m_db.dashboardDAO()
|
||||||
|
.getDashboardMonstersWithMonster()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Flowable<List<DashboardMonster>> getDashboardMonsterEntries() {
|
||||||
|
return m_db.dashboardDAO()
|
||||||
|
.getDashboardMonsterEntries()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable addMonsterToDashboard(@NonNull UUID monsterId) {
|
||||||
|
DashboardMonster entry = new DashboardMonster(monsterId, 0);
|
||||||
|
Completable result = m_db.dashboardDAO().addMonsterToDashboard(entry);
|
||||||
|
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable addCollectionToDashboard(@NonNull UUID collectionId) {
|
||||||
|
return m_db.collectionDAO().getMonstersForCollection(collectionId.toString())
|
||||||
|
.firstOrError()
|
||||||
|
.flatMapCompletable(monsters -> {
|
||||||
|
if (monsters.isEmpty()) {
|
||||||
|
return Completable.complete();
|
||||||
|
}
|
||||||
|
DashboardMonster[] entries = new DashboardMonster[monsters.size()];
|
||||||
|
for (int i = 0; i < monsters.size(); i++) {
|
||||||
|
entries[i] = new DashboardMonster(monsters.get(i).id, i);
|
||||||
|
}
|
||||||
|
return m_db.dashboardDAO().addMonsterToDashboard(entries);
|
||||||
|
})
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable removeMonsterFromDashboard(@NonNull UUID monsterId) {
|
||||||
|
Completable result = m_db.dashboardDAO().removeMonsterFromDashboard(monsterId.toString());
|
||||||
|
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable removeDashboardMonsterById(long id) {
|
||||||
|
Completable result = m_db.dashboardDAO().removeDashboardMonsterById(id);
|
||||||
|
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable clearDashboard() {
|
||||||
|
Completable result = m_db.dashboardDAO().clearDashboard();
|
||||||
|
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable clearAllData() {
|
||||||
|
return Completable.fromAction(() -> {
|
||||||
|
m_db.dashboardDAO().clearDashboard().blockingAwait();
|
||||||
|
m_db.collectionDAO().deleteAllCollectionMonsters().blockingAwait();
|
||||||
|
m_db.collectionDAO().deleteAllCollections().blockingAwait();
|
||||||
|
m_db.monsterDAO().deleteAllMonsters().blockingAwait();
|
||||||
|
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable removeAllCollections() {
|
||||||
|
return Completable.fromAction(() -> {
|
||||||
|
m_db.collectionDAO().deleteAllCollectionMonsters().blockingAwait();
|
||||||
|
m_db.collectionDAO().deleteAllCollections().blockingAwait();
|
||||||
|
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable removeAllMonstersFromCollection(@NonNull UUID collectionId) {
|
||||||
|
Completable result = m_db.collectionDAO().removeAllMonstersFromCollection(collectionId.toString());
|
||||||
|
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Single<String> exportCollections() {
|
||||||
|
return m_db.collectionDAO().getAll().first(new ArrayList<>())
|
||||||
|
.map(collections -> {
|
||||||
|
List<BinderExport.CollectionExport> colExports = new ArrayList<>();
|
||||||
|
for (Collection col : collections) {
|
||||||
|
List<Monster> colMonsters = m_db.collectionDAO().getMonstersForCollection(col.id.toString())
|
||||||
|
.first(new ArrayList<>()).blockingGet();
|
||||||
|
colExports.add(new BinderExport.CollectionExport(
|
||||||
|
col.id.toString(),
|
||||||
|
col.name,
|
||||||
|
col.description,
|
||||||
|
colMonsters
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return new com.majinnaibu.monstercards.exporters.BinderExporter().exportFullBackup(colExports, null);
|
||||||
|
})
|
||||||
|
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Single<String> exportEverything() {
|
||||||
|
return Single.zip(
|
||||||
|
m_db.collectionDAO().getAll().first(new ArrayList<>()),
|
||||||
|
m_db.monsterDAO().getAll().first(new ArrayList<>()),
|
||||||
|
m_db.dashboardDAO().getDashboardMonsters().first(new ArrayList<>()),
|
||||||
|
(collections, allMonsters, dashboardMonsters) -> {
|
||||||
|
List<BinderExport.CollectionExport> colExports = new ArrayList<>();
|
||||||
|
Set<UUID> monstersInCollections = new HashSet<>();
|
||||||
|
|
||||||
|
for (Collection col : collections) {
|
||||||
|
List<Monster> colMonsters = m_db.collectionDAO().getMonstersForCollection(col.id.toString())
|
||||||
|
.first(new ArrayList<>()).blockingGet();
|
||||||
|
for (Monster m : colMonsters) {
|
||||||
|
monstersInCollections.add(m.id);
|
||||||
|
}
|
||||||
|
colExports.add(new BinderExport.CollectionExport(
|
||||||
|
col.id.toString(),
|
||||||
|
col.name,
|
||||||
|
col.description,
|
||||||
|
colMonsters
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Monster> uncategorized = new ArrayList<>();
|
||||||
|
for (Monster m : allMonsters) {
|
||||||
|
if (!monstersInCollections.contains(m.id)) {
|
||||||
|
uncategorized.add(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!uncategorized.isEmpty() || colExports.isEmpty()) {
|
||||||
|
colExports.add(new BinderExport.CollectionExport(
|
||||||
|
UUID.randomUUID().toString(),
|
||||||
|
colExports.isEmpty() ? "All Monsters" : "Uncategorized",
|
||||||
|
"",
|
||||||
|
colExports.isEmpty() ? allMonsters : uncategorized
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new com.majinnaibu.monstercards.exporters.BinderExporter().exportFullBackup(colExports, dashboardMonsters);
|
||||||
|
}
|
||||||
|
).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable updateDashboardMonsters(List<DashboardMonster> items) {
|
||||||
|
Completable result = m_db.dashboardDAO().updateDashboardMonsters(items);
|
||||||
|
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable importBinder(@NonNull BinderExport binder) {
|
||||||
|
return Completable.fromAction(() -> {
|
||||||
|
// 1. Process Collections & Cards in Binder
|
||||||
|
if (binder.collections != null && !binder.collections.isEmpty()) {
|
||||||
|
List<Collection> existingCols = m_db.collectionDAO().getAll().first(new ArrayList<>()).blockingGet();
|
||||||
|
|
||||||
|
for (BinderExport.CollectionExport colExport : binder.collections) {
|
||||||
|
if (colExport == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String colName = colExport.name != null ? colExport.name.trim() : "";
|
||||||
|
String colDesc = colExport.description != null ? colExport.description.trim() : "";
|
||||||
|
|
||||||
|
UUID colExportId = null;
|
||||||
|
if (colExport.id != null && !colExport.id.trim().isEmpty()) {
|
||||||
|
try {
|
||||||
|
colExportId = UUID.fromString(colExport.id.trim());
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection targetCollection = null;
|
||||||
|
if (colExportId != null) {
|
||||||
|
for (Collection existing : existingCols) {
|
||||||
|
if (existing.id.equals(colExportId)) {
|
||||||
|
targetCollection = existing;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (targetCollection == null && !colName.isEmpty()) {
|
||||||
|
for (Collection existing : existingCols) {
|
||||||
|
if (existing.name != null && existing.name.equalsIgnoreCase(colName)) {
|
||||||
|
targetCollection = existing;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID targetCollectionId;
|
||||||
|
if (targetCollection != null) {
|
||||||
|
targetCollectionId = targetCollection.id;
|
||||||
|
if (!colDesc.isEmpty() && (targetCollection.description == null || targetCollection.description.isEmpty())) {
|
||||||
|
targetCollection.description = colDesc;
|
||||||
|
m_db.collectionDAO().save(targetCollection).blockingAwait();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Collection newCol = new Collection();
|
||||||
|
newCol.id = colExportId != null ? colExportId : UUID.randomUUID();
|
||||||
|
newCol.name = !colName.isEmpty() ? colName : "Imported Collection";
|
||||||
|
newCol.description = colDesc;
|
||||||
|
m_db.collectionDAO().save(newCol).blockingAwait();
|
||||||
|
targetCollectionId = newCol.id;
|
||||||
|
existingCols.add(newCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (colExport.cards != null && !colExport.cards.isEmpty()) {
|
||||||
|
List<Monster> monstersToSave = new ArrayList<>();
|
||||||
|
Map<UUID, Integer> importedCardCounts = new HashMap<>();
|
||||||
|
|
||||||
|
for (Monster card : colExport.cards) {
|
||||||
|
if (card != null) {
|
||||||
|
if (card.id == null) {
|
||||||
|
card.id = UUID.randomUUID();
|
||||||
|
}
|
||||||
|
monstersToSave.add(card);
|
||||||
|
importedCardCounts.put(card.id, getCount(importedCardCounts, card.id) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!monstersToSave.isEmpty()) {
|
||||||
|
m_db.monsterDAO().save(monstersToSave.toArray(new Monster[0])).blockingAwait();
|
||||||
|
|
||||||
|
List<Monster> existingColMonsters = m_db.collectionDAO().getMonstersForCollection(targetCollectionId.toString())
|
||||||
|
.first(new ArrayList<>()).blockingGet();
|
||||||
|
Map<UUID, Integer> existingCardCounts = new HashMap<>();
|
||||||
|
for (Monster m : existingColMonsters) {
|
||||||
|
existingCardCounts.put(m.id, getCount(existingCardCounts, m.id) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ordinal = existingColMonsters.size();
|
||||||
|
for (Map.Entry<UUID, Integer> entry : importedCardCounts.entrySet()) {
|
||||||
|
UUID monsterId = entry.getKey();
|
||||||
|
int importedCount = entry.getValue();
|
||||||
|
int existingCount = getCount(existingCardCounts, monsterId);
|
||||||
|
|
||||||
|
if (importedCount > existingCount) {
|
||||||
|
int extraCopiesNeeded = importedCount - existingCount;
|
||||||
|
for (int k = 0; k < extraCopiesNeeded; k++) {
|
||||||
|
m_db.collectionDAO().addMonsterToCollection(new CollectionMonster(targetCollectionId, monsterId, ordinal++)).blockingAwait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Process Dashboard entries in Binder if present
|
||||||
|
if (binder.dashboard != null && !binder.dashboard.isEmpty()) {
|
||||||
|
List<Monster> dashboardMonstersToSave = new ArrayList<>();
|
||||||
|
Map<UUID, Integer> importedDashboardCounts = new HashMap<>();
|
||||||
|
|
||||||
|
for (Monster card : binder.dashboard) {
|
||||||
|
if (card != null) {
|
||||||
|
if (card.id == null) {
|
||||||
|
card.id = UUID.randomUUID();
|
||||||
|
}
|
||||||
|
dashboardMonstersToSave.add(card);
|
||||||
|
importedDashboardCounts.put(card.id, getCount(importedDashboardCounts, card.id) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dashboardMonstersToSave.isEmpty()) {
|
||||||
|
m_db.monsterDAO().save(dashboardMonstersToSave.toArray(new Monster[0])).blockingAwait();
|
||||||
|
|
||||||
|
List<DashboardMonsterWithMonster> existingDashboard = m_db.dashboardDAO().getDashboardMonstersWithMonster()
|
||||||
|
.first(new ArrayList<>()).blockingGet();
|
||||||
|
Map<UUID, Integer> existingDashboardCounts = new HashMap<>();
|
||||||
|
int currentMaxOrdinal = 0;
|
||||||
|
for (DashboardMonsterWithMonster entry : existingDashboard) {
|
||||||
|
if (entry.monster != null) {
|
||||||
|
existingDashboardCounts.put(entry.monster.id, getCount(existingDashboardCounts, entry.monster.id) + 1);
|
||||||
|
}
|
||||||
|
if (entry.dashboardEntry != null && entry.dashboardEntry.ordinal > currentMaxOrdinal) {
|
||||||
|
currentMaxOrdinal = entry.dashboardEntry.ordinal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int ordinal = currentMaxOrdinal + 1;
|
||||||
|
List<DashboardMonster> extraEntriesToAdd = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Map.Entry<UUID, Integer> entry : importedDashboardCounts.entrySet()) {
|
||||||
|
UUID monsterId = entry.getKey();
|
||||||
|
int importedCount = entry.getValue();
|
||||||
|
int existingCount = getCount(existingDashboardCounts, monsterId);
|
||||||
|
|
||||||
|
if (importedCount > existingCount) {
|
||||||
|
int extraNeeded = importedCount - existingCount;
|
||||||
|
for (int k = 0; k < extraNeeded; k++) {
|
||||||
|
extraEntriesToAdd.add(new DashboardMonster(monsterId, ordinal++));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!extraEntriesToAdd.isEmpty()) {
|
||||||
|
m_db.dashboardDAO().addMonsterToDashboard(extraEntriesToAdd.toArray(new DashboardMonster[0])).blockingAwait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getCount(Map<UUID, Integer> map, UUID key) {
|
||||||
|
if (map == null || key == null) return 0;
|
||||||
|
Integer val = map.get(key);
|
||||||
|
return val != null ? val : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Helpers {
|
||||||
|
static boolean monsterMatchesSearch(Monster monster, String searchText) {
|
||||||
|
if (StringHelper.isNullOrEmpty(searchText)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringHelper.containsCaseInsensitive(monster.name, searchText)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringHelper.containsCaseInsensitive(monster.size, searchText)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringHelper.containsCaseInsensitive(monster.type, searchText)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringHelper.containsCaseInsensitive(monster.subtype, searchText)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringHelper.containsCaseInsensitive(monster.alignment, searchText)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.majinnaibu.monstercards.data.converters;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.room.TypeConverter;
|
||||||
|
|
||||||
|
import com.majinnaibu.monstercards.data.enums.ArmorType;
|
||||||
|
|
||||||
|
public class ArmorTypeConverter {
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static String fromArmorType(@NonNull ArmorType armorType) {
|
||||||
|
return armorType.stringValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static ArmorType armorTypeFromStringValue(String stringValue) {
|
||||||
|
return ArmorType.valueOfString(stringValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.majinnaibu.monstercards.data.converters;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.room.TypeConverter;
|
||||||
|
|
||||||
|
import com.majinnaibu.monstercards.data.enums.ChallengeRating;
|
||||||
|
|
||||||
|
public class ChallengeRatingConverter {
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static String fromChallengeRating(@NonNull ChallengeRating challengeRating) {
|
||||||
|
return challengeRating.stringValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static ChallengeRating challengeRatingFromStringValue(String stringValue) {
|
||||||
|
return ChallengeRating.valueOfString(stringValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.majinnaibu.monstercards.data.converters;
|
||||||
|
|
||||||
|
import androidx.room.TypeConverter;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import com.majinnaibu.monstercards.models.Trait;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ListOfTraitsConverter {
|
||||||
|
@TypeConverter
|
||||||
|
public static String fromListOfTraits(List<Trait> traits) {
|
||||||
|
Gson gson = new Gson();
|
||||||
|
return gson.toJson(traits);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static List<Trait> listOfTraitsFromString(String string) {
|
||||||
|
Gson gson = new Gson();
|
||||||
|
Type setType = new TypeToken<ArrayList<Trait>>() {
|
||||||
|
}.getType();
|
||||||
|
return gson.fromJson(string, setType);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.majinnaibu.monstercards.data.converters;
|
||||||
|
|
||||||
|
import androidx.room.TypeConverter;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import com.majinnaibu.monstercards.models.Language;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class SetOfLanguageConverter {
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static String fromSetOfLanguage(Set<Language> languages) {
|
||||||
|
Gson gson = new Gson();
|
||||||
|
return gson.toJson(languages);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static Set<Language> setOfLanguageFromString(String string) {
|
||||||
|
Gson gson = new Gson();
|
||||||
|
Type setType = new TypeToken<HashSet<Language>>() {
|
||||||
|
}.getType();
|
||||||
|
return gson.fromJson(string, setType);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.majinnaibu.monstercards.data.converters;
|
||||||
|
|
||||||
|
import androidx.room.TypeConverter;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import com.majinnaibu.monstercards.models.Skill;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class SetOfSkillConverter {
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static String fromSetOfSkill(Set<Skill> skills) {
|
||||||
|
Gson gson = new Gson();
|
||||||
|
return gson.toJson(skills);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static Set<Skill> setOfSkillFromString(String string) {
|
||||||
|
Gson gson = new Gson();
|
||||||
|
Type setType = new TypeToken<HashSet<Skill>>() {
|
||||||
|
}.getType();
|
||||||
|
return gson.fromJson(string, setType);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.majinnaibu.monstercards.data.converters;
|
||||||
|
|
||||||
|
import androidx.room.TypeConverter;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class SetOfStringConverter {
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static String fromSetOfString(Set<String> strings) {
|
||||||
|
Gson gson = new Gson();
|
||||||
|
return gson.toJson(strings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static Set<String> setOfStringFromString(String string) {
|
||||||
|
Gson gson = new Gson();
|
||||||
|
Type setType = new TypeToken<HashSet<String>>() {
|
||||||
|
}.getType();
|
||||||
|
return gson.fromJson(string, setType);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.majinnaibu.monstercards.data.converters;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.room.TypeConverter;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class UUIDConverter {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@TypeConverter
|
||||||
|
public static String fromUUID(@NonNull UUID uuid) {
|
||||||
|
return uuid.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static UUID uuidFromString(String string) {
|
||||||
|
return UUID.fromString(string);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.majinnaibu.monstercards.data.enums;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public enum AbilityScore {
|
||||||
|
STRENGTH("strength", "Strength", "STR"),
|
||||||
|
DEXTERITY("dexterity", "Dexterity", "DEX"),
|
||||||
|
CONSTITUTION("constitution", "Constitution", "CON"),
|
||||||
|
INTELLIGENCE("intelligence", "Intelligence", "INT"),
|
||||||
|
WISDOM("wisdom", "Wisdom", "WIS"),
|
||||||
|
CHARISMA("charisma", "Charisma", "CHA"),
|
||||||
|
;
|
||||||
|
|
||||||
|
public final String displayName;
|
||||||
|
public final String shortDisplayName;
|
||||||
|
public final String stringValue;
|
||||||
|
|
||||||
|
AbilityScore(String stringValue, String displayName, String shortDisplayName) {
|
||||||
|
this.displayName = displayName;
|
||||||
|
this.stringValue = stringValue;
|
||||||
|
this.shortDisplayName = shortDisplayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AbilityScore valueOfString(String string) {
|
||||||
|
if (string == null) {
|
||||||
|
return AbilityScore.STRENGTH;
|
||||||
|
}
|
||||||
|
String lower = string.trim().toLowerCase(java.util.Locale.ROOT);
|
||||||
|
for (AbilityScore abilityScore : values()) {
|
||||||
|
if (abilityScore.stringValue.equalsIgnoreCase(lower)
|
||||||
|
|| abilityScore.shortDisplayName.equalsIgnoreCase(lower)
|
||||||
|
|| abilityScore.name().equalsIgnoreCase(lower)) {
|
||||||
|
return abilityScore;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lower.startsWith("str")) return STRENGTH;
|
||||||
|
if (lower.startsWith("dex")) return DEXTERITY;
|
||||||
|
if (lower.startsWith("con")) return CONSTITUTION;
|
||||||
|
if (lower.startsWith("int")) return INTELLIGENCE;
|
||||||
|
if (lower.startsWith("wis")) return WISDOM;
|
||||||
|
if (lower.startsWith("cha")) return CHARISMA;
|
||||||
|
return AbilityScore.STRENGTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.majinnaibu.monstercards.data.enums;
|
||||||
|
|
||||||
|
public enum AdvantageType {
|
||||||
|
NONE("none", "None", ""),
|
||||||
|
ADVANTAGE("advantage", "Advantage", "A"),
|
||||||
|
DISADVANTAGE("disadvantage", "Disadvantage", "D"),
|
||||||
|
;
|
||||||
|
|
||||||
|
public final String displayName;
|
||||||
|
public final String stringValue;
|
||||||
|
public final String label;
|
||||||
|
|
||||||
|
AdvantageType(String stringValue, String displayName, String label) {
|
||||||
|
this.displayName = displayName;
|
||||||
|
this.stringValue = stringValue;
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AdvantageType valueOfString(String string) {
|
||||||
|
for (AdvantageType advantageType : values()) {
|
||||||
|
if (advantageType.stringValue.equals(string)) {
|
||||||
|
return advantageType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return AdvantageType.NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.majinnaibu.monstercards.data.enums;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public enum ArmorType {
|
||||||
|
NONE("none", "None", 10),
|
||||||
|
NATURAL_ARMOR("natural armor", "Natural Armor", 10),
|
||||||
|
MAGE_ARMOR("mage armor", "Mage Armor", 10),
|
||||||
|
PADDED("padded", "Padded", 11),
|
||||||
|
LEATHER("leather", "Leather", 11),
|
||||||
|
STUDDED_LEATHER("studded", "Studded Leather", 12),
|
||||||
|
HIDE("hide", "Hide", 12),
|
||||||
|
CHAIN_SHIRT("chain shirt", "Chain Shirt", 13),
|
||||||
|
SCALE_MAIL("scale mail", "Scale Mail", 14),
|
||||||
|
BREASTPLATE("breastplate", "Breastplate", 14),
|
||||||
|
HALF_PLATE("half plate", "Half Plate", 15),
|
||||||
|
RING_MAIL("ring mail", "Ring Mail", 14),
|
||||||
|
CHAIN_MAIL("chain mail", "Chain Mail", 16),
|
||||||
|
SPLINT_MAIL("splint", "Splint Mail", 17),
|
||||||
|
PLATE_MAIL("plate", "Plate Mail", 18),
|
||||||
|
OTHER("other", "Other", 10),
|
||||||
|
;
|
||||||
|
|
||||||
|
public final String displayName;
|
||||||
|
public final String stringValue;
|
||||||
|
public final int baseArmorClass;
|
||||||
|
|
||||||
|
ArmorType(String stringValue, String displayName, int baseArmorClass) {
|
||||||
|
this.displayName = displayName;
|
||||||
|
this.stringValue = stringValue;
|
||||||
|
this.baseArmorClass = baseArmorClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArmorType valueOfString(String string) {
|
||||||
|
for (ArmorType armorType : values()) {
|
||||||
|
if (armorType.stringValue.equals(string)) {
|
||||||
|
return armorType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ArmorType.NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.majinnaibu.monstercards.data.enums;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public enum ChallengeRating {
|
||||||
|
CUSTOM("custom", "Custom", 0),
|
||||||
|
ZERO("zero", "0 (10 XP)", 2),
|
||||||
|
ONE_EIGHTH("1/8", "1/8 (25 XP)", 2),
|
||||||
|
ONE_QUARTER("1/4", "1/4 (50 XP)", 2),
|
||||||
|
ONE_HALF("1/2", "1/2 (100 XP)", 2),
|
||||||
|
ONE("1", "1 (200 XP)", 2),
|
||||||
|
TWO("2", "2 (450 XP)", 2),
|
||||||
|
THREE("3", "3 (700 XP)", 2),
|
||||||
|
FOUR("4", "4 (1,100 XP)", 2),
|
||||||
|
FIVE("5", "5 (1,800 XP)", 3),
|
||||||
|
SIX("6", "6 (2,300 XP)", 3),
|
||||||
|
SEVEN("7", "7 (2,900 XP)", 3),
|
||||||
|
EIGHT("8", "8 (3,900 XP)", 3),
|
||||||
|
NINE("9", "9 (5,000 XP)", 4),
|
||||||
|
TEN("10", "10 (5,900 XP)", 4),
|
||||||
|
ELEVEN("11", "11 (7,200 XP)", 4),
|
||||||
|
TWELVE("12", "12 (8,400 XP)", 4),
|
||||||
|
THIRTEEN("13", "13 (10,000 XP)", 5),
|
||||||
|
FOURTEEN("14", "14 (11,500 XP)", 5),
|
||||||
|
FIFTEEN("15", "15 (13,000 XP)", 5),
|
||||||
|
SIXTEEN("16", "16 (15,000 XP)", 5),
|
||||||
|
SEVENTEEN("17", "17 (18,000 XP)", 6),
|
||||||
|
EIGHTEEN("18", "18 (20,000 XP)", 6),
|
||||||
|
NINETEEN("19", "19 (22,000 XP)", 6),
|
||||||
|
TWENTY("20", "20 (25,000 XP)", 6),
|
||||||
|
TWENTY_ONE("21", "21 (33,000 XP)", 7),
|
||||||
|
TWENTY_TWO("22", "22 (41,000 XP)", 7),
|
||||||
|
TWENTY_THREE("23", "23 (50,000 XP)", 7),
|
||||||
|
TWENTY_FOUR("24", "24 (62,000 XP)", 7),
|
||||||
|
TWENTY_FIVE("25", "25 (75,000 XP)", 8),
|
||||||
|
TWENTY_SIX("26", "26 (90,000 XP)", 8),
|
||||||
|
TWENTY_SEVEN("27", "27 (105,000 XP)", 8),
|
||||||
|
TWENTY_EIGHT("28", "28 (120,000 XP)", 8),
|
||||||
|
TWENTY_NINE("29", "29 (135,000 XP)", 9),
|
||||||
|
THIRTY("30", "30 (155,000 XP)", 9),
|
||||||
|
;
|
||||||
|
|
||||||
|
public final String displayName;
|
||||||
|
public final String stringValue;
|
||||||
|
public final int proficiencyBonus;
|
||||||
|
|
||||||
|
ChallengeRating(String stringValue, String displayName, int proficiencyBonus) {
|
||||||
|
this.displayName = displayName;
|
||||||
|
this.stringValue = stringValue;
|
||||||
|
this.proficiencyBonus = proficiencyBonus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ChallengeRating valueOfString(String string) {
|
||||||
|
for (ChallengeRating challengeRating : values()) {
|
||||||
|
if (challengeRating.stringValue.equals(string)) {
|
||||||
|
return challengeRating;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ChallengeRating.ONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.majinnaibu.monstercards.data.enums;
|
||||||
|
|
||||||
|
public enum ProficiencyType {
|
||||||
|
NONE("none", "None", ""),
|
||||||
|
PROFICIENT("proficient", "Proficient", "P"),
|
||||||
|
EXPERTISE("expertise", "Expertise", "Ex"),
|
||||||
|
;
|
||||||
|
|
||||||
|
public final String displayName;
|
||||||
|
public final String stringValue;
|
||||||
|
public final String label;
|
||||||
|
|
||||||
|
ProficiencyType(String stringValue, String displayName, String label) {
|
||||||
|
this.displayName = displayName;
|
||||||
|
this.stringValue = stringValue;
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ProficiencyType valueOfString(String string) {
|
||||||
|
for (ProficiencyType proficiencyType : values()) {
|
||||||
|
if (proficiencyType.stringValue.equals(string)) {
|
||||||
|
return proficiencyType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ProficiencyType.NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.majinnaibu.monstercards.data.enums;
|
||||||
|
|
||||||
|
public enum StringType {
|
||||||
|
CONDITION_IMMUNITY,
|
||||||
|
DAMAGE_IMMUNITY,
|
||||||
|
DAMAGE_RESISTANCE,
|
||||||
|
DAMAGE_VULNERABILITY,
|
||||||
|
SENSE
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.majinnaibu.monstercards.data.enums;
|
||||||
|
|
||||||
|
public enum TraitType {
|
||||||
|
ABILITY,
|
||||||
|
ACTION,
|
||||||
|
LAIR_ACTION,
|
||||||
|
LEGENDARY_ACTION,
|
||||||
|
REGIONAL_ACTION,
|
||||||
|
REACTIONS
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package com.majinnaibu.monstercards.exporters;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.majinnaibu.monstercards.models.BinderExport;
|
||||||
|
import com.majinnaibu.monstercards.models.Collection;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class BinderExporter {
|
||||||
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String exportBinder(String collectionName, List<Monster> monsters) {
|
||||||
|
return exportBinder(new Collection(collectionName != null ? collectionName : "", ""), monsters, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String exportBinder(@Nullable Collection collection, List<Monster> monsters) {
|
||||||
|
return exportBinder(collection, monsters, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String exportBinder(@Nullable Collection collection, @Nullable List<Monster> monsters, @Nullable List<Monster> dashboardMonsters) {
|
||||||
|
BinderExport export = new BinderExport();
|
||||||
|
export.schema = "https://majinnaibu.com/schemas/binder.schema.json";
|
||||||
|
export.schemaVersion = 1;
|
||||||
|
|
||||||
|
if (monsters != null) {
|
||||||
|
for (Monster monster : monsters) {
|
||||||
|
monster.schema = "https://majinnaibu.com/schemas/monster-card.schema.json";
|
||||||
|
monster.schemaVersion = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dashboardMonsters != null && !dashboardMonsters.isEmpty()) {
|
||||||
|
for (Monster monster : dashboardMonsters) {
|
||||||
|
monster.schema = "https://majinnaibu.com/schemas/monster-card.schema.json";
|
||||||
|
monster.schemaVersion = 1;
|
||||||
|
}
|
||||||
|
export.dashboard = dashboardMonsters;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collection != null) {
|
||||||
|
String colId = collection.id.toString();
|
||||||
|
String colName = collection.name;
|
||||||
|
String colDesc = collection.description;
|
||||||
|
|
||||||
|
BinderExport.CollectionExport col = new BinderExport.CollectionExport(
|
||||||
|
colId,
|
||||||
|
colName,
|
||||||
|
colDesc,
|
||||||
|
monsters
|
||||||
|
);
|
||||||
|
export.collections = Collections.singletonList(col);
|
||||||
|
}
|
||||||
|
return GSON.toJson(export);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String exportFullBackup(@Nullable List<BinderExport.CollectionExport> collections, @Nullable List<Monster> dashboardMonsters) {
|
||||||
|
BinderExport export = new BinderExport();
|
||||||
|
export.schema = "https://majinnaibu.com/schemas/binder.schema.json";
|
||||||
|
export.schemaVersion = 1;
|
||||||
|
|
||||||
|
if (collections != null) {
|
||||||
|
for (BinderExport.CollectionExport col : collections) {
|
||||||
|
if (col.cards != null) {
|
||||||
|
for (Monster m : col.cards) {
|
||||||
|
m.schema = "https://majinnaibu.com/schemas/monster-card.schema.json";
|
||||||
|
m.schemaVersion = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export.collections = collections;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dashboardMonsters != null && !dashboardMonsters.isEmpty()) {
|
||||||
|
for (Monster m : dashboardMonsters) {
|
||||||
|
m.schema = "https://majinnaibu.com/schemas/monster-card.schema.json";
|
||||||
|
m.schemaVersion = 1;
|
||||||
|
}
|
||||||
|
export.dashboard = dashboardMonsters;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GSON.toJson(export);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.majinnaibu.monstercards.exporters;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
|
||||||
|
public class MonsterCardExporter {
|
||||||
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String exportCard(@NonNull Monster monster) {
|
||||||
|
monster.schema = "https://majinnaibu.com/schemas/monster-card.schema.json";
|
||||||
|
monster.schemaVersion = 1;
|
||||||
|
return GSON.toJson(monster);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
package com.majinnaibu.monstercards.exporters;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonNull;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.ArmorType;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||||
|
import com.majinnaibu.monstercards.models.Language;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
import com.majinnaibu.monstercards.models.Skill;
|
||||||
|
import com.majinnaibu.monstercards.models.Trait;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class Open5eExporter {
|
||||||
|
|
||||||
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String exportMonster(@NonNull Monster monster) {
|
||||||
|
JsonObject root = new JsonObject();
|
||||||
|
root.addProperty("$schema", "../../../schema/entity.json");
|
||||||
|
root.addProperty("uuid", monster.id != null ? monster.id.toString() : UUID.randomUUID().toString());
|
||||||
|
root.addProperty("ruleset_id", "open5e");
|
||||||
|
root.addProperty("entity_type", "character");
|
||||||
|
root.addProperty("display_name", monster.name != null && !monster.name.isEmpty() ? monster.name : "Unnamed Entity");
|
||||||
|
root.addProperty("description", buildDescription(monster));
|
||||||
|
root.addProperty("version", "1.0.0");
|
||||||
|
root.addProperty("template_id", "stat_block");
|
||||||
|
|
||||||
|
JsonObject props = new JsonObject();
|
||||||
|
props.addProperty("name", monster.name != null ? monster.name : "");
|
||||||
|
props.addProperty("size", monster.size != null ? monster.size : "");
|
||||||
|
props.addProperty("type", monster.type != null ? monster.type : "");
|
||||||
|
props.addProperty("subtype", monster.subtype != null ? monster.subtype : "");
|
||||||
|
props.addProperty("alignment", monster.alignment != null ? monster.alignment : "");
|
||||||
|
props.addProperty("armor_class", monster.getArmorClassValue());
|
||||||
|
props.addProperty("armor_description", monster.getArmorClass());
|
||||||
|
|
||||||
|
if (monster.armorType != null && !monster.armorType.equals(ArmorType.NONE)) {
|
||||||
|
JsonObject armorObj = new JsonObject();
|
||||||
|
armorObj.addProperty("id", monster.armorType.stringValue);
|
||||||
|
armorObj.addProperty("name", monster.armorType.displayName);
|
||||||
|
armorObj.addProperty("armor_category", getArmorCategory(monster.armorType));
|
||||||
|
armorObj.addProperty("ac_base", monster.armorType.baseArmorClass);
|
||||||
|
|
||||||
|
Integer dexCap = getDexCap(monster.armorType);
|
||||||
|
if (dexCap != null) {
|
||||||
|
armorObj.addProperty("dex_cap", dexCap);
|
||||||
|
} else {
|
||||||
|
armorObj.add("dex_cap", JsonNull.INSTANCE);
|
||||||
|
}
|
||||||
|
armorObj.addProperty("stealth_disadvantage", isStealthDisadvantage(monster.armorType));
|
||||||
|
props.add("equipped_armor", armorObj);
|
||||||
|
} else {
|
||||||
|
props.add("equipped_armor", JsonNull.INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (monster.shieldBonus > 0) {
|
||||||
|
JsonObject shieldObj = new JsonObject();
|
||||||
|
shieldObj.addProperty("id", "shield");
|
||||||
|
shieldObj.addProperty("name", "Shield");
|
||||||
|
shieldObj.addProperty("ac_bonus", monster.shieldBonus);
|
||||||
|
props.add("equipped_shield", shieldObj);
|
||||||
|
} else {
|
||||||
|
props.add("equipped_shield", JsonNull.INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject hpObj = new JsonObject();
|
||||||
|
hpObj.addProperty("max", monster.getHitPointsValue());
|
||||||
|
hpObj.addProperty("current", monster.getHitPointsValue());
|
||||||
|
hpObj.addProperty("formula", monster.hasCustomHP && !monster.customHPDescription.isEmpty() ? monster.customHPDescription : monster.getHitPoints());
|
||||||
|
hpObj.addProperty("hit_dice", monster.hitDice + "d8");
|
||||||
|
props.add("hit_points", hpObj);
|
||||||
|
|
||||||
|
props.addProperty("speed", monster.walkSpeed);
|
||||||
|
props.addProperty("speed_desc", monster.getSpeedText());
|
||||||
|
props.addProperty("challenge_rating", monster.getChallengeRatingDescription());
|
||||||
|
props.addProperty("cr", monster.challengeRating != null ? monster.challengeRating.stringValue : "1");
|
||||||
|
|
||||||
|
JsonObject abilitiesObj = new JsonObject();
|
||||||
|
abilitiesObj.addProperty("strength", monster.strengthScore);
|
||||||
|
abilitiesObj.addProperty("dexterity", monster.dexterityScore);
|
||||||
|
abilitiesObj.addProperty("constitution", monster.constitutionScore);
|
||||||
|
abilitiesObj.addProperty("intelligence", monster.intelligenceScore);
|
||||||
|
abilitiesObj.addProperty("wisdom", monster.wisdomScore);
|
||||||
|
abilitiesObj.addProperty("charisma", monster.charismaScore);
|
||||||
|
props.add("abilities", abilitiesObj);
|
||||||
|
|
||||||
|
if (monster.playerName != null && !monster.playerName.isEmpty()) props.addProperty("player_name", monster.playerName);
|
||||||
|
if (monster.background != null && !monster.background.isEmpty()) props.addProperty("background", monster.background);
|
||||||
|
if (monster.personalityTraits != null && !monster.personalityTraits.isEmpty()) props.addProperty("personality_traits", monster.personalityTraits);
|
||||||
|
if (monster.ideals != null && !monster.ideals.isEmpty()) props.addProperty("ideals", monster.ideals);
|
||||||
|
if (monster.bonds != null && !monster.bonds.isEmpty()) props.addProperty("bonds", monster.bonds);
|
||||||
|
if (monster.flaws != null && !monster.flaws.isEmpty()) props.addProperty("flaws", monster.flaws);
|
||||||
|
|
||||||
|
if (hasDetails(monster)) {
|
||||||
|
JsonObject detailsObj = new JsonObject();
|
||||||
|
if (monster.age != null && !monster.age.isEmpty()) detailsObj.addProperty("age", monster.age);
|
||||||
|
if (monster.height != null && !monster.height.isEmpty()) detailsObj.addProperty("height", monster.height);
|
||||||
|
if (monster.weight != null && !monster.weight.isEmpty()) detailsObj.addProperty("weight", monster.weight);
|
||||||
|
if (monster.eyes != null && !monster.eyes.isEmpty()) detailsObj.addProperty("eyes", monster.eyes);
|
||||||
|
if (monster.skin != null && !monster.skin.isEmpty()) detailsObj.addProperty("skin", monster.skin);
|
||||||
|
if (monster.hair != null && !monster.hair.isEmpty()) detailsObj.addProperty("hair", monster.hair);
|
||||||
|
if (monster.appearance != null && !monster.appearance.isEmpty()) detailsObj.addProperty("appearance", monster.appearance);
|
||||||
|
if (monster.backstory != null && !monster.backstory.isEmpty()) detailsObj.addProperty("backstory", monster.backstory);
|
||||||
|
if (monster.alliesAndOrganizations != null && !monster.alliesAndOrganizations.isEmpty()) detailsObj.addProperty("allies_and_organizations", monster.alliesAndOrganizations);
|
||||||
|
props.add("character_details", detailsObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonArray savesArr = buildSavingThrowsArray(monster);
|
||||||
|
if (savesArr.size() > 0) props.add("saving_throws", savesArr);
|
||||||
|
|
||||||
|
JsonArray skillsArr = buildSkillsArray(monster);
|
||||||
|
if (skillsArr.size() > 0) props.add("skills", skillsArr);
|
||||||
|
|
||||||
|
if (monster.damageImmunities != null && !monster.damageImmunities.isEmpty()) props.add("damage_immunities", toJsonArray(monster.damageImmunities));
|
||||||
|
if (monster.damageResistances != null && !monster.damageResistances.isEmpty()) props.add("damage_resistances", toJsonArray(monster.damageResistances));
|
||||||
|
if (monster.damageVulnerabilities != null && !monster.damageVulnerabilities.isEmpty()) props.add("damage_vulnerabilities", toJsonArray(monster.damageVulnerabilities));
|
||||||
|
if (monster.conditionImmunities != null && !monster.conditionImmunities.isEmpty()) props.add("condition_immunities", toJsonArray(monster.conditionImmunities));
|
||||||
|
|
||||||
|
props.addProperty("senses", monster.getSensesDescription());
|
||||||
|
|
||||||
|
if (monster.languages != null && !monster.languages.isEmpty()) {
|
||||||
|
JsonArray langArr = new JsonArray();
|
||||||
|
for (Language l : monster.languages) {
|
||||||
|
if (l != null && l.getName() != null && !l.getName().isEmpty()) {
|
||||||
|
langArr.add(l.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
props.add("languages", langArr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (monster.abilities != null && !monster.abilities.isEmpty()) props.add("traits", traitsToJsonArray(monster.abilities));
|
||||||
|
if (monster.actions != null && !monster.actions.isEmpty()) props.add("actions", traitsToJsonArray(monster.actions));
|
||||||
|
if (monster.bonusActions != null && !monster.bonusActions.isEmpty()) props.add("bonus_actions", traitsToJsonArray(monster.bonusActions));
|
||||||
|
if (monster.reactions != null && !monster.reactions.isEmpty()) props.add("reactions", traitsToJsonArray(monster.reactions));
|
||||||
|
if (monster.legendaryActions != null && !monster.legendaryActions.isEmpty()) props.add("legendary_actions", traitsToJsonArray(monster.legendaryActions));
|
||||||
|
if (monster.legendaryActionsDescription != null && !monster.legendaryActionsDescription.isEmpty()) props.addProperty("legendary_desc", monster.legendaryActionsDescription);
|
||||||
|
if (monster.lairActions != null && !monster.lairActions.isEmpty()) props.add("lair_actions", traitsToJsonArray(monster.lairActions));
|
||||||
|
if (monster.lairActionsDescription != null && !monster.lairActionsDescription.isEmpty()) props.addProperty("lair_desc", monster.lairActionsDescription);
|
||||||
|
if (monster.regionalActions != null && !monster.regionalActions.isEmpty()) props.add("regional_effects", traitsToJsonArray(monster.regionalActions));
|
||||||
|
if (monster.regionalActionsDescription != null && !monster.regionalActionsDescription.isEmpty()) props.addProperty("regional_desc", monster.regionalActionsDescription);
|
||||||
|
if (monster.mythicActions != null && !monster.mythicActions.isEmpty()) props.add("mythic_actions", traitsToJsonArray(monster.mythicActions));
|
||||||
|
if (monster.mythicActionsDescription != null && !monster.mythicActionsDescription.isEmpty()) props.addProperty("mythic_desc", monster.mythicActionsDescription);
|
||||||
|
|
||||||
|
if (monster.sourceUrl != null && !monster.sourceUrl.isEmpty()) props.addProperty("source_url", monster.sourceUrl);
|
||||||
|
|
||||||
|
root.add("properties", props);
|
||||||
|
return GSON.toJson(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getArmorCategory(ArmorType armorType) {
|
||||||
|
switch (armorType) {
|
||||||
|
case PADDED:
|
||||||
|
case LEATHER:
|
||||||
|
case STUDDED_LEATHER:
|
||||||
|
return "light";
|
||||||
|
case HIDE:
|
||||||
|
case CHAIN_SHIRT:
|
||||||
|
case SCALE_MAIL:
|
||||||
|
case BREASTPLATE:
|
||||||
|
case HALF_PLATE:
|
||||||
|
return "medium";
|
||||||
|
case RING_MAIL:
|
||||||
|
case CHAIN_MAIL:
|
||||||
|
case SPLINT_MAIL:
|
||||||
|
case PLATE_MAIL:
|
||||||
|
return "heavy";
|
||||||
|
default:
|
||||||
|
return "natural";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer getDexCap(ArmorType armorType) {
|
||||||
|
switch (armorType) {
|
||||||
|
case HIDE:
|
||||||
|
case CHAIN_SHIRT:
|
||||||
|
case SCALE_MAIL:
|
||||||
|
case BREASTPLATE:
|
||||||
|
case HALF_PLATE:
|
||||||
|
return 2;
|
||||||
|
case RING_MAIL:
|
||||||
|
case CHAIN_MAIL:
|
||||||
|
case SPLINT_MAIL:
|
||||||
|
case PLATE_MAIL:
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isStealthDisadvantage(ArmorType armorType) {
|
||||||
|
switch (armorType) {
|
||||||
|
case PADDED:
|
||||||
|
case SCALE_MAIL:
|
||||||
|
case HALF_PLATE:
|
||||||
|
case RING_MAIL:
|
||||||
|
case CHAIN_MAIL:
|
||||||
|
case SPLINT_MAIL:
|
||||||
|
case PLATE_MAIL:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasDetails(Monster monster) {
|
||||||
|
return (monster.age != null && !monster.age.isEmpty()) ||
|
||||||
|
(monster.height != null && !monster.height.isEmpty()) ||
|
||||||
|
(monster.weight != null && !monster.weight.isEmpty()) ||
|
||||||
|
(monster.eyes != null && !monster.eyes.isEmpty()) ||
|
||||||
|
(monster.skin != null && !monster.skin.isEmpty()) ||
|
||||||
|
(monster.hair != null && !monster.hair.isEmpty()) ||
|
||||||
|
(monster.appearance != null && !monster.appearance.isEmpty()) ||
|
||||||
|
(monster.backstory != null && !monster.backstory.isEmpty()) ||
|
||||||
|
(monster.alliesAndOrganizations != null && !monster.alliesAndOrganizations.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildDescription(Monster monster) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (monster.size != null && !monster.size.isEmpty()) sb.append(monster.size).append(" ");
|
||||||
|
if (monster.type != null && !monster.type.isEmpty()) sb.append(monster.type);
|
||||||
|
if (monster.subtype != null && !monster.subtype.isEmpty()) sb.append(" (").append(monster.subtype).append(")");
|
||||||
|
if (monster.alignment != null && !monster.alignment.isEmpty()) sb.append(", ").append(monster.alignment);
|
||||||
|
return sb.toString().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonArray buildSavingThrowsArray(Monster monster) {
|
||||||
|
JsonArray arr = new JsonArray();
|
||||||
|
checkSave(arr, "Strength", monster.getSavingThrowProficiencyType(AbilityScore.STRENGTH), monster.getAbilityModifier(AbilityScore.STRENGTH) + monster.getProficiencyBonus(monster.getSavingThrowProficiencyType(AbilityScore.STRENGTH)));
|
||||||
|
checkSave(arr, "Dexterity", monster.getSavingThrowProficiencyType(AbilityScore.DEXTERITY), monster.getAbilityModifier(AbilityScore.DEXTERITY) + monster.getProficiencyBonus(monster.getSavingThrowProficiencyType(AbilityScore.DEXTERITY)));
|
||||||
|
checkSave(arr, "Constitution", monster.getSavingThrowProficiencyType(AbilityScore.CONSTITUTION), monster.getAbilityModifier(AbilityScore.CONSTITUTION) + monster.getProficiencyBonus(monster.getSavingThrowProficiencyType(AbilityScore.CONSTITUTION)));
|
||||||
|
checkSave(arr, "Intelligence", monster.getSavingThrowProficiencyType(AbilityScore.INTELLIGENCE), monster.getAbilityModifier(AbilityScore.INTELLIGENCE) + monster.getProficiencyBonus(monster.getSavingThrowProficiencyType(AbilityScore.INTELLIGENCE)));
|
||||||
|
checkSave(arr, "Wisdom", monster.getSavingThrowProficiencyType(AbilityScore.WISDOM), monster.getAbilityModifier(AbilityScore.WISDOM) + monster.getProficiencyBonus(monster.getSavingThrowProficiencyType(AbilityScore.WISDOM)));
|
||||||
|
checkSave(arr, "Charisma", monster.getSavingThrowProficiencyType(AbilityScore.CHARISMA), monster.getAbilityModifier(AbilityScore.CHARISMA) + monster.getProficiencyBonus(monster.getSavingThrowProficiencyType(AbilityScore.CHARISMA)));
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkSave(JsonArray arr, String statName, ProficiencyType prof, int bonus) {
|
||||||
|
if (prof != null && !prof.equals(ProficiencyType.NONE)) {
|
||||||
|
arr.add(String.format(Locale.ROOT, "%s %+d", statName, bonus));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonArray buildSkillsArray(Monster monster) {
|
||||||
|
JsonArray arr = new JsonArray();
|
||||||
|
if (monster.skills != null) {
|
||||||
|
for (Skill s : monster.skills) {
|
||||||
|
if (s != null && s.proficiencyType != null && !s.proficiencyType.equals(ProficiencyType.NONE)) {
|
||||||
|
int bonus = s.getSkillBonus(monster);
|
||||||
|
arr.add(String.format(Locale.ROOT, "%s %+d", s.name.toLowerCase(Locale.ROOT), bonus));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonArray toJsonArray(Collection<String> items) {
|
||||||
|
JsonArray arr = new JsonArray();
|
||||||
|
for (String s : items) {
|
||||||
|
if (s != null && !s.isEmpty()) arr.add(s);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonArray traitsToJsonArray(List<Trait> traits) {
|
||||||
|
JsonArray arr = new JsonArray();
|
||||||
|
for (Trait t : traits) {
|
||||||
|
if (t != null && t.name != null && !t.name.isEmpty()) {
|
||||||
|
JsonObject obj = new JsonObject();
|
||||||
|
obj.addProperty("name", t.name);
|
||||||
|
obj.addProperty("desc", t.description != null ? t.description : "");
|
||||||
|
arr.add(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.majinnaibu.monstercards.helpers;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public final class ArrayHelper {
|
||||||
|
public static int indexOf(@NonNull Object[] array, Object target) {
|
||||||
|
for (int index = 0; index < array.length; index++) {
|
||||||
|
if (Objects.equals(array[index], target)) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.majinnaibu.monstercards.helpers;
|
||||||
|
|
||||||
|
import org.commonmark.node.Document;
|
||||||
|
import org.commonmark.node.Node;
|
||||||
|
import org.commonmark.node.Paragraph;
|
||||||
|
import org.commonmark.parser.Parser;
|
||||||
|
import org.commonmark.renderer.html.HtmlRenderer;
|
||||||
|
|
||||||
|
public final class CommonMarkHelper {
|
||||||
|
public static String toHtml(String rawCommonMark) {
|
||||||
|
Parser parser = Parser.builder().build();
|
||||||
|
Node document = parser.parse(rawCommonMark);
|
||||||
|
Node parent1 = document.getFirstChild();
|
||||||
|
Node parent2 = document.getLastChild();
|
||||||
|
if (parent1 == parent2 && parent1 instanceof Paragraph) {
|
||||||
|
document = new Document();
|
||||||
|
Node child = parent1.getFirstChild();
|
||||||
|
while (child != null) {
|
||||||
|
Node nextChild = child.getNext();
|
||||||
|
document.appendChild(child);
|
||||||
|
child = nextChild;//child.getNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HtmlRenderer renderer = HtmlRenderer.builder().build();
|
||||||
|
return renderer.render(document);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package com.majinnaibu.monstercards.helpers;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.majinnaibu.monstercards.importers.DnDBeyondImporter;
|
||||||
|
import com.majinnaibu.monstercards.importers.EntityImporter;
|
||||||
|
import com.majinnaibu.monstercards.importers.MonsterJsonImporter;
|
||||||
|
import com.majinnaibu.monstercards.importers.Open5eImporter;
|
||||||
|
import com.majinnaibu.monstercards.importers.TetraCubeMonsterImporter;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
import com.majinnaibu.monstercards.utils.Logger;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class MonsterImportHelper {
|
||||||
|
private static final List<EntityImporter<Monster>> IMPORTERS = new ArrayList<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
IMPORTERS.add(new TetraCubeMonsterImporter());
|
||||||
|
IMPORTERS.add(new MonsterJsonImporter());
|
||||||
|
IMPORTERS.add(new Open5eImporter());
|
||||||
|
IMPORTERS.add(new DnDBeyondImporter());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static Monster fromJSON(String json) {
|
||||||
|
return fromJSON(json, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static Monster fromJSON(String json, @Nullable String fileName) {
|
||||||
|
if (json == null || json.trim().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("JSON payload is null or empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Check for explicit $schema reference matching monster-card or tetracube schema
|
||||||
|
try {
|
||||||
|
JsonElement el = JsonParser.parseString(json);
|
||||||
|
if (el.isJsonObject()) {
|
||||||
|
JsonObject obj = el.getAsJsonObject();
|
||||||
|
if (obj.has("$schema")) {
|
||||||
|
String schemaVal = obj.get("$schema").getAsString();
|
||||||
|
if (schemaVal.contains("monster-card.schema.json")) {
|
||||||
|
return new MonsterJsonImporter().parse(json);
|
||||||
|
} else if (schemaVal.contains("tetracube-monster.schema.json")) {
|
||||||
|
return new TetraCubeMonsterImporter().parse(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.logError("Error checking $schema reference tag", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. If filename ends in .monster, try TetraCube importer first
|
||||||
|
String lowerFileName = fileName != null ? fileName.toLowerCase(Locale.ROOT) : "";
|
||||||
|
boolean isMonsterFile = lowerFileName.endsWith(".monster") || lowerFileName.endsWith(".monster.txt");
|
||||||
|
if (isMonsterFile) {
|
||||||
|
TetraCubeMonsterImporter tetraImporter = new TetraCubeMonsterImporter();
|
||||||
|
if (tetraImporter.canImport(json)) {
|
||||||
|
try {
|
||||||
|
return tetraImporter.parse(json);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.logError("TetraCube importer failed for .monster file: " + fileName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Fallback to current importer detection chain
|
||||||
|
for (EntityImporter<Monster> importer : IMPORTERS) {
|
||||||
|
if (importer.canImport(json)) {
|
||||||
|
try {
|
||||||
|
return importer.parse(json);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.logError("Importer " + importer.getClass().getSimpleName() + " failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("No importer found capable of parsing the given JSON payload.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package com.majinnaibu.monstercards.helpers;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
@SuppressWarnings({"RedundantIfStatement"})
|
||||||
|
public final class StringHelper {
|
||||||
|
public static boolean isNullOrEmpty(CharSequence value) {
|
||||||
|
if (value == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("".contentEquals(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static String join(String delimiter, @NonNull Collection<String> strings) {
|
||||||
|
int length = strings.size();
|
||||||
|
if (length < 1) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
boolean isFirst = true;
|
||||||
|
for (String element : strings) {
|
||||||
|
if (!isFirst) {
|
||||||
|
sb.append(delimiter);
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(element);
|
||||||
|
|
||||||
|
isFirst = false;
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String oxfordJoin(String delimiter, String lastDelimiter, String onlyDelimiter, @NonNull Collection<String> strings) {
|
||||||
|
int length = strings.size();
|
||||||
|
if (length < 1) {
|
||||||
|
return "";
|
||||||
|
} else if (length == 2) {
|
||||||
|
return join(onlyDelimiter, strings);
|
||||||
|
} else {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
int index = 0;
|
||||||
|
int lastIndex = length - 1;
|
||||||
|
for (String element : strings) {
|
||||||
|
if (index > 0 && index < lastIndex) {
|
||||||
|
sb.append(delimiter);
|
||||||
|
} else if (index > 0 && index >= lastIndex) {
|
||||||
|
sb.append(lastDelimiter);
|
||||||
|
}
|
||||||
|
sb.append(element);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static Integer parseInt(String s) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(s);
|
||||||
|
} catch (NumberFormatException _ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean containsCaseInsensitive(@NonNull String text, @NonNull String search) {
|
||||||
|
// TODO: find a locale independent way to do this
|
||||||
|
return text.toLowerCase().contains(search.toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.majinnaibu.monstercards.importers;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.majinnaibu.monstercards.models.BinderExport;
|
||||||
|
|
||||||
|
public class BinderImporter implements EntityImporter<BinderExport> {
|
||||||
|
|
||||||
|
private static final Gson GSON = new Gson();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canImport(@NonNull String input) {
|
||||||
|
try {
|
||||||
|
JsonElement el = JsonParser.parseString(input);
|
||||||
|
if (el.isJsonObject()) {
|
||||||
|
JsonObject obj = el.getAsJsonObject();
|
||||||
|
if (obj.has("$schema")) {
|
||||||
|
String schemaVal = obj.get("$schema").getAsString();
|
||||||
|
if (schemaVal.contains("binder.schema.json")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj.has("collections") && obj.get("collections").isJsonArray();
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public BinderExport parse(@NonNull String input) throws Exception {
|
||||||
|
BinderExport binder = GSON.fromJson(input, BinderExport.class);
|
||||||
|
if (binder == null || binder.collections == null) {
|
||||||
|
throw new IllegalArgumentException("Failed to deserialize Binder JSON");
|
||||||
|
}
|
||||||
|
return binder;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,418 @@
|
|||||||
|
package com.majinnaibu.monstercards.importers;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||||
|
import com.majinnaibu.monstercards.models.Language;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
import com.majinnaibu.monstercards.models.Skill;
|
||||||
|
import com.majinnaibu.monstercards.models.Trait;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
|
||||||
|
public class DnDBeyondImporter implements EntityImporter<Monster> {
|
||||||
|
|
||||||
|
private static final Pattern DNDBEYOND_URL_PATTERN = Pattern.compile("https?://(?:www\\.)?dndbeyond\\.com/characters/(\\d+)(?:/([a-zA-Z0-9]+))?", Pattern.CASE_INSENSITIVE);
|
||||||
|
private static final Pattern CHARACTER_ID_PATTERN = Pattern.compile("^\\d+$");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canImport(@NonNull String input) {
|
||||||
|
String trimmed = input.trim();
|
||||||
|
if (trimmed.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (DNDBEYOND_URL_PATTERN.matcher(trimmed).find() || CHARACTER_ID_PATTERN.matcher(trimmed).matches()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JsonElement element = JsonParser.parseString(trimmed);
|
||||||
|
if (element.isJsonObject()) {
|
||||||
|
JsonObject root = element.getAsJsonObject();
|
||||||
|
if (root.has("data") && root.getAsJsonObject("data").has("baseHitPoints")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Monster parse(@NonNull String input) throws Exception {
|
||||||
|
String trimmed = input.trim();
|
||||||
|
JsonObject characterData;
|
||||||
|
String sourceUrl = "";
|
||||||
|
|
||||||
|
if (trimmed.startsWith("{")) {
|
||||||
|
JsonObject root = JsonParser.parseString(trimmed).getAsJsonObject();
|
||||||
|
characterData = root.has("data") ? root.getAsJsonObject("data") : root;
|
||||||
|
} else {
|
||||||
|
String characterId = extractCharacterId(trimmed);
|
||||||
|
if (characterId == null) {
|
||||||
|
throw new IllegalArgumentException("Could not extract D&D Beyond character ID from input: " + input);
|
||||||
|
}
|
||||||
|
sourceUrl = trimmed.startsWith("http") ? trimmed : "https://www.dndbeyond.com/characters/" + characterId;
|
||||||
|
String jsonPayload = fetchCharacterJson(characterId);
|
||||||
|
JsonObject root = JsonParser.parseString(jsonPayload).getAsJsonObject();
|
||||||
|
if (!root.has("data") || root.get("data").isJsonNull()) {
|
||||||
|
throw new IllegalArgumentException("D&D Beyond response did not contain character data.");
|
||||||
|
}
|
||||||
|
characterData = root.getAsJsonObject("data");
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildMonsterFromCharacterData(characterData, sourceUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static String extractCharacterId(@NonNull String input) {
|
||||||
|
String trimmed = input.trim();
|
||||||
|
Matcher urlMatcher = DNDBEYOND_URL_PATTERN.matcher(trimmed);
|
||||||
|
if (urlMatcher.find()) {
|
||||||
|
return urlMatcher.group(1);
|
||||||
|
}
|
||||||
|
if (CHARACTER_ID_PATTERN.matcher(trimmed).matches()) {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private String fetchCharacterJson(@NonNull String characterId) throws Exception {
|
||||||
|
String serviceUrl = "https://character-service.dndbeyond.com/character/v5/character/" + characterId + "?includeCustomItems=true";
|
||||||
|
URL url = new URL(serviceUrl);
|
||||||
|
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
|
||||||
|
conn.setRequestMethod("GET");
|
||||||
|
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Android; Mobile)");
|
||||||
|
conn.setRequestProperty("Accept", "application/json");
|
||||||
|
conn.setConnectTimeout(10000);
|
||||||
|
conn.setReadTimeout(10000);
|
||||||
|
|
||||||
|
int code = conn.getResponseCode();
|
||||||
|
if (code != 200) {
|
||||||
|
throw new IllegalStateException("D&D Beyond service returned HTTP " + code);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
try (InputStream in = conn.getInputStream();
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
sb.append(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Monster buildMonsterFromCharacterData(@NonNull JsonObject data, @NonNull String sourceUrl) {
|
||||||
|
Monster monster = new Monster();
|
||||||
|
monster.sourceUrl = sourceUrl;
|
||||||
|
|
||||||
|
String rawName = getString(data, "name");
|
||||||
|
if (rawName.startsWith("\"") && rawName.endsWith("\"") && rawName.length() > 1) {
|
||||||
|
rawName = rawName.substring(1, rawName.length() - 1);
|
||||||
|
}
|
||||||
|
monster.name = rawName.isEmpty() ? "D&D Beyond Character" : rawName;
|
||||||
|
|
||||||
|
monster.size = "Medium";
|
||||||
|
monster.type = "Humanoid";
|
||||||
|
|
||||||
|
parseClassesAndLevel(data, monster);
|
||||||
|
parseStats(data, monster);
|
||||||
|
parseHitPoints(data, monster);
|
||||||
|
parseSpeedAndSenses(data, monster);
|
||||||
|
parseModifiers(data, monster);
|
||||||
|
parseTraitsAndFeatures(data, monster);
|
||||||
|
parseActionsAndAttacks(data, monster);
|
||||||
|
|
||||||
|
return monster;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseClassesAndLevel(@NonNull JsonObject data, @NonNull Monster monster) {
|
||||||
|
StringBuilder classSummary = new StringBuilder();
|
||||||
|
int totalLevel = 0;
|
||||||
|
if (data.has("classes") && data.get("classes").isJsonArray()) {
|
||||||
|
JsonArray classes = data.getAsJsonArray("classes");
|
||||||
|
for (int i = 0; i < classes.size(); i++) {
|
||||||
|
JsonObject cls = classes.get(i).getAsJsonObject();
|
||||||
|
int level = getInt(cls, "level", 1);
|
||||||
|
totalLevel += level;
|
||||||
|
String className = "";
|
||||||
|
if (cls.has("definition") && cls.get("definition").isJsonObject()) {
|
||||||
|
className = getString(cls.getAsJsonObject("definition"), "name");
|
||||||
|
}
|
||||||
|
String subclassName = "";
|
||||||
|
if (cls.has("subclassDefinition") && cls.get("subclassDefinition").isJsonObject()) {
|
||||||
|
subclassName = getString(cls.getAsJsonObject("subclassDefinition"), "name");
|
||||||
|
}
|
||||||
|
if (classSummary.length() > 0) {
|
||||||
|
classSummary.append(" / ");
|
||||||
|
}
|
||||||
|
classSummary.append(className);
|
||||||
|
if (!subclassName.isEmpty()) {
|
||||||
|
classSummary.append(" (").append(subclassName).append(")");
|
||||||
|
}
|
||||||
|
classSummary.append(" ").append(level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
monster.hitDice = totalLevel > 0 ? totalLevel : 1;
|
||||||
|
String raceName = "";
|
||||||
|
if (data.has("race") && data.get("race").isJsonObject()) {
|
||||||
|
JsonObject race = data.getAsJsonObject("race");
|
||||||
|
raceName = getString(race, "fullName");
|
||||||
|
if (raceName.isEmpty()) {
|
||||||
|
raceName = getString(race, "baseRaceName");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StringBuilder subtypeSb = new StringBuilder();
|
||||||
|
if (totalLevel > 0) {
|
||||||
|
subtypeSb.append("Level ").append(totalLevel);
|
||||||
|
}
|
||||||
|
if (!raceName.isEmpty()) {
|
||||||
|
if (subtypeSb.length() > 0) subtypeSb.append(" ");
|
||||||
|
subtypeSb.append(raceName);
|
||||||
|
}
|
||||||
|
if (classSummary.length() > 0) {
|
||||||
|
if (subtypeSb.length() > 0) subtypeSb.append(" ");
|
||||||
|
subtypeSb.append(classSummary);
|
||||||
|
}
|
||||||
|
monster.subtype = subtypeSb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseStats(@NonNull JsonObject data, @NonNull Monster monster) {
|
||||||
|
if (data.has("stats") && data.get("stats").isJsonArray()) {
|
||||||
|
JsonArray stats = data.getAsJsonArray("stats");
|
||||||
|
for (int i = 0; i < stats.size(); i++) {
|
||||||
|
JsonObject stat = stats.get(i).getAsJsonObject();
|
||||||
|
int id = getInt(stat, "id");
|
||||||
|
int val = getInt(stat, "value", 10);
|
||||||
|
switch (id) {
|
||||||
|
case 1: monster.strengthScore = val; break;
|
||||||
|
case 2: monster.dexterityScore = val; break;
|
||||||
|
case 3: monster.constitutionScore = val; break;
|
||||||
|
case 4: monster.intelligenceScore = val; break;
|
||||||
|
case 5: monster.wisdomScore = val; break;
|
||||||
|
case 6: monster.charismaScore = val; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseHitPoints(@NonNull JsonObject data, @NonNull Monster monster) {
|
||||||
|
int baseHp = getInt(data, "baseHitPoints");
|
||||||
|
int overrideHp = getInt(data, "overrideHitPoints");
|
||||||
|
int maxHp = overrideHp > 0 ? overrideHp : baseHp;
|
||||||
|
if (maxHp > 0) {
|
||||||
|
monster.hasCustomHP = true;
|
||||||
|
monster.customHPDescription = String.format(Locale.ROOT, "%d (%dd%d)", maxHp, monster.hitDice, 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseSpeedAndSenses(@NonNull JsonObject data, @NonNull Monster monster) {
|
||||||
|
if (data.has("race") && data.get("race").isJsonObject()) {
|
||||||
|
JsonObject race = data.getAsJsonObject("race");
|
||||||
|
if (race.has("weightSpeeds") && race.get("weightSpeeds").isJsonObject()) {
|
||||||
|
JsonObject weightSpeeds = race.getAsJsonObject("weightSpeeds");
|
||||||
|
if (weightSpeeds.has("normal") && weightSpeeds.get("normal").isJsonObject()) {
|
||||||
|
JsonObject normal = weightSpeeds.getAsJsonObject("normal");
|
||||||
|
monster.walkSpeed = getInt(normal, "walk", 30);
|
||||||
|
monster.flySpeed = getInt(normal, "fly", 0);
|
||||||
|
monster.burrowSpeed = getInt(normal, "burrow", 0);
|
||||||
|
monster.swimSpeed = getInt(normal, "swim", 0);
|
||||||
|
monster.climbSpeed = getInt(normal, "climb", 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (monster.walkSpeed == 0) {
|
||||||
|
monster.walkSpeed = 30;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseModifiers(@NonNull JsonObject data, @NonNull Monster monster) {
|
||||||
|
if (!data.has("modifiers") || !data.get("modifiers").isJsonObject()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JsonObject modifiersObj = data.getAsJsonObject("modifiers");
|
||||||
|
String[] categories = new String[]{"race", "class", "background", "item", "feat"};
|
||||||
|
|
||||||
|
for (String cat : categories) {
|
||||||
|
if (modifiersObj.has(cat) && modifiersObj.get(cat).isJsonArray()) {
|
||||||
|
JsonArray mods = modifiersObj.getAsJsonArray(cat);
|
||||||
|
for (int i = 0; i < mods.size(); i++) {
|
||||||
|
JsonObject mod = mods.get(i).getAsJsonObject();
|
||||||
|
String type = getString(mod, "type");
|
||||||
|
String subType = getString(mod, "subType");
|
||||||
|
int fixedVal = getInt(mod, "fixedValue");
|
||||||
|
|
||||||
|
if ("set-base".equals(type) && "darkvision".equals(subType)) {
|
||||||
|
int dist = fixedVal > 0 ? fixedVal : 60;
|
||||||
|
monster.senses.add("darkvision " + dist + " ft.");
|
||||||
|
} else if ("language".equals(type)) {
|
||||||
|
String langName = capitalize(subType.replace("-", " "));
|
||||||
|
monster.languages.add(new Language(langName, true));
|
||||||
|
} else if ("proficiency".equals(type)) {
|
||||||
|
if (subType.endsWith("-saving-throws")) {
|
||||||
|
String stat = subType.replace("-saving-throws", "");
|
||||||
|
applySavingThrowProficiency(monster, stat);
|
||||||
|
} else {
|
||||||
|
applySkillProficiency(monster, subType, ProficiencyType.PROFICIENT);
|
||||||
|
}
|
||||||
|
} else if ("expertise".equals(type)) {
|
||||||
|
applySkillProficiency(monster, subType, ProficiencyType.EXPERTISE);
|
||||||
|
} else if ("immunity".equals(type)) {
|
||||||
|
monster.damageImmunities.add(capitalize(subType.replace("-", " ")));
|
||||||
|
} else if ("resistance".equals(type)) {
|
||||||
|
monster.damageResistances.add(capitalize(subType.replace("-", " ")));
|
||||||
|
} else if ("vulnerability".equals(type)) {
|
||||||
|
monster.damageVulnerabilities.add(capitalize(subType.replace("-", " ")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applySavingThrowProficiency(@NonNull Monster monster, String stat) {
|
||||||
|
if ("strength".equals(stat) || "str".equals(stat)) monster.strengthSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
else if ("dexterity".equals(stat) || "dex".equals(stat)) monster.dexteritySavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
else if ("constitution".equals(stat) || "con".equals(stat)) monster.constitutionSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
else if ("intelligence".equals(stat) || "int".equals(stat)) monster.intelligenceSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
else if ("wisdom".equals(stat) || "wis".equals(stat)) monster.wisdomSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
else if ("charisma".equals(stat) || "cha".equals(stat)) monster.charismaSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applySkillProficiency(@NonNull Monster monster, String subType, ProficiencyType profType) {
|
||||||
|
String skillName = capitalize(subType.replace("-", " "));
|
||||||
|
AbilityScore abilityScore = AbilityScore.STRENGTH;
|
||||||
|
if ("Acrobatics".equalsIgnoreCase(skillName) || "Sleight Of Hand".equalsIgnoreCase(skillName) || "Stealth".equalsIgnoreCase(skillName)) {
|
||||||
|
abilityScore = AbilityScore.DEXTERITY;
|
||||||
|
} else if ("Arcana".equalsIgnoreCase(skillName) || "History".equalsIgnoreCase(skillName) || "Investigation".equalsIgnoreCase(skillName) || "Nature".equalsIgnoreCase(skillName) || "Religion".equalsIgnoreCase(skillName)) {
|
||||||
|
abilityScore = AbilityScore.INTELLIGENCE;
|
||||||
|
} else if ("Animal Handling".equalsIgnoreCase(skillName) || "Insight".equalsIgnoreCase(skillName) || "Medicine".equalsIgnoreCase(skillName) || "Perception".equalsIgnoreCase(skillName) || "Survival".equalsIgnoreCase(skillName)) {
|
||||||
|
abilityScore = AbilityScore.WISDOM;
|
||||||
|
} else if ("Deception".equalsIgnoreCase(skillName) || "Intimidation".equalsIgnoreCase(skillName) || "Performance".equalsIgnoreCase(skillName) || "Persuasion".equalsIgnoreCase(skillName)) {
|
||||||
|
abilityScore = AbilityScore.CHARISMA;
|
||||||
|
} else if ("Athletics".equalsIgnoreCase(skillName)) {
|
||||||
|
abilityScore = AbilityScore.STRENGTH;
|
||||||
|
}
|
||||||
|
monster.skills.add(new Skill(skillName, abilityScore, AdvantageType.NONE, profType));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseTraitsAndFeatures(@NonNull JsonObject data, @NonNull Monster monster) {
|
||||||
|
if (data.has("race") && data.get("race").isJsonObject()) {
|
||||||
|
JsonObject race = data.getAsJsonObject("race");
|
||||||
|
if (race.has("racialTraits") && race.get("racialTraits").isJsonArray()) {
|
||||||
|
JsonArray traits = race.getAsJsonArray("racialTraits");
|
||||||
|
for (int i = 0; i < traits.size(); i++) {
|
||||||
|
JsonObject tr = traits.get(i).getAsJsonObject();
|
||||||
|
if (tr.has("definition") && tr.get("definition").isJsonObject()) {
|
||||||
|
JsonObject def = tr.getAsJsonObject("definition");
|
||||||
|
String name = getString(def, "name");
|
||||||
|
String desc = stripHtml(getString(def, "snippet"));
|
||||||
|
if (desc.isEmpty()) {
|
||||||
|
desc = stripHtml(getString(def, "description"));
|
||||||
|
}
|
||||||
|
if (!name.isEmpty() && !desc.isEmpty()) {
|
||||||
|
monster.abilities.add(new Trait(name, desc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseActionsAndAttacks(@NonNull JsonObject data, @NonNull Monster monster) {
|
||||||
|
if (data.has("actions") && data.get("actions").isJsonObject()) {
|
||||||
|
JsonObject actionsObj = data.getAsJsonObject("actions");
|
||||||
|
String[] categories = new String[]{"class", "race", "background", "item", "feat"};
|
||||||
|
for (String cat : categories) {
|
||||||
|
if (actionsObj.has(cat) && actionsObj.get(cat).isJsonArray()) {
|
||||||
|
JsonArray acts = actionsObj.getAsJsonArray(cat);
|
||||||
|
for (int i = 0; i < acts.size(); i++) {
|
||||||
|
JsonObject act = acts.get(i).getAsJsonObject();
|
||||||
|
String name = getString(act, "name");
|
||||||
|
String desc = stripHtml(getString(act, "snippet"));
|
||||||
|
if (desc.isEmpty()) {
|
||||||
|
desc = stripHtml(getString(act, "description"));
|
||||||
|
}
|
||||||
|
int actType = 1;
|
||||||
|
if (act.has("activation") && act.get("activation").isJsonObject()) {
|
||||||
|
actType = getInt(act.getAsJsonObject("activation"), "activationType", 1);
|
||||||
|
}
|
||||||
|
if (!name.isEmpty()) {
|
||||||
|
Trait trait = new Trait(name, desc);
|
||||||
|
if (actType == 3) {
|
||||||
|
monster.bonusActions.add(trait);
|
||||||
|
} else if (actType == 4) {
|
||||||
|
monster.reactions.add(trait);
|
||||||
|
} else {
|
||||||
|
monster.actions.add(trait);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getString(@NonNull JsonObject obj, String key) {
|
||||||
|
return getString(obj, key, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getString(@NonNull JsonObject obj, String key, String defaultVal) {
|
||||||
|
if (obj.has(key) && !obj.get(key).isJsonNull()) {
|
||||||
|
return obj.get(key).getAsString();
|
||||||
|
}
|
||||||
|
return defaultVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getInt(@NonNull JsonObject obj, String key) {
|
||||||
|
return getInt(obj, key, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getInt(@NonNull JsonObject obj, String key, int defaultVal) {
|
||||||
|
if (obj.has(key) && !obj.get(key).isJsonNull()) {
|
||||||
|
try {
|
||||||
|
return obj.get(key).getAsInt();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String stripHtml(String html) {
|
||||||
|
if (html == null) return "";
|
||||||
|
return html.replaceAll("<[^>]*>", "").replaceAll(" ", " ").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String capitalize(String str) {
|
||||||
|
if (str == null || str.isEmpty()) return str;
|
||||||
|
String[] words = str.split("\\s+");
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (String w : words) {
|
||||||
|
if (!w.isEmpty()) {
|
||||||
|
if (sb.length() > 0) sb.append(" ");
|
||||||
|
sb.append(Character.toUpperCase(w.charAt(0))).append(w.substring(1).toLowerCase(Locale.ROOT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.majinnaibu.monstercards.importers;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public interface EntityImporter<T> {
|
||||||
|
/**
|
||||||
|
* Determines whether the given input string or payload can be handled by this importer.
|
||||||
|
*/
|
||||||
|
boolean canImport(@NonNull String input);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the raw input string into internal domain memory objects (e.g., Monster).
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
T parse(@NonNull String input) throws Exception;
|
||||||
|
}
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
package com.majinnaibu.monstercards.importers;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Environment;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.majinnaibu.monstercards.MonsterCardsApplication;
|
||||||
|
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||||
|
import com.majinnaibu.monstercards.models.ImportSource;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
import com.majinnaibu.monstercards.utils.Logger;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
|
||||||
|
public class GitRepoImporterService {
|
||||||
|
|
||||||
|
public static int importFromGitRepository(Context context, ImportSource source, BooleanSupplier isCancelled) throws Exception {
|
||||||
|
int totalImported = 0;
|
||||||
|
|
||||||
|
File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||||
|
if (!downloadsDir.exists()) {
|
||||||
|
downloadsDir.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
File tempZip = null;
|
||||||
|
File[] existingZips = downloadsDir.listFiles((dir, name) -> name.startsWith(source.id + "_download") && name.endsWith(".zip"));
|
||||||
|
if (existingZips != null && existingZips.length > 0) {
|
||||||
|
tempZip = existingZips[0];
|
||||||
|
} else {
|
||||||
|
tempZip = new File(downloadsDir, source.id + "_download_" + System.currentTimeMillis() + ".zip");
|
||||||
|
downloadZip(source.downloadUrl, tempZip, isCancelled);
|
||||||
|
}
|
||||||
|
|
||||||
|
File extractDir = new File(context.getCacheDir(), "repo_extracted_" + System.currentTimeMillis());
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (isCancelled.getAsBoolean()) return 0;
|
||||||
|
|
||||||
|
if (!extractDir.exists()) {
|
||||||
|
extractDir.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
int extractedFiles = unzipAndFilter(tempZip, extractDir, source.fileExtension, source.subfolder, isCancelled);
|
||||||
|
Logger.logWTF("Extracted " + extractedFiles + " files to " + extractDir.getAbsolutePath());
|
||||||
|
|
||||||
|
if (isCancelled.getAsBoolean()) return 0;
|
||||||
|
|
||||||
|
// Instantiate Importer dynamically
|
||||||
|
Class<?> clazz = Class.forName(source.importerClassName);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
EntityImporter<Monster> importer = (EntityImporter<Monster>) clazz.newInstance();
|
||||||
|
|
||||||
|
MonsterRepository repository = ((MonsterCardsApplication) context.getApplicationContext()).getMonsterRepository();
|
||||||
|
|
||||||
|
// Process files
|
||||||
|
totalImported = processDirectory(extractDir, importer, repository, isCancelled);
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
// We no longer delete tempZip since the user wants to keep the downloaded zip
|
||||||
|
// deleteFileOrDir(tempZip);
|
||||||
|
// DO NOT DELETE EXTRACT DIR FOR NOW
|
||||||
|
// deleteFileOrDir(extractDir);
|
||||||
|
Logger.logWTF("Skipping deletion of extract directory: " + extractDir.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalImported;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void downloadZip(String urlStr, File dest, BooleanSupplier isCancelled) throws IOException, InterruptedException {
|
||||||
|
URL url = new URL(urlStr);
|
||||||
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
|
conn.setRequestMethod("GET");
|
||||||
|
conn.setConnectTimeout(15000);
|
||||||
|
conn.setReadTimeout(30000);
|
||||||
|
|
||||||
|
int code = conn.getResponseCode();
|
||||||
|
// GitHub might redirect archive links, so handle redirect
|
||||||
|
if (code == HttpURLConnection.HTTP_MOVED_TEMP || code == HttpURLConnection.HTTP_MOVED_PERM || code == HttpURLConnection.HTTP_SEE_OTHER) {
|
||||||
|
String newUrl = conn.getHeaderField("Location");
|
||||||
|
conn = (HttpURLConnection) new URL(newUrl).openConnection();
|
||||||
|
conn.setConnectTimeout(15000);
|
||||||
|
conn.setReadTimeout(30000);
|
||||||
|
code = conn.getResponseCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code != 200) {
|
||||||
|
throw new IllegalStateException("Git repo download failed with HTTP " + code);
|
||||||
|
}
|
||||||
|
|
||||||
|
try (InputStream in = new BufferedInputStream(conn.getInputStream());
|
||||||
|
FileOutputStream out = new FileOutputStream(dest)) {
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int count;
|
||||||
|
while ((count = in.read(buffer)) != -1) {
|
||||||
|
if (isCancelled.getAsBoolean()) {
|
||||||
|
throw new InterruptedException("Git download was cancelled.");
|
||||||
|
}
|
||||||
|
out.write(buffer, 0, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int unzipAndFilter(File zipFile, File extractDir, String filterExtension, String subfolder, BooleanSupplier isCancelled) throws InterruptedException {
|
||||||
|
int extractedCount = 0;
|
||||||
|
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {
|
||||||
|
ZipEntry entry;
|
||||||
|
while ((entry = zis.getNextEntry()) != null) {
|
||||||
|
if (isCancelled.getAsBoolean()) {
|
||||||
|
try {
|
||||||
|
zis.closeEntry();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.logError("Failed to close zip entry upon cancellation", e);
|
||||||
|
}
|
||||||
|
throw new InterruptedException("Git extraction was cancelled.");
|
||||||
|
}
|
||||||
|
if (!entry.isDirectory()) {
|
||||||
|
String name = entry.getName();
|
||||||
|
boolean inSubfolder = subfolder == null || subfolder.isEmpty() || name.contains("/" + subfolder + "/") || name.startsWith(subfolder + "/");
|
||||||
|
if (inSubfolder && (filterExtension == null || name.endsWith(filterExtension))) {
|
||||||
|
File outFile = new File(extractDir, new File(name).getName());
|
||||||
|
if (outFile.exists()) {
|
||||||
|
outFile = new File(extractDir, System.currentTimeMillis() + "_" + new File(name).getName());
|
||||||
|
}
|
||||||
|
try (FileOutputStream fos = new FileOutputStream(outFile)) {
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int count;
|
||||||
|
while ((count = zis.read(buffer)) != -1) {
|
||||||
|
if (isCancelled.getAsBoolean()) {
|
||||||
|
throw new InterruptedException("Git extraction was cancelled during file read.");
|
||||||
|
}
|
||||||
|
fos.write(buffer, 0, count);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.logError("Failed to write extracted file to disk: " + name, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
zis.closeEntry();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.logError("Failed to close zip entry", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.logError("Failed during unzip stream processing", e);
|
||||||
|
}
|
||||||
|
return extractedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int processDirectory(File dir, EntityImporter<Monster> importer, MonsterRepository repository, BooleanSupplier isCancelled) throws InterruptedException {
|
||||||
|
int importedCount = 0;
|
||||||
|
File[] files = dir.listFiles();
|
||||||
|
if (files == null) {
|
||||||
|
Logger.logWTF("processDirectory: files array is null for " + dir.getAbsolutePath());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.logWTF("Attempting to process " + files.length + " JSON files.");
|
||||||
|
|
||||||
|
for (File file : files) {
|
||||||
|
if (isCancelled.getAsBoolean()) {
|
||||||
|
throw new InterruptedException("Git import processing was cancelled.");
|
||||||
|
}
|
||||||
|
if (file.isFile()) {
|
||||||
|
String content = readFileContent(file);
|
||||||
|
if (content != null) {
|
||||||
|
if (importer.canImport(content)) {
|
||||||
|
try {
|
||||||
|
Monster monster = importer.parse(content);
|
||||||
|
repository.saveMonster(monster).blockingAwait();
|
||||||
|
importedCount++;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.logError("Failed to parse/save monster from file: " + file.getName(), e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logger.logWTF("Importer rejected file: " + file.getName());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logger.logError("Failed to read content from file: " + file.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return importedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readFileContent(File file) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
try (InputStream in = new FileInputStream(file);
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
sb.append(line).append("\n");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Logger.logError("Error reading extracted file", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void deleteFileOrDir(File fileOrDir) {
|
||||||
|
if (fileOrDir.isDirectory()) {
|
||||||
|
File[] children = fileOrDir.listFiles();
|
||||||
|
if (children != null) {
|
||||||
|
for (File child : children) {
|
||||||
|
deleteFileOrDir(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fileOrDir.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.majinnaibu.monstercards.importers;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
|
||||||
|
public class MonsterJsonImporter implements EntityImporter<Monster> {
|
||||||
|
|
||||||
|
private static final Gson GSON = new Gson();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canImport(@NonNull String input) {
|
||||||
|
try {
|
||||||
|
JsonElement el = JsonParser.parseString(input);
|
||||||
|
if (el.isJsonObject()) {
|
||||||
|
JsonObject obj = el.getAsJsonObject();
|
||||||
|
if (obj.has("$schema")) {
|
||||||
|
String schemaVal = obj.get("$schema").getAsString();
|
||||||
|
if (schemaVal.contains("monster-card.schema.json")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj.has("name") && (obj.has("strengthScore") || obj.has("strengthSavingThrowProficiency"));
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Monster parse(@NonNull String input) throws Exception {
|
||||||
|
Monster monster = GSON.fromJson(input, Monster.class);
|
||||||
|
if (monster == null) {
|
||||||
|
throw new IllegalArgumentException("Failed to deserialize Monster JSON");
|
||||||
|
}
|
||||||
|
return monster;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package com.majinnaibu.monstercards.importers;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.InterruptedIOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
|
||||||
|
import com.majinnaibu.monstercards.utils.Logger;
|
||||||
|
|
||||||
|
public class Open5eApiWrapper {
|
||||||
|
private static final String BASE_URL = "https://api.open5e.com/v2/creatures/";
|
||||||
|
|
||||||
|
public static class Open5ePageResult {
|
||||||
|
public List<Monster> monsters = new ArrayList<>();
|
||||||
|
@Nullable public String nextUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static Open5ePageResult fetchPage(@Nullable String urlStr, BooleanSupplier isCancelled) throws Exception {
|
||||||
|
if (urlStr == null || urlStr.isEmpty()) {
|
||||||
|
urlStr = BASE_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
URL url = new URL(urlStr);
|
||||||
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
|
conn.setRequestMethod("GET");
|
||||||
|
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Android; Mobile)");
|
||||||
|
conn.setRequestProperty("Accept", "application/json");
|
||||||
|
conn.setConnectTimeout(15000);
|
||||||
|
conn.setReadTimeout(15000);
|
||||||
|
|
||||||
|
int code = conn.getResponseCode();
|
||||||
|
if (code != 200) {
|
||||||
|
throw new IllegalStateException("Open5e API returned HTTP " + code);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
try (InputStream in = conn.getInputStream();
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
if (isCancelled.getAsBoolean()) {
|
||||||
|
throw new InterruptedException("Open5e network read was cancelled.");
|
||||||
|
}
|
||||||
|
sb.append(line);
|
||||||
|
}
|
||||||
|
} catch (InterruptedIOException e) {
|
||||||
|
throw new InterruptedException("Open5e network read was cancelled.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String jsonPayload = sb.toString();
|
||||||
|
JsonObject root = JsonParser.parseString(jsonPayload).getAsJsonObject();
|
||||||
|
|
||||||
|
Open5ePageResult result = new Open5ePageResult();
|
||||||
|
if (root.has("next") && !root.get("next").isJsonNull()) {
|
||||||
|
result.nextUrl = root.get("next").getAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.has("results") && root.get("results").isJsonArray()) {
|
||||||
|
JsonArray results = root.getAsJsonArray("results");
|
||||||
|
Open5eImporter importer = new Open5eImporter();
|
||||||
|
for (int i = 0; i < results.size(); i++) {
|
||||||
|
JsonElement el = results.get(i);
|
||||||
|
if (el.isJsonObject()) {
|
||||||
|
try {
|
||||||
|
Monster monster = importer.parse(el.toString());
|
||||||
|
result.monsters.add(monster);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.logError("Failed to parse Open5e monster", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,450 @@
|
|||||||
|
package com.majinnaibu.monstercards.importers;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.ArmorType;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.ChallengeRating;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||||
|
import com.majinnaibu.monstercards.models.Language;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
import com.majinnaibu.monstercards.models.Skill;
|
||||||
|
import com.majinnaibu.monstercards.models.Trait;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class Open5eImporter implements EntityImporter<Monster> {
|
||||||
|
|
||||||
|
private static final Pattern HIT_DICE_PATTERN = Pattern.compile("(\\d+)d(\\d+)");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canImport(@NonNull String input) {
|
||||||
|
String trimmed = input.trim();
|
||||||
|
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JsonElement element = JsonParser.parseString(trimmed);
|
||||||
|
if (!element.isJsonObject()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
JsonObject root = element.getAsJsonObject();
|
||||||
|
if (root.has("ruleset_id") && root.get("ruleset_id").getAsString().equals("open5e")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (root.has("properties") && root.getAsJsonObject("properties").has("abilities")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Monster parse(@NonNull String input) throws Exception {
|
||||||
|
JsonObject root = JsonParser.parseString(input.trim()).getAsJsonObject();
|
||||||
|
|
||||||
|
Monster monster = new Monster();
|
||||||
|
|
||||||
|
if (root.has("uuid") && !root.get("uuid").isJsonNull()) {
|
||||||
|
try {
|
||||||
|
monster.id = UUID.fromString(root.get("uuid").getAsString());
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.has("display_name") && !root.get("display_name").isJsonNull()) {
|
||||||
|
monster.name = root.get("display_name").getAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject props = root.has("properties") ? root.getAsJsonObject("properties") : root;
|
||||||
|
|
||||||
|
if (monster.name == null || monster.name.isEmpty()) {
|
||||||
|
monster.name = getString(props, "name", "Unnamed Entity");
|
||||||
|
}
|
||||||
|
|
||||||
|
monster.size = getString(props, "size");
|
||||||
|
monster.type = getString(props, "type");
|
||||||
|
monster.subtype = getString(props, "subtype");
|
||||||
|
monster.alignment = getString(props, "alignment");
|
||||||
|
|
||||||
|
parseArmorClass(props, monster);
|
||||||
|
parseHitPoints(props, monster);
|
||||||
|
|
||||||
|
if (props.has("speed") && props.get("speed").isJsonObject()) {
|
||||||
|
JsonObject speedObj = props.getAsJsonObject("speed");
|
||||||
|
monster.walkSpeed = getInt(speedObj, "walk", 30);
|
||||||
|
monster.flySpeed = getInt(speedObj, "fly", 0);
|
||||||
|
monster.swimSpeed = getInt(speedObj, "swim", 0);
|
||||||
|
monster.burrowSpeed = getInt(speedObj, "burrow", 0);
|
||||||
|
monster.climbSpeed = getInt(speedObj, "climb", 0);
|
||||||
|
} else {
|
||||||
|
monster.walkSpeed = getInt(props, "speed", 30);
|
||||||
|
}
|
||||||
|
if (props.has("speed_desc")) {
|
||||||
|
String speedDesc = getString(props, "speed_desc");
|
||||||
|
if (!speedDesc.isEmpty()) {
|
||||||
|
monster.hasCustomSpeed = true;
|
||||||
|
monster.customSpeedDescription = speedDesc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parseChallengeRating(props, monster);
|
||||||
|
parseAbilities(props, monster);
|
||||||
|
parseIdentityFields(props, monster);
|
||||||
|
|
||||||
|
parseSavingThrows(props, monster);
|
||||||
|
parseSkills(props, monster);
|
||||||
|
parseSenses(props, monster);
|
||||||
|
parseLanguages(props, monster);
|
||||||
|
parseDamageAndConditions(props, monster);
|
||||||
|
|
||||||
|
parseTraitsAndActions(props, monster);
|
||||||
|
|
||||||
|
if (props.has("source_url")) {
|
||||||
|
monster.sourceUrl = getString(props, "source_url");
|
||||||
|
}
|
||||||
|
|
||||||
|
return monster;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseArmorClass(JsonObject props, Monster monster) {
|
||||||
|
if (props.has("equipped_armor") && props.get("equipped_armor").isJsonObject()) {
|
||||||
|
JsonObject armorObj = props.getAsJsonObject("equipped_armor");
|
||||||
|
String armorId = getString(armorObj, "id");
|
||||||
|
ArmorType armorType = ArmorType.valueOfString(armorId);
|
||||||
|
if (armorType != ArmorType.NONE) {
|
||||||
|
monster.armorType = armorType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.has("equipped_shield") && props.get("equipped_shield").isJsonObject()) {
|
||||||
|
JsonObject shieldObj = props.getAsJsonObject("equipped_shield");
|
||||||
|
monster.shieldBonus = getInt(shieldObj, "ac_bonus", 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (monster.armorType == ArmorType.NONE && props.has("armor_description")) {
|
||||||
|
String armorDesc = getString(props, "armor_description");
|
||||||
|
if (!armorDesc.isEmpty()) {
|
||||||
|
monster.otherArmorDescription = armorDesc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseHitPoints(JsonObject props, Monster monster) {
|
||||||
|
if (props.has("hit_points") && props.get("hit_points").isJsonObject()) {
|
||||||
|
JsonObject hpObj = props.getAsJsonObject("hit_points");
|
||||||
|
int maxHp = getInt(hpObj, "max", getInt(hpObj, "current", 10));
|
||||||
|
if (hpObj.has("hit_dice") && !hpObj.get("hit_dice").isJsonNull()) {
|
||||||
|
String hitDiceStr = hpObj.get("hit_dice").getAsString();
|
||||||
|
Matcher m = HIT_DICE_PATTERN.matcher(hitDiceStr);
|
||||||
|
if (m.find()) {
|
||||||
|
monster.hitDice = Integer.parseInt(m.group(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hpObj.has("formula") && !hpObj.get("formula").isJsonNull()) {
|
||||||
|
monster.hasCustomHP = true;
|
||||||
|
monster.customHPDescription = String.format(Locale.ROOT, "%d (%s)", maxHp, hpObj.get("formula").getAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseChallengeRating(JsonObject props, Monster monster) {
|
||||||
|
String crStr = "";
|
||||||
|
if (props.has("cr") && !props.get("cr").isJsonNull()) {
|
||||||
|
crStr = props.get("cr").getAsString();
|
||||||
|
} else if (props.has("challenge_rating") && !props.get("challenge_rating").isJsonNull()) {
|
||||||
|
crStr = props.get("challenge_rating").getAsString();
|
||||||
|
}
|
||||||
|
if (!crStr.isEmpty()) {
|
||||||
|
crStr = crStr.replace(".0", "");
|
||||||
|
if (crStr.equals("0.125")) crStr = "1/8";
|
||||||
|
else if (crStr.equals("0.25")) crStr = "1/4";
|
||||||
|
else if (crStr.equals("0.5")) crStr = "1/2";
|
||||||
|
monster.challengeRating = ChallengeRating.valueOfString(crStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseAbilities(JsonObject props, Monster monster) {
|
||||||
|
if (!props.has("abilities") || !props.get("abilities").isJsonObject()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JsonObject abs = props.getAsJsonObject("abilities");
|
||||||
|
monster.strengthScore = extractAbilityScore(abs, "strength");
|
||||||
|
monster.dexterityScore = extractAbilityScore(abs, "dexterity");
|
||||||
|
monster.constitutionScore = extractAbilityScore(abs, "constitution");
|
||||||
|
monster.intelligenceScore = extractAbilityScore(abs, "intelligence");
|
||||||
|
monster.wisdomScore = extractAbilityScore(abs, "wisdom");
|
||||||
|
monster.charismaScore = extractAbilityScore(abs, "charisma");
|
||||||
|
}
|
||||||
|
|
||||||
|
private int extractAbilityScore(JsonObject abs, String key) {
|
||||||
|
if (!abs.has(key) || abs.get(key).isJsonNull()) {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
JsonElement el = abs.get(key);
|
||||||
|
if (el.isJsonPrimitive()) {
|
||||||
|
return el.getAsInt();
|
||||||
|
} else if (el.isJsonObject() && el.getAsJsonObject().has("value")) {
|
||||||
|
return el.getAsJsonObject().get("value").getAsInt();
|
||||||
|
}
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseIdentityFields(JsonObject props, Monster monster) {
|
||||||
|
monster.playerName = getString(props, "player_name");
|
||||||
|
monster.background = getString(props, "background");
|
||||||
|
monster.personalityTraits = getString(props, "personality_traits");
|
||||||
|
monster.ideals = getString(props, "ideals");
|
||||||
|
monster.bonds = getString(props, "bonds");
|
||||||
|
monster.flaws = getString(props, "flaws");
|
||||||
|
|
||||||
|
if (props.has("character_details") && props.get("character_details").isJsonObject()) {
|
||||||
|
JsonObject details = props.getAsJsonObject("character_details");
|
||||||
|
monster.age = getString(details, "age");
|
||||||
|
monster.height = getString(details, "height");
|
||||||
|
monster.weight = getString(details, "weight");
|
||||||
|
monster.eyes = getString(details, "eyes");
|
||||||
|
monster.skin = getString(details, "skin");
|
||||||
|
monster.hair = getString(details, "hair");
|
||||||
|
monster.appearance = getString(details, "appearance");
|
||||||
|
monster.backstory = getString(details, "backstory");
|
||||||
|
monster.alliesAndOrganizations = getString(details, "allies_and_organizations");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseSavingThrows(JsonObject props, Monster monster) {
|
||||||
|
if (!props.has("saving_throws")) return;
|
||||||
|
JsonElement savesEl = props.get("saving_throws");
|
||||||
|
if (savesEl.isJsonObject()) {
|
||||||
|
JsonObject savesObj = savesEl.getAsJsonObject();
|
||||||
|
for (String key : savesObj.keySet()) {
|
||||||
|
applySaveProficiency(monster, key.toLowerCase(Locale.ROOT));
|
||||||
|
}
|
||||||
|
} else if (savesEl.isJsonArray()) {
|
||||||
|
JsonArray arr = savesEl.getAsJsonArray();
|
||||||
|
for (int i = 0; i < arr.size(); i++) {
|
||||||
|
String saveStr = arr.get(i).getAsString().toLowerCase(Locale.ROOT);
|
||||||
|
applySaveProficiency(monster, saveStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applySaveProficiency(Monster monster, String saveStr) {
|
||||||
|
if (saveStr.contains("str")) monster.strengthSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
else if (saveStr.contains("dex")) monster.dexteritySavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
else if (saveStr.contains("con")) monster.constitutionSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
else if (saveStr.contains("int")) monster.intelligenceSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
else if (saveStr.contains("wis")) monster.wisdomSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
else if (saveStr.contains("cha")) monster.charismaSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseSkills(JsonObject props, Monster monster) {
|
||||||
|
if (props.has("skill_bonuses") && props.get("skill_bonuses").isJsonObject()) {
|
||||||
|
JsonObject skillsObj = props.getAsJsonObject("skill_bonuses");
|
||||||
|
for (String key : skillsObj.keySet()) {
|
||||||
|
AbilityScore stat = getAbilityForSkill(key);
|
||||||
|
monster.skills.add(new Skill(capitalize(key.replace("_", " ")), stat, AdvantageType.NONE, ProficiencyType.PROFICIENT));
|
||||||
|
}
|
||||||
|
} else if (props.has("skills")) {
|
||||||
|
JsonElement skillsEl = props.get("skills");
|
||||||
|
if (skillsEl.isJsonArray()) {
|
||||||
|
JsonArray arr = skillsEl.getAsJsonArray();
|
||||||
|
for (int i = 0; i < arr.size(); i++) {
|
||||||
|
String skillStr = arr.get(i).getAsString();
|
||||||
|
String skillName = skillStr.replaceAll("[+-]\\d+", "").trim();
|
||||||
|
if (!skillName.isEmpty()) {
|
||||||
|
AbilityScore stat = getAbilityForSkill(skillName);
|
||||||
|
monster.skills.add(new Skill(capitalize(skillName), stat, AdvantageType.NONE, ProficiencyType.PROFICIENT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AbilityScore getAbilityForSkill(String skillName) {
|
||||||
|
String lower = skillName.toLowerCase(Locale.ROOT);
|
||||||
|
if (lower.contains("acro") || lower.contains("sleight") || lower.contains("stealth")) return AbilityScore.DEXTERITY;
|
||||||
|
if (lower.contains("arca") || lower.contains("hist") || lower.contains("inve") || lower.contains("natu") || lower.contains("reli")) return AbilityScore.INTELLIGENCE;
|
||||||
|
if (lower.contains("anim") || lower.contains("insi") || lower.contains("medi") || lower.contains("perc") || lower.contains("surv")) return AbilityScore.WISDOM;
|
||||||
|
if (lower.contains("dece") || lower.contains("inti") || lower.contains("perf") || lower.contains("pers")) return AbilityScore.CHARISMA;
|
||||||
|
return AbilityScore.STRENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseSenses(JsonObject props, Monster monster) {
|
||||||
|
if (props.has("senses")) {
|
||||||
|
String sensesStr = getString(props, "senses");
|
||||||
|
if (!sensesStr.isEmpty()) {
|
||||||
|
String[] parts = sensesStr.split(",");
|
||||||
|
for (String p : parts) {
|
||||||
|
if (!p.trim().isEmpty()) {
|
||||||
|
monster.senses.add(p.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseLanguages(JsonObject props, Monster monster) {
|
||||||
|
if (props.has("languages")) {
|
||||||
|
JsonElement langEl = props.get("languages");
|
||||||
|
if (langEl.isJsonObject()) {
|
||||||
|
if (langEl.getAsJsonObject().has("data") && langEl.getAsJsonObject().get("data").isJsonArray()) {
|
||||||
|
JsonArray arr = langEl.getAsJsonObject().getAsJsonArray("data");
|
||||||
|
for (int i = 0; i < arr.size(); i++) {
|
||||||
|
if (arr.get(i).isJsonObject()) {
|
||||||
|
String langName = getString(arr.get(i).getAsJsonObject(), "name");
|
||||||
|
if (!langName.isEmpty()) {
|
||||||
|
monster.languages.add(new Language(langName, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (langEl.getAsJsonObject().has("as_string")) {
|
||||||
|
String langStr = langEl.getAsJsonObject().get("as_string").getAsString();
|
||||||
|
for (String l : langStr.split(",")) {
|
||||||
|
if (!l.trim().isEmpty()) {
|
||||||
|
monster.languages.add(new Language(l.trim(), true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (langEl.isJsonArray()) {
|
||||||
|
JsonArray arr = langEl.getAsJsonArray();
|
||||||
|
for (int i = 0; i < arr.size(); i++) {
|
||||||
|
String langName = arr.get(i).getAsString();
|
||||||
|
if (!langName.isEmpty()) {
|
||||||
|
monster.languages.add(new Language(langName, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (langEl.isJsonPrimitive()) {
|
||||||
|
String langStr = langEl.getAsString();
|
||||||
|
for (String l : langStr.split(",")) {
|
||||||
|
if (!l.trim().isEmpty()) {
|
||||||
|
monster.languages.add(new Language(l.trim(), true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseDamageAndConditions(JsonObject props, Monster monster) {
|
||||||
|
if (props.has("resistances_and_immunities") && props.get("resistances_and_immunities").isJsonObject()) {
|
||||||
|
JsonObject resObj = props.getAsJsonObject("resistances_and_immunities");
|
||||||
|
parseStringSet(resObj, "damage_immunities", monster.damageImmunities);
|
||||||
|
parseStringSet(resObj, "damage_resistances", monster.damageResistances);
|
||||||
|
parseStringSet(resObj, "damage_vulnerabilities", monster.damageVulnerabilities);
|
||||||
|
parseStringSet(resObj, "condition_immunities", monster.conditionImmunities);
|
||||||
|
} else {
|
||||||
|
parseStringSet(props, "damage_immunities", monster.damageImmunities);
|
||||||
|
parseStringSet(props, "damage_resistances", monster.damageResistances);
|
||||||
|
parseStringSet(props, "damage_vulnerabilities", monster.damageVulnerabilities);
|
||||||
|
parseStringSet(props, "condition_immunities", monster.conditionImmunities);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseStringSet(JsonObject props, String key, java.util.Set<String> set) {
|
||||||
|
if (!props.has(key)) return;
|
||||||
|
JsonElement el = props.get(key);
|
||||||
|
if (el.isJsonArray()) {
|
||||||
|
JsonArray arr = el.getAsJsonArray();
|
||||||
|
for (int i = 0; i < arr.size(); i++) {
|
||||||
|
JsonElement item = arr.get(i);
|
||||||
|
if (item.isJsonObject() && item.getAsJsonObject().has("name")) {
|
||||||
|
String val = item.getAsJsonObject().get("name").getAsString();
|
||||||
|
if (!val.isEmpty()) set.add(capitalize(val));
|
||||||
|
} else if (item.isJsonPrimitive()) {
|
||||||
|
String val = item.getAsString();
|
||||||
|
if (!val.isEmpty()) set.add(capitalize(val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (el.isJsonPrimitive()) {
|
||||||
|
for (String val : el.getAsString().split(",")) {
|
||||||
|
if (!val.trim().isEmpty()) set.add(capitalize(val.trim()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseTraitsAndActions(JsonObject props, Monster monster) {
|
||||||
|
parseTraitList(props, "traits", monster.abilities);
|
||||||
|
parseTraitList(props, "actions", monster.actions);
|
||||||
|
parseTraitList(props, "bonus_actions", monster.bonusActions);
|
||||||
|
parseTraitList(props, "reactions", monster.reactions);
|
||||||
|
parseTraitList(props, "legendary_actions", monster.legendaryActions);
|
||||||
|
parseTraitList(props, "lair_actions", monster.lairActions);
|
||||||
|
parseTraitList(props, "regional_effects", monster.regionalActions);
|
||||||
|
parseTraitList(props, "mythic_actions", monster.mythicActions);
|
||||||
|
|
||||||
|
if (props.has("legendary_desc")) monster.legendaryActionsDescription = getString(props, "legendary_desc");
|
||||||
|
if (props.has("lair_desc")) monster.lairActionsDescription = getString(props, "lair_desc");
|
||||||
|
if (props.has("lair_end_note")) monster.lairActionsEndNote = getString(props, "lair_end_note");
|
||||||
|
if (props.has("regional_desc")) monster.regionalActionsDescription = getString(props, "regional_desc");
|
||||||
|
if (props.has("regional_end_note")) monster.regionalActionsEndNote = getString(props, "regional_end_note");
|
||||||
|
if (props.has("mythic_desc")) monster.mythicActionsDescription = getString(props, "mythic_desc");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseTraitList(JsonObject props, String key, List<Trait> list) {
|
||||||
|
if (!props.has(key) || !props.get(key).isJsonArray()) return;
|
||||||
|
JsonArray arr = props.getAsJsonArray(key);
|
||||||
|
for (int i = 0; i < arr.size(); i++) {
|
||||||
|
JsonElement el = arr.get(i);
|
||||||
|
if (el.isJsonObject()) {
|
||||||
|
JsonObject obj = el.getAsJsonObject();
|
||||||
|
String name = getString(obj, "name");
|
||||||
|
String desc = getString(obj, "desc");
|
||||||
|
if (desc.isEmpty()) desc = getString(obj, "description");
|
||||||
|
if (!name.isEmpty()) {
|
||||||
|
list.add(new Trait(name, desc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getString(JsonObject obj, String key) {
|
||||||
|
return getString(obj, key, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getString(JsonObject obj, String key, String defaultVal) {
|
||||||
|
if (obj.has(key) && !obj.get(key).isJsonNull()) {
|
||||||
|
JsonElement el = obj.get(key);
|
||||||
|
if (el.isJsonPrimitive()) {
|
||||||
|
return el.getAsString();
|
||||||
|
} else if (el.isJsonObject() && el.getAsJsonObject().has("name")) {
|
||||||
|
return el.getAsJsonObject().get("name").getAsString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getInt(JsonObject obj, String key, int defaultVal) {
|
||||||
|
if (obj.has(key) && !obj.get(key).isJsonNull()) {
|
||||||
|
try {
|
||||||
|
return obj.get(key).getAsInt();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String capitalize(String str) {
|
||||||
|
if (str == null || str.isEmpty()) return str;
|
||||||
|
String[] words = str.split("\\s+");
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (String w : words) {
|
||||||
|
if (!w.isEmpty()) {
|
||||||
|
if (sb.length() > 0) sb.append(" ");
|
||||||
|
sb.append(Character.toUpperCase(w.charAt(0))).append(w.substring(1).toLowerCase(Locale.ROOT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package com.majinnaibu.monstercards.importers;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.ArmorType;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.ChallengeRating;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||||
|
import com.majinnaibu.monstercards.models.Language;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
import com.majinnaibu.monstercards.models.Skill;
|
||||||
|
import com.majinnaibu.monstercards.models.Trait;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class Pf2eImporter implements EntityImporter<Monster> {
|
||||||
|
@Override
|
||||||
|
public boolean canImport(@NonNull String input) {
|
||||||
|
String trimmed = input.trim();
|
||||||
|
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JsonObject root = JsonParser.parseString(trimmed).getAsJsonObject();
|
||||||
|
if (root.has("type") && "npc".equals(root.get("type").getAsString())) {
|
||||||
|
if (root.has("system") && root.getAsJsonObject("system").has("attributes")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Monster parse(@NonNull String input) throws Exception {
|
||||||
|
JsonObject root = JsonParser.parseString(input.trim()).getAsJsonObject();
|
||||||
|
JsonObject system = root.has("system") ? root.getAsJsonObject("system") : new JsonObject();
|
||||||
|
|
||||||
|
Monster monster = new Monster();
|
||||||
|
monster.id = UUID.randomUUID();
|
||||||
|
|
||||||
|
// Name
|
||||||
|
if (root.has("name") && !root.get("name").isJsonNull()) {
|
||||||
|
monster.name = root.get("name").getAsString();
|
||||||
|
} else {
|
||||||
|
monster.name = "Unknown PF2e Monster";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type / Size / Alignment
|
||||||
|
if (system.has("traits") && system.get("traits").isJsonObject()) {
|
||||||
|
JsonObject traits = system.getAsJsonObject("traits");
|
||||||
|
if (traits.has("size") && traits.get("size").isJsonObject()) {
|
||||||
|
monster.size = mapPf2eSize(getString(traits.getAsJsonObject("size"), "value"));
|
||||||
|
}
|
||||||
|
if (traits.has("value") && traits.get("value").isJsonArray()) {
|
||||||
|
// PF2e holds type/alignment tags in traits array
|
||||||
|
StringBuilder typeSb = new StringBuilder();
|
||||||
|
for (JsonElement el : traits.getAsJsonArray("value")) {
|
||||||
|
String trait = el.getAsString();
|
||||||
|
if (typeSb.length() > 0) typeSb.append(", ");
|
||||||
|
typeSb.append(capitalize(trait));
|
||||||
|
}
|
||||||
|
monster.type = typeSb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ability Scores
|
||||||
|
if (system.has("abilities") && system.get("abilities").isJsonObject()) {
|
||||||
|
JsonObject abilities = system.getAsJsonObject("abilities");
|
||||||
|
monster.strengthScore = 10 + (getInt(abilities.getAsJsonObject("str"), "mod") * 2);
|
||||||
|
monster.dexterityScore = 10 + (getInt(abilities.getAsJsonObject("dex"), "mod") * 2);
|
||||||
|
monster.constitutionScore = 10 + (getInt(abilities.getAsJsonObject("con"), "mod") * 2);
|
||||||
|
monster.intelligenceScore = 10 + (getInt(abilities.getAsJsonObject("int"), "mod") * 2);
|
||||||
|
monster.wisdomScore = 10 + (getInt(abilities.getAsJsonObject("wis"), "mod") * 2);
|
||||||
|
monster.charismaScore = 10 + (getInt(abilities.getAsJsonObject("cha"), "mod") * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attributes (HP, AC, Speed, Level)
|
||||||
|
if (system.has("attributes") && system.get("attributes").isJsonObject()) {
|
||||||
|
JsonObject attributes = system.getAsJsonObject("attributes");
|
||||||
|
|
||||||
|
if (attributes.has("ac") && attributes.get("ac").isJsonObject()) {
|
||||||
|
monster.armorType = ArmorType.NONE;
|
||||||
|
monster.otherArmorDescription = String.valueOf(getInt(attributes.getAsJsonObject("ac"), "value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attributes.has("hp") && attributes.get("hp").isJsonObject()) {
|
||||||
|
monster.hasCustomHP = true;
|
||||||
|
monster.customHPDescription = String.valueOf(getInt(attributes.getAsJsonObject("hp"), "max"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attributes.has("speed") && attributes.get("speed").isJsonObject()) {
|
||||||
|
monster.walkSpeed = getInt(attributes.getAsJsonObject("speed"), "value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Level (Mapped to CR)
|
||||||
|
if (system.has("details") && system.get("details").isJsonObject()) {
|
||||||
|
JsonObject details = system.getAsJsonObject("details");
|
||||||
|
if (details.has("level") && details.get("level").isJsonObject()) {
|
||||||
|
int level = getInt(details.getAsJsonObject("level"), "value");
|
||||||
|
monster.challengeRating = mapLevelToCr(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (details.has("languages") && details.get("languages").isJsonObject()) {
|
||||||
|
JsonObject langObj = details.getAsJsonObject("languages");
|
||||||
|
if (langObj.has("value") && langObj.get("value").isJsonArray()) {
|
||||||
|
for (JsonElement el : langObj.getAsJsonArray("value")) {
|
||||||
|
monster.languages.add(new Language(capitalize(el.getAsString()), true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (details.has("publicNotes") && !details.get("publicNotes").isJsonNull()) {
|
||||||
|
String notes = details.get("publicNotes").getAsString().replaceAll("<[^>]*>", "").trim();
|
||||||
|
monster.backstory = notes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return monster;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String mapPf2eSize(String pf2eSize) {
|
||||||
|
if (pf2eSize == null) return "Medium";
|
||||||
|
switch (pf2eSize.toLowerCase(Locale.ROOT)) {
|
||||||
|
case "tiny": return "Tiny";
|
||||||
|
case "sm": return "Small";
|
||||||
|
case "med": return "Medium";
|
||||||
|
case "lg": return "Large";
|
||||||
|
case "huge": return "Huge";
|
||||||
|
case "grg": return "Gargantuan";
|
||||||
|
default: return "Medium";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChallengeRating mapLevelToCr(int level) {
|
||||||
|
if (level <= 0) return ChallengeRating.ZERO;
|
||||||
|
if (level > 30) return ChallengeRating.THIRTY;
|
||||||
|
return ChallengeRating.valueOfString(String.valueOf(level));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getString(JsonObject obj, String key) {
|
||||||
|
if (obj != null && obj.has(key) && !obj.get(key).isJsonNull()) {
|
||||||
|
return obj.get(key).getAsString();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getInt(JsonObject obj, String key) {
|
||||||
|
if (obj != null && obj.has(key) && !obj.get(key).isJsonNull()) {
|
||||||
|
try {
|
||||||
|
return obj.get(key).getAsInt();
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String capitalize(String str) {
|
||||||
|
if (str == null || str.isEmpty()) return str;
|
||||||
|
return str.substring(0, 1).toUpperCase(Locale.ROOT) + str.substring(1).toLowerCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,364 @@
|
|||||||
|
package com.majinnaibu.monstercards.importers;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.google.gson.JsonPrimitive;
|
||||||
|
import com.majinnaibu.monstercards.data.converters.ArmorTypeConverter;
|
||||||
|
import com.majinnaibu.monstercards.data.converters.ChallengeRatingConverter;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||||
|
import com.majinnaibu.monstercards.models.Language;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
import com.majinnaibu.monstercards.models.Skill;
|
||||||
|
import com.majinnaibu.monstercards.models.Trait;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class TetraCubeMonsterImporter implements EntityImporter<Monster> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canImport(@NonNull String input) {
|
||||||
|
if (input.trim().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JsonElement element = JsonParser.parseString(input);
|
||||||
|
if (!element.isJsonObject()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
JsonObject rootDict = element.getAsJsonObject();
|
||||||
|
if (rootDict.has("$schema")) {
|
||||||
|
String schemaVal = rootDict.get("$schema").getAsString();
|
||||||
|
if (schemaVal.contains("tetracube-monster.schema.json")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rootDict.has("armorName") || rootDict.has("strPoints")
|
||||||
|
|| rootDict.has("isLegendary") || rootDict.has("isMythic")
|
||||||
|
|| rootDict.has("legendariesDescription");
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Monster parse(@NonNull String input) throws Exception {
|
||||||
|
JsonObject rootDict = JsonParser.parseString(input).getAsJsonObject();
|
||||||
|
|
||||||
|
Monster monster = new Monster();
|
||||||
|
monster.name = getString(rootDict, "name");
|
||||||
|
monster.size = getString(rootDict, "size");
|
||||||
|
monster.type = getString(rootDict, "type");
|
||||||
|
monster.subtype = getString(rootDict, "tag");
|
||||||
|
monster.alignment = getString(rootDict, "alignment");
|
||||||
|
monster.hitDice = getInt(rootDict, "hitDice");
|
||||||
|
monster.armorType = ArmorTypeConverter.armorTypeFromStringValue(getString(rootDict, "armorName"));
|
||||||
|
monster.shieldBonus = getInt(rootDict, "shieldBonus");
|
||||||
|
monster.naturalArmorBonus = getInt(rootDict, "natArmorBonus");
|
||||||
|
monster.otherArmorDescription = getString(rootDict, "otherArmorDesc");
|
||||||
|
monster.walkSpeed = getInt(rootDict, "speed");
|
||||||
|
monster.burrowSpeed = getInt(rootDict, "burrowSpeed");
|
||||||
|
monster.climbSpeed = getInt(rootDict, "climbSpeed");
|
||||||
|
monster.flySpeed = getInt(rootDict, "flySpeed");
|
||||||
|
monster.canHover = getBool(rootDict, "hover");
|
||||||
|
monster.swimSpeed = getInt(rootDict, "swimSpeed");
|
||||||
|
monster.hasCustomHP = getBool(rootDict, "customHP");
|
||||||
|
monster.hasCustomSpeed = getBool(rootDict, "customSpeed");
|
||||||
|
monster.customHPDescription = getString(rootDict, "hpText");
|
||||||
|
monster.customSpeedDescription = getString(rootDict, "speedDesc");
|
||||||
|
monster.strengthScore = getInt(rootDict, "strPoints");
|
||||||
|
monster.dexterityScore = getInt(rootDict, "dexPoints");
|
||||||
|
monster.constitutionScore = getInt(rootDict, "conPoints");
|
||||||
|
monster.intelligenceScore = getInt(rootDict, "intPoints");
|
||||||
|
monster.wisdomScore = getInt(rootDict, "wisPoints");
|
||||||
|
monster.charismaScore = getInt(rootDict, "chaPoints");
|
||||||
|
addSense(monster, rootDict, "blindsight");
|
||||||
|
addSense(monster, rootDict, "darkvision");
|
||||||
|
addSense(monster, rootDict, "tremorsense");
|
||||||
|
addSense(monster, rootDict, "truesight");
|
||||||
|
monster.telepathyRange = getInt(rootDict, "telepathy");
|
||||||
|
monster.challengeRating = ChallengeRatingConverter.challengeRatingFromStringValue(getString(rootDict, "cr"));
|
||||||
|
monster.customChallengeRatingDescription = getString(rootDict, "customCr");
|
||||||
|
monster.customProficiencyBonus = getInt(rootDict, "customProf");
|
||||||
|
|
||||||
|
monster.abilities = getListOfTraits(rootDict, "abilities");
|
||||||
|
monster.actions = getListOfTraits(rootDict, "actions");
|
||||||
|
monster.bonusActions = getListOfTraits(rootDict, "bonusActions");
|
||||||
|
monster.reactions = getListOfTraits(rootDict, "reactions");
|
||||||
|
monster.legendaryActions = getListOfTraits(rootDict, "legendaries");
|
||||||
|
monster.mythicActions = getListOfTraits(rootDict, "mythics");
|
||||||
|
monster.lairActions = getListOfTraits(rootDict, "lairs");
|
||||||
|
monster.regionalActions = getListOfTraits(rootDict, "regionals");
|
||||||
|
|
||||||
|
monster.legendaryActionsDescription = getString(rootDict, "legendariesDescription");
|
||||||
|
if (!monster.legendaryActionsDescription.isEmpty()) {
|
||||||
|
monster.legendaryActions.add(0, new Trait("Legendary Actions", monster.legendaryActionsDescription));
|
||||||
|
}
|
||||||
|
|
||||||
|
monster.mythicActionsDescription = getString(rootDict, "mythicDescription");
|
||||||
|
if (!monster.mythicActionsDescription.isEmpty() || !monster.mythicActions.isEmpty()) {
|
||||||
|
if (!monster.mythicActionsDescription.isEmpty()) {
|
||||||
|
monster.legendaryActions.add(new Trait("Mythic Actions", monster.mythicActionsDescription));
|
||||||
|
}
|
||||||
|
for (Trait mythic : monster.mythicActions) {
|
||||||
|
String traitName = mythic.name;
|
||||||
|
if (!traitName.toLowerCase(Locale.ROOT).startsWith("mythic action")) {
|
||||||
|
traitName = "Mythic Action: " + traitName;
|
||||||
|
}
|
||||||
|
monster.legendaryActions.add(new Trait(traitName, mythic.description));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
monster.lairActionsDescription = getString(rootDict, "lairDescription");
|
||||||
|
monster.lairActionsEndNote = getString(rootDict, "lairDescriptionEnd");
|
||||||
|
String fullLairDesc = combineDescriptions(monster.lairActionsDescription, monster.lairActionsEndNote);
|
||||||
|
if (!fullLairDesc.isEmpty()) {
|
||||||
|
monster.lairActions.add(0, new Trait("Lair Actions", fullLairDesc));
|
||||||
|
}
|
||||||
|
|
||||||
|
monster.regionalActionsDescription = getString(rootDict, "regionalDescription");
|
||||||
|
monster.regionalActionsEndNote = getString(rootDict, "regionalDescriptionEnd");
|
||||||
|
String fullRegionalDesc = combineDescriptions(monster.regionalActionsDescription, monster.regionalActionsEndNote);
|
||||||
|
if (!fullRegionalDesc.isEmpty()) {
|
||||||
|
monster.regionalActions.add(0, new Trait("Regional Effects", fullRegionalDesc));
|
||||||
|
}
|
||||||
|
|
||||||
|
addSavingThrows(monster, rootDict);
|
||||||
|
monster.skills = getSetOfSkills(rootDict);
|
||||||
|
monster.damageImmunities = getSetOfDamageTypes(rootDict, "damageTypes", "i");
|
||||||
|
monster.damageImmunities.addAll(getSetOfDamageTypes(rootDict, "specialdamage", "i"));
|
||||||
|
monster.damageResistances = getSetOfDamageTypes(rootDict, "damageTypes", "r");
|
||||||
|
monster.damageResistances.addAll(getSetOfDamageTypes(rootDict, "specialdamage", "r"));
|
||||||
|
monster.damageVulnerabilities = getSetOfDamageTypes(rootDict, "damageTypes", "v");
|
||||||
|
monster.damageVulnerabilities.addAll(getSetOfDamageTypes(rootDict, "specialdamage", "v"));
|
||||||
|
monster.conditionImmunities = getSetOfDamageTypes(rootDict, "conditions");
|
||||||
|
monster.languages = getSetOfLanguages(rootDict, "languages");
|
||||||
|
monster.understandsButDescription = getString(rootDict, "understandsBut");
|
||||||
|
|
||||||
|
return monster;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static String combineDescriptions(String desc1, String desc2) {
|
||||||
|
if (!desc1.isEmpty() && !desc2.isEmpty()) {
|
||||||
|
return desc1 + "\n\n" + desc2;
|
||||||
|
} else if (!desc1.isEmpty()) {
|
||||||
|
return desc1;
|
||||||
|
} else {
|
||||||
|
return desc2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getString(JsonObject dict, String name) {
|
||||||
|
return getString(dict, name, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getString(@NonNull JsonObject dict, String name, String defaultValue) {
|
||||||
|
if (dict.has(name)) {
|
||||||
|
return dict.get(name).getAsString();
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getInt(JsonObject dict, String name) {
|
||||||
|
return getInt(dict, name, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getInt(@NonNull JsonObject dict, String name, int defaultValue) {
|
||||||
|
if (dict.has(name)) {
|
||||||
|
JsonElement element = dict.get(name);
|
||||||
|
if (element.isJsonPrimitive()) {
|
||||||
|
JsonPrimitive rawValue = element.getAsJsonPrimitive();
|
||||||
|
if (rawValue.isNumber()) {
|
||||||
|
return rawValue.getAsInt();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return rawValue.getAsInt();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean getBool(JsonObject dict, String name) {
|
||||||
|
return getBool(dict, name, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean getBool(@NonNull JsonObject dict, String name, boolean defaultValue) {
|
||||||
|
if (dict.has(name)) {
|
||||||
|
JsonElement element = dict.get(name);
|
||||||
|
if (element.isJsonPrimitive()) {
|
||||||
|
JsonPrimitive rawValue = element.getAsJsonPrimitive();
|
||||||
|
if (rawValue.isBoolean()) {
|
||||||
|
return rawValue.getAsBoolean();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return rawValue.getAsBoolean();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static String formatDistance(String name, int distance) {
|
||||||
|
return String.format(Locale.getDefault(), "%s %d ft.", name, distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addSense(Monster monster, JsonObject root, String name) {
|
||||||
|
int distance = getInt(root, name);
|
||||||
|
if (distance > 0) {
|
||||||
|
String senseStr = formatDistance(name, distance);
|
||||||
|
if ("blindsight".equals(name) && getBool(root, "blind")) {
|
||||||
|
senseStr += " (blind beyond this radius)";
|
||||||
|
}
|
||||||
|
monster.senses.add(senseStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static List<Trait> getListOfTraits(@NonNull JsonObject dict, String name) {
|
||||||
|
ArrayList<Trait> traits = new ArrayList<>();
|
||||||
|
if (dict.has(name)) {
|
||||||
|
JsonElement arrayElement = dict.get(name);
|
||||||
|
if (arrayElement.isJsonArray()) {
|
||||||
|
JsonArray array = arrayElement.getAsJsonArray();
|
||||||
|
int size = array.size();
|
||||||
|
for (int index = 0; index < size; index++) {
|
||||||
|
JsonElement jsonElement = array.get(index);
|
||||||
|
if (jsonElement.isJsonObject()) {
|
||||||
|
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
||||||
|
String traitName = getString(jsonObject, "name");
|
||||||
|
String description = getString(jsonObject, "desc");
|
||||||
|
Trait trait = new Trait(traitName, description);
|
||||||
|
traits.add(trait);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return traits;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addSavingThrows(Monster monster, @NonNull JsonObject root) {
|
||||||
|
if (root.has("sthrows")) {
|
||||||
|
JsonElement arrayElement = root.get("sthrows");
|
||||||
|
if (arrayElement.isJsonArray()) {
|
||||||
|
JsonArray array = arrayElement.getAsJsonArray();
|
||||||
|
int size = array.size();
|
||||||
|
for (int index = 0; index < size; index++) {
|
||||||
|
JsonElement jsonElement = array.get(index);
|
||||||
|
if (jsonElement.isJsonObject()) {
|
||||||
|
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
||||||
|
String name = getString(jsonObject, "name");
|
||||||
|
if ("str".equals(name)) {
|
||||||
|
monster.strengthSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
} else if ("dex".equals(name)) {
|
||||||
|
monster.dexteritySavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
} else if ("con".equals(name)) {
|
||||||
|
monster.constitutionSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
} else if ("int".equals(name)) {
|
||||||
|
monster.intelligenceSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
} else if ("wis".equals(name)) {
|
||||||
|
monster.wisdomSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
} else if ("cha".equals(name)) {
|
||||||
|
monster.charismaSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static Set<Skill> getSetOfSkills(@NonNull JsonObject root) {
|
||||||
|
HashSet<Skill> skills = new HashSet<>();
|
||||||
|
if (root.has("skills")) {
|
||||||
|
JsonElement arrayElement = root.get("skills");
|
||||||
|
if (arrayElement.isJsonArray()) {
|
||||||
|
JsonArray array = arrayElement.getAsJsonArray();
|
||||||
|
int size = array.size();
|
||||||
|
for (int index = 0; index < size; index++) {
|
||||||
|
JsonElement jsonElement = array.get(index);
|
||||||
|
if (jsonElement.isJsonObject()) {
|
||||||
|
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
||||||
|
String name = getString(jsonObject, "name");
|
||||||
|
String stat = getString(jsonObject, "stat");
|
||||||
|
String note = getString(jsonObject, "note");
|
||||||
|
|
||||||
|
Skill skill = new Skill(name, AbilityScore.valueOfString(stat), AdvantageType.NONE, " (ex)".equals(note) ? ProficiencyType.EXPERTISE : ProficiencyType.PROFICIENT);
|
||||||
|
skills.add(skill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return skills;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static Set<String> getSetOfDamageTypes(JsonObject rootDict, String name) {
|
||||||
|
return getSetOfDamageTypes(rootDict, name, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static Set<String> getSetOfDamageTypes(JsonObject rootDict, String name, String type) {
|
||||||
|
HashSet<String> damageTypes = new HashSet<>();
|
||||||
|
if (rootDict.has(name)) {
|
||||||
|
JsonElement arrayElement = rootDict.get(name);
|
||||||
|
if (arrayElement.isJsonArray()) {
|
||||||
|
JsonArray array = arrayElement.getAsJsonArray();
|
||||||
|
int size = array.size();
|
||||||
|
for (int index = 0; index < size; index++) {
|
||||||
|
JsonElement jsonElement = array.get(index);
|
||||||
|
if (jsonElement.isJsonObject()) {
|
||||||
|
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
||||||
|
String dtName = getString(jsonObject, "name");
|
||||||
|
String dtType = getString(jsonObject, "type");
|
||||||
|
if (type == null || type.equals(dtType)) {
|
||||||
|
damageTypes.add(dtName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return damageTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static Set<Language> getSetOfLanguages(@NonNull JsonObject root, String name) {
|
||||||
|
HashSet<Language> languages = new HashSet<>();
|
||||||
|
if (root.has(name)) {
|
||||||
|
JsonElement arrayElement = root.get(name);
|
||||||
|
if (arrayElement.isJsonArray()) {
|
||||||
|
JsonArray array = arrayElement.getAsJsonArray();
|
||||||
|
int size = array.size();
|
||||||
|
for (int index = 0; index < size; index++) {
|
||||||
|
JsonElement jsonElement = array.get(index);
|
||||||
|
if (jsonElement.isJsonObject()) {
|
||||||
|
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
||||||
|
String languageName = getString(jsonObject, "name");
|
||||||
|
boolean canSpeak = getBool(jsonObject, "speaks");
|
||||||
|
Language language = new Language(languageName, canSpeak);
|
||||||
|
languages.add(language);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return languages;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.majinnaibu.monstercards.models;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class BinderExport {
|
||||||
|
@SerializedName("$schema")
|
||||||
|
public String schema = "https://majinnaibu.com/schemas/binder.schema.json";
|
||||||
|
|
||||||
|
@SerializedName("schemaVersion")
|
||||||
|
public int schemaVersion = 1;
|
||||||
|
|
||||||
|
@SerializedName("collections")
|
||||||
|
public List<CollectionExport> collections = new ArrayList<>();
|
||||||
|
|
||||||
|
@SerializedName("dashboard")
|
||||||
|
public List<Monster> dashboard = new ArrayList<>();
|
||||||
|
|
||||||
|
public static class CollectionExport {
|
||||||
|
@SerializedName("id")
|
||||||
|
public String id;
|
||||||
|
|
||||||
|
@SerializedName("name")
|
||||||
|
public String name;
|
||||||
|
|
||||||
|
@SerializedName("description")
|
||||||
|
public String description;
|
||||||
|
|
||||||
|
@SerializedName("cards")
|
||||||
|
public List<Monster> cards = new ArrayList<>();
|
||||||
|
|
||||||
|
public CollectionExport() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public CollectionExport(String id, String name, String description, List<Monster> cards) {
|
||||||
|
this.id = id != null ? id : UUID.randomUUID().toString();
|
||||||
|
this.name = name != null ? name : "";
|
||||||
|
this.description = description != null ? description : "";
|
||||||
|
this.cards = cards != null ? cards : new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CollectionExport(String name, List<Monster> cards) {
|
||||||
|
this(UUID.randomUUID().toString(), name, "", cards);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.majinnaibu.monstercards.models;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.room.ColumnInfo;
|
||||||
|
import androidx.room.Entity;
|
||||||
|
import androidx.room.Ignore;
|
||||||
|
import androidx.room.PrimaryKey;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity(tableName = "collections")
|
||||||
|
public class Collection {
|
||||||
|
|
||||||
|
@PrimaryKey
|
||||||
|
@NonNull
|
||||||
|
public UUID id;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@ColumnInfo(defaultValue = "")
|
||||||
|
public String name;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@ColumnInfo(defaultValue = "")
|
||||||
|
public String description;
|
||||||
|
|
||||||
|
public Collection() {
|
||||||
|
this.id = UUID.randomUUID();
|
||||||
|
this.name = "";
|
||||||
|
this.description = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public Collection(@NonNull String name, @NonNull String description) {
|
||||||
|
this.id = UUID.randomUUID();
|
||||||
|
this.name = name;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (obj == null || getClass() != obj.getClass()) return false;
|
||||||
|
Collection that = (Collection) obj;
|
||||||
|
return Objects.equals(id, that.id) &&
|
||||||
|
Objects.equals(name, that.name) &&
|
||||||
|
Objects.equals(description, that.description);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(id, name, description);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.majinnaibu.monstercards.models;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.room.ColumnInfo;
|
||||||
|
import androidx.room.Entity;
|
||||||
|
import androidx.room.ForeignKey;
|
||||||
|
import androidx.room.Ignore;
|
||||||
|
import androidx.room.Index;
|
||||||
|
import androidx.room.PrimaryKey;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
tableName = "collection_monsters",
|
||||||
|
foreignKeys = {
|
||||||
|
@ForeignKey(
|
||||||
|
entity = Collection.class,
|
||||||
|
parentColumns = "id",
|
||||||
|
childColumns = "collection_id",
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
),
|
||||||
|
@ForeignKey(
|
||||||
|
entity = Monster.class,
|
||||||
|
parentColumns = "id",
|
||||||
|
childColumns = "monster_id",
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
)
|
||||||
|
},
|
||||||
|
indices = {
|
||||||
|
@Index(value = {"collection_id"}),
|
||||||
|
@Index(value = {"monster_id"})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public class CollectionMonster {
|
||||||
|
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
public long id;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@ColumnInfo(name = "collection_id")
|
||||||
|
public UUID collectionId;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@ColumnInfo(name = "monster_id")
|
||||||
|
public UUID monsterId;
|
||||||
|
|
||||||
|
@ColumnInfo(name = "ordinal", defaultValue = "0")
|
||||||
|
public int ordinal;
|
||||||
|
|
||||||
|
public CollectionMonster() {
|
||||||
|
this.collectionId = UUID.fromString("00000000-0000-0000-0000-000000000000");
|
||||||
|
this.monsterId = UUID.fromString("00000000-0000-0000-0000-000000000000");
|
||||||
|
this.ordinal = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public CollectionMonster(@NonNull UUID collectionId, @NonNull UUID monsterId, int ordinal) {
|
||||||
|
this.collectionId = collectionId;
|
||||||
|
this.monsterId = monsterId;
|
||||||
|
this.ordinal = ordinal;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.majinnaibu.monstercards.models;
|
||||||
|
|
||||||
|
import androidx.room.Embedded;
|
||||||
|
import androidx.room.Ignore;
|
||||||
|
|
||||||
|
public class CollectionWithCount {
|
||||||
|
@Embedded
|
||||||
|
public Collection collection;
|
||||||
|
|
||||||
|
public int monsterCount;
|
||||||
|
|
||||||
|
public CollectionWithCount() {
|
||||||
|
this.collection = new Collection();
|
||||||
|
this.monsterCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public CollectionWithCount(Collection collection, int monsterCount) {
|
||||||
|
this.collection = collection;
|
||||||
|
this.monsterCount = monsterCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.majinnaibu.monstercards.models;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.room.ColumnInfo;
|
||||||
|
import androidx.room.Entity;
|
||||||
|
import androidx.room.ForeignKey;
|
||||||
|
import androidx.room.Ignore;
|
||||||
|
import androidx.room.Index;
|
||||||
|
import androidx.room.PrimaryKey;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
tableName = "dashboard_monsters",
|
||||||
|
foreignKeys = @ForeignKey(
|
||||||
|
entity = Monster.class,
|
||||||
|
parentColumns = "id",
|
||||||
|
childColumns = "monster_id",
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
),
|
||||||
|
indices = @Index(value = {"monster_id"})
|
||||||
|
)
|
||||||
|
public class DashboardMonster {
|
||||||
|
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
public long id;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@ColumnInfo(name = "monster_id")
|
||||||
|
public UUID monsterId;
|
||||||
|
|
||||||
|
@ColumnInfo(name = "ordinal", defaultValue = "0")
|
||||||
|
public int ordinal;
|
||||||
|
|
||||||
|
public DashboardMonster() {
|
||||||
|
this.monsterId = UUID.fromString("00000000-0000-0000-0000-000000000000");
|
||||||
|
this.ordinal = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public DashboardMonster(@NonNull UUID monsterId, int ordinal) {
|
||||||
|
this.monsterId = monsterId;
|
||||||
|
this.ordinal = ordinal;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.majinnaibu.monstercards.models;
|
||||||
|
|
||||||
|
import androidx.room.Embedded;
|
||||||
|
import androidx.room.Ignore;
|
||||||
|
import androidx.room.Relation;
|
||||||
|
|
||||||
|
public class DashboardMonsterWithMonster {
|
||||||
|
@Embedded
|
||||||
|
public DashboardMonster dashboardEntry;
|
||||||
|
|
||||||
|
@Relation(
|
||||||
|
parentColumn = "monster_id",
|
||||||
|
entityColumn = "id"
|
||||||
|
)
|
||||||
|
public Monster monster;
|
||||||
|
|
||||||
|
public DashboardMonsterWithMonster() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public DashboardMonsterWithMonster(DashboardMonster dashboardEntry, Monster monster) {
|
||||||
|
this.dashboardEntry = dashboardEntry;
|
||||||
|
this.monster = monster;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.majinnaibu.monstercards.models;
|
||||||
|
|
||||||
|
public class ImportSource {
|
||||||
|
public enum ImportType {
|
||||||
|
GIT_ARCHIVE,
|
||||||
|
OPEN5E_API
|
||||||
|
}
|
||||||
|
|
||||||
|
public String id;
|
||||||
|
public String projectName;
|
||||||
|
public String creatorName;
|
||||||
|
public String creatorPageLink;
|
||||||
|
public ImportType importType;
|
||||||
|
|
||||||
|
// Optional attributes specific to Git archive
|
||||||
|
public String downloadUrl;
|
||||||
|
public String importerClassName;
|
||||||
|
public String fileExtension;
|
||||||
|
public String subfolder;
|
||||||
|
|
||||||
|
public ImportSource(String id, String projectName, String creatorName, String creatorPageLink, ImportType importType, String downloadUrl, String importerClassName, String fileExtension, String subfolder) {
|
||||||
|
this.id = id;
|
||||||
|
this.projectName = projectName;
|
||||||
|
this.creatorName = creatorName;
|
||||||
|
this.creatorPageLink = creatorPageLink;
|
||||||
|
this.importType = importType;
|
||||||
|
this.downloadUrl = downloadUrl;
|
||||||
|
this.importerClassName = importerClassName;
|
||||||
|
this.fileExtension = fileExtension;
|
||||||
|
this.subfolder = subfolder;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package com.majinnaibu.monstercards.models;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class Language implements Comparator<Language>, Comparable<Language> {
|
||||||
|
|
||||||
|
private String mName;
|
||||||
|
private boolean mSpeaks;
|
||||||
|
|
||||||
|
public Language(String name, boolean speaks) {
|
||||||
|
mName = name;
|
||||||
|
mSpeaks = speaks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String value) {
|
||||||
|
mName = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getSpeaks() {
|
||||||
|
return mSpeaks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSpeaks(boolean value) {
|
||||||
|
mSpeaks = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Language o) {
|
||||||
|
if (this.mSpeaks && !o.mSpeaks) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!this.mSpeaks && o.mSpeaks) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return this.mName.compareToIgnoreCase(o.mName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(@NonNull Language o1, Language o2) {
|
||||||
|
if (o1.mSpeaks && !o2.mSpeaks) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!o1.mSpeaks && o2.mSpeaks) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return o1.mName.compareToIgnoreCase(o2.mName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof Language)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Language otherLanguage = (Language) obj;
|
||||||
|
if (!Objects.equals(this.mName, otherLanguage.mName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.mSpeaks != otherLanguage.mSpeaks) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
|||||||
|
package com.majinnaibu.monstercards.models;
|
||||||
|
|
||||||
|
import androidx.room.Entity;
|
||||||
|
import androidx.room.Fts4;
|
||||||
|
|
||||||
|
@Entity(tableName = "monsters_fts")
|
||||||
|
@Fts4(contentEntity = Monster.class)
|
||||||
|
public class MonsterFTS {
|
||||||
|
public String name;
|
||||||
|
public String size;
|
||||||
|
public String type;
|
||||||
|
public String subtype;
|
||||||
|
public String alignment;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.majinnaibu.monstercards.models;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
public class SearchResultItem {
|
||||||
|
public enum Type {
|
||||||
|
MONSTER,
|
||||||
|
COLLECTION
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public final Type type;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public final Monster monster;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public final Collection collection;
|
||||||
|
|
||||||
|
public SearchResultItem(@NonNull Monster monster) {
|
||||||
|
this.type = Type.MONSTER;
|
||||||
|
this.monster = monster;
|
||||||
|
this.collection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchResultItem(@NonNull Collection collection) {
|
||||||
|
this.type = Type.COLLECTION;
|
||||||
|
this.monster = null;
|
||||||
|
this.collection = collection;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package com.majinnaibu.monstercards.models;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
|
public class Skill implements Comparator<Skill>, Comparable<Skill> {
|
||||||
|
|
||||||
|
public String name;
|
||||||
|
public AbilityScore abilityScore;
|
||||||
|
public AdvantageType advantageType;
|
||||||
|
public ProficiencyType proficiencyType;
|
||||||
|
|
||||||
|
public Skill(String name, AbilityScore abilityScore) {
|
||||||
|
this(name, abilityScore, AdvantageType.NONE, ProficiencyType.PROFICIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Skill(String name, AbilityScore abilityScore, AdvantageType advantageType) {
|
||||||
|
this(name, abilityScore, advantageType, ProficiencyType.PROFICIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Skill(String name, AbilityScore abilityScore, AdvantageType advantageType, ProficiencyType proficiencyType) {
|
||||||
|
this.name = name;
|
||||||
|
this.abilityScore = abilityScore;
|
||||||
|
this.advantageType = advantageType;
|
||||||
|
this.proficiencyType = proficiencyType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbilityScore getAbilityScore() {
|
||||||
|
if (abilityScore != null) {
|
||||||
|
return abilityScore;
|
||||||
|
}
|
||||||
|
if (name != null) {
|
||||||
|
return AbilityScore.valueOfString(name);
|
||||||
|
}
|
||||||
|
return AbilityScore.STRENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSkillBonus(Monster monster) {
|
||||||
|
AbilityScore score = getAbilityScore();
|
||||||
|
int modifier = monster != null ? monster.getAbilityModifier(score) : 0;
|
||||||
|
ProficiencyType prof = proficiencyType != null ? proficiencyType : ProficiencyType.PROFICIENT;
|
||||||
|
switch (prof) {
|
||||||
|
case PROFICIENT:
|
||||||
|
return modifier + (monster != null ? monster.getProficiencyBonus() : 0);
|
||||||
|
case EXPERTISE:
|
||||||
|
return modifier + (monster != null ? monster.getProficiencyBonus() * 2 : 0);
|
||||||
|
case NONE:
|
||||||
|
default:
|
||||||
|
return modifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText(Monster monster) {
|
||||||
|
int bonus = getSkillBonus(monster);
|
||||||
|
|
||||||
|
return String.format(
|
||||||
|
"%s%s %+d%s",
|
||||||
|
name.charAt(0),
|
||||||
|
name.substring(1),
|
||||||
|
bonus,
|
||||||
|
advantageType == AdvantageType.ADVANTAGE ? " A" : advantageType == AdvantageType.DISADVANTAGE ? " D" : ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Skill o) {
|
||||||
|
return this.name.compareToIgnoreCase(o.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(Skill o1, Skill o2) {
|
||||||
|
return o1.name.compareToIgnoreCase(o2.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof Skill)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Skill otherSkill = (Skill) obj;
|
||||||
|
if (!Objects.equals(this.name, otherSkill.name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.abilityScore != otherSkill.abilityScore) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.advantageType != otherSkill.advantageType) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.proficiencyType != otherSkill.proficiencyType) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.majinnaibu.monstercards.models;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class Trait implements Comparator<Trait>, Comparable<Trait> {
|
||||||
|
|
||||||
|
public String name;
|
||||||
|
public String description;
|
||||||
|
|
||||||
|
public Trait(String name, String description) {
|
||||||
|
this.name = name;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Trait o) {
|
||||||
|
return compare(this, o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(Trait o1, Trait o2) {
|
||||||
|
int result = o1.name.compareToIgnoreCase(o2.name);
|
||||||
|
if (result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return o1.description.compareToIgnoreCase(o2.description);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof Trait)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Trait otherTrait = (Trait) obj;
|
||||||
|
if (!Objects.equals(this.name, otherTrait.name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(this.description, otherTrait.description)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
package com.majinnaibu.monstercards.ui.collections;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
import androidx.navigation.NavDirections;
|
||||||
|
import androidx.navigation.Navigation;
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
import com.majinnaibu.monstercards.R;
|
||||||
|
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||||
|
import com.majinnaibu.monstercards.models.Collection;
|
||||||
|
import com.majinnaibu.monstercards.models.DashboardMonster;
|
||||||
|
import com.majinnaibu.monstercards.models.DashboardMonsterWithMonster;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
import com.majinnaibu.monstercards.ui.dashboard.DashboardRecyclerViewAdapter;
|
||||||
|
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||||
|
import com.majinnaibu.monstercards.utils.Logger;
|
||||||
|
import com.majinnaibu.monstercards.utils.SnackbarHelper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
|
||||||
|
public class CollectionDetailFragment extends MCFragment {
|
||||||
|
|
||||||
|
private CollectionDetailViewModel mViewModel;
|
||||||
|
private UUID mCollectionId;
|
||||||
|
private DashboardRecyclerViewAdapter mAdapter;
|
||||||
|
private final CompositeDisposable mDisposables = new CompositeDisposable();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
View root = inflater.inflate(R.layout.fragment_collection_detail, container, false);
|
||||||
|
|
||||||
|
Bundle arguments = getArguments();
|
||||||
|
assert arguments != null;
|
||||||
|
mCollectionId = UUID.fromString(CollectionDetailFragmentArgs.fromBundle(arguments).getCollectionId());
|
||||||
|
|
||||||
|
mViewModel = new ViewModelProvider(this).get(CollectionDetailViewModel.class);
|
||||||
|
|
||||||
|
TextView nameView = root.findViewById(R.id.detail_collection_name);
|
||||||
|
TextView descriptionView = root.findViewById(R.id.detail_collection_description);
|
||||||
|
RecyclerView recyclerView = root.findViewById(R.id.collection_monster_list);
|
||||||
|
FloatingActionButton fab = root.findViewById(R.id.fab_add_monster_to_collection);
|
||||||
|
|
||||||
|
if (fab != null) {
|
||||||
|
fab.setOnClickListener(v -> showCollectionDetailFabOptionsDialog());
|
||||||
|
}
|
||||||
|
|
||||||
|
mViewModel.getCollection().observe(getViewLifecycleOwner(), collection -> {
|
||||||
|
if (collection != null) {
|
||||||
|
nameView.setText(collection.name);
|
||||||
|
setTitle(collection.name);
|
||||||
|
if (!TextUtils.isEmpty(collection.description)) {
|
||||||
|
descriptionView.setVisibility(View.VISIBLE);
|
||||||
|
descriptionView.setText(collection.description);
|
||||||
|
} else {
|
||||||
|
descriptionView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mViewModel.getMonsters().observe(getViewLifecycleOwner(), monsters -> {
|
||||||
|
if (mAdapter != null) {
|
||||||
|
List<DashboardMonsterWithMonster> items = new ArrayList<>();
|
||||||
|
if (monsters != null) {
|
||||||
|
for (int i = 0; i < monsters.size(); i++) {
|
||||||
|
DashboardMonster dm = new DashboardMonster(monsters.get(i).id, i);
|
||||||
|
dm.id = i + 1;
|
||||||
|
items.add(new DashboardMonsterWithMonster(dm, monsters.get(i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mAdapter.submitList(items);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setupRecyclerView(recyclerView);
|
||||||
|
loadCollectionData();
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadCollectionData() {
|
||||||
|
MonsterRepository repository = getMonsterRepository();
|
||||||
|
mDisposables.add(repository.getCollection(mCollectionId)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(collection -> {
|
||||||
|
if (collection != null) {
|
||||||
|
mViewModel.setCollection(collection);
|
||||||
|
}
|
||||||
|
}, Logger::logError));
|
||||||
|
|
||||||
|
mDisposables.add(repository.getMonstersForCollection(mCollectionId)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(monsters -> mViewModel.setMonsters(monsters), Logger::logError));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||||
|
int columnCount = Math.max(1, getResources().getConfiguration().screenWidthDp / 396);
|
||||||
|
Context context = requireContext();
|
||||||
|
GridLayoutManager layoutManager = new GridLayoutManager(context, columnCount);
|
||||||
|
recyclerView.setLayoutManager(layoutManager);
|
||||||
|
|
||||||
|
mAdapter = new DashboardRecyclerViewAdapter(item -> {
|
||||||
|
if (item != null && item.monster != null) {
|
||||||
|
navigateToMonsterDetail(item.monster.id);
|
||||||
|
} else {
|
||||||
|
Logger.logError("Can't navigate to MonsterDetailFragment with a null monster");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
recyclerView.setAdapter(mAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showCollectionDetailFabOptionsDialog() {
|
||||||
|
String[] options = new String[]{
|
||||||
|
getString(R.string.action_add_monster),
|
||||||
|
getString(R.string.action_create_monster),
|
||||||
|
getString(R.string.action_import)
|
||||||
|
};
|
||||||
|
new AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.title_collection_actions)
|
||||||
|
.setItems(options, (dialog, which) -> {
|
||||||
|
if (which == 0) {
|
||||||
|
showAddMonsterDialog();
|
||||||
|
} else if (which == 1) {
|
||||||
|
createNewMonster();
|
||||||
|
} else if (which == 2) {
|
||||||
|
showImportDialog();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.dialog_cancel, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showAddMonsterDialog() {
|
||||||
|
MonsterRepository repository = getMonsterRepository();
|
||||||
|
mDisposables.add(repository.getMonsters()
|
||||||
|
.firstOrError()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(monsters -> {
|
||||||
|
if (monsters.isEmpty()) {
|
||||||
|
View view = getView();
|
||||||
|
if (view != null) {
|
||||||
|
SnackbarHelper.showLong(view, getString(R.string.snackbar_failed_to_create_monster));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String[] monsterNames = new String[monsters.size()];
|
||||||
|
for (int i = 0; i < monsters.size(); i++) {
|
||||||
|
monsterNames[i] = monsters.get(i).name;
|
||||||
|
}
|
||||||
|
new AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.action_add_monster)
|
||||||
|
.setItems(monsterNames, (dialog, which) -> {
|
||||||
|
Monster selectedMonster = monsters.get(which);
|
||||||
|
addMonsterToCollection(selectedMonster);
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.dialog_cancel, null)
|
||||||
|
.show();
|
||||||
|
}, Logger::logError));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addMonsterToCollection(Monster monster) {
|
||||||
|
MonsterRepository repository = getMonsterRepository();
|
||||||
|
mDisposables.add(repository.addMonsterToCollection(mCollectionId, monster.id)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(() -> {
|
||||||
|
View view = getView();
|
||||||
|
if (view != null) {
|
||||||
|
Collection collection = mViewModel.getCollection().getValue();
|
||||||
|
String collectionName = collection != null ? collection.name : "";
|
||||||
|
SnackbarHelper.showLong(view, getString(R.string.snackbar_monster_added_to_collection, monster.name, collectionName));
|
||||||
|
}
|
||||||
|
}, Logger::logError));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void navigateToMonsterDetail(@NonNull UUID monsterId) {
|
||||||
|
NavDirections action = CollectionDetailFragmentDirections.actionCollectionDetailFragmentToNavigationMonster(monsterId.toString());
|
||||||
|
Navigation.findNavController(requireView()).navigate(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.collection_detail_menu, menu);
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.menu_action_add_collection_to_dashboard) {
|
||||||
|
addCollectionToDashboard();
|
||||||
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.menu_action_export_collection) {
|
||||||
|
exportCollection();
|
||||||
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.menu_action_remove_all_monsters_from_collection) {
|
||||||
|
showRemoveAllMonstersConfirmationDialog();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showRemoveAllMonstersConfirmationDialog() {
|
||||||
|
new AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.dialog_remove_all_from_collection_title)
|
||||||
|
.setMessage(R.string.dialog_remove_all_from_collection_message)
|
||||||
|
.setPositiveButton(R.string.action_remove_all, (dialog, which) -> {
|
||||||
|
mDisposables.add(getMonsterRepository().removeAllMonstersFromCollection(mCollectionId)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(() -> {
|
||||||
|
View view = getView();
|
||||||
|
if (view != null) {
|
||||||
|
SnackbarHelper.showLong(view, R.string.snackbar_collection_cleared);
|
||||||
|
}
|
||||||
|
}, throwable -> Logger.logError("Failed to remove all monsters from collection", throwable)));
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.dialog_cancel, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exportCollection() {
|
||||||
|
Collection collection = mViewModel.getCollection().getValue();
|
||||||
|
List<Monster> monsters = mViewModel.getMonsters().getValue();
|
||||||
|
if (collection != null) {
|
||||||
|
String json = new com.majinnaibu.monstercards.exporters.BinderExporter().exportBinder(
|
||||||
|
collection.name,
|
||||||
|
monsters != null ? monsters : new java.util.ArrayList<>()
|
||||||
|
);
|
||||||
|
String collectionName = (collection.name != null && !collection.name.trim().isEmpty())
|
||||||
|
? collection.name.trim()
|
||||||
|
: "collection";
|
||||||
|
exportToFile(collectionName + ".binder", json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addCollectionToDashboard() {
|
||||||
|
MonsterRepository repository = getMonsterRepository();
|
||||||
|
mDisposables.add(repository.addCollectionToDashboard(mCollectionId)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(() -> {
|
||||||
|
View view = getView();
|
||||||
|
if (view != null) {
|
||||||
|
Collection collection = mViewModel.getCollection().getValue();
|
||||||
|
String collectionName = collection != null ? collection.name : "";
|
||||||
|
SnackbarHelper.showLong(view, getString(R.string.snackbar_collection_added_to_dashboard, collectionName));
|
||||||
|
}
|
||||||
|
}, Logger::logError));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
mDisposables.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.majinnaibu.monstercards.ui.collections;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.majinnaibu.monstercards.models.Collection;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CollectionDetailViewModel extends ViewModel {
|
||||||
|
private final MutableLiveData<Collection> mCollection = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<List<Monster>> mMonsters = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
|
||||||
|
public CollectionDetailViewModel() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Collection> getCollection() {
|
||||||
|
return mCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCollection(Collection collection) {
|
||||||
|
mCollection.setValue(collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<Monster>> getMonsters() {
|
||||||
|
return mMonsters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMonsters(List<Monster> monsters) {
|
||||||
|
mMonsters.setValue(monsters);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
package com.majinnaibu.monstercards.ui.collections;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.navigation.NavDirections;
|
||||||
|
import androidx.navigation.Navigation;
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
import com.majinnaibu.monstercards.R;
|
||||||
|
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||||
|
import com.majinnaibu.monstercards.models.Collection;
|
||||||
|
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||||
|
import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback;
|
||||||
|
import com.majinnaibu.monstercards.utils.Logger;
|
||||||
|
import com.majinnaibu.monstercards.utils.SnackbarHelper;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||||
|
import io.reactivex.rxjava3.observers.DisposableCompletableObserver;
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
|
||||||
|
public class CollectionsFragment extends MCFragment {
|
||||||
|
private final CompositeDisposable mDisposables = new CompositeDisposable();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
|
@Nullable ViewGroup container,
|
||||||
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
View root = inflater.inflate(R.layout.fragment_collections, container, false);
|
||||||
|
|
||||||
|
FloatingActionButton fab = root.findViewById(R.id.fab_add_collection);
|
||||||
|
if (fab != null) {
|
||||||
|
fab.setOnClickListener(v -> showCollectionFabOptionsDialog());
|
||||||
|
}
|
||||||
|
|
||||||
|
View emptyState = root.findViewById(R.id.empty_state);
|
||||||
|
View emptyStateButton = root.findViewById(R.id.empty_state_button);
|
||||||
|
if (emptyStateButton != null) {
|
||||||
|
emptyStateButton.setOnClickListener(v -> showCollectionFabOptionsDialog());
|
||||||
|
}
|
||||||
|
|
||||||
|
RecyclerView recyclerView = root.findViewById(R.id.collection_list);
|
||||||
|
if (recyclerView != null) {
|
||||||
|
setupRecyclerView(recyclerView, emptyState);
|
||||||
|
}
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupRecyclerView(@NonNull RecyclerView recyclerView, @Nullable View emptyState) {
|
||||||
|
Context context = requireContext();
|
||||||
|
MonsterRepository repository = getMonsterRepository();
|
||||||
|
|
||||||
|
mDisposables.add(repository.getCollectionsWithCount()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(collections -> {
|
||||||
|
boolean isEmpty = (collections == null || collections.isEmpty());
|
||||||
|
if (emptyState != null) {
|
||||||
|
emptyState.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
recyclerView.setVisibility(isEmpty ? View.GONE : View.VISIBLE);
|
||||||
|
}, Logger::logError));
|
||||||
|
|
||||||
|
CollectionsRecyclerViewAdapter adapter = new CollectionsRecyclerViewAdapter(
|
||||||
|
context,
|
||||||
|
repository.getCollectionsWithCount(),
|
||||||
|
collection -> navigateToCollectionDetail(collection.id),
|
||||||
|
collection -> repository.deleteCollection(collection)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(new DisposableCompletableObserver() {
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||||
|
Logger.logError(e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
recyclerView.setAdapter(adapter);
|
||||||
|
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||||
|
recyclerView.setLayoutManager(layoutManager);
|
||||||
|
|
||||||
|
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
|
||||||
|
recyclerView.addItemDecoration(dividerItemDecoration);
|
||||||
|
|
||||||
|
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, (position, direction) -> adapter.deleteItem(position), null));
|
||||||
|
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showCollectionFabOptionsDialog() {
|
||||||
|
String[] options = new String[]{
|
||||||
|
getString(R.string.title_new_collection),
|
||||||
|
getString(R.string.action_import_collection)
|
||||||
|
};
|
||||||
|
new AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.title_collection_actions)
|
||||||
|
.setItems(options, (dialog, which) -> {
|
||||||
|
if (which == 0) {
|
||||||
|
showCreateCollectionDialog();
|
||||||
|
} else if (which == 1) {
|
||||||
|
importCollectionFromFile();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.dialog_cancel, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showCreateCollectionDialog() {
|
||||||
|
Context context = requireContext();
|
||||||
|
LinearLayout layout = new LinearLayout(context);
|
||||||
|
layout.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
int padding = (int) (16 * getResources().getDisplayMetrics().density);
|
||||||
|
layout.setPadding(padding, padding, padding, padding);
|
||||||
|
|
||||||
|
final EditText nameInput = new EditText(context);
|
||||||
|
nameInput.setHint(R.string.label_collection_name);
|
||||||
|
layout.addView(nameInput);
|
||||||
|
|
||||||
|
final EditText descInput = new EditText(context);
|
||||||
|
descInput.setHint(R.string.label_collection_description);
|
||||||
|
layout.addView(descInput);
|
||||||
|
|
||||||
|
new AlertDialog.Builder(context)
|
||||||
|
.setTitle(R.string.title_new_collection)
|
||||||
|
.setView(layout)
|
||||||
|
.setPositiveButton(R.string.dialog_create, (dialog, which) -> {
|
||||||
|
String name = nameInput.getText().toString().trim();
|
||||||
|
String description = descInput.getText().toString().trim();
|
||||||
|
if (TextUtils.isEmpty(name)) {
|
||||||
|
name = getString(R.string.title_new_collection);
|
||||||
|
}
|
||||||
|
createCollection(name, description);
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.dialog_cancel, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createCollection(@NonNull String name, @NonNull String description) {
|
||||||
|
Collection collection = new Collection(name, description);
|
||||||
|
MonsterRepository repository = getMonsterRepository();
|
||||||
|
repository.saveCollection(collection)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(new DisposableCompletableObserver() {
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
View view = getView();
|
||||||
|
if (view != null) {
|
||||||
|
SnackbarHelper.makeLong(view, getString(R.string.snackbar_collection_created, collection.name))
|
||||||
|
.setAction("View", v -> navigateToCollectionDetail(collection.id))
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||||
|
Logger.logError("Error creating collection", e);
|
||||||
|
View view = getView();
|
||||||
|
if (view != null) {
|
||||||
|
SnackbarHelper.showLong(view, getString(R.string.snackbar_failed_to_create_collection));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void navigateToCollectionDetail(@NonNull UUID collectionId) {
|
||||||
|
NavDirections action = CollectionsFragmentDirections.actionNavigationCollectionsToCollectionDetailFragment(collectionId.toString());
|
||||||
|
Navigation.findNavController(requireView()).navigate(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.collections_menu, menu);
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.menu_action_export_collections) {
|
||||||
|
exportCollections();
|
||||||
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.menu_action_remove_all_collections) {
|
||||||
|
showRemoveAllCollectionsConfirmationDialog();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exportCollections() {
|
||||||
|
mDisposables.add(getMonsterRepository().exportCollections()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(json -> exportToFile("collections.binder", json),
|
||||||
|
throwable -> Logger.logError("Failed to export collections", throwable)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showRemoveAllCollectionsConfirmationDialog() {
|
||||||
|
new AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.dialog_remove_all_collections_title)
|
||||||
|
.setMessage(R.string.dialog_remove_all_collections_message)
|
||||||
|
.setPositiveButton(R.string.action_remove_all, (dialog, which) -> {
|
||||||
|
mDisposables.add(getMonsterRepository().removeAllCollections()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(() -> {
|
||||||
|
View view = getView();
|
||||||
|
if (view != null) {
|
||||||
|
SnackbarHelper.showLong(view, R.string.snackbar_all_collections_removed);
|
||||||
|
}
|
||||||
|
}, throwable -> Logger.logError("Failed to remove all collections", throwable)));
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.dialog_cancel, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
mDisposables.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package com.majinnaibu.monstercards.ui.collections;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.majinnaibu.monstercards.R;
|
||||||
|
import com.majinnaibu.monstercards.models.Collection;
|
||||||
|
import com.majinnaibu.monstercards.models.CollectionWithCount;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.rxjava3.core.Flowable;
|
||||||
|
import io.reactivex.rxjava3.disposables.Disposable;
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
|
||||||
|
public class CollectionsRecyclerViewAdapter extends RecyclerView.Adapter<CollectionsRecyclerViewAdapter.ViewHolder> {
|
||||||
|
private final Context mContext;
|
||||||
|
private final CollectionCallback mOnClick;
|
||||||
|
private final CollectionCallback mOnDelete;
|
||||||
|
private final Flowable<List<CollectionWithCount>> mItemsObservable;
|
||||||
|
private List<CollectionWithCount> mValues;
|
||||||
|
private Disposable mDisposable;
|
||||||
|
|
||||||
|
private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(@NonNull View view) {
|
||||||
|
Collection collection = (Collection) view.getTag();
|
||||||
|
if (mOnClick != null) {
|
||||||
|
mOnClick.onCallback(collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public CollectionsRecyclerViewAdapter(Context context,
|
||||||
|
Flowable<List<CollectionWithCount>> itemsObservable,
|
||||||
|
CollectionCallback onClick,
|
||||||
|
CollectionCallback onDelete) {
|
||||||
|
mContext = context;
|
||||||
|
mItemsObservable = itemsObservable;
|
||||||
|
mOnClick = onClick;
|
||||||
|
mOnDelete = onDelete;
|
||||||
|
mValues = new ArrayList<>();
|
||||||
|
mDisposable = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
|
.inflate(R.layout.collection_list_item, parent, false);
|
||||||
|
return new ViewHolder(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||||
|
CollectionWithCount item = mValues.get(position);
|
||||||
|
Collection collection = item.collection;
|
||||||
|
holder.mNameView.setText(collection.name);
|
||||||
|
|
||||||
|
String countText;
|
||||||
|
if (item.monsterCount == 1) {
|
||||||
|
countText = mContext.getString(R.string.format_monster_count_one);
|
||||||
|
} else {
|
||||||
|
countText = mContext.getString(R.string.format_monster_count_other, item.monsterCount);
|
||||||
|
}
|
||||||
|
holder.mCountView.setText(countText);
|
||||||
|
holder.mCountView.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(collection.description)) {
|
||||||
|
holder.mDescriptionView.setVisibility(View.VISIBLE);
|
||||||
|
holder.mDescriptionView.setText(collection.description);
|
||||||
|
} else {
|
||||||
|
holder.mDescriptionView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.itemView.setTag(collection);
|
||||||
|
holder.itemView.setOnClickListener(mOnClickListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return mValues.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||||
|
super.onAttachedToRecyclerView(recyclerView);
|
||||||
|
mDisposable = mItemsObservable
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(collections -> {
|
||||||
|
mValues = collections;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||||
|
super.onDetachedFromRecyclerView(recyclerView);
|
||||||
|
if (mDisposable != null) {
|
||||||
|
mDisposable.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteItem(int position) {
|
||||||
|
if (mOnDelete != null && position >= 0 && position < mValues.size()) {
|
||||||
|
Collection collection = mValues.get(position).collection;
|
||||||
|
mOnDelete.onCallback(collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface CollectionCallback {
|
||||||
|
void onCallback(Collection collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
final TextView mNameView;
|
||||||
|
final TextView mDescriptionView;
|
||||||
|
final TextView mCountView;
|
||||||
|
|
||||||
|
ViewHolder(View view) {
|
||||||
|
super(view);
|
||||||
|
mNameView = view.findViewById(R.id.collection_name);
|
||||||
|
mDescriptionView = view.findViewById(R.id.collection_description);
|
||||||
|
mCountView = view.findViewById(R.id.collection_count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.majinnaibu.monstercards.ui.collections;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
public class CollectionsViewModel extends ViewModel {
|
||||||
|
|
||||||
|
private final MutableLiveData<String> mText;
|
||||||
|
|
||||||
|
public CollectionsViewModel() {
|
||||||
|
mText = new MutableLiveData<>();
|
||||||
|
mText.setValue("This is collections fragment");
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<String> getText() {
|
||||||
|
return mText;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package com.majinnaibu.monstercards.ui.components;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.Spinner;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.majinnaibu.monstercards.R;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||||
|
import com.majinnaibu.monstercards.helpers.ArrayHelper;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class AbilityScorePicker extends LinearLayout {
|
||||||
|
private final ViewHolder mHolder;
|
||||||
|
private OnValueChangedListener mOnValueChangedListener;
|
||||||
|
private AbilityScore mSelectedValue;
|
||||||
|
private String mLabel;
|
||||||
|
|
||||||
|
public AbilityScorePicker(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
|
||||||
|
mSelectedValue = AbilityScore.STRENGTH;
|
||||||
|
mOnValueChangedListener = null;
|
||||||
|
// TODO: use this as default but allow setting via attribute
|
||||||
|
mLabel = "Ability Score";
|
||||||
|
|
||||||
|
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AbilityScorePicker, 0, 0);
|
||||||
|
String label = a.getString(R.styleable.AbilityScorePicker_label);
|
||||||
|
if (label != null) {
|
||||||
|
mLabel = label;
|
||||||
|
}
|
||||||
|
a.recycle();
|
||||||
|
|
||||||
|
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
View root = inflater.inflate(R.layout.component_ability_score_picker, this, true);
|
||||||
|
|
||||||
|
mHolder = new ViewHolder(root);
|
||||||
|
|
||||||
|
mHolder.label.setText(mLabel);
|
||||||
|
|
||||||
|
mHolder.spinner.setAdapter(new ArrayAdapter<AbilityScore>(getContext(), R.layout.dropdown_list_item, AbilityScore.values()) {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||||
|
AbilityScore item = getItem(position);
|
||||||
|
TextView view = (TextView) super.getView(position, convertView, parent);
|
||||||
|
view.setText(item.displayName);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||||
|
AbilityScore item = getItem(position);
|
||||||
|
TextView view = (TextView) super.getDropDownView(position, convertView, parent);
|
||||||
|
view.setText(item.displayName);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mHolder.spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
setValue((AbilityScore) parent.getItemAtPosition(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNothingSelected(AdapterView<?> parent) {
|
||||||
|
setValue(mSelectedValue = AbilityScore.STRENGTH);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mHolder.spinner.setSelection(ArrayHelper.indexOf(AbilityScore.values(), mSelectedValue));
|
||||||
|
|
||||||
|
setValue(AbilityScore.STRENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbilityScorePicker(@NonNull Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbilityScore getValue() {
|
||||||
|
return mSelectedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(AbilityScore value) {
|
||||||
|
if (value != mSelectedValue) {
|
||||||
|
mSelectedValue = value;
|
||||||
|
mHolder.spinner.setSelection(ArrayHelper.indexOf(AbilityScore.values(), value));
|
||||||
|
if (mOnValueChangedListener != null) {
|
||||||
|
mOnValueChangedListener.onValueChanged(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnValueChangedListener(OnValueChangedListener listener) {
|
||||||
|
mOnValueChangedListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLabel() {
|
||||||
|
return mLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLabel(String label) {
|
||||||
|
if (!Objects.equals(mLabel, label)) {
|
||||||
|
mLabel = label;
|
||||||
|
mHolder.label.setText(label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnValueChangedListener {
|
||||||
|
void onValueChanged(AbilityScore value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ViewHolder {
|
||||||
|
|
||||||
|
private final Spinner spinner;
|
||||||
|
private final TextView label;
|
||||||
|
|
||||||
|
ViewHolder(@NonNull View root) {
|
||||||
|
spinner = root.findViewById(R.id.spinner);
|
||||||
|
label = root.findViewById(R.id.label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user