Cleans up import and export options.
This commit is contained in:
17
.idea/runConfigurations.xml
generated
Normal file
17
.idea/runConfigurations.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RunConfigurationProducerService">
|
||||||
|
<option name="ignoredProducers">
|
||||||
|
<set>
|
||||||
|
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -28,6 +28,8 @@ import com.majinnaibu.monstercards.helpers.StringHelper;
|
|||||||
import com.majinnaibu.monstercards.importers.BinderImporter;
|
import com.majinnaibu.monstercards.importers.BinderImporter;
|
||||||
import com.majinnaibu.monstercards.importers.DnDBeyondImporter;
|
import com.majinnaibu.monstercards.importers.DnDBeyondImporter;
|
||||||
import com.majinnaibu.monstercards.init.AppCenterInitializer;
|
import com.majinnaibu.monstercards.init.AppCenterInitializer;
|
||||||
|
import com.majinnaibu.monstercards.models.BinderExport;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
import com.majinnaibu.monstercards.utils.Logger;
|
import com.majinnaibu.monstercards.utils.Logger;
|
||||||
import com.majinnaibu.monstercards.utils.ToastHelper;
|
import com.majinnaibu.monstercards.utils.ToastHelper;
|
||||||
|
|
||||||
@@ -35,6 +37,8 @@ import java.io.BufferedReader;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@@ -131,46 +135,161 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Uri intentUri = getUriFromIntent(intent);
|
||||||
|
String fileName = intentUri != null ? getFileNameFromUri(intentUri) : null;
|
||||||
String json = readMonsterJSONFromIntent(intent);
|
String json = readMonsterJSONFromIntent(intent);
|
||||||
if (!StringHelper.isNullOrEmpty(json)) {
|
if (!StringHelper.isNullOrEmpty(json)) {
|
||||||
importMonsterFromInputAndNavigate(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) {
|
public void importMonsterFromInputAndNavigate(@NonNull String input) {
|
||||||
BinderImporter binderImporter = new BinderImporter();
|
importMonsterFromInputAndNavigate(input, null);
|
||||||
if (binderImporter.canImport(input)) {
|
}
|
||||||
|
|
||||||
|
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);
|
ToastHelper.showShort(this, R.string.toast_importing_url);
|
||||||
Single.fromCallable(() -> binderImporter.parse(input))
|
Single.fromCallable(() -> MonsterImportHelper.fromJSON(input, fileName))
|
||||||
.flatMapCompletable(binder -> ((MonsterCardsApplication) getApplication()).getMonsterRepository().importBinder(binder))
|
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(() -> {
|
.subscribe(monster -> {
|
||||||
ToastHelper.showLong(this, R.string.snackbar_import_binder_success);
|
String serializedJson = new Gson().toJson(monster);
|
||||||
NavHostFragment navHostFragment = Objects.requireNonNull((NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment));
|
NavHostFragment navHostFragment = Objects.requireNonNull((NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment));
|
||||||
NavController navController = navHostFragment.getNavController();
|
NavController navController = navHostFragment.getNavController();
|
||||||
navController.navigate(R.id.navigation_library);
|
NavDirections navAction = MobileNavigationDirections.actionGlobalMonsterImportFragment(serializedJson);
|
||||||
|
navController.navigate(navAction);
|
||||||
}, throwable -> {
|
}, throwable -> {
|
||||||
Logger.logError("Failed to import binder from input", throwable);
|
Logger.logError("Failed to import monster from input: " + fileName, throwable);
|
||||||
ToastHelper.showLong(this, R.string.failed_to_import_url);
|
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 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ToastHelper.showShort(this, R.string.toast_importing_url);
|
ToastHelper.showShort(this, R.string.toast_importing_url);
|
||||||
Single.fromCallable(() -> MonsterImportHelper.fromJSON(input))
|
Single.fromCallable(() -> {
|
||||||
.subscribeOn(Schedulers.io())
|
int monstersImported = 0;
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
int bindersImported = 0;
|
||||||
.subscribe(monster -> {
|
|
||||||
String serializedJson = new Gson().toJson(monster);
|
for (Uri uri : uris) {
|
||||||
NavHostFragment navHostFragment = Objects.requireNonNull((NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment));
|
try {
|
||||||
NavController navController = navHostFragment.getNavController();
|
String fileName = getFileNameFromUri(uri);
|
||||||
NavDirections navAction = MobileNavigationDirections.actionGlobalMonsterImportFragment(serializedJson);
|
String content = readContentsOfUri(uri);
|
||||||
navController.navigate(navAction);
|
if (content == null || content.trim().isEmpty()) {
|
||||||
}, throwable -> {
|
continue;
|
||||||
Logger.logError("Failed to import monster from input", throwable);
|
}
|
||||||
ToastHelper.showLong(this, R.string.failed_to_import_url);
|
|
||||||
});
|
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
|
@Nullable
|
||||||
|
|||||||
@@ -47,9 +47,18 @@ public interface CollectionDAO {
|
|||||||
@Query("DELETE FROM collection_monsters WHERE collection_id = :collectionId AND monster_id = :monsterId")
|
@Query("DELETE FROM collection_monsters WHERE collection_id = :collectionId AND monster_id = :monsterId")
|
||||||
Completable removeMonsterFromCollection(String collectionId, String 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")
|
@Query("DELETE FROM collection_monsters WHERE id = :id")
|
||||||
Completable removeCollectionMonsterById(long id);
|
Completable removeCollectionMonsterById(long id);
|
||||||
|
|
||||||
|
@Query("DELETE FROM collection_monsters")
|
||||||
|
Completable deleteAllCollectionMonsters();
|
||||||
|
|
||||||
|
@Query("DELETE FROM collections")
|
||||||
|
Completable deleteAllCollections();
|
||||||
|
|
||||||
@Update
|
@Update
|
||||||
Completable updateCollectionMonsters(List<CollectionMonster> collectionMonsters);
|
Completable updateCollectionMonsters(List<CollectionMonster> collectionMonsters);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,4 +36,7 @@ public interface MonsterDAO {
|
|||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
Completable delete(Monster monster);
|
Completable delete(Monster monster);
|
||||||
|
|
||||||
|
@Query("DELETE FROM monsters")
|
||||||
|
Completable deleteAllMonsters();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,18 +9,22 @@ import com.majinnaibu.monstercards.models.Collection;
|
|||||||
import com.majinnaibu.monstercards.models.CollectionMonster;
|
import com.majinnaibu.monstercards.models.CollectionMonster;
|
||||||
import com.majinnaibu.monstercards.models.CollectionWithCount;
|
import com.majinnaibu.monstercards.models.CollectionWithCount;
|
||||||
import com.majinnaibu.monstercards.models.DashboardMonster;
|
import com.majinnaibu.monstercards.models.DashboardMonster;
|
||||||
|
import com.majinnaibu.monstercards.models.DashboardMonsterWithMonster;
|
||||||
import com.majinnaibu.monstercards.models.Monster;
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
import com.majinnaibu.monstercards.models.SearchResultItem;
|
import com.majinnaibu.monstercards.models.SearchResultItem;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.rxjava3.core.Completable;
|
import io.reactivex.rxjava3.core.Completable;
|
||||||
import io.reactivex.rxjava3.core.Flowable;
|
import io.reactivex.rxjava3.core.Flowable;
|
||||||
|
import io.reactivex.rxjava3.core.Single;
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
|
||||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
@@ -264,6 +268,91 @@ public class MonsterRepository {
|
|||||||
return result;
|
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) {
|
public Completable updateDashboardMonsters(List<DashboardMonster> items) {
|
||||||
Completable result = m_db.dashboardDAO().updateDashboardMonsters(items);
|
Completable result = m_db.dashboardDAO().updateDashboardMonsters(items);
|
||||||
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
@@ -272,65 +361,164 @@ public class MonsterRepository {
|
|||||||
|
|
||||||
public Completable importBinder(@NonNull BinderExport binder) {
|
public Completable importBinder(@NonNull BinderExport binder) {
|
||||||
return Completable.fromAction(() -> {
|
return Completable.fromAction(() -> {
|
||||||
if (binder.collections == null || binder.collections.isEmpty()) {
|
// 1. Process Collections & Cards in Binder
|
||||||
return;
|
if (binder.collections != null && !binder.collections.isEmpty()) {
|
||||||
}
|
List<Collection> existingCols = m_db.collectionDAO().getAll().first(new ArrayList<>()).blockingGet();
|
||||||
|
|
||||||
for (BinderExport.CollectionExport colExport : binder.collections) {
|
for (BinderExport.CollectionExport colExport : binder.collections) {
|
||||||
if (colExport.cards == null || colExport.cards.isEmpty()) {
|
if (colExport == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Monster> monstersToSave = new ArrayList<>();
|
String colName = colExport.name != null ? colExport.name.trim() : "";
|
||||||
for (Monster card : colExport.cards) {
|
String colDesc = colExport.description != null ? colExport.description.trim() : "";
|
||||||
if (card.id == null) {
|
|
||||||
card.id = UUID.randomUUID();
|
UUID colExportId = null;
|
||||||
|
if (colExport.id != null && !colExport.id.trim().isEmpty()) {
|
||||||
|
try {
|
||||||
|
colExportId = UUID.fromString(colExport.id.trim());
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
monstersToSave.add(card);
|
|
||||||
}
|
|
||||||
m_db.monsterDAO().save(monstersToSave.toArray(new Monster[0])).blockingAwait();
|
|
||||||
|
|
||||||
String colName = colExport.name != null ? colExport.name.trim() : "";
|
|
||||||
if (!colName.isEmpty()) {
|
|
||||||
List<Collection> existingCols = m_db.collectionDAO().getAll().first(new ArrayList<>()).blockingGet();
|
|
||||||
Collection targetCollection = null;
|
Collection targetCollection = null;
|
||||||
for (Collection existing : existingCols) {
|
if (colExportId != null) {
|
||||||
if (existing.name != null && existing.name.equalsIgnoreCase(colName)) {
|
for (Collection existing : existingCols) {
|
||||||
targetCollection = existing;
|
if (existing.id.equals(colExportId)) {
|
||||||
break;
|
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;
|
UUID targetCollectionId;
|
||||||
if (targetCollection != null) {
|
if (targetCollection != null) {
|
||||||
targetCollectionId = targetCollection.id;
|
targetCollectionId = targetCollection.id;
|
||||||
|
if (!colDesc.isEmpty() && (targetCollection.description == null || targetCollection.description.isEmpty())) {
|
||||||
|
targetCollection.description = colDesc;
|
||||||
|
m_db.collectionDAO().save(targetCollection).blockingAwait();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Collection newCol = new Collection();
|
Collection newCol = new Collection();
|
||||||
newCol.id = UUID.randomUUID();
|
newCol.id = colExportId != null ? colExportId : UUID.randomUUID();
|
||||||
newCol.name = colName;
|
newCol.name = !colName.isEmpty() ? colName : "Imported Collection";
|
||||||
|
newCol.description = colDesc;
|
||||||
m_db.collectionDAO().save(newCol).blockingAwait();
|
m_db.collectionDAO().save(newCol).blockingAwait();
|
||||||
targetCollectionId = newCol.id;
|
targetCollectionId = newCol.id;
|
||||||
|
existingCols.add(newCol);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Monster> colMonsters = m_db.collectionDAO().getMonstersForCollection(targetCollectionId.toString())
|
if (colExport.cards != null && !colExport.cards.isEmpty()) {
|
||||||
.first(new ArrayList<>()).blockingGet();
|
List<Monster> monstersToSave = new ArrayList<>();
|
||||||
Set<UUID> existingMonsterIds = new HashSet<>();
|
Map<UUID, Integer> importedCardCounts = new HashMap<>();
|
||||||
for (Monster m : colMonsters) {
|
|
||||||
existingMonsterIds.add(m.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ordinal = colMonsters.size();
|
for (Monster card : colExport.cards) {
|
||||||
for (Monster monster : monstersToSave) {
|
if (card != null) {
|
||||||
if (!existingMonsterIds.contains(monster.id)) {
|
if (card.id == null) {
|
||||||
m_db.collectionDAO().addMonsterToCollection(new CollectionMonster(targetCollectionId, monster.id, ordinal++)).blockingAwait();
|
card.id = UUID.randomUUID();
|
||||||
existingMonsterIds.add(monster.id);
|
}
|
||||||
|
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());
|
}).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 {
|
private static class Helpers {
|
||||||
static boolean monsterMatchesSearch(Monster monster, String searchText) {
|
static boolean monsterMatchesSearch(Monster monster, String searchText) {
|
||||||
if (StringHelper.isNullOrEmpty(searchText)) {
|
if (StringHelper.isNullOrEmpty(searchText)) {
|
||||||
|
|||||||
@@ -21,11 +21,23 @@ public enum AbilityScore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static AbilityScore valueOfString(String string) {
|
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()) {
|
for (AbilityScore abilityScore : values()) {
|
||||||
if (abilityScore.stringValue.equals(string)) {
|
if (abilityScore.stringValue.equalsIgnoreCase(lower)
|
||||||
|
|| abilityScore.shortDisplayName.equalsIgnoreCase(lower)
|
||||||
|
|| abilityScore.name().equalsIgnoreCase(lower)) {
|
||||||
return abilityScore;
|
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;
|
return AbilityScore.STRENGTH;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,94 @@
|
|||||||
package com.majinnaibu.monstercards.exporters;
|
package com.majinnaibu.monstercards.exporters;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.majinnaibu.monstercards.models.BinderExport;
|
import com.majinnaibu.monstercards.models.BinderExport;
|
||||||
|
import com.majinnaibu.monstercards.models.Collection;
|
||||||
import com.majinnaibu.monstercards.models.Monster;
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class BinderExporter {
|
public class BinderExporter {
|
||||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public String exportBinder(String collectionName, List<Monster> monsters) {
|
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();
|
BinderExport export = new BinderExport();
|
||||||
|
export.schema = "https://majinnaibu.com/schemas/binder.schema.json";
|
||||||
export.schemaVersion = 1;
|
export.schemaVersion = 1;
|
||||||
|
|
||||||
if (monsters != null) {
|
if (monsters != null) {
|
||||||
for (Monster monster : monsters) {
|
for (Monster monster : monsters) {
|
||||||
|
monster.schema = "https://majinnaibu.com/schemas/monster-card.schema.json";
|
||||||
monster.schemaVersion = 1;
|
monster.schemaVersion = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BinderExport.CollectionExport col = new BinderExport.CollectionExport(
|
if (dashboardMonsters != null && !dashboardMonsters.isEmpty()) {
|
||||||
collectionName != null ? collectionName : "",
|
for (Monster monster : dashboardMonsters) {
|
||||||
monsters
|
monster.schema = "https://majinnaibu.com/schemas/monster-card.schema.json";
|
||||||
);
|
monster.schemaVersion = 1;
|
||||||
export.collections = Collections.singletonList(col);
|
}
|
||||||
|
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);
|
return GSON.toJson(export);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ public class MonsterCardExporter {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public String exportCard(@NonNull Monster monster) {
|
public String exportCard(@NonNull Monster monster) {
|
||||||
|
monster.schema = "https://majinnaibu.com/schemas/monster-card.schema.json";
|
||||||
monster.schemaVersion = 1;
|
monster.schemaVersion = 1;
|
||||||
return GSON.toJson(monster);
|
return GSON.toJson(monster);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,85 @@
|
|||||||
package com.majinnaibu.monstercards.helpers;
|
package com.majinnaibu.monstercards.helpers;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
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.DnDBeyondImporter;
|
||||||
import com.majinnaibu.monstercards.importers.EntityImporter;
|
import com.majinnaibu.monstercards.importers.EntityImporter;
|
||||||
import com.majinnaibu.monstercards.importers.MonsterJsonImporter;
|
import com.majinnaibu.monstercards.importers.MonsterJsonImporter;
|
||||||
import com.majinnaibu.monstercards.importers.Open5eImporter;
|
import com.majinnaibu.monstercards.importers.Open5eImporter;
|
||||||
import com.majinnaibu.monstercards.importers.TetraCubeMonsterImporter;
|
import com.majinnaibu.monstercards.importers.TetraCubeMonsterImporter;
|
||||||
import com.majinnaibu.monstercards.models.Monster;
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
import com.majinnaibu.monstercards.utils.Logger;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
public class MonsterImportHelper {
|
public class MonsterImportHelper {
|
||||||
private static final List<EntityImporter<Monster>> IMPORTERS = new ArrayList<>();
|
private static final List<EntityImporter<Monster>> IMPORTERS = new ArrayList<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
IMPORTERS.add(new TetraCubeMonsterImporter());
|
||||||
IMPORTERS.add(new MonsterJsonImporter());
|
IMPORTERS.add(new MonsterJsonImporter());
|
||||||
IMPORTERS.add(new Open5eImporter());
|
IMPORTERS.add(new Open5eImporter());
|
||||||
IMPORTERS.add(new TetraCubeMonsterImporter());
|
|
||||||
IMPORTERS.add(new DnDBeyondImporter());
|
IMPORTERS.add(new DnDBeyondImporter());
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static Monster fromJSON(String json) {
|
public static Monster fromJSON(String json) {
|
||||||
if (json != null) {
|
return fromJSON(json, null);
|
||||||
for (EntityImporter<Monster> importer : IMPORTERS) {
|
}
|
||||||
if (importer.canImport(json)) {
|
|
||||||
try {
|
@NonNull
|
||||||
return importer.parse(json);
|
public static Monster fromJSON(String json, @Nullable String fileName) {
|
||||||
} catch (Exception e) {
|
if (json == null || json.trim().isEmpty()) {
|
||||||
throw new IllegalArgumentException("Failed to parse monster JSON", e);
|
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.");
|
throw new IllegalArgumentException("No importer found capable of parsing the given JSON payload.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ public class BinderImporter implements EntityImporter<BinderExport> {
|
|||||||
JsonElement el = JsonParser.parseString(input);
|
JsonElement el = JsonParser.parseString(input);
|
||||||
if (el.isJsonObject()) {
|
if (el.isJsonObject()) {
|
||||||
JsonObject obj = el.getAsJsonObject();
|
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();
|
return obj.has("collections") && obj.get("collections").isJsonArray();
|
||||||
}
|
}
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
|
|||||||
@@ -18,7 +18,13 @@ public class MonsterJsonImporter implements EntityImporter<Monster> {
|
|||||||
JsonElement el = JsonParser.parseString(input);
|
JsonElement el = JsonParser.parseString(input);
|
||||||
if (el.isJsonObject()) {
|
if (el.isJsonObject()) {
|
||||||
JsonObject obj = el.getAsJsonObject();
|
JsonObject obj = el.getAsJsonObject();
|
||||||
return obj.has("name") && (obj.has("strengthScore") || obj.has("hitDice") || obj.has("size"));
|
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) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ public class TetraCubeMonsterImporter implements EntityImporter<Monster> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
JsonObject rootDict = element.getAsJsonObject();
|
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")
|
return rootDict.has("armorName") || rootDict.has("strPoints")
|
||||||
|| rootDict.has("isLegendary") || rootDict.has("isMythic")
|
|| rootDict.has("isLegendary") || rootDict.has("isMythic")
|
||||||
|| rootDict.has("legendariesDescription");
|
|| rootDict.has("legendariesDescription");
|
||||||
|
|||||||
@@ -4,27 +4,46 @@ import com.google.gson.annotations.SerializedName;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class BinderExport {
|
public class BinderExport {
|
||||||
|
@SerializedName("$schema")
|
||||||
|
public String schema = "https://majinnaibu.com/schemas/binder.schema.json";
|
||||||
|
|
||||||
@SerializedName("schemaVersion")
|
@SerializedName("schemaVersion")
|
||||||
public int schemaVersion = 1;
|
public int schemaVersion = 1;
|
||||||
|
|
||||||
@SerializedName("collections")
|
@SerializedName("collections")
|
||||||
public List<CollectionExport> collections = new ArrayList<>();
|
public List<CollectionExport> collections = new ArrayList<>();
|
||||||
|
|
||||||
|
@SerializedName("dashboard")
|
||||||
|
public List<Monster> dashboard = new ArrayList<>();
|
||||||
|
|
||||||
public static class CollectionExport {
|
public static class CollectionExport {
|
||||||
|
@SerializedName("id")
|
||||||
|
public String id;
|
||||||
|
|
||||||
@SerializedName("name")
|
@SerializedName("name")
|
||||||
public String name;
|
public String name;
|
||||||
|
|
||||||
|
@SerializedName("description")
|
||||||
|
public String description;
|
||||||
|
|
||||||
@SerializedName("cards")
|
@SerializedName("cards")
|
||||||
public List<Monster> cards = new ArrayList<>();
|
public List<Monster> cards = new ArrayList<>();
|
||||||
|
|
||||||
public CollectionExport() {
|
public CollectionExport() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public CollectionExport(String name, List<Monster> cards) {
|
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.name = name != null ? name : "";
|
||||||
|
this.description = description != null ? description : "";
|
||||||
this.cards = cards != null ? cards : new ArrayList<>();
|
this.cards = cards != null ? cards : new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CollectionExport(String name, List<Monster> cards) {
|
||||||
|
this(UUID.randomUUID().toString(), name, "", cards);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import androidx.room.Entity;
|
|||||||
import androidx.room.Ignore;
|
import androidx.room.Ignore;
|
||||||
import androidx.room.PrimaryKey;
|
import androidx.room.PrimaryKey;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||||
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
||||||
import com.majinnaibu.monstercards.data.enums.ArmorType;
|
import com.majinnaibu.monstercards.data.enums.ArmorType;
|
||||||
@@ -30,11 +31,16 @@ import java.util.UUID;
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class Monster {
|
public class Monster {
|
||||||
|
|
||||||
|
@SerializedName("$schema")
|
||||||
|
@Ignore
|
||||||
|
public String schema = "https://majinnaibu.com/schemas/monster-card.schema.json";
|
||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
public int schemaVersion = 1;
|
public int schemaVersion = 1;
|
||||||
|
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@NonNull
|
@NonNull
|
||||||
|
@SerializedName("id")
|
||||||
public UUID id;
|
public UUID id;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -438,7 +444,10 @@ public class Monster {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAbilityModifier(@NonNull AbilityScore abilityScore) {
|
public int getAbilityModifier(@Nullable AbilityScore abilityScore) {
|
||||||
|
if (abilityScore == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
switch (abilityScore) {
|
switch (abilityScore) {
|
||||||
case STRENGTH:
|
case STRENGTH:
|
||||||
return getStrengthModifier();
|
return getStrengthModifier();
|
||||||
|
|||||||
@@ -34,13 +34,25 @@ public class Skill implements Comparator<Skill>, Comparable<Skill> {
|
|||||||
this.proficiencyType = proficiencyType;
|
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) {
|
public int getSkillBonus(Monster monster) {
|
||||||
int modifier = monster.getAbilityModifier(abilityScore);
|
AbilityScore score = getAbilityScore();
|
||||||
switch (proficiencyType) {
|
int modifier = monster != null ? monster.getAbilityModifier(score) : 0;
|
||||||
|
ProficiencyType prof = proficiencyType != null ? proficiencyType : ProficiencyType.PROFICIENT;
|
||||||
|
switch (prof) {
|
||||||
case PROFICIENT:
|
case PROFICIENT:
|
||||||
return modifier + monster.getProficiencyBonus();
|
return modifier + (monster != null ? monster.getProficiencyBonus() : 0);
|
||||||
case EXPERTISE:
|
case EXPERTISE:
|
||||||
return modifier + monster.getProficiencyBonus() * 2;
|
return modifier + (monster != null ? monster.getProficiencyBonus() * 2 : 0);
|
||||||
case NONE:
|
case NONE:
|
||||||
default:
|
default:
|
||||||
return modifier;
|
return modifier;
|
||||||
|
|||||||
@@ -221,10 +221,32 @@ public class CollectionDetailFragment extends MCFragment {
|
|||||||
} else if (item.getItemId() == R.id.menu_action_export_collection) {
|
} else if (item.getItemId() == R.id.menu_action_export_collection) {
|
||||||
exportCollection();
|
exportCollection();
|
||||||
return true;
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.menu_action_remove_all_monsters_from_collection) {
|
||||||
|
showRemoveAllMonstersConfirmationDialog();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
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() {
|
private void exportCollection() {
|
||||||
Collection collection = mViewModel.getCollection().getValue();
|
Collection collection = mViewModel.getCollection().getValue();
|
||||||
List<Monster> monsters = mViewModel.getMonsters().getValue();
|
List<Monster> monsters = mViewModel.getMonsters().getValue();
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import android.content.Context;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
@@ -43,6 +46,7 @@ public class CollectionsFragment extends MCFragment {
|
|||||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
@Nullable ViewGroup container,
|
@Nullable ViewGroup container,
|
||||||
@Nullable Bundle savedInstanceState) {
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
setHasOptionsMenu(true);
|
||||||
View root = inflater.inflate(R.layout.fragment_collections, container, false);
|
View root = inflater.inflate(R.layout.fragment_collections, container, false);
|
||||||
|
|
||||||
FloatingActionButton fab = root.findViewById(R.id.fab_add_collection);
|
FloatingActionButton fab = root.findViewById(R.id.fab_add_collection);
|
||||||
@@ -190,6 +194,51 @@ public class CollectionsFragment extends MCFragment {
|
|||||||
Navigation.findNavController(requireView()).navigate(action);
|
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
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
|||||||
import com.majinnaibu.monstercards.R;
|
import com.majinnaibu.monstercards.R;
|
||||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||||
import com.majinnaibu.monstercards.models.Collection;
|
import com.majinnaibu.monstercards.models.Collection;
|
||||||
|
import com.majinnaibu.monstercards.models.CollectionWithCount;
|
||||||
import com.majinnaibu.monstercards.models.Monster;
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||||
import com.majinnaibu.monstercards.utils.Logger;
|
import com.majinnaibu.monstercards.utils.Logger;
|
||||||
@@ -265,7 +266,7 @@ public class DashboardFragment extends MCFragment {
|
|||||||
|
|
||||||
private void showAddCollectionPicker() {
|
private void showAddCollectionPicker() {
|
||||||
MonsterRepository repository = getMonsterRepository();
|
MonsterRepository repository = getMonsterRepository();
|
||||||
mDisposables.add(repository.getCollections()
|
mDisposables.add(repository.getCollectionsWithCount()
|
||||||
.firstOrError()
|
.firstOrError()
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
@@ -279,12 +280,16 @@ public class DashboardFragment extends MCFragment {
|
|||||||
}
|
}
|
||||||
String[] names = new String[collections.size()];
|
String[] names = new String[collections.size()];
|
||||||
for (int i = 0; i < collections.size(); i++) {
|
for (int i = 0; i < collections.size(); i++) {
|
||||||
names[i] = collections.get(i).name;
|
CollectionWithCount item = collections.get(i);
|
||||||
|
int count = item.monsterCount;
|
||||||
|
String countText = count == 1 ? "1 monster" : count + " monsters";
|
||||||
|
String colName = item.collection != null && item.collection.name != null ? item.collection.name : "";
|
||||||
|
names[i] = colName + " (" + countText + ")";
|
||||||
}
|
}
|
||||||
new AlertDialog.Builder(requireContext())
|
new AlertDialog.Builder(requireContext())
|
||||||
.setTitle(R.string.action_add_collection_option)
|
.setTitle(R.string.action_add_collection_option)
|
||||||
.setItems(names, (dialog, which) -> {
|
.setItems(names, (dialog, which) -> {
|
||||||
Collection selected = collections.get(which);
|
Collection selected = collections.get(which).collection;
|
||||||
addCollectionToDashboard(selected);
|
addCollectionToDashboard(selected);
|
||||||
})
|
})
|
||||||
.setNegativeButton(R.string.dialog_cancel, null)
|
.setNegativeButton(R.string.dialog_cancel, null)
|
||||||
@@ -365,9 +370,6 @@ public class DashboardFragment extends MCFragment {
|
|||||||
} else if (item.getItemId() == R.id.menu_action_add_collection_option) {
|
} else if (item.getItemId() == R.id.menu_action_add_collection_option) {
|
||||||
showAddCollectionPicker();
|
showAddCollectionPicker();
|
||||||
return true;
|
return true;
|
||||||
} else if (item.getItemId() == R.id.menu_action_remove_single_monster) {
|
|
||||||
showRemoveMonsterPicker();
|
|
||||||
return true;
|
|
||||||
} else if (item.getItemId() == R.id.menu_action_clear_dashboard) {
|
} else if (item.getItemId() == R.id.menu_action_clear_dashboard) {
|
||||||
clearDashboard();
|
clearDashboard();
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -85,10 +85,35 @@ public class LibraryFragment extends MCFragment {
|
|||||||
} else if (item.getItemId() == R.id.menu_action_export_library) {
|
} else if (item.getItemId() == R.id.menu_action_export_library) {
|
||||||
exportLibrary();
|
exportLibrary();
|
||||||
return true;
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.menu_action_export_everything) {
|
||||||
|
exportEverything();
|
||||||
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.menu_action_clear_all_data) {
|
||||||
|
showClearAllDataConfirmationDialog();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showClearAllDataConfirmationDialog() {
|
||||||
|
new AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.dialog_clear_all_data_title)
|
||||||
|
.setMessage(R.string.dialog_clear_all_data_message)
|
||||||
|
.setPositiveButton(R.string.action_clear, (dialog, which) -> {
|
||||||
|
mDisposables.add(getMonsterRepository().clearAllData()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(() -> {
|
||||||
|
View view = getView();
|
||||||
|
if (view != null) {
|
||||||
|
SnackbarHelper.showLong(view, R.string.snackbar_all_data_cleared);
|
||||||
|
}
|
||||||
|
}, throwable -> Logger.logError("Failed to clear all data", throwable)));
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.dialog_cancel, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
private void exportLibrary() {
|
private void exportLibrary() {
|
||||||
getMonsterRepository().getMonsters()
|
getMonsterRepository().getMonsters()
|
||||||
.firstOrError()
|
.firstOrError()
|
||||||
@@ -101,6 +126,16 @@ public class LibraryFragment extends MCFragment {
|
|||||||
}, Logger::logError);
|
}, Logger::logError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void exportEverything() {
|
||||||
|
mDisposables.add(getMonsterRepository().exportEverything()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(json -> {
|
||||||
|
String fileName = getString(R.string.default_filename_export_everything) + ".binder";
|
||||||
|
exportToFile(fileName, json);
|
||||||
|
}, throwable -> Logger.logError("Failed to export everything", throwable)));
|
||||||
|
}
|
||||||
|
|
||||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView, @Nullable View emptyState) {
|
private void setupRecyclerView(@NonNull RecyclerView recyclerView, @Nullable View emptyState) {
|
||||||
Context context = requireContext();
|
Context context = requireContext();
|
||||||
MonsterRepository repository = this.getMonsterRepository();
|
MonsterRepository repository = this.getMonsterRepository();
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ import java.io.InputStream;
|
|||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@@ -71,17 +73,22 @@ public class MCFragment extends Fragment {
|
|||||||
new ActivityResultContracts.StartActivityForResult(),
|
new ActivityResultContracts.StartActivityForResult(),
|
||||||
result -> {
|
result -> {
|
||||||
if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
|
if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
|
||||||
Uri uri = result.getData().getData();
|
Intent data = result.getData();
|
||||||
if (uri != null && getActivity() instanceof MainActivity) {
|
List<Uri> uris = new ArrayList<>();
|
||||||
String content = readContentsFromUri(uri);
|
if (data.getClipData() != null) {
|
||||||
if (content != null && !content.trim().isEmpty()) {
|
ClipData clipData = data.getClipData();
|
||||||
((MainActivity) getActivity()).importMonsterFromInputAndNavigate(content);
|
for (int i = 0; i < clipData.getItemCount(); i++) {
|
||||||
} else {
|
Uri uri = clipData.getItemAt(i).getUri();
|
||||||
View view = getView();
|
if (uri != null) {
|
||||||
if (view != null) {
|
uris.add(uri);
|
||||||
SnackbarHelper.showLong(view, R.string.failed_to_import_url);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (data.getData() != null) {
|
||||||
|
uris.add(data.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!uris.isEmpty() && getActivity() instanceof MainActivity) {
|
||||||
|
((MainActivity) getActivity()).importMultipleFilesFromUris(uris);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -91,6 +98,7 @@ public class MCFragment extends Fragment {
|
|||||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
intent.setType("*/*");
|
intent.setType("*/*");
|
||||||
|
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
|
||||||
try {
|
try {
|
||||||
mOpenDocumentLauncher.launch(intent);
|
mOpenDocumentLauncher.launch(intent);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@@ -11,4 +11,9 @@
|
|||||||
android:id="@+id/menu_action_export_collection"
|
android:id="@+id/menu_action_export_collection"
|
||||||
android:title="@string/action_export_collection"
|
android:title="@string/action_export_collection"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_action_remove_all_monsters_from_collection"
|
||||||
|
android:title="@string/action_remove_all"
|
||||||
|
app:showAsAction="never" />
|
||||||
</menu>
|
</menu>
|
||||||
|
|||||||
14
Android/app/src/main/res/menu/collections_menu.xml
Normal file
14
Android/app/src/main/res/menu/collections_menu.xml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_action_export_collections"
|
||||||
|
android:title="@string/action_export"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_action_remove_all_collections"
|
||||||
|
android:title="@string/action_remove_all"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
</menu>
|
||||||
@@ -12,11 +12,6 @@
|
|||||||
android:title="@string/action_add_collection_option"
|
android:title="@string/action_add_collection_option"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/menu_action_remove_single_monster"
|
|
||||||
android:title="@string/action_remove_single_monster"
|
|
||||||
app:showAsAction="never" />
|
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/menu_action_clear_dashboard"
|
android:id="@+id/menu_action_clear_dashboard"
|
||||||
android:title="@string/action_clear_dashboard"
|
android:title="@string/action_clear_dashboard"
|
||||||
|
|||||||
@@ -16,4 +16,14 @@
|
|||||||
android:id="@+id/menu_action_export_library"
|
android:id="@+id/menu_action_export_library"
|
||||||
android:title="@string/action_export_library"
|
android:title="@string/action_export_library"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_action_export_everything"
|
||||||
|
android:title="@string/action_export_everything"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_action_clear_all_data"
|
||||||
|
android:title="@string/action_clear_all_data"
|
||||||
|
app:showAsAction="never" />
|
||||||
</menu>
|
</menu>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<string name="action_add_skill">Add Skill</string>
|
<string name="action_add_skill">Add Skill</string>
|
||||||
<string name="action_add_trait">Add Trait</string>
|
<string name="action_add_trait">Add Trait</string>
|
||||||
<string name="action_edit">Edit</string>
|
<string name="action_edit">Edit</string>
|
||||||
<string name="action_import_monster">Import Monster</string>
|
<string name="action_import_monster">Import File</string>
|
||||||
<string name="app_name">MonsterCards</string>
|
<string name="app_name">MonsterCards</string>
|
||||||
<string name="charisma_abbreviation">CHA</string>
|
<string name="charisma_abbreviation">CHA</string>
|
||||||
<string name="constitution_abbreviation">CON</string>
|
<string name="constitution_abbreviation">CON</string>
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
<string name="title_editStrings">Strings</string>
|
<string name="title_editStrings">Strings</string>
|
||||||
<string name="title_editTrait">Trait</string>
|
<string name="title_editTrait">Trait</string>
|
||||||
<string name="title_editTraits">Traits</string>
|
<string name="title_editTraits">Traits</string>
|
||||||
<string name="title_importMonster">Import Monster</string>
|
<string name="title_importMonster">Import File</string>
|
||||||
<string name="title_library">Library</string>
|
<string name="title_library">Library</string>
|
||||||
<string name="title_monsterDetails">Monster Details</string>
|
<string name="title_monsterDetails">Monster Details</string>
|
||||||
<string name="title_monsterDetails_fmt">%1$s Details</string>
|
<string name="title_monsterDetails_fmt">%1$s Details</string>
|
||||||
@@ -156,25 +156,25 @@
|
|||||||
<string name="action_add_collection_to_dashboard">Add Collection to Dashboard</string>
|
<string name="action_add_collection_to_dashboard">Add Collection to Dashboard</string>
|
||||||
<string name="action_add_single_monster">Add Monster</string>
|
<string name="action_add_single_monster">Add Monster</string>
|
||||||
<string name="action_add_collection_option">Add Collection</string>
|
<string name="action_add_collection_option">Add Collection</string>
|
||||||
<string name="action_clear_dashboard">Clear Dashboard</string>
|
<string name="action_clear_dashboard">Clear</string>
|
||||||
<string name="dialog_add_to_dashboard">Add to Dashboard</string>
|
<string name="dialog_add_to_dashboard">Add to Dashboard</string>
|
||||||
<string name="snackbar_added_to_dashboard">Added %1$s to Dashboard</string>
|
<string name="snackbar_added_to_dashboard">Added %1$s to Dashboard</string>
|
||||||
<string name="snackbar_collection_added_to_dashboard">Added collection %1$s to Dashboard</string>
|
<string name="snackbar_collection_added_to_dashboard">Added collection %1$s to Dashboard</string>
|
||||||
<string name="snackbar_dashboard_cleared">Dashboard cleared</string>
|
<string name="snackbar_dashboard_cleared">Dashboard cleared</string>
|
||||||
<string name="no_monsters_available">No monsters available in library. Create one first!</string>
|
<string name="no_monsters_available">No monsters available in library. Create one first!</string>
|
||||||
<string name="action_import_from_url">Import URL...</string>
|
<string name="action_import_from_url">Import URL</string>
|
||||||
<string name="dialog_import_url_title">Import Entity or Character</string>
|
<string name="dialog_import_url_title">Import Character</string>
|
||||||
<string name="dialog_import_url_message">Enter or paste a D&D Beyond URL, Character ID, or Open5e JSON payload:</string>
|
<string name="dialog_import_url_message">Enter or paste a D&D Beyond URL, Character ID, or Open5e JSON payload:</string>
|
||||||
<string name="dialog_import_url_hint">e.g. D&D Beyond URL or Open5e JSON</string>
|
<string name="dialog_import_url_hint">e.g. D&D Beyond URL or Open5e JSON</string>
|
||||||
<string name="dialog_import">Import</string>
|
<string name="dialog_import">Import</string>
|
||||||
<string name="toast_importing_url">Importing entity...</string>
|
<string name="toast_importing_url">Importing…</string>
|
||||||
<string name="failed_to_import_url">Failed to import entity. Please check the URL, ID, or JSON format.</string>
|
<string name="failed_to_import_url">Failed to import URL. Please check the URL, ID, or JSON format.</string>
|
||||||
<string name="action_share_monster">Share / Export Monster (.card)</string>
|
<string name="action_share_monster">Share / Export Monster (.card)</string>
|
||||||
<string name="failed_to_share_monster">Failed to share monster</string>
|
<string name="failed_to_share_monster">Failed to share monster</string>
|
||||||
<string name="action_export_card">Export Card</string>
|
<string name="action_export_card">Export Card</string>
|
||||||
<string name="action_export_collection">Export Collection</string>
|
<string name="action_export_collection">Export Collection</string>
|
||||||
<string name="action_export_library">Export Library</string>
|
<string name="action_export_library">Export Library</string>
|
||||||
<string name="action_export_dashboard">Export Dashboard</string>
|
<string name="action_export_dashboard">Export</string>
|
||||||
<string name="snackbar_export_success">Exported to %1$s</string>
|
<string name="snackbar_export_success">Exported to %1$s</string>
|
||||||
<string name="snackbar_export_failed">Failed to export file</string>
|
<string name="snackbar_export_failed">Failed to export file</string>
|
||||||
<string name="snackbar_import_binder_success">Imported binder successfully</string>
|
<string name="snackbar_import_binder_success">Imported binder successfully</string>
|
||||||
@@ -189,7 +189,7 @@
|
|||||||
<string name="empty_library_subtitle">Create new monster cards or import them from D&D Beyond or Open5e.</string>
|
<string name="empty_library_subtitle">Create new monster cards or import them from D&D Beyond or Open5e.</string>
|
||||||
<string name="app_title_splash">Monster Cards</string>
|
<string name="app_title_splash">Monster Cards</string>
|
||||||
|
|
||||||
<string name="action_remove_single_monster">Remove Monster...</string>
|
<string name="action_remove_single_monster">Remove Monster</string>
|
||||||
<string name="action_remove">Remove</string>
|
<string name="action_remove">Remove</string>
|
||||||
<string name="action_undo">Undo</string>
|
<string name="action_undo">Undo</string>
|
||||||
<string name="action_view_details">View Details</string>
|
<string name="action_view_details">View Details</string>
|
||||||
@@ -200,10 +200,29 @@
|
|||||||
<string name="dialog_remove_from_dashboard_message">Remove %1$s from your dashboard?</string>
|
<string name="dialog_remove_from_dashboard_message">Remove %1$s from your dashboard?</string>
|
||||||
|
|
||||||
<string name="action_create_monster">Create Monster</string>
|
<string name="action_create_monster">Create Monster</string>
|
||||||
<string name="action_import_monster_from_url">Import Monster from Url</string>
|
<string name="action_import_monster_from_url">Import URL</string>
|
||||||
<string name="action_import_monster_from_file">Import Monster from File</string>
|
<string name="action_import_monster_from_file">Import File</string>
|
||||||
<string name="action_import_collection">Import Collection</string>
|
<string name="action_import_collection">Import File</string>
|
||||||
<string name="title_library_actions">Library Actions</string>
|
<string name="title_library_actions">Library Actions</string>
|
||||||
<string name="title_collection_actions">Collection Actions</string>
|
<string name="title_collection_actions">Collection Actions</string>
|
||||||
<string name="title_dashboard_actions">Dashboard Actions</string>
|
<string name="title_dashboard_actions">Dashboard Actions</string>
|
||||||
|
|
||||||
|
<string name="action_clear">Clear</string>
|
||||||
|
<string name="action_clear_all_data">Clear All Data</string>
|
||||||
|
<string name="dialog_clear_all_data_title">Clear All Data?</string>
|
||||||
|
<string name="dialog_clear_all_data_message">This will permanently delete all monsters, collections, and clear your dashboard. Are you sure?</string>
|
||||||
|
<string name="snackbar_all_data_cleared">All data cleared</string>
|
||||||
|
|
||||||
|
<string name="action_export_everything">Export All Data</string>
|
||||||
|
<string name="default_filename_export_everything">monster_cards_backup</string>
|
||||||
|
|
||||||
|
<string name="action_export">Export</string>
|
||||||
|
<string name="action_remove_all">Remove All</string>
|
||||||
|
<string name="dialog_remove_all_collections_title">Remove All Collections?</string>
|
||||||
|
<string name="dialog_remove_all_collections_message">Are you sure you want to remove all collections? Monsters in your library will not be deleted.</string>
|
||||||
|
<string name="snackbar_all_collections_removed">All collections removed</string>
|
||||||
|
|
||||||
|
<string name="dialog_remove_all_from_collection_title">Remove All Monsters?</string>
|
||||||
|
<string name="dialog_remove_all_from_collection_message">Are you sure you want to remove all monsters from this collection?</string>
|
||||||
|
<string name="snackbar_collection_cleared">Removed all monsters from collection</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
84
monsters/Unnamed Monster.card
Normal file
84
monsters/Unnamed Monster.card
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
{
|
||||||
|
"abilities": [],
|
||||||
|
"actions": [],
|
||||||
|
"age": "",
|
||||||
|
"alignment": "",
|
||||||
|
"alliesAndOrganizations": "",
|
||||||
|
"appearance": "",
|
||||||
|
"armorType": "NONE",
|
||||||
|
"background": "",
|
||||||
|
"backstory": "",
|
||||||
|
"bonds": "",
|
||||||
|
"bonusActions": [],
|
||||||
|
"burrowSpeed": 0,
|
||||||
|
"canHover": false,
|
||||||
|
"challengeRating": "ONE",
|
||||||
|
"charismaSavingThrowAdvantage": "NONE",
|
||||||
|
"charismaSavingThrowProficiency": "NONE",
|
||||||
|
"charismaScore": 10,
|
||||||
|
"climbSpeed": 0,
|
||||||
|
"conditionImmunities": [],
|
||||||
|
"constitutionSavingThrowAdvantage": "NONE",
|
||||||
|
"constitutionSavingThrowProficiency": "NONE",
|
||||||
|
"constitutionScore": 10,
|
||||||
|
"customChallengeRatingDescription": "",
|
||||||
|
"customHPDescription": "",
|
||||||
|
"customProficiencyBonus": 0,
|
||||||
|
"customSpeedDescription": "",
|
||||||
|
"damageImmunities": [],
|
||||||
|
"damageResistances": [],
|
||||||
|
"damageVulnerabilities": [],
|
||||||
|
"dexteritySavingThrowAdvantage": "NONE",
|
||||||
|
"dexteritySavingThrowProficiency": "NONE",
|
||||||
|
"dexterityScore": 10,
|
||||||
|
"eyes": "",
|
||||||
|
"flaws": "",
|
||||||
|
"flySpeed": 0,
|
||||||
|
"hair": "",
|
||||||
|
"hasCustomHP": false,
|
||||||
|
"hasCustomSpeed": false,
|
||||||
|
"height": "",
|
||||||
|
"hitDice": 1,
|
||||||
|
"id": "dc7d4d51-86f7-41c6-9e4d-fb187cfa047b",
|
||||||
|
"ideals": "",
|
||||||
|
"intelligenceSavingThrowAdvantage": "NONE",
|
||||||
|
"intelligenceSavingThrowProficiency": "NONE",
|
||||||
|
"intelligenceScore": 10,
|
||||||
|
"lairActions": [],
|
||||||
|
"lairActionsDescription": "",
|
||||||
|
"lairActionsEndNote": "",
|
||||||
|
"languages": [],
|
||||||
|
"legendaryActions": [],
|
||||||
|
"legendaryActionsDescription": "",
|
||||||
|
"mythicActions": [],
|
||||||
|
"mythicActionsDescription": "",
|
||||||
|
"name": "Unnamed Monster",
|
||||||
|
"naturalArmorBonus": 0,
|
||||||
|
"otherArmorDescription": "",
|
||||||
|
"personalityTraits": "",
|
||||||
|
"playerName": "",
|
||||||
|
"reactions": [],
|
||||||
|
"regionalActions": [],
|
||||||
|
"regionalActionsDescription": "",
|
||||||
|
"regionalActionsEndNote": "",
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"senses": [],
|
||||||
|
"shieldBonus": 0,
|
||||||
|
"size": "",
|
||||||
|
"skills": [],
|
||||||
|
"skin": "",
|
||||||
|
"sourceUrl": "",
|
||||||
|
"strengthSavingThrowAdvantage": "NONE",
|
||||||
|
"strengthSavingThrowProficiency": "NONE",
|
||||||
|
"strengthScore": 10,
|
||||||
|
"subtype": "",
|
||||||
|
"swimSpeed": 0,
|
||||||
|
"telepathyRange": 0,
|
||||||
|
"type": "",
|
||||||
|
"understandsButDescription": "",
|
||||||
|
"walkSpeed": 0,
|
||||||
|
"weight": "",
|
||||||
|
"wisdomSavingThrowAdvantage": "NONE",
|
||||||
|
"wisdomSavingThrowProficiency": "NONE",
|
||||||
|
"wisdomScore": 10
|
||||||
|
}
|
||||||
514
monsters/baddies (1).binder
Normal file
514
monsters/baddies (1).binder
Normal file
@@ -0,0 +1,514 @@
|
|||||||
|
{
|
||||||
|
"collections": [
|
||||||
|
{
|
||||||
|
"cards": [
|
||||||
|
{
|
||||||
|
"abilities": [],
|
||||||
|
"actions": [],
|
||||||
|
"age": "",
|
||||||
|
"alignment": "",
|
||||||
|
"alliesAndOrganizations": "",
|
||||||
|
"appearance": "",
|
||||||
|
"armorType": "NONE",
|
||||||
|
"background": "",
|
||||||
|
"backstory": "",
|
||||||
|
"bonds": "",
|
||||||
|
"bonusActions": [],
|
||||||
|
"burrowSpeed": 0,
|
||||||
|
"canHover": false,
|
||||||
|
"challengeRating": "ONE",
|
||||||
|
"charismaSavingThrowAdvantage": "NONE",
|
||||||
|
"charismaSavingThrowProficiency": "NONE",
|
||||||
|
"charismaScore": 10,
|
||||||
|
"climbSpeed": 0,
|
||||||
|
"conditionImmunities": [],
|
||||||
|
"constitutionSavingThrowAdvantage": "NONE",
|
||||||
|
"constitutionSavingThrowProficiency": "NONE",
|
||||||
|
"constitutionScore": 10,
|
||||||
|
"customChallengeRatingDescription": "",
|
||||||
|
"customHPDescription": "",
|
||||||
|
"customProficiencyBonus": 0,
|
||||||
|
"customSpeedDescription": "",
|
||||||
|
"damageImmunities": [],
|
||||||
|
"damageResistances": [],
|
||||||
|
"damageVulnerabilities": [],
|
||||||
|
"dexteritySavingThrowAdvantage": "NONE",
|
||||||
|
"dexteritySavingThrowProficiency": "NONE",
|
||||||
|
"dexterityScore": 10,
|
||||||
|
"eyes": "",
|
||||||
|
"flaws": "",
|
||||||
|
"flySpeed": 0,
|
||||||
|
"hair": "",
|
||||||
|
"hasCustomHP": false,
|
||||||
|
"hasCustomSpeed": false,
|
||||||
|
"height": "",
|
||||||
|
"hitDice": 1,
|
||||||
|
"id": "dc7d4d51-86f7-41c6-9e4d-fb187cfa047b",
|
||||||
|
"ideals": "",
|
||||||
|
"intelligenceSavingThrowAdvantage": "NONE",
|
||||||
|
"intelligenceSavingThrowProficiency": "NONE",
|
||||||
|
"intelligenceScore": 10,
|
||||||
|
"lairActions": [],
|
||||||
|
"lairActionsDescription": "",
|
||||||
|
"lairActionsEndNote": "",
|
||||||
|
"languages": [],
|
||||||
|
"legendaryActions": [],
|
||||||
|
"legendaryActionsDescription": "",
|
||||||
|
"mythicActions": [],
|
||||||
|
"mythicActionsDescription": "",
|
||||||
|
"name": "Unnamed Monster",
|
||||||
|
"naturalArmorBonus": 0,
|
||||||
|
"otherArmorDescription": "",
|
||||||
|
"personalityTraits": "",
|
||||||
|
"playerName": "",
|
||||||
|
"reactions": [],
|
||||||
|
"regionalActions": [],
|
||||||
|
"regionalActionsDescription": "",
|
||||||
|
"regionalActionsEndNote": "",
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"senses": [],
|
||||||
|
"shieldBonus": 0,
|
||||||
|
"size": "",
|
||||||
|
"skills": [],
|
||||||
|
"skin": "",
|
||||||
|
"sourceUrl": "",
|
||||||
|
"strengthSavingThrowAdvantage": "NONE",
|
||||||
|
"strengthSavingThrowProficiency": "NONE",
|
||||||
|
"strengthScore": 10,
|
||||||
|
"subtype": "",
|
||||||
|
"swimSpeed": 0,
|
||||||
|
"telepathyRange": 0,
|
||||||
|
"type": "",
|
||||||
|
"understandsButDescription": "",
|
||||||
|
"walkSpeed": 0,
|
||||||
|
"weight": "",
|
||||||
|
"wisdomSavingThrowAdvantage": "NONE",
|
||||||
|
"wisdomSavingThrowProficiency": "NONE",
|
||||||
|
"wisdomScore": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"abilities": [],
|
||||||
|
"actions": [],
|
||||||
|
"age": "",
|
||||||
|
"alignment": "",
|
||||||
|
"alliesAndOrganizations": "",
|
||||||
|
"appearance": "",
|
||||||
|
"armorType": "NONE",
|
||||||
|
"background": "",
|
||||||
|
"backstory": "",
|
||||||
|
"bonds": "",
|
||||||
|
"bonusActions": [],
|
||||||
|
"burrowSpeed": 0,
|
||||||
|
"canHover": false,
|
||||||
|
"challengeRating": "ONE",
|
||||||
|
"charismaSavingThrowAdvantage": "NONE",
|
||||||
|
"charismaSavingThrowProficiency": "NONE",
|
||||||
|
"charismaScore": 10,
|
||||||
|
"climbSpeed": 0,
|
||||||
|
"conditionImmunities": [],
|
||||||
|
"constitutionSavingThrowAdvantage": "NONE",
|
||||||
|
"constitutionSavingThrowProficiency": "NONE",
|
||||||
|
"constitutionScore": 10,
|
||||||
|
"customChallengeRatingDescription": "",
|
||||||
|
"customHPDescription": "",
|
||||||
|
"customProficiencyBonus": 0,
|
||||||
|
"customSpeedDescription": "",
|
||||||
|
"damageImmunities": [],
|
||||||
|
"damageResistances": [],
|
||||||
|
"damageVulnerabilities": [],
|
||||||
|
"dexteritySavingThrowAdvantage": "NONE",
|
||||||
|
"dexteritySavingThrowProficiency": "NONE",
|
||||||
|
"dexterityScore": 10,
|
||||||
|
"eyes": "",
|
||||||
|
"flaws": "",
|
||||||
|
"flySpeed": 0,
|
||||||
|
"hair": "",
|
||||||
|
"hasCustomHP": false,
|
||||||
|
"hasCustomSpeed": false,
|
||||||
|
"height": "",
|
||||||
|
"hitDice": 1,
|
||||||
|
"id": "dc7d4d51-86f7-41c6-9e4d-fb187cfa047b",
|
||||||
|
"ideals": "",
|
||||||
|
"intelligenceSavingThrowAdvantage": "NONE",
|
||||||
|
"intelligenceSavingThrowProficiency": "NONE",
|
||||||
|
"intelligenceScore": 10,
|
||||||
|
"lairActions": [],
|
||||||
|
"lairActionsDescription": "",
|
||||||
|
"lairActionsEndNote": "",
|
||||||
|
"languages": [],
|
||||||
|
"legendaryActions": [],
|
||||||
|
"legendaryActionsDescription": "",
|
||||||
|
"mythicActions": [],
|
||||||
|
"mythicActionsDescription": "",
|
||||||
|
"name": "Unnamed Monster",
|
||||||
|
"naturalArmorBonus": 0,
|
||||||
|
"otherArmorDescription": "",
|
||||||
|
"personalityTraits": "",
|
||||||
|
"playerName": "",
|
||||||
|
"reactions": [],
|
||||||
|
"regionalActions": [],
|
||||||
|
"regionalActionsDescription": "",
|
||||||
|
"regionalActionsEndNote": "",
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"senses": [],
|
||||||
|
"shieldBonus": 0,
|
||||||
|
"size": "",
|
||||||
|
"skills": [],
|
||||||
|
"skin": "",
|
||||||
|
"sourceUrl": "",
|
||||||
|
"strengthSavingThrowAdvantage": "NONE",
|
||||||
|
"strengthSavingThrowProficiency": "NONE",
|
||||||
|
"strengthScore": 10,
|
||||||
|
"subtype": "",
|
||||||
|
"swimSpeed": 0,
|
||||||
|
"telepathyRange": 0,
|
||||||
|
"type": "",
|
||||||
|
"understandsButDescription": "",
|
||||||
|
"walkSpeed": 0,
|
||||||
|
"weight": "",
|
||||||
|
"wisdomSavingThrowAdvantage": "NONE",
|
||||||
|
"wisdomSavingThrowProficiency": "NONE",
|
||||||
|
"wisdomScore": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"abilities": [],
|
||||||
|
"actions": [],
|
||||||
|
"age": "",
|
||||||
|
"alignment": "",
|
||||||
|
"alliesAndOrganizations": "",
|
||||||
|
"appearance": "",
|
||||||
|
"armorType": "NONE",
|
||||||
|
"background": "",
|
||||||
|
"backstory": "",
|
||||||
|
"bonds": "",
|
||||||
|
"bonusActions": [],
|
||||||
|
"burrowSpeed": 0,
|
||||||
|
"canHover": false,
|
||||||
|
"challengeRating": "ONE",
|
||||||
|
"charismaSavingThrowAdvantage": "NONE",
|
||||||
|
"charismaSavingThrowProficiency": "NONE",
|
||||||
|
"charismaScore": 10,
|
||||||
|
"climbSpeed": 0,
|
||||||
|
"conditionImmunities": [],
|
||||||
|
"constitutionSavingThrowAdvantage": "NONE",
|
||||||
|
"constitutionSavingThrowProficiency": "NONE",
|
||||||
|
"constitutionScore": 10,
|
||||||
|
"customChallengeRatingDescription": "",
|
||||||
|
"customHPDescription": "",
|
||||||
|
"customProficiencyBonus": 0,
|
||||||
|
"customSpeedDescription": "",
|
||||||
|
"damageImmunities": [],
|
||||||
|
"damageResistances": [],
|
||||||
|
"damageVulnerabilities": [],
|
||||||
|
"dexteritySavingThrowAdvantage": "NONE",
|
||||||
|
"dexteritySavingThrowProficiency": "NONE",
|
||||||
|
"dexterityScore": 10,
|
||||||
|
"eyes": "",
|
||||||
|
"flaws": "",
|
||||||
|
"flySpeed": 0,
|
||||||
|
"hair": "",
|
||||||
|
"hasCustomHP": false,
|
||||||
|
"hasCustomSpeed": false,
|
||||||
|
"height": "",
|
||||||
|
"hitDice": 1,
|
||||||
|
"id": "dc7d4d51-86f7-41c6-9e4d-fb187cfa047b",
|
||||||
|
"ideals": "",
|
||||||
|
"intelligenceSavingThrowAdvantage": "NONE",
|
||||||
|
"intelligenceSavingThrowProficiency": "NONE",
|
||||||
|
"intelligenceScore": 10,
|
||||||
|
"lairActions": [],
|
||||||
|
"lairActionsDescription": "",
|
||||||
|
"lairActionsEndNote": "",
|
||||||
|
"languages": [],
|
||||||
|
"legendaryActions": [],
|
||||||
|
"legendaryActionsDescription": "",
|
||||||
|
"mythicActions": [],
|
||||||
|
"mythicActionsDescription": "",
|
||||||
|
"name": "Unnamed Monster",
|
||||||
|
"naturalArmorBonus": 0,
|
||||||
|
"otherArmorDescription": "",
|
||||||
|
"personalityTraits": "",
|
||||||
|
"playerName": "",
|
||||||
|
"reactions": [],
|
||||||
|
"regionalActions": [],
|
||||||
|
"regionalActionsDescription": "",
|
||||||
|
"regionalActionsEndNote": "",
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"senses": [],
|
||||||
|
"shieldBonus": 0,
|
||||||
|
"size": "",
|
||||||
|
"skills": [],
|
||||||
|
"skin": "",
|
||||||
|
"sourceUrl": "",
|
||||||
|
"strengthSavingThrowAdvantage": "NONE",
|
||||||
|
"strengthSavingThrowProficiency": "NONE",
|
||||||
|
"strengthScore": 10,
|
||||||
|
"subtype": "",
|
||||||
|
"swimSpeed": 0,
|
||||||
|
"telepathyRange": 0,
|
||||||
|
"type": "",
|
||||||
|
"understandsButDescription": "",
|
||||||
|
"walkSpeed": 0,
|
||||||
|
"weight": "",
|
||||||
|
"wisdomSavingThrowAdvantage": "NONE",
|
||||||
|
"wisdomSavingThrowProficiency": "NONE",
|
||||||
|
"wisdomScore": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"abilities": [],
|
||||||
|
"actions": [],
|
||||||
|
"age": "",
|
||||||
|
"alignment": "",
|
||||||
|
"alliesAndOrganizations": "",
|
||||||
|
"appearance": "",
|
||||||
|
"armorType": "NONE",
|
||||||
|
"background": "",
|
||||||
|
"backstory": "",
|
||||||
|
"bonds": "",
|
||||||
|
"bonusActions": [],
|
||||||
|
"burrowSpeed": 0,
|
||||||
|
"canHover": false,
|
||||||
|
"challengeRating": "ONE",
|
||||||
|
"charismaSavingThrowAdvantage": "NONE",
|
||||||
|
"charismaSavingThrowProficiency": "NONE",
|
||||||
|
"charismaScore": 10,
|
||||||
|
"climbSpeed": 0,
|
||||||
|
"conditionImmunities": [],
|
||||||
|
"constitutionSavingThrowAdvantage": "NONE",
|
||||||
|
"constitutionSavingThrowProficiency": "NONE",
|
||||||
|
"constitutionScore": 10,
|
||||||
|
"customChallengeRatingDescription": "",
|
||||||
|
"customHPDescription": "",
|
||||||
|
"customProficiencyBonus": 0,
|
||||||
|
"customSpeedDescription": "",
|
||||||
|
"damageImmunities": [],
|
||||||
|
"damageResistances": [],
|
||||||
|
"damageVulnerabilities": [],
|
||||||
|
"dexteritySavingThrowAdvantage": "NONE",
|
||||||
|
"dexteritySavingThrowProficiency": "NONE",
|
||||||
|
"dexterityScore": 10,
|
||||||
|
"eyes": "",
|
||||||
|
"flaws": "",
|
||||||
|
"flySpeed": 0,
|
||||||
|
"hair": "",
|
||||||
|
"hasCustomHP": false,
|
||||||
|
"hasCustomSpeed": false,
|
||||||
|
"height": "",
|
||||||
|
"hitDice": 1,
|
||||||
|
"id": "dc7d4d51-86f7-41c6-9e4d-fb187cfa047b",
|
||||||
|
"ideals": "",
|
||||||
|
"intelligenceSavingThrowAdvantage": "NONE",
|
||||||
|
"intelligenceSavingThrowProficiency": "NONE",
|
||||||
|
"intelligenceScore": 10,
|
||||||
|
"lairActions": [],
|
||||||
|
"lairActionsDescription": "",
|
||||||
|
"lairActionsEndNote": "",
|
||||||
|
"languages": [],
|
||||||
|
"legendaryActions": [],
|
||||||
|
"legendaryActionsDescription": "",
|
||||||
|
"mythicActions": [],
|
||||||
|
"mythicActionsDescription": "",
|
||||||
|
"name": "Unnamed Monster",
|
||||||
|
"naturalArmorBonus": 0,
|
||||||
|
"otherArmorDescription": "",
|
||||||
|
"personalityTraits": "",
|
||||||
|
"playerName": "",
|
||||||
|
"reactions": [],
|
||||||
|
"regionalActions": [],
|
||||||
|
"regionalActionsDescription": "",
|
||||||
|
"regionalActionsEndNote": "",
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"senses": [],
|
||||||
|
"shieldBonus": 0,
|
||||||
|
"size": "",
|
||||||
|
"skills": [],
|
||||||
|
"skin": "",
|
||||||
|
"sourceUrl": "",
|
||||||
|
"strengthSavingThrowAdvantage": "NONE",
|
||||||
|
"strengthSavingThrowProficiency": "NONE",
|
||||||
|
"strengthScore": 10,
|
||||||
|
"subtype": "",
|
||||||
|
"swimSpeed": 0,
|
||||||
|
"telepathyRange": 0,
|
||||||
|
"type": "",
|
||||||
|
"understandsButDescription": "",
|
||||||
|
"walkSpeed": 0,
|
||||||
|
"weight": "",
|
||||||
|
"wisdomSavingThrowAdvantage": "NONE",
|
||||||
|
"wisdomSavingThrowProficiency": "NONE",
|
||||||
|
"wisdomScore": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"abilities": [],
|
||||||
|
"actions": [],
|
||||||
|
"age": "",
|
||||||
|
"alignment": "",
|
||||||
|
"alliesAndOrganizations": "",
|
||||||
|
"appearance": "",
|
||||||
|
"armorType": "NONE",
|
||||||
|
"background": "",
|
||||||
|
"backstory": "",
|
||||||
|
"bonds": "",
|
||||||
|
"bonusActions": [],
|
||||||
|
"burrowSpeed": 0,
|
||||||
|
"canHover": false,
|
||||||
|
"challengeRating": "ONE",
|
||||||
|
"charismaSavingThrowAdvantage": "NONE",
|
||||||
|
"charismaSavingThrowProficiency": "NONE",
|
||||||
|
"charismaScore": 10,
|
||||||
|
"climbSpeed": 0,
|
||||||
|
"conditionImmunities": [],
|
||||||
|
"constitutionSavingThrowAdvantage": "NONE",
|
||||||
|
"constitutionSavingThrowProficiency": "NONE",
|
||||||
|
"constitutionScore": 10,
|
||||||
|
"customChallengeRatingDescription": "",
|
||||||
|
"customHPDescription": "",
|
||||||
|
"customProficiencyBonus": 0,
|
||||||
|
"customSpeedDescription": "",
|
||||||
|
"damageImmunities": [],
|
||||||
|
"damageResistances": [],
|
||||||
|
"damageVulnerabilities": [],
|
||||||
|
"dexteritySavingThrowAdvantage": "NONE",
|
||||||
|
"dexteritySavingThrowProficiency": "NONE",
|
||||||
|
"dexterityScore": 10,
|
||||||
|
"eyes": "",
|
||||||
|
"flaws": "",
|
||||||
|
"flySpeed": 0,
|
||||||
|
"hair": "",
|
||||||
|
"hasCustomHP": false,
|
||||||
|
"hasCustomSpeed": false,
|
||||||
|
"height": "",
|
||||||
|
"hitDice": 1,
|
||||||
|
"id": "dc7d4d51-86f7-41c6-9e4d-fb187cfa047b",
|
||||||
|
"ideals": "",
|
||||||
|
"intelligenceSavingThrowAdvantage": "NONE",
|
||||||
|
"intelligenceSavingThrowProficiency": "NONE",
|
||||||
|
"intelligenceScore": 10,
|
||||||
|
"lairActions": [],
|
||||||
|
"lairActionsDescription": "",
|
||||||
|
"lairActionsEndNote": "",
|
||||||
|
"languages": [],
|
||||||
|
"legendaryActions": [],
|
||||||
|
"legendaryActionsDescription": "",
|
||||||
|
"mythicActions": [],
|
||||||
|
"mythicActionsDescription": "",
|
||||||
|
"name": "Unnamed Monster",
|
||||||
|
"naturalArmorBonus": 0,
|
||||||
|
"otherArmorDescription": "",
|
||||||
|
"personalityTraits": "",
|
||||||
|
"playerName": "",
|
||||||
|
"reactions": [],
|
||||||
|
"regionalActions": [],
|
||||||
|
"regionalActionsDescription": "",
|
||||||
|
"regionalActionsEndNote": "",
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"senses": [],
|
||||||
|
"shieldBonus": 0,
|
||||||
|
"size": "",
|
||||||
|
"skills": [],
|
||||||
|
"skin": "",
|
||||||
|
"sourceUrl": "",
|
||||||
|
"strengthSavingThrowAdvantage": "NONE",
|
||||||
|
"strengthSavingThrowProficiency": "NONE",
|
||||||
|
"strengthScore": 10,
|
||||||
|
"subtype": "",
|
||||||
|
"swimSpeed": 0,
|
||||||
|
"telepathyRange": 0,
|
||||||
|
"type": "",
|
||||||
|
"understandsButDescription": "",
|
||||||
|
"walkSpeed": 0,
|
||||||
|
"weight": "",
|
||||||
|
"wisdomSavingThrowAdvantage": "NONE",
|
||||||
|
"wisdomSavingThrowProficiency": "NONE",
|
||||||
|
"wisdomScore": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"abilities": [],
|
||||||
|
"actions": [],
|
||||||
|
"age": "",
|
||||||
|
"alignment": "",
|
||||||
|
"alliesAndOrganizations": "",
|
||||||
|
"appearance": "",
|
||||||
|
"armorType": "NONE",
|
||||||
|
"background": "",
|
||||||
|
"backstory": "",
|
||||||
|
"bonds": "",
|
||||||
|
"bonusActions": [],
|
||||||
|
"burrowSpeed": 0,
|
||||||
|
"canHover": false,
|
||||||
|
"challengeRating": "ONE",
|
||||||
|
"charismaSavingThrowAdvantage": "NONE",
|
||||||
|
"charismaSavingThrowProficiency": "NONE",
|
||||||
|
"charismaScore": 10,
|
||||||
|
"climbSpeed": 0,
|
||||||
|
"conditionImmunities": [],
|
||||||
|
"constitutionSavingThrowAdvantage": "NONE",
|
||||||
|
"constitutionSavingThrowProficiency": "NONE",
|
||||||
|
"constitutionScore": 10,
|
||||||
|
"customChallengeRatingDescription": "",
|
||||||
|
"customHPDescription": "",
|
||||||
|
"customProficiencyBonus": 0,
|
||||||
|
"customSpeedDescription": "",
|
||||||
|
"damageImmunities": [],
|
||||||
|
"damageResistances": [],
|
||||||
|
"damageVulnerabilities": [],
|
||||||
|
"dexteritySavingThrowAdvantage": "NONE",
|
||||||
|
"dexteritySavingThrowProficiency": "NONE",
|
||||||
|
"dexterityScore": 10,
|
||||||
|
"eyes": "",
|
||||||
|
"flaws": "",
|
||||||
|
"flySpeed": 0,
|
||||||
|
"hair": "",
|
||||||
|
"hasCustomHP": false,
|
||||||
|
"hasCustomSpeed": false,
|
||||||
|
"height": "",
|
||||||
|
"hitDice": 1,
|
||||||
|
"id": "dc7d4d51-86f7-41c6-9e4d-fb187cfa047b",
|
||||||
|
"ideals": "",
|
||||||
|
"intelligenceSavingThrowAdvantage": "NONE",
|
||||||
|
"intelligenceSavingThrowProficiency": "NONE",
|
||||||
|
"intelligenceScore": 10,
|
||||||
|
"lairActions": [],
|
||||||
|
"lairActionsDescription": "",
|
||||||
|
"lairActionsEndNote": "",
|
||||||
|
"languages": [],
|
||||||
|
"legendaryActions": [],
|
||||||
|
"legendaryActionsDescription": "",
|
||||||
|
"mythicActions": [],
|
||||||
|
"mythicActionsDescription": "",
|
||||||
|
"name": "Unnamed Monster",
|
||||||
|
"naturalArmorBonus": 0,
|
||||||
|
"otherArmorDescription": "",
|
||||||
|
"personalityTraits": "",
|
||||||
|
"playerName": "",
|
||||||
|
"reactions": [],
|
||||||
|
"regionalActions": [],
|
||||||
|
"regionalActionsDescription": "",
|
||||||
|
"regionalActionsEndNote": "",
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"senses": [],
|
||||||
|
"shieldBonus": 0,
|
||||||
|
"size": "",
|
||||||
|
"skills": [],
|
||||||
|
"skin": "",
|
||||||
|
"sourceUrl": "",
|
||||||
|
"strengthSavingThrowAdvantage": "NONE",
|
||||||
|
"strengthSavingThrowProficiency": "NONE",
|
||||||
|
"strengthScore": 10,
|
||||||
|
"subtype": "",
|
||||||
|
"swimSpeed": 0,
|
||||||
|
"telepathyRange": 0,
|
||||||
|
"type": "",
|
||||||
|
"understandsButDescription": "",
|
||||||
|
"walkSpeed": 0,
|
||||||
|
"weight": "",
|
||||||
|
"wisdomSavingThrowAdvantage": "NONE",
|
||||||
|
"wisdomSavingThrowProficiency": "NONE",
|
||||||
|
"wisdomScore": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "baddies"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"schemaVersion": 1
|
||||||
|
}
|
||||||
514
monsters/baddies.binder
Normal file
514
monsters/baddies.binder
Normal file
@@ -0,0 +1,514 @@
|
|||||||
|
{
|
||||||
|
"collections": [
|
||||||
|
{
|
||||||
|
"cards": [
|
||||||
|
{
|
||||||
|
"abilities": [],
|
||||||
|
"actions": [],
|
||||||
|
"age": "",
|
||||||
|
"alignment": "",
|
||||||
|
"alliesAndOrganizations": "",
|
||||||
|
"appearance": "",
|
||||||
|
"armorType": "NONE",
|
||||||
|
"background": "",
|
||||||
|
"backstory": "",
|
||||||
|
"bonds": "",
|
||||||
|
"bonusActions": [],
|
||||||
|
"burrowSpeed": 0,
|
||||||
|
"canHover": false,
|
||||||
|
"challengeRating": "ONE",
|
||||||
|
"charismaSavingThrowAdvantage": "NONE",
|
||||||
|
"charismaSavingThrowProficiency": "NONE",
|
||||||
|
"charismaScore": 10,
|
||||||
|
"climbSpeed": 0,
|
||||||
|
"conditionImmunities": [],
|
||||||
|
"constitutionSavingThrowAdvantage": "NONE",
|
||||||
|
"constitutionSavingThrowProficiency": "NONE",
|
||||||
|
"constitutionScore": 10,
|
||||||
|
"customChallengeRatingDescription": "",
|
||||||
|
"customHPDescription": "",
|
||||||
|
"customProficiencyBonus": 0,
|
||||||
|
"customSpeedDescription": "",
|
||||||
|
"damageImmunities": [],
|
||||||
|
"damageResistances": [],
|
||||||
|
"damageVulnerabilities": [],
|
||||||
|
"dexteritySavingThrowAdvantage": "NONE",
|
||||||
|
"dexteritySavingThrowProficiency": "NONE",
|
||||||
|
"dexterityScore": 10,
|
||||||
|
"eyes": "",
|
||||||
|
"flaws": "",
|
||||||
|
"flySpeed": 0,
|
||||||
|
"hair": "",
|
||||||
|
"hasCustomHP": false,
|
||||||
|
"hasCustomSpeed": false,
|
||||||
|
"height": "",
|
||||||
|
"hitDice": 1,
|
||||||
|
"id": "dc7d4d51-86f7-41c6-9e4d-fb187cfa047b",
|
||||||
|
"ideals": "",
|
||||||
|
"intelligenceSavingThrowAdvantage": "NONE",
|
||||||
|
"intelligenceSavingThrowProficiency": "NONE",
|
||||||
|
"intelligenceScore": 10,
|
||||||
|
"lairActions": [],
|
||||||
|
"lairActionsDescription": "",
|
||||||
|
"lairActionsEndNote": "",
|
||||||
|
"languages": [],
|
||||||
|
"legendaryActions": [],
|
||||||
|
"legendaryActionsDescription": "",
|
||||||
|
"mythicActions": [],
|
||||||
|
"mythicActionsDescription": "",
|
||||||
|
"name": "Unnamed Monster",
|
||||||
|
"naturalArmorBonus": 0,
|
||||||
|
"otherArmorDescription": "",
|
||||||
|
"personalityTraits": "",
|
||||||
|
"playerName": "",
|
||||||
|
"reactions": [],
|
||||||
|
"regionalActions": [],
|
||||||
|
"regionalActionsDescription": "",
|
||||||
|
"regionalActionsEndNote": "",
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"senses": [],
|
||||||
|
"shieldBonus": 0,
|
||||||
|
"size": "",
|
||||||
|
"skills": [],
|
||||||
|
"skin": "",
|
||||||
|
"sourceUrl": "",
|
||||||
|
"strengthSavingThrowAdvantage": "NONE",
|
||||||
|
"strengthSavingThrowProficiency": "NONE",
|
||||||
|
"strengthScore": 10,
|
||||||
|
"subtype": "",
|
||||||
|
"swimSpeed": 0,
|
||||||
|
"telepathyRange": 0,
|
||||||
|
"type": "",
|
||||||
|
"understandsButDescription": "",
|
||||||
|
"walkSpeed": 0,
|
||||||
|
"weight": "",
|
||||||
|
"wisdomSavingThrowAdvantage": "NONE",
|
||||||
|
"wisdomSavingThrowProficiency": "NONE",
|
||||||
|
"wisdomScore": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"abilities": [],
|
||||||
|
"actions": [],
|
||||||
|
"age": "",
|
||||||
|
"alignment": "",
|
||||||
|
"alliesAndOrganizations": "",
|
||||||
|
"appearance": "",
|
||||||
|
"armorType": "NONE",
|
||||||
|
"background": "",
|
||||||
|
"backstory": "",
|
||||||
|
"bonds": "",
|
||||||
|
"bonusActions": [],
|
||||||
|
"burrowSpeed": 0,
|
||||||
|
"canHover": false,
|
||||||
|
"challengeRating": "ONE",
|
||||||
|
"charismaSavingThrowAdvantage": "NONE",
|
||||||
|
"charismaSavingThrowProficiency": "NONE",
|
||||||
|
"charismaScore": 10,
|
||||||
|
"climbSpeed": 0,
|
||||||
|
"conditionImmunities": [],
|
||||||
|
"constitutionSavingThrowAdvantage": "NONE",
|
||||||
|
"constitutionSavingThrowProficiency": "NONE",
|
||||||
|
"constitutionScore": 10,
|
||||||
|
"customChallengeRatingDescription": "",
|
||||||
|
"customHPDescription": "",
|
||||||
|
"customProficiencyBonus": 0,
|
||||||
|
"customSpeedDescription": "",
|
||||||
|
"damageImmunities": [],
|
||||||
|
"damageResistances": [],
|
||||||
|
"damageVulnerabilities": [],
|
||||||
|
"dexteritySavingThrowAdvantage": "NONE",
|
||||||
|
"dexteritySavingThrowProficiency": "NONE",
|
||||||
|
"dexterityScore": 10,
|
||||||
|
"eyes": "",
|
||||||
|
"flaws": "",
|
||||||
|
"flySpeed": 0,
|
||||||
|
"hair": "",
|
||||||
|
"hasCustomHP": false,
|
||||||
|
"hasCustomSpeed": false,
|
||||||
|
"height": "",
|
||||||
|
"hitDice": 1,
|
||||||
|
"id": "dc7d4d51-86f7-41c6-9e4d-fb187cfa047b",
|
||||||
|
"ideals": "",
|
||||||
|
"intelligenceSavingThrowAdvantage": "NONE",
|
||||||
|
"intelligenceSavingThrowProficiency": "NONE",
|
||||||
|
"intelligenceScore": 10,
|
||||||
|
"lairActions": [],
|
||||||
|
"lairActionsDescription": "",
|
||||||
|
"lairActionsEndNote": "",
|
||||||
|
"languages": [],
|
||||||
|
"legendaryActions": [],
|
||||||
|
"legendaryActionsDescription": "",
|
||||||
|
"mythicActions": [],
|
||||||
|
"mythicActionsDescription": "",
|
||||||
|
"name": "Unnamed Monster",
|
||||||
|
"naturalArmorBonus": 0,
|
||||||
|
"otherArmorDescription": "",
|
||||||
|
"personalityTraits": "",
|
||||||
|
"playerName": "",
|
||||||
|
"reactions": [],
|
||||||
|
"regionalActions": [],
|
||||||
|
"regionalActionsDescription": "",
|
||||||
|
"regionalActionsEndNote": "",
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"senses": [],
|
||||||
|
"shieldBonus": 0,
|
||||||
|
"size": "",
|
||||||
|
"skills": [],
|
||||||
|
"skin": "",
|
||||||
|
"sourceUrl": "",
|
||||||
|
"strengthSavingThrowAdvantage": "NONE",
|
||||||
|
"strengthSavingThrowProficiency": "NONE",
|
||||||
|
"strengthScore": 10,
|
||||||
|
"subtype": "",
|
||||||
|
"swimSpeed": 0,
|
||||||
|
"telepathyRange": 0,
|
||||||
|
"type": "",
|
||||||
|
"understandsButDescription": "",
|
||||||
|
"walkSpeed": 0,
|
||||||
|
"weight": "",
|
||||||
|
"wisdomSavingThrowAdvantage": "NONE",
|
||||||
|
"wisdomSavingThrowProficiency": "NONE",
|
||||||
|
"wisdomScore": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"abilities": [],
|
||||||
|
"actions": [],
|
||||||
|
"age": "",
|
||||||
|
"alignment": "",
|
||||||
|
"alliesAndOrganizations": "",
|
||||||
|
"appearance": "",
|
||||||
|
"armorType": "NONE",
|
||||||
|
"background": "",
|
||||||
|
"backstory": "",
|
||||||
|
"bonds": "",
|
||||||
|
"bonusActions": [],
|
||||||
|
"burrowSpeed": 0,
|
||||||
|
"canHover": false,
|
||||||
|
"challengeRating": "ONE",
|
||||||
|
"charismaSavingThrowAdvantage": "NONE",
|
||||||
|
"charismaSavingThrowProficiency": "NONE",
|
||||||
|
"charismaScore": 10,
|
||||||
|
"climbSpeed": 0,
|
||||||
|
"conditionImmunities": [],
|
||||||
|
"constitutionSavingThrowAdvantage": "NONE",
|
||||||
|
"constitutionSavingThrowProficiency": "NONE",
|
||||||
|
"constitutionScore": 10,
|
||||||
|
"customChallengeRatingDescription": "",
|
||||||
|
"customHPDescription": "",
|
||||||
|
"customProficiencyBonus": 0,
|
||||||
|
"customSpeedDescription": "",
|
||||||
|
"damageImmunities": [],
|
||||||
|
"damageResistances": [],
|
||||||
|
"damageVulnerabilities": [],
|
||||||
|
"dexteritySavingThrowAdvantage": "NONE",
|
||||||
|
"dexteritySavingThrowProficiency": "NONE",
|
||||||
|
"dexterityScore": 10,
|
||||||
|
"eyes": "",
|
||||||
|
"flaws": "",
|
||||||
|
"flySpeed": 0,
|
||||||
|
"hair": "",
|
||||||
|
"hasCustomHP": false,
|
||||||
|
"hasCustomSpeed": false,
|
||||||
|
"height": "",
|
||||||
|
"hitDice": 1,
|
||||||
|
"id": "dc7d4d51-86f7-41c6-9e4d-fb187cfa047b",
|
||||||
|
"ideals": "",
|
||||||
|
"intelligenceSavingThrowAdvantage": "NONE",
|
||||||
|
"intelligenceSavingThrowProficiency": "NONE",
|
||||||
|
"intelligenceScore": 10,
|
||||||
|
"lairActions": [],
|
||||||
|
"lairActionsDescription": "",
|
||||||
|
"lairActionsEndNote": "",
|
||||||
|
"languages": [],
|
||||||
|
"legendaryActions": [],
|
||||||
|
"legendaryActionsDescription": "",
|
||||||
|
"mythicActions": [],
|
||||||
|
"mythicActionsDescription": "",
|
||||||
|
"name": "Unnamed Monster",
|
||||||
|
"naturalArmorBonus": 0,
|
||||||
|
"otherArmorDescription": "",
|
||||||
|
"personalityTraits": "",
|
||||||
|
"playerName": "",
|
||||||
|
"reactions": [],
|
||||||
|
"regionalActions": [],
|
||||||
|
"regionalActionsDescription": "",
|
||||||
|
"regionalActionsEndNote": "",
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"senses": [],
|
||||||
|
"shieldBonus": 0,
|
||||||
|
"size": "",
|
||||||
|
"skills": [],
|
||||||
|
"skin": "",
|
||||||
|
"sourceUrl": "",
|
||||||
|
"strengthSavingThrowAdvantage": "NONE",
|
||||||
|
"strengthSavingThrowProficiency": "NONE",
|
||||||
|
"strengthScore": 10,
|
||||||
|
"subtype": "",
|
||||||
|
"swimSpeed": 0,
|
||||||
|
"telepathyRange": 0,
|
||||||
|
"type": "",
|
||||||
|
"understandsButDescription": "",
|
||||||
|
"walkSpeed": 0,
|
||||||
|
"weight": "",
|
||||||
|
"wisdomSavingThrowAdvantage": "NONE",
|
||||||
|
"wisdomSavingThrowProficiency": "NONE",
|
||||||
|
"wisdomScore": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"abilities": [],
|
||||||
|
"actions": [],
|
||||||
|
"age": "",
|
||||||
|
"alignment": "",
|
||||||
|
"alliesAndOrganizations": "",
|
||||||
|
"appearance": "",
|
||||||
|
"armorType": "NONE",
|
||||||
|
"background": "",
|
||||||
|
"backstory": "",
|
||||||
|
"bonds": "",
|
||||||
|
"bonusActions": [],
|
||||||
|
"burrowSpeed": 0,
|
||||||
|
"canHover": false,
|
||||||
|
"challengeRating": "ONE",
|
||||||
|
"charismaSavingThrowAdvantage": "NONE",
|
||||||
|
"charismaSavingThrowProficiency": "NONE",
|
||||||
|
"charismaScore": 10,
|
||||||
|
"climbSpeed": 0,
|
||||||
|
"conditionImmunities": [],
|
||||||
|
"constitutionSavingThrowAdvantage": "NONE",
|
||||||
|
"constitutionSavingThrowProficiency": "NONE",
|
||||||
|
"constitutionScore": 10,
|
||||||
|
"customChallengeRatingDescription": "",
|
||||||
|
"customHPDescription": "",
|
||||||
|
"customProficiencyBonus": 0,
|
||||||
|
"customSpeedDescription": "",
|
||||||
|
"damageImmunities": [],
|
||||||
|
"damageResistances": [],
|
||||||
|
"damageVulnerabilities": [],
|
||||||
|
"dexteritySavingThrowAdvantage": "NONE",
|
||||||
|
"dexteritySavingThrowProficiency": "NONE",
|
||||||
|
"dexterityScore": 10,
|
||||||
|
"eyes": "",
|
||||||
|
"flaws": "",
|
||||||
|
"flySpeed": 0,
|
||||||
|
"hair": "",
|
||||||
|
"hasCustomHP": false,
|
||||||
|
"hasCustomSpeed": false,
|
||||||
|
"height": "",
|
||||||
|
"hitDice": 1,
|
||||||
|
"id": "dc7d4d51-86f7-41c6-9e4d-fb187cfa047b",
|
||||||
|
"ideals": "",
|
||||||
|
"intelligenceSavingThrowAdvantage": "NONE",
|
||||||
|
"intelligenceSavingThrowProficiency": "NONE",
|
||||||
|
"intelligenceScore": 10,
|
||||||
|
"lairActions": [],
|
||||||
|
"lairActionsDescription": "",
|
||||||
|
"lairActionsEndNote": "",
|
||||||
|
"languages": [],
|
||||||
|
"legendaryActions": [],
|
||||||
|
"legendaryActionsDescription": "",
|
||||||
|
"mythicActions": [],
|
||||||
|
"mythicActionsDescription": "",
|
||||||
|
"name": "Unnamed Monster",
|
||||||
|
"naturalArmorBonus": 0,
|
||||||
|
"otherArmorDescription": "",
|
||||||
|
"personalityTraits": "",
|
||||||
|
"playerName": "",
|
||||||
|
"reactions": [],
|
||||||
|
"regionalActions": [],
|
||||||
|
"regionalActionsDescription": "",
|
||||||
|
"regionalActionsEndNote": "",
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"senses": [],
|
||||||
|
"shieldBonus": 0,
|
||||||
|
"size": "",
|
||||||
|
"skills": [],
|
||||||
|
"skin": "",
|
||||||
|
"sourceUrl": "",
|
||||||
|
"strengthSavingThrowAdvantage": "NONE",
|
||||||
|
"strengthSavingThrowProficiency": "NONE",
|
||||||
|
"strengthScore": 10,
|
||||||
|
"subtype": "",
|
||||||
|
"swimSpeed": 0,
|
||||||
|
"telepathyRange": 0,
|
||||||
|
"type": "",
|
||||||
|
"understandsButDescription": "",
|
||||||
|
"walkSpeed": 0,
|
||||||
|
"weight": "",
|
||||||
|
"wisdomSavingThrowAdvantage": "NONE",
|
||||||
|
"wisdomSavingThrowProficiency": "NONE",
|
||||||
|
"wisdomScore": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"abilities": [],
|
||||||
|
"actions": [],
|
||||||
|
"age": "",
|
||||||
|
"alignment": "",
|
||||||
|
"alliesAndOrganizations": "",
|
||||||
|
"appearance": "",
|
||||||
|
"armorType": "NONE",
|
||||||
|
"background": "",
|
||||||
|
"backstory": "",
|
||||||
|
"bonds": "",
|
||||||
|
"bonusActions": [],
|
||||||
|
"burrowSpeed": 0,
|
||||||
|
"canHover": false,
|
||||||
|
"challengeRating": "ONE",
|
||||||
|
"charismaSavingThrowAdvantage": "NONE",
|
||||||
|
"charismaSavingThrowProficiency": "NONE",
|
||||||
|
"charismaScore": 10,
|
||||||
|
"climbSpeed": 0,
|
||||||
|
"conditionImmunities": [],
|
||||||
|
"constitutionSavingThrowAdvantage": "NONE",
|
||||||
|
"constitutionSavingThrowProficiency": "NONE",
|
||||||
|
"constitutionScore": 10,
|
||||||
|
"customChallengeRatingDescription": "",
|
||||||
|
"customHPDescription": "",
|
||||||
|
"customProficiencyBonus": 0,
|
||||||
|
"customSpeedDescription": "",
|
||||||
|
"damageImmunities": [],
|
||||||
|
"damageResistances": [],
|
||||||
|
"damageVulnerabilities": [],
|
||||||
|
"dexteritySavingThrowAdvantage": "NONE",
|
||||||
|
"dexteritySavingThrowProficiency": "NONE",
|
||||||
|
"dexterityScore": 10,
|
||||||
|
"eyes": "",
|
||||||
|
"flaws": "",
|
||||||
|
"flySpeed": 0,
|
||||||
|
"hair": "",
|
||||||
|
"hasCustomHP": false,
|
||||||
|
"hasCustomSpeed": false,
|
||||||
|
"height": "",
|
||||||
|
"hitDice": 1,
|
||||||
|
"id": "dc7d4d51-86f7-41c6-9e4d-fb187cfa047b",
|
||||||
|
"ideals": "",
|
||||||
|
"intelligenceSavingThrowAdvantage": "NONE",
|
||||||
|
"intelligenceSavingThrowProficiency": "NONE",
|
||||||
|
"intelligenceScore": 10,
|
||||||
|
"lairActions": [],
|
||||||
|
"lairActionsDescription": "",
|
||||||
|
"lairActionsEndNote": "",
|
||||||
|
"languages": [],
|
||||||
|
"legendaryActions": [],
|
||||||
|
"legendaryActionsDescription": "",
|
||||||
|
"mythicActions": [],
|
||||||
|
"mythicActionsDescription": "",
|
||||||
|
"name": "Unnamed Monster",
|
||||||
|
"naturalArmorBonus": 0,
|
||||||
|
"otherArmorDescription": "",
|
||||||
|
"personalityTraits": "",
|
||||||
|
"playerName": "",
|
||||||
|
"reactions": [],
|
||||||
|
"regionalActions": [],
|
||||||
|
"regionalActionsDescription": "",
|
||||||
|
"regionalActionsEndNote": "",
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"senses": [],
|
||||||
|
"shieldBonus": 0,
|
||||||
|
"size": "",
|
||||||
|
"skills": [],
|
||||||
|
"skin": "",
|
||||||
|
"sourceUrl": "",
|
||||||
|
"strengthSavingThrowAdvantage": "NONE",
|
||||||
|
"strengthSavingThrowProficiency": "NONE",
|
||||||
|
"strengthScore": 10,
|
||||||
|
"subtype": "",
|
||||||
|
"swimSpeed": 0,
|
||||||
|
"telepathyRange": 0,
|
||||||
|
"type": "",
|
||||||
|
"understandsButDescription": "",
|
||||||
|
"walkSpeed": 0,
|
||||||
|
"weight": "",
|
||||||
|
"wisdomSavingThrowAdvantage": "NONE",
|
||||||
|
"wisdomSavingThrowProficiency": "NONE",
|
||||||
|
"wisdomScore": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"abilities": [],
|
||||||
|
"actions": [],
|
||||||
|
"age": "",
|
||||||
|
"alignment": "",
|
||||||
|
"alliesAndOrganizations": "",
|
||||||
|
"appearance": "",
|
||||||
|
"armorType": "NONE",
|
||||||
|
"background": "",
|
||||||
|
"backstory": "",
|
||||||
|
"bonds": "",
|
||||||
|
"bonusActions": [],
|
||||||
|
"burrowSpeed": 0,
|
||||||
|
"canHover": false,
|
||||||
|
"challengeRating": "ONE",
|
||||||
|
"charismaSavingThrowAdvantage": "NONE",
|
||||||
|
"charismaSavingThrowProficiency": "NONE",
|
||||||
|
"charismaScore": 10,
|
||||||
|
"climbSpeed": 0,
|
||||||
|
"conditionImmunities": [],
|
||||||
|
"constitutionSavingThrowAdvantage": "NONE",
|
||||||
|
"constitutionSavingThrowProficiency": "NONE",
|
||||||
|
"constitutionScore": 10,
|
||||||
|
"customChallengeRatingDescription": "",
|
||||||
|
"customHPDescription": "",
|
||||||
|
"customProficiencyBonus": 0,
|
||||||
|
"customSpeedDescription": "",
|
||||||
|
"damageImmunities": [],
|
||||||
|
"damageResistances": [],
|
||||||
|
"damageVulnerabilities": [],
|
||||||
|
"dexteritySavingThrowAdvantage": "NONE",
|
||||||
|
"dexteritySavingThrowProficiency": "NONE",
|
||||||
|
"dexterityScore": 10,
|
||||||
|
"eyes": "",
|
||||||
|
"flaws": "",
|
||||||
|
"flySpeed": 0,
|
||||||
|
"hair": "",
|
||||||
|
"hasCustomHP": false,
|
||||||
|
"hasCustomSpeed": false,
|
||||||
|
"height": "",
|
||||||
|
"hitDice": 1,
|
||||||
|
"id": "dc7d4d51-86f7-41c6-9e4d-fb187cfa047b",
|
||||||
|
"ideals": "",
|
||||||
|
"intelligenceSavingThrowAdvantage": "NONE",
|
||||||
|
"intelligenceSavingThrowProficiency": "NONE",
|
||||||
|
"intelligenceScore": 10,
|
||||||
|
"lairActions": [],
|
||||||
|
"lairActionsDescription": "",
|
||||||
|
"lairActionsEndNote": "",
|
||||||
|
"languages": [],
|
||||||
|
"legendaryActions": [],
|
||||||
|
"legendaryActionsDescription": "",
|
||||||
|
"mythicActions": [],
|
||||||
|
"mythicActionsDescription": "",
|
||||||
|
"name": "Unnamed Monster",
|
||||||
|
"naturalArmorBonus": 0,
|
||||||
|
"otherArmorDescription": "",
|
||||||
|
"personalityTraits": "",
|
||||||
|
"playerName": "",
|
||||||
|
"reactions": [],
|
||||||
|
"regionalActions": [],
|
||||||
|
"regionalActionsDescription": "",
|
||||||
|
"regionalActionsEndNote": "",
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"senses": [],
|
||||||
|
"shieldBonus": 0,
|
||||||
|
"size": "",
|
||||||
|
"skills": [],
|
||||||
|
"skin": "",
|
||||||
|
"sourceUrl": "",
|
||||||
|
"strengthSavingThrowAdvantage": "NONE",
|
||||||
|
"strengthSavingThrowProficiency": "NONE",
|
||||||
|
"strengthScore": 10,
|
||||||
|
"subtype": "",
|
||||||
|
"swimSpeed": 0,
|
||||||
|
"telepathyRange": 0,
|
||||||
|
"type": "",
|
||||||
|
"understandsButDescription": "",
|
||||||
|
"walkSpeed": 0,
|
||||||
|
"weight": "",
|
||||||
|
"wisdomSavingThrowAdvantage": "NONE",
|
||||||
|
"wisdomSavingThrowProficiency": "NONE",
|
||||||
|
"wisdomScore": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "baddies"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"schemaVersion": 1
|
||||||
|
}
|
||||||
42
schemas/binder.schema.json
Normal file
42
schemas/binder.schema.json
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://majinnaibu.com/schemas/binder.schema.json",
|
||||||
|
"title": "BinderExport",
|
||||||
|
"description": "JSON Schema for MonsterCards .binder collection files",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"collections",
|
||||||
|
"schemaVersion"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"$schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uri"
|
||||||
|
},
|
||||||
|
"schemaVersion": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"collections": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"cards"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"cards": {
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
87
schemas/monster-card.schema.json
Normal file
87
schemas/monster-card.schema.json
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://majinnaibu.com/schemas/monster-card.schema.json",
|
||||||
|
"title": "MonsterCard",
|
||||||
|
"description": "JSON Schema for MonsterCards .card files",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"schemaVersion"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"$schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uri"
|
||||||
|
},
|
||||||
|
"schemaVersion": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"subtype": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"alignment": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"strengthScore": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"dexterityScore": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"constitutionScore": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"intelligenceScore": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"wisdomScore": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"charismaScore": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"hitDice": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"walkSpeed": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"burrowSpeed": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"climbSpeed": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"flySpeed": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"swimSpeed": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"abilities": {
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"reactions": {
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"legendaryActions": {
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
257
schemas/tetracube-monster.schema.json
Normal file
257
schemas/tetracube-monster.schema.json
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://majinnaibu.com/schemas/tetracube-monster.schema.json",
|
||||||
|
"title": "TetraCubeMonster",
|
||||||
|
"description": "JSON Schema for TetraCube .monster files based on all_the_fields.monster",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"size",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tag": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"race": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"class": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"alignment": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"armorName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"strPoints": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"integer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dexPoints": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"integer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"conPoints": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"integer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"intPoints": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"integer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"wisPoints": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"integer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"chaPoints": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"integer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hitDice": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"integer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"speed": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"integer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"burrowSpeed": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"integer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"climbSpeed": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"integer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"flySpeed": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"integer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"swimSpeed": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"integer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cr": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"customCr": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"customProf": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"shieldBonus": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"natArmorBonus": {
|
||||||
|
"type": [
|
||||||
|
"integer",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"otherArmorDesc": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"hpText": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"speedDesc": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"customHP": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"customSpeed": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"hover": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"blind": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"blindsight": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"integer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"darkvision": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"integer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tremorsense": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"integer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"truesight": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"integer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"telepathy": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"understandsBut": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"shortName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"pluralName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"isLegendary": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"legendariesDescription": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"isMythic": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"mythicDescription": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"isLair": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"lairDescription": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"lairDescriptionEnd": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"isRegional": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"regionalDescription": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"regionalDescriptionEnd": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"abilities": {
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"bonusActions": {
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"reactions": {
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"legendaries": {
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"mythics": {
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"lairs": {
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"regionals": {
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"skills": {
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"sthrows": {
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"languages": {
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"conditions": {
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"damagetypes": {
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"specialdamage": {
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user