Compare commits

..

2 Commits

Author SHA1 Message Date
70d8ac8038 Set theme jekyll-theme-tactile 2021-03-25 19:56:17 -07:00
7b995f0d7b Adds docs folder and a test file for pages. 2021-03-25 19:52:30 -07:00
610 changed files with 843 additions and 63173 deletions

10
.gitignore vendored
View File

@@ -1,10 +0,0 @@
# OS / System
.DS_Store
._*
thumbs.db
# Build & Tooling
.artifacts/
.worktrees/
.build/

18
.idea/.gitignore generated vendored
View File

@@ -1,18 +0,0 @@
# 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
View File

@@ -1 +0,0 @@
MonsterCards

View File

@@ -1,6 +0,0 @@
<?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
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

18
.idea/gradle.xml generated
View File

@@ -1,18 +0,0 @@
<?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
View File

@@ -1,60 +0,0 @@
<?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>

View File

@@ -1,17 +0,0 @@
<?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>

View File

@@ -1,68 +0,0 @@
<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
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

15
Android/.gitignore vendored
View File

@@ -1,15 +0,0 @@
# 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
View File

@@ -1,18 +0,0 @@
# 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
View File

@@ -1 +0,0 @@
MonsterCards

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

View File

@@ -1,23 +0,0 @@
<?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>

View File

@@ -1,116 +0,0 @@
<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>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

View File

@@ -1,18 +0,0 @@
<?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>

View File

@@ -1,7 +0,0 @@
<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>

View File

@@ -1,30 +0,0 @@
<?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>

View File

@@ -1,10 +0,0 @@
<?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
View File

@@ -1,60 +0,0 @@
<?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>

View File

@@ -1,20 +0,0 @@
<?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>

View File

@@ -1,68 +0,0 @@
<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>

View File

@@ -1,17 +0,0 @@
<?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
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View File

@@ -1 +0,0 @@
/build

View File

@@ -1,105 +0,0 @@
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"
}

View File

@@ -1,32 +0,0 @@
# 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);
}

View File

@@ -1,440 +0,0 @@
{
"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')"
]
}
}

View File

@@ -1,499 +0,0 @@
{
"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')"
]
}
}

View File

@@ -1,483 +0,0 @@
{
"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')"
]
}
}

View File

@@ -1,554 +0,0 @@
{
"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')"
]
}
}

View File

@@ -1,609 +0,0 @@
{
"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')"
]
}
}

View File

@@ -1,663 +0,0 @@
{
"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')"
]
}
}

View File

@@ -1,768 +0,0 @@
{
"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')"
]
}
}

View File

@@ -1,768 +0,0 @@
{
"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')"
]
}
}

View File

@@ -1,73 +0,0 @@
## 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"

View File

@@ -1,26 +0,0 @@
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());
}
}

View File

@@ -1,22 +0,0 @@
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
);
}
}
}

View File

@@ -1,99 +0,0 @@
<?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>

View File

@@ -1,37 +0,0 @@
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();
}

View File

@@ -1,480 +0,0 @@
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();
}
}
}

View File

@@ -1,163 +0,0 @@
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();
}
}

View File

@@ -1,36 +0,0 @@
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);
}
}

View File

@@ -1,64 +0,0 @@
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);
}

View File

@@ -1,48 +0,0 @@
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);
}

View File

@@ -1,113 +0,0 @@
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;
}
}

View File

@@ -1,48 +0,0 @@
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"
));
}
}

View File

@@ -1,42 +0,0 @@
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();
}

View File

@@ -1,551 +0,0 @@
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;
}
}
}

View File

@@ -1,19 +0,0 @@
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);
}
}

View File

@@ -1,19 +0,0 @@
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);
}
}

View File

@@ -1,27 +0,0 @@
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);
}
}

View File

@@ -1,28 +0,0 @@
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);
}
}

View File

@@ -1,28 +0,0 @@
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);
}
}

View File

@@ -1,27 +0,0 @@
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);
}
}

View File

@@ -1,20 +0,0 @@
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);
}
}

View File

@@ -1,43 +0,0 @@
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;
}
}

View File

@@ -1,27 +0,0 @@
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;
}
}

View File

@@ -1,41 +0,0 @@
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;
}
}

View File

@@ -1,60 +0,0 @@
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;
}
}

View File

@@ -1,27 +0,0 @@
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;
}
}

View File

@@ -1,9 +0,0 @@
package com.majinnaibu.monstercards.data.enums;
public enum StringType {
CONDITION_IMMUNITY,
DAMAGE_IMMUNITY,
DAMAGE_RESISTANCE,
DAMAGE_VULNERABILITY,
SENSE
}

View File

@@ -1,10 +0,0 @@
package com.majinnaibu.monstercards.data.enums;
public enum TraitType {
ABILITY,
ACTION,
LAIR_ACTION,
LEGENDARY_ACTION,
REGIONAL_ACTION,
REACTIONS
}

View File

@@ -1,94 +0,0 @@
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);
}
}

View File

@@ -1,18 +0,0 @@
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);
}
}

View File

@@ -1,287 +0,0 @@
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;
}
}

View File

@@ -1,17 +0,0 @@
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;
}
}

View File

@@ -1,27 +0,0 @@
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);
}
}

View File

@@ -1,86 +0,0 @@
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.");
}
}

View File

@@ -1,79 +0,0 @@
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());
}
}

View File

@@ -1,43 +0,0 @@
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;
}
}

View File

@@ -1,418 +0,0 @@
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("&nbsp;", " ").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();
}
}

View File

@@ -1,16 +0,0 @@
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;
}

View File

@@ -1,230 +0,0 @@
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();
}
}

View File

@@ -1,43 +0,0 @@
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;
}
}

View File

@@ -1,92 +0,0 @@
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;
}
}

View File

@@ -1,450 +0,0 @@
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();
}
}

View File

@@ -1,169 +0,0 @@
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);
}
}

View File

@@ -1,364 +0,0 @@
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;
}
}

View File

@@ -1,49 +0,0 @@
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);
}
}
}

View File

@@ -1,55 +0,0 @@
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);
}
}

View File

@@ -1,62 +0,0 @@
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;
}
}

View File

@@ -1,22 +0,0 @@
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;
}
}

View File

@@ -1,45 +0,0 @@
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;
}
}

View File

@@ -1,25 +0,0 @@
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;
}
}

View File

@@ -1,32 +0,0 @@
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;
}
}

View File

@@ -1,74 +0,0 @@
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;
}
}

View File

@@ -1,14 +0,0 @@
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;
}

View File

@@ -1,32 +0,0 @@
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;
}
}

View File

@@ -1,107 +0,0 @@
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;
}
}

View File

@@ -1,49 +0,0 @@
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;
}
}

View File

@@ -1,282 +0,0 @@
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();
}
}

View File

@@ -1,36 +0,0 @@
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);
}
}

View File

@@ -1,247 +0,0 @@
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();
}
}

View File

@@ -1,137 +0,0 @@
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);
}
}
}

View File

@@ -1,19 +0,0 @@
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;
}
}

View File

@@ -1,133 +0,0 @@
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