Adds import support from our other project.
This commit is contained in:
@@ -35,7 +35,7 @@
|
||||
<data android:mimeType="application/octet-stream" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Intent filter for file:// URIs ending in .monster or .monster.txt -->
|
||||
<!-- Intent filter for file:// URIs ending in .monster, .monster.txt, .card, or .card.txt -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.intent.action.EDIT" />
|
||||
@@ -46,6 +46,8 @@
|
||||
<data android:scheme="file" android:host="*" />
|
||||
<data android:pathPattern=".*\\.monster" />
|
||||
<data android:pathPattern=".*\\.monster\\.txt" />
|
||||
<data android:pathPattern=".*\\.card" />
|
||||
<data android:pathPattern=".*\\.card\\.txt" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Intent filter for explicitly sharing files/text to Monster Cards -->
|
||||
|
||||
@@ -20,7 +20,7 @@ import com.majinnaibu.monstercards.models.DashboardMonster;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.models.MonsterFTS;
|
||||
|
||||
@Database(entities = {Monster.class, MonsterFTS.class, Collection.class, CollectionMonster.class, DashboardMonster.class}, version = 6)
|
||||
@Database(entities = {Monster.class, MonsterFTS.class, Collection.class, CollectionMonster.class, DashboardMonster.class}, version = 8)
|
||||
@TypeConverters({
|
||||
ArmorTypeConverter.class,
|
||||
ChallengeRatingConverter.class,
|
||||
|
||||
@@ -143,7 +143,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
if (uri == null || !isMonsterFile(uri)) {
|
||||
if (uri != null) {
|
||||
Logger.logError("Ignored file because extension is not .monster or .monster.txt: " + uri);
|
||||
Logger.logError("Ignored file because extension is not supported (.monster, .card): " + uri);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -161,7 +161,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
return false;
|
||||
}
|
||||
String lowerName = fileName.toLowerCase(Locale.ROOT);
|
||||
return lowerName.endsWith(".monster") || lowerName.endsWith(".monster.txt");
|
||||
return lowerName.endsWith(".monster") || lowerName.endsWith(".monster.txt")
|
||||
|| lowerName.endsWith(".card") || lowerName.endsWith(".card.txt");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -66,6 +66,35 @@ public class MonsterCardsApplication extends Application {
|
||||
database.execSQL("ALTER TABLE monsters ADD COLUMN `mythic_actions_description` TEXT DEFAULT ''");
|
||||
}
|
||||
};
|
||||
private static final Migration MIGRATION_6_7 = new Migration(6, 7) {
|
||||
@Override
|
||||
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||
database.execSQL("ALTER TABLE monsters ADD COLUMN `player_name` TEXT NOT NULL DEFAULT ''");
|
||||
database.execSQL("ALTER TABLE monsters ADD COLUMN `background` TEXT NOT NULL DEFAULT ''");
|
||||
database.execSQL("ALTER TABLE monsters ADD COLUMN `personality_traits` TEXT NOT NULL DEFAULT ''");
|
||||
database.execSQL("ALTER TABLE monsters ADD COLUMN `ideals` TEXT NOT NULL DEFAULT ''");
|
||||
database.execSQL("ALTER TABLE monsters ADD COLUMN `bonds` TEXT NOT NULL DEFAULT ''");
|
||||
database.execSQL("ALTER TABLE monsters ADD COLUMN `flaws` TEXT NOT NULL DEFAULT ''");
|
||||
database.execSQL("ALTER TABLE monsters ADD COLUMN `age` TEXT NOT NULL DEFAULT ''");
|
||||
database.execSQL("ALTER TABLE monsters ADD COLUMN `height` TEXT NOT NULL DEFAULT ''");
|
||||
database.execSQL("ALTER TABLE monsters ADD COLUMN `weight` TEXT NOT NULL DEFAULT ''");
|
||||
database.execSQL("ALTER TABLE monsters ADD COLUMN `eyes` TEXT NOT NULL DEFAULT ''");
|
||||
database.execSQL("ALTER TABLE monsters ADD COLUMN `skin` TEXT NOT NULL DEFAULT ''");
|
||||
database.execSQL("ALTER TABLE monsters ADD COLUMN `hair` TEXT NOT NULL DEFAULT ''");
|
||||
database.execSQL("ALTER TABLE monsters ADD COLUMN `appearance` TEXT NOT NULL DEFAULT ''");
|
||||
database.execSQL("ALTER TABLE monsters ADD COLUMN `backstory` TEXT NOT NULL DEFAULT ''");
|
||||
database.execSQL("ALTER TABLE monsters ADD COLUMN `allies_and_organizations` TEXT NOT NULL DEFAULT ''");
|
||||
}
|
||||
};
|
||||
private static final Migration MIGRATION_7_8 = new Migration(7, 8) {
|
||||
@Override
|
||||
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS `new_monsters` (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `size` TEXT NOT NULL DEFAULT '', `type` TEXT NOT NULL DEFAULT '', `subtype` TEXT NOT NULL DEFAULT '', `alignment` TEXT NOT NULL DEFAULT '', `strength_score` INTEGER NOT NULL DEFAULT 10, `strength_saving_throw_advantage` TEXT DEFAULT 'none', `strength_saving_throw_proficiency` TEXT DEFAULT 'none', `dexterity_score` INTEGER NOT NULL DEFAULT 10, `dexterity_saving_throw_advantage` TEXT DEFAULT 'none', `dexterity_saving_throw_proficiency` TEXT DEFAULT 'none', `constitution_score` INTEGER NOT NULL DEFAULT 10, `constitution_saving_throw_advantage` TEXT DEFAULT 'none', `constitution_saving_throw_proficiency` TEXT DEFAULT 'none', `intelligence_score` INTEGER NOT NULL DEFAULT 10, `intelligence_saving_throw_advantage` TEXT DEFAULT 'none', `intelligence_saving_throw_proficiency` TEXT DEFAULT 'none', `wisdom_score` INTEGER NOT NULL DEFAULT 10, `wisdom_saving_throw_advantage` TEXT DEFAULT 'none', `wisdom_saving_throw_proficiency` TEXT DEFAULT 'none', `charisma_score` INTEGER NOT NULL DEFAULT 10, `charisma_saving_throw_advantage` TEXT DEFAULT 'none', `charisma_saving_throw_proficiency` TEXT DEFAULT 'none', `armor_type` TEXT DEFAULT 'none', `shield_bonus` INTEGER NOT NULL DEFAULT 0, `natural_armor_bonus` INTEGER NOT NULL DEFAULT 0, `other_armor_description` TEXT DEFAULT '', `hit_dice` INTEGER NOT NULL DEFAULT 1, `has_custom_hit_points` INTEGER NOT NULL, `custom_hit_points_description` TEXT DEFAULT '', `walk_speed` INTEGER NOT NULL DEFAULT 0, `burrow_speed` INTEGER NOT NULL DEFAULT 0, `climb_speed` INTEGER NOT NULL DEFAULT 0, `fly_speed` INTEGER NOT NULL DEFAULT 0, `can_hover` INTEGER NOT NULL DEFAULT false, `swim_speed` INTEGER NOT NULL DEFAULT 0, `has_custom_speed` INTEGER NOT NULL DEFAULT false, `custom_speed_description` TEXT, `challenge_rating` TEXT DEFAULT '1', `custom_challenge_rating_description` TEXT DEFAULT '', `custom_proficiency_bonus` INTEGER NOT NULL DEFAULT 0, `telepathy_range` INTEGER NOT NULL DEFAULT 0, `understands_but_description` TEXT DEFAULT '', `senses` TEXT DEFAULT '[]', `skills` TEXT DEFAULT '[]', `damage_immunities` TEXT DEFAULT '[]', `damage_resistances` TEXT DEFAULT '[]', `damage_vulnerabilities` TEXT DEFAULT '[]', `condition_immunities` TEXT DEFAULT '[]', `languages` TEXT DEFAULT '[]', `abilities` TEXT DEFAULT '[]', `actions` TEXT DEFAULT '[]', `reactions` TEXT DEFAULT '[]', `lair_actions` TEXT DEFAULT '[]', `legendary_actions` TEXT DEFAULT '[]', `regional_actions` TEXT DEFAULT '[]', `source_url` TEXT DEFAULT '', `bonus_actions` TEXT DEFAULT '[]', `mythic_actions` TEXT DEFAULT '[]', `legendary_actions_description` TEXT DEFAULT '', `lair_actions_description` TEXT DEFAULT '', `lair_actions_end_note` TEXT DEFAULT '', `regional_actions_description` TEXT DEFAULT '', `regional_actions_end_note` TEXT DEFAULT '', `mythic_actions_description` TEXT DEFAULT '', `player_name` TEXT NOT NULL DEFAULT '', `background` TEXT NOT NULL DEFAULT '', `personality_traits` TEXT NOT NULL DEFAULT '', `ideals` TEXT NOT NULL DEFAULT '', `bonds` TEXT NOT NULL DEFAULT '', `flaws` TEXT NOT NULL DEFAULT '', `age` TEXT NOT NULL DEFAULT '', `height` TEXT NOT NULL DEFAULT '', `weight` TEXT NOT NULL DEFAULT '', `eyes` TEXT NOT NULL DEFAULT '', `skin` TEXT NOT NULL DEFAULT '', `hair` TEXT NOT NULL DEFAULT '', `appearance` TEXT NOT NULL DEFAULT '', `backstory` TEXT NOT NULL DEFAULT '', `allies_and_organizations` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))");
|
||||
database.execSQL("INSERT INTO `new_monsters` SELECT id, name, size, type, subtype, alignment, strength_score, strength_saving_throw_advantage, strength_saving_throw_proficiency, dexterity_score, dexterity_saving_throw_advantage, dexterity_saving_throw_proficiency, constitution_score, constitution_saving_throw_advantage, constitution_saving_throw_proficiency, intelligence_score, intelligence_saving_throw_advantage, intelligence_saving_throw_proficiency, wisdom_score, wisdom_saving_throw_advantage, wisdom_saving_throw_proficiency, charisma_score, charisma_saving_throw_advantage, charisma_saving_throw_proficiency, armor_type, shield_bonus, natural_armor_bonus, other_armor_description, hit_dice, has_custom_hit_points, custom_hit_points_description, walk_speed, burrow_speed, climb_speed, fly_speed, can_hover, swim_speed, has_custom_speed, custom_speed_description, challenge_rating, custom_challenge_rating_description, custom_proficiency_bonus, telepathy_range, understands_but_description, senses, skills, damage_immunities, damage_resistances, damage_vulnerabilities, condition_immunities, languages, abilities, actions, reactions, lair_actions, legendary_actions, regional_actions, source_url, bonus_actions, mythic_actions, legendary_actions_description, lair_actions_description, lair_actions_end_note, regional_actions_description, regional_actions_end_note, mythic_actions_description, COALESCE(player_name, ''), COALESCE(background, ''), COALESCE(personality_traits, ''), COALESCE(ideals, ''), COALESCE(bonds, ''), COALESCE(flaws, ''), COALESCE(age, ''), COALESCE(height, ''), COALESCE(weight, ''), COALESCE(eyes, ''), COALESCE(skin, ''), COALESCE(hair, ''), COALESCE(appearance, ''), COALESCE(backstory, ''), COALESCE(allies_and_organizations, '') FROM `monsters`");
|
||||
database.execSQL("DROP TABLE `monsters`");
|
||||
database.execSQL("ALTER TABLE `new_monsters` RENAME TO `monsters`");
|
||||
}
|
||||
};
|
||||
private MonsterRepository m_monsterLibraryRepository;
|
||||
|
||||
|
||||
@@ -90,6 +119,8 @@ public class MonsterCardsApplication extends Application {
|
||||
.addMigrations(MIGRATION_3_4)
|
||||
.addMigrations(MIGRATION_4_5)
|
||||
.addMigrations(MIGRATION_5_6)
|
||||
.addMigrations(MIGRATION_6_7)
|
||||
.addMigrations(MIGRATION_7_8)
|
||||
.fallbackToDestructiveMigrationOnDowngrade()
|
||||
// .fallbackToDestructiveMigration()
|
||||
.build();
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
package com.majinnaibu.monstercards.exporters;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonNull;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||
import com.majinnaibu.monstercards.data.enums.ArmorType;
|
||||
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||
import com.majinnaibu.monstercards.models.Language;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.models.Skill;
|
||||
import com.majinnaibu.monstercards.models.Trait;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Open5eExporter {
|
||||
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
||||
|
||||
@NonNull
|
||||
public String exportMonster(@NonNull Monster monster) {
|
||||
JsonObject root = new JsonObject();
|
||||
root.addProperty("$schema", "../../../schema/entity.json");
|
||||
root.addProperty("uuid", monster.id != null ? monster.id.toString() : UUID.randomUUID().toString());
|
||||
root.addProperty("ruleset_id", "open5e");
|
||||
root.addProperty("entity_type", "character");
|
||||
root.addProperty("display_name", monster.name != null && !monster.name.isEmpty() ? monster.name : "Unnamed Entity");
|
||||
root.addProperty("description", buildDescription(monster));
|
||||
root.addProperty("version", "1.0.0");
|
||||
root.addProperty("template_id", "stat_block");
|
||||
|
||||
JsonObject props = new JsonObject();
|
||||
props.addProperty("name", monster.name != null ? monster.name : "");
|
||||
props.addProperty("size", monster.size != null ? monster.size : "");
|
||||
props.addProperty("type", monster.type != null ? monster.type : "");
|
||||
props.addProperty("subtype", monster.subtype != null ? monster.subtype : "");
|
||||
props.addProperty("alignment", monster.alignment != null ? monster.alignment : "");
|
||||
props.addProperty("armor_class", monster.getArmorClassValue());
|
||||
props.addProperty("armor_description", monster.getArmorClass());
|
||||
|
||||
if (monster.armorType != null && !monster.armorType.equals(ArmorType.NONE)) {
|
||||
JsonObject armorObj = new JsonObject();
|
||||
armorObj.addProperty("id", monster.armorType.stringValue);
|
||||
armorObj.addProperty("name", monster.armorType.displayName);
|
||||
armorObj.addProperty("armor_category", getArmorCategory(monster.armorType));
|
||||
armorObj.addProperty("ac_base", monster.armorType.baseArmorClass);
|
||||
|
||||
Integer dexCap = getDexCap(monster.armorType);
|
||||
if (dexCap != null) {
|
||||
armorObj.addProperty("dex_cap", dexCap);
|
||||
} else {
|
||||
armorObj.add("dex_cap", JsonNull.INSTANCE);
|
||||
}
|
||||
armorObj.addProperty("stealth_disadvantage", isStealthDisadvantage(monster.armorType));
|
||||
props.add("equipped_armor", armorObj);
|
||||
} else {
|
||||
props.add("equipped_armor", JsonNull.INSTANCE);
|
||||
}
|
||||
|
||||
if (monster.shieldBonus > 0) {
|
||||
JsonObject shieldObj = new JsonObject();
|
||||
shieldObj.addProperty("id", "shield");
|
||||
shieldObj.addProperty("name", "Shield");
|
||||
shieldObj.addProperty("ac_bonus", monster.shieldBonus);
|
||||
props.add("equipped_shield", shieldObj);
|
||||
} else {
|
||||
props.add("equipped_shield", JsonNull.INSTANCE);
|
||||
}
|
||||
|
||||
JsonObject hpObj = new JsonObject();
|
||||
hpObj.addProperty("max", monster.getHitPointsValue());
|
||||
hpObj.addProperty("current", monster.getHitPointsValue());
|
||||
hpObj.addProperty("formula", monster.hasCustomHP && !monster.customHPDescription.isEmpty() ? monster.customHPDescription : monster.getHitPoints());
|
||||
hpObj.addProperty("hit_dice", monster.hitDice + "d8");
|
||||
props.add("hit_points", hpObj);
|
||||
|
||||
props.addProperty("speed", monster.walkSpeed);
|
||||
props.addProperty("speed_desc", monster.getSpeedText());
|
||||
props.addProperty("challenge_rating", monster.getChallengeRatingDescription());
|
||||
props.addProperty("cr", monster.challengeRating != null ? monster.challengeRating.stringValue : "1");
|
||||
|
||||
JsonObject abilitiesObj = new JsonObject();
|
||||
abilitiesObj.addProperty("strength", monster.strengthScore);
|
||||
abilitiesObj.addProperty("dexterity", monster.dexterityScore);
|
||||
abilitiesObj.addProperty("constitution", monster.constitutionScore);
|
||||
abilitiesObj.addProperty("intelligence", monster.intelligenceScore);
|
||||
abilitiesObj.addProperty("wisdom", monster.wisdomScore);
|
||||
abilitiesObj.addProperty("charisma", monster.charismaScore);
|
||||
props.add("abilities", abilitiesObj);
|
||||
|
||||
if (monster.playerName != null && !monster.playerName.isEmpty()) props.addProperty("player_name", monster.playerName);
|
||||
if (monster.background != null && !monster.background.isEmpty()) props.addProperty("background", monster.background);
|
||||
if (monster.personalityTraits != null && !monster.personalityTraits.isEmpty()) props.addProperty("personality_traits", monster.personalityTraits);
|
||||
if (monster.ideals != null && !monster.ideals.isEmpty()) props.addProperty("ideals", monster.ideals);
|
||||
if (monster.bonds != null && !monster.bonds.isEmpty()) props.addProperty("bonds", monster.bonds);
|
||||
if (monster.flaws != null && !monster.flaws.isEmpty()) props.addProperty("flaws", monster.flaws);
|
||||
|
||||
if (hasDetails(monster)) {
|
||||
JsonObject detailsObj = new JsonObject();
|
||||
if (monster.age != null && !monster.age.isEmpty()) detailsObj.addProperty("age", monster.age);
|
||||
if (monster.height != null && !monster.height.isEmpty()) detailsObj.addProperty("height", monster.height);
|
||||
if (monster.weight != null && !monster.weight.isEmpty()) detailsObj.addProperty("weight", monster.weight);
|
||||
if (monster.eyes != null && !monster.eyes.isEmpty()) detailsObj.addProperty("eyes", monster.eyes);
|
||||
if (monster.skin != null && !monster.skin.isEmpty()) detailsObj.addProperty("skin", monster.skin);
|
||||
if (monster.hair != null && !monster.hair.isEmpty()) detailsObj.addProperty("hair", monster.hair);
|
||||
if (monster.appearance != null && !monster.appearance.isEmpty()) detailsObj.addProperty("appearance", monster.appearance);
|
||||
if (monster.backstory != null && !monster.backstory.isEmpty()) detailsObj.addProperty("backstory", monster.backstory);
|
||||
if (monster.alliesAndOrganizations != null && !monster.alliesAndOrganizations.isEmpty()) detailsObj.addProperty("allies_and_organizations", monster.alliesAndOrganizations);
|
||||
props.add("character_details", detailsObj);
|
||||
}
|
||||
|
||||
JsonArray savesArr = buildSavingThrowsArray(monster);
|
||||
if (savesArr.size() > 0) props.add("saving_throws", savesArr);
|
||||
|
||||
JsonArray skillsArr = buildSkillsArray(monster);
|
||||
if (skillsArr.size() > 0) props.add("skills", skillsArr);
|
||||
|
||||
if (monster.damageImmunities != null && !monster.damageImmunities.isEmpty()) props.add("damage_immunities", toJsonArray(monster.damageImmunities));
|
||||
if (monster.damageResistances != null && !monster.damageResistances.isEmpty()) props.add("damage_resistances", toJsonArray(monster.damageResistances));
|
||||
if (monster.damageVulnerabilities != null && !monster.damageVulnerabilities.isEmpty()) props.add("damage_vulnerabilities", toJsonArray(monster.damageVulnerabilities));
|
||||
if (monster.conditionImmunities != null && !monster.conditionImmunities.isEmpty()) props.add("condition_immunities", toJsonArray(monster.conditionImmunities));
|
||||
|
||||
props.addProperty("senses", monster.getSensesDescription());
|
||||
|
||||
if (monster.languages != null && !monster.languages.isEmpty()) {
|
||||
JsonArray langArr = new JsonArray();
|
||||
for (Language l : monster.languages) {
|
||||
if (l != null && l.getName() != null && !l.getName().isEmpty()) {
|
||||
langArr.add(l.getName());
|
||||
}
|
||||
}
|
||||
props.add("languages", langArr);
|
||||
}
|
||||
|
||||
if (monster.abilities != null && !monster.abilities.isEmpty()) props.add("traits", traitsToJsonArray(monster.abilities));
|
||||
if (monster.actions != null && !monster.actions.isEmpty()) props.add("actions", traitsToJsonArray(monster.actions));
|
||||
if (monster.bonusActions != null && !monster.bonusActions.isEmpty()) props.add("bonus_actions", traitsToJsonArray(monster.bonusActions));
|
||||
if (monster.reactions != null && !monster.reactions.isEmpty()) props.add("reactions", traitsToJsonArray(monster.reactions));
|
||||
if (monster.legendaryActions != null && !monster.legendaryActions.isEmpty()) props.add("legendary_actions", traitsToJsonArray(monster.legendaryActions));
|
||||
if (monster.legendaryActionsDescription != null && !monster.legendaryActionsDescription.isEmpty()) props.addProperty("legendary_desc", monster.legendaryActionsDescription);
|
||||
if (monster.lairActions != null && !monster.lairActions.isEmpty()) props.add("lair_actions", traitsToJsonArray(monster.lairActions));
|
||||
if (monster.lairActionsDescription != null && !monster.lairActionsDescription.isEmpty()) props.addProperty("lair_desc", monster.lairActionsDescription);
|
||||
if (monster.regionalActions != null && !monster.regionalActions.isEmpty()) props.add("regional_effects", traitsToJsonArray(monster.regionalActions));
|
||||
if (monster.regionalActionsDescription != null && !monster.regionalActionsDescription.isEmpty()) props.addProperty("regional_desc", monster.regionalActionsDescription);
|
||||
if (monster.mythicActions != null && !monster.mythicActions.isEmpty()) props.add("mythic_actions", traitsToJsonArray(monster.mythicActions));
|
||||
if (monster.mythicActionsDescription != null && !monster.mythicActionsDescription.isEmpty()) props.addProperty("mythic_desc", monster.mythicActionsDescription);
|
||||
|
||||
if (monster.sourceUrl != null && !monster.sourceUrl.isEmpty()) props.addProperty("source_url", monster.sourceUrl);
|
||||
|
||||
root.add("properties", props);
|
||||
return GSON.toJson(root);
|
||||
}
|
||||
|
||||
private String getArmorCategory(ArmorType armorType) {
|
||||
switch (armorType) {
|
||||
case PADDED:
|
||||
case LEATHER:
|
||||
case STUDDED_LEATHER:
|
||||
return "light";
|
||||
case HIDE:
|
||||
case CHAIN_SHIRT:
|
||||
case SCALE_MAIL:
|
||||
case BREASTPLATE:
|
||||
case HALF_PLATE:
|
||||
return "medium";
|
||||
case RING_MAIL:
|
||||
case CHAIN_MAIL:
|
||||
case SPLINT_MAIL:
|
||||
case PLATE_MAIL:
|
||||
return "heavy";
|
||||
default:
|
||||
return "natural";
|
||||
}
|
||||
}
|
||||
|
||||
private Integer getDexCap(ArmorType armorType) {
|
||||
switch (armorType) {
|
||||
case HIDE:
|
||||
case CHAIN_SHIRT:
|
||||
case SCALE_MAIL:
|
||||
case BREASTPLATE:
|
||||
case HALF_PLATE:
|
||||
return 2;
|
||||
case RING_MAIL:
|
||||
case CHAIN_MAIL:
|
||||
case SPLINT_MAIL:
|
||||
case PLATE_MAIL:
|
||||
return 0;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isStealthDisadvantage(ArmorType armorType) {
|
||||
switch (armorType) {
|
||||
case PADDED:
|
||||
case SCALE_MAIL:
|
||||
case HALF_PLATE:
|
||||
case RING_MAIL:
|
||||
case CHAIN_MAIL:
|
||||
case SPLINT_MAIL:
|
||||
case PLATE_MAIL:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasDetails(Monster monster) {
|
||||
return (monster.age != null && !monster.age.isEmpty()) ||
|
||||
(monster.height != null && !monster.height.isEmpty()) ||
|
||||
(monster.weight != null && !monster.weight.isEmpty()) ||
|
||||
(monster.eyes != null && !monster.eyes.isEmpty()) ||
|
||||
(monster.skin != null && !monster.skin.isEmpty()) ||
|
||||
(monster.hair != null && !monster.hair.isEmpty()) ||
|
||||
(monster.appearance != null && !monster.appearance.isEmpty()) ||
|
||||
(monster.backstory != null && !monster.backstory.isEmpty()) ||
|
||||
(monster.alliesAndOrganizations != null && !monster.alliesAndOrganizations.isEmpty());
|
||||
}
|
||||
|
||||
private String buildDescription(Monster monster) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (monster.size != null && !monster.size.isEmpty()) sb.append(monster.size).append(" ");
|
||||
if (monster.type != null && !monster.type.isEmpty()) sb.append(monster.type);
|
||||
if (monster.subtype != null && !monster.subtype.isEmpty()) sb.append(" (").append(monster.subtype).append(")");
|
||||
if (monster.alignment != null && !monster.alignment.isEmpty()) sb.append(", ").append(monster.alignment);
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
private JsonArray buildSavingThrowsArray(Monster monster) {
|
||||
JsonArray arr = new JsonArray();
|
||||
checkSave(arr, "Strength", monster.getSavingThrowProficiencyType(AbilityScore.STRENGTH), monster.getAbilityModifier(AbilityScore.STRENGTH) + monster.getProficiencyBonus(monster.getSavingThrowProficiencyType(AbilityScore.STRENGTH)));
|
||||
checkSave(arr, "Dexterity", monster.getSavingThrowProficiencyType(AbilityScore.DEXTERITY), monster.getAbilityModifier(AbilityScore.DEXTERITY) + monster.getProficiencyBonus(monster.getSavingThrowProficiencyType(AbilityScore.DEXTERITY)));
|
||||
checkSave(arr, "Constitution", monster.getSavingThrowProficiencyType(AbilityScore.CONSTITUTION), monster.getAbilityModifier(AbilityScore.CONSTITUTION) + monster.getProficiencyBonus(monster.getSavingThrowProficiencyType(AbilityScore.CONSTITUTION)));
|
||||
checkSave(arr, "Intelligence", monster.getSavingThrowProficiencyType(AbilityScore.INTELLIGENCE), monster.getAbilityModifier(AbilityScore.INTELLIGENCE) + monster.getProficiencyBonus(monster.getSavingThrowProficiencyType(AbilityScore.INTELLIGENCE)));
|
||||
checkSave(arr, "Wisdom", monster.getSavingThrowProficiencyType(AbilityScore.WISDOM), monster.getAbilityModifier(AbilityScore.WISDOM) + monster.getProficiencyBonus(monster.getSavingThrowProficiencyType(AbilityScore.WISDOM)));
|
||||
checkSave(arr, "Charisma", monster.getSavingThrowProficiencyType(AbilityScore.CHARISMA), monster.getAbilityModifier(AbilityScore.CHARISMA) + monster.getProficiencyBonus(monster.getSavingThrowProficiencyType(AbilityScore.CHARISMA)));
|
||||
return arr;
|
||||
}
|
||||
|
||||
private void checkSave(JsonArray arr, String statName, ProficiencyType prof, int bonus) {
|
||||
if (prof != null && !prof.equals(ProficiencyType.NONE)) {
|
||||
arr.add(String.format(Locale.ROOT, "%s %+d", statName, bonus));
|
||||
}
|
||||
}
|
||||
|
||||
private JsonArray buildSkillsArray(Monster monster) {
|
||||
JsonArray arr = new JsonArray();
|
||||
if (monster.skills != null) {
|
||||
for (Skill s : monster.skills) {
|
||||
if (s != null && s.proficiencyType != null && !s.proficiencyType.equals(ProficiencyType.NONE)) {
|
||||
int bonus = s.getSkillBonus(monster);
|
||||
arr.add(String.format(Locale.ROOT, "%s %+d", s.name.toLowerCase(Locale.ROOT), bonus));
|
||||
}
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
private JsonArray toJsonArray(Collection<String> items) {
|
||||
JsonArray arr = new JsonArray();
|
||||
for (String s : items) {
|
||||
if (s != null && !s.isEmpty()) arr.add(s);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
private JsonArray traitsToJsonArray(List<Trait> traits) {
|
||||
JsonArray arr = new JsonArray();
|
||||
for (Trait t : traits) {
|
||||
if (t != null && t.name != null && !t.name.isEmpty()) {
|
||||
JsonObject obj = new JsonObject();
|
||||
obj.addProperty("name", t.name);
|
||||
obj.addProperty("desc", t.description != null ? t.description : "");
|
||||
arr.add(obj);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import androidx.annotation.NonNull;
|
||||
import com.majinnaibu.monstercards.importers.DnDBeyondImporter;
|
||||
import com.majinnaibu.monstercards.importers.EntityImporter;
|
||||
import com.majinnaibu.monstercards.importers.MonsterJsonImporter;
|
||||
import com.majinnaibu.monstercards.importers.Open5eImporter;
|
||||
import com.majinnaibu.monstercards.importers.TetraCubeMonsterImporter;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
|
||||
@@ -16,6 +17,7 @@ public class MonsterImportHelper {
|
||||
|
||||
static {
|
||||
IMPORTERS.add(new MonsterJsonImporter());
|
||||
IMPORTERS.add(new Open5eImporter());
|
||||
IMPORTERS.add(new TetraCubeMonsterImporter());
|
||||
IMPORTERS.add(new DnDBeyondImporter());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,387 @@
|
||||
package com.majinnaibu.monstercards.importers;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
||||
import com.majinnaibu.monstercards.data.enums.ArmorType;
|
||||
import com.majinnaibu.monstercards.data.enums.ChallengeRating;
|
||||
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||
import com.majinnaibu.monstercards.models.Language;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.models.Skill;
|
||||
import com.majinnaibu.monstercards.models.Trait;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class Open5eImporter implements EntityImporter<Monster> {
|
||||
|
||||
private static final Pattern HIT_DICE_PATTERN = Pattern.compile("(\\d+)d(\\d+)");
|
||||
|
||||
@Override
|
||||
public boolean canImport(@NonNull String input) {
|
||||
String trimmed = input.trim();
|
||||
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
JsonElement element = JsonParser.parseString(trimmed);
|
||||
if (!element.isJsonObject()) {
|
||||
return false;
|
||||
}
|
||||
JsonObject root = element.getAsJsonObject();
|
||||
if (root.has("ruleset_id") && root.get("ruleset_id").getAsString().equals("open5e")) {
|
||||
return true;
|
||||
}
|
||||
if (root.has("properties") && root.getAsJsonObject("properties").has("abilities")) {
|
||||
return true;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Monster parse(@NonNull String input) throws Exception {
|
||||
JsonObject root = JsonParser.parseString(input.trim()).getAsJsonObject();
|
||||
|
||||
Monster monster = new Monster();
|
||||
|
||||
if (root.has("uuid") && !root.get("uuid").isJsonNull()) {
|
||||
try {
|
||||
monster.id = UUID.fromString(root.get("uuid").getAsString());
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
if (root.has("display_name") && !root.get("display_name").isJsonNull()) {
|
||||
monster.name = root.get("display_name").getAsString();
|
||||
}
|
||||
|
||||
JsonObject props = root.has("properties") ? root.getAsJsonObject("properties") : root;
|
||||
|
||||
if (monster.name == null || monster.name.isEmpty()) {
|
||||
monster.name = getString(props, "name", "Unnamed Entity");
|
||||
}
|
||||
|
||||
monster.size = getString(props, "size");
|
||||
monster.type = getString(props, "type");
|
||||
monster.subtype = getString(props, "subtype");
|
||||
monster.alignment = getString(props, "alignment");
|
||||
|
||||
parseArmorClass(props, monster);
|
||||
parseHitPoints(props, monster);
|
||||
|
||||
monster.walkSpeed = getInt(props, "speed", 30);
|
||||
if (props.has("speed_desc")) {
|
||||
String speedDesc = getString(props, "speed_desc");
|
||||
if (!speedDesc.isEmpty()) {
|
||||
monster.hasCustomSpeed = true;
|
||||
monster.customSpeedDescription = speedDesc;
|
||||
}
|
||||
}
|
||||
|
||||
parseChallengeRating(props, monster);
|
||||
parseAbilities(props, monster);
|
||||
parseIdentityFields(props, monster);
|
||||
|
||||
parseSavingThrows(props, monster);
|
||||
parseSkills(props, monster);
|
||||
parseSenses(props, monster);
|
||||
parseLanguages(props, monster);
|
||||
parseDamageAndConditions(props, monster);
|
||||
|
||||
parseTraitsAndActions(props, monster);
|
||||
|
||||
if (props.has("source_url")) {
|
||||
monster.sourceUrl = getString(props, "source_url");
|
||||
}
|
||||
|
||||
return monster;
|
||||
}
|
||||
|
||||
private void parseArmorClass(JsonObject props, Monster monster) {
|
||||
if (props.has("equipped_armor") && props.get("equipped_armor").isJsonObject()) {
|
||||
JsonObject armorObj = props.getAsJsonObject("equipped_armor");
|
||||
String armorId = getString(armorObj, "id");
|
||||
ArmorType armorType = ArmorType.valueOfString(armorId);
|
||||
if (armorType != ArmorType.NONE) {
|
||||
monster.armorType = armorType;
|
||||
}
|
||||
}
|
||||
|
||||
if (props.has("equipped_shield") && props.get("equipped_shield").isJsonObject()) {
|
||||
JsonObject shieldObj = props.getAsJsonObject("equipped_shield");
|
||||
monster.shieldBonus = getInt(shieldObj, "ac_bonus", 2);
|
||||
}
|
||||
|
||||
if (monster.armorType == ArmorType.NONE && props.has("armor_description")) {
|
||||
String armorDesc = getString(props, "armor_description");
|
||||
if (!armorDesc.isEmpty()) {
|
||||
monster.otherArmorDescription = armorDesc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseHitPoints(JsonObject props, Monster monster) {
|
||||
if (props.has("hit_points") && props.get("hit_points").isJsonObject()) {
|
||||
JsonObject hpObj = props.getAsJsonObject("hit_points");
|
||||
int maxHp = getInt(hpObj, "max", getInt(hpObj, "current", 10));
|
||||
if (hpObj.has("hit_dice") && !hpObj.get("hit_dice").isJsonNull()) {
|
||||
String hitDiceStr = hpObj.get("hit_dice").getAsString();
|
||||
Matcher m = HIT_DICE_PATTERN.matcher(hitDiceStr);
|
||||
if (m.find()) {
|
||||
monster.hitDice = Integer.parseInt(m.group(1));
|
||||
}
|
||||
}
|
||||
if (hpObj.has("formula") && !hpObj.get("formula").isJsonNull()) {
|
||||
monster.hasCustomHP = true;
|
||||
monster.customHPDescription = String.format(Locale.ROOT, "%d (%s)", maxHp, hpObj.get("formula").getAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseChallengeRating(JsonObject props, Monster monster) {
|
||||
String crStr = "";
|
||||
if (props.has("cr") && !props.get("cr").isJsonNull()) {
|
||||
crStr = props.get("cr").getAsString();
|
||||
} else if (props.has("challenge_rating") && !props.get("challenge_rating").isJsonNull()) {
|
||||
crStr = props.get("challenge_rating").getAsString();
|
||||
}
|
||||
if (!crStr.isEmpty()) {
|
||||
monster.challengeRating = ChallengeRating.valueOfString(crStr);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseAbilities(JsonObject props, Monster monster) {
|
||||
if (!props.has("abilities") || !props.get("abilities").isJsonObject()) {
|
||||
return;
|
||||
}
|
||||
JsonObject abs = props.getAsJsonObject("abilities");
|
||||
monster.strengthScore = extractAbilityScore(abs, "strength");
|
||||
monster.dexterityScore = extractAbilityScore(abs, "dexterity");
|
||||
monster.constitutionScore = extractAbilityScore(abs, "constitution");
|
||||
monster.intelligenceScore = extractAbilityScore(abs, "intelligence");
|
||||
monster.wisdomScore = extractAbilityScore(abs, "wisdom");
|
||||
monster.charismaScore = extractAbilityScore(abs, "charisma");
|
||||
}
|
||||
|
||||
private int extractAbilityScore(JsonObject abs, String key) {
|
||||
if (!abs.has(key) || abs.get(key).isJsonNull()) {
|
||||
return 10;
|
||||
}
|
||||
JsonElement el = abs.get(key);
|
||||
if (el.isJsonPrimitive()) {
|
||||
return el.getAsInt();
|
||||
} else if (el.isJsonObject() && el.getAsJsonObject().has("value")) {
|
||||
return el.getAsJsonObject().get("value").getAsInt();
|
||||
}
|
||||
return 10;
|
||||
}
|
||||
|
||||
private void parseIdentityFields(JsonObject props, Monster monster) {
|
||||
monster.playerName = getString(props, "player_name");
|
||||
monster.background = getString(props, "background");
|
||||
monster.personalityTraits = getString(props, "personality_traits");
|
||||
monster.ideals = getString(props, "ideals");
|
||||
monster.bonds = getString(props, "bonds");
|
||||
monster.flaws = getString(props, "flaws");
|
||||
|
||||
if (props.has("character_details") && props.get("character_details").isJsonObject()) {
|
||||
JsonObject details = props.getAsJsonObject("character_details");
|
||||
monster.age = getString(details, "age");
|
||||
monster.height = getString(details, "height");
|
||||
monster.weight = getString(details, "weight");
|
||||
monster.eyes = getString(details, "eyes");
|
||||
monster.skin = getString(details, "skin");
|
||||
monster.hair = getString(details, "hair");
|
||||
monster.appearance = getString(details, "appearance");
|
||||
monster.backstory = getString(details, "backstory");
|
||||
monster.alliesAndOrganizations = getString(details, "allies_and_organizations");
|
||||
}
|
||||
}
|
||||
|
||||
private void parseSavingThrows(JsonObject props, Monster monster) {
|
||||
if (!props.has("saving_throws")) return;
|
||||
JsonElement savesEl = props.get("saving_throws");
|
||||
if (savesEl.isJsonArray()) {
|
||||
JsonArray arr = savesEl.getAsJsonArray();
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
String saveStr = arr.get(i).getAsString().toLowerCase(Locale.ROOT);
|
||||
applySaveProficiency(monster, saveStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applySaveProficiency(Monster monster, String saveStr) {
|
||||
if (saveStr.contains("str")) monster.strengthSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||
else if (saveStr.contains("dex")) monster.dexteritySavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||
else if (saveStr.contains("con")) monster.constitutionSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||
else if (saveStr.contains("int")) monster.intelligenceSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||
else if (saveStr.contains("wis")) monster.wisdomSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||
else if (saveStr.contains("cha")) monster.charismaSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||
}
|
||||
|
||||
private void parseSkills(JsonObject props, Monster monster) {
|
||||
if (!props.has("skills")) return;
|
||||
JsonElement skillsEl = props.get("skills");
|
||||
if (skillsEl.isJsonArray()) {
|
||||
JsonArray arr = skillsEl.getAsJsonArray();
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
String skillStr = arr.get(i).getAsString();
|
||||
String skillName = skillStr.replaceAll("[+-]\\d+", "").trim();
|
||||
if (!skillName.isEmpty()) {
|
||||
AbilityScore stat = getAbilityForSkill(skillName);
|
||||
monster.skills.add(new Skill(capitalize(skillName), stat, AdvantageType.NONE, ProficiencyType.PROFICIENT));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AbilityScore getAbilityForSkill(String skillName) {
|
||||
String lower = skillName.toLowerCase(Locale.ROOT);
|
||||
if (lower.contains("acro") || lower.contains("sleight") || lower.contains("stealth")) return AbilityScore.DEXTERITY;
|
||||
if (lower.contains("arca") || lower.contains("hist") || lower.contains("inve") || lower.contains("natu") || lower.contains("reli")) return AbilityScore.INTELLIGENCE;
|
||||
if (lower.contains("anim") || lower.contains("insi") || lower.contains("medi") || lower.contains("perc") || lower.contains("surv")) return AbilityScore.WISDOM;
|
||||
if (lower.contains("dece") || lower.contains("inti") || lower.contains("perf") || lower.contains("pers")) return AbilityScore.CHARISMA;
|
||||
return AbilityScore.STRENGTH;
|
||||
}
|
||||
|
||||
private void parseSenses(JsonObject props, Monster monster) {
|
||||
if (props.has("senses")) {
|
||||
String sensesStr = getString(props, "senses");
|
||||
if (!sensesStr.isEmpty()) {
|
||||
String[] parts = sensesStr.split(",");
|
||||
for (String p : parts) {
|
||||
if (!p.trim().isEmpty()) {
|
||||
monster.senses.add(p.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseLanguages(JsonObject props, Monster monster) {
|
||||
if (props.has("languages")) {
|
||||
JsonElement langEl = props.get("languages");
|
||||
if (langEl.isJsonArray()) {
|
||||
JsonArray arr = langEl.getAsJsonArray();
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
String langName = arr.get(i).getAsString();
|
||||
if (!langName.isEmpty()) {
|
||||
monster.languages.add(new Language(langName, true));
|
||||
}
|
||||
}
|
||||
} else if (langEl.isJsonPrimitive()) {
|
||||
String langStr = langEl.getAsString();
|
||||
for (String l : langStr.split(",")) {
|
||||
if (!l.trim().isEmpty()) {
|
||||
monster.languages.add(new Language(l.trim(), true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseDamageAndConditions(JsonObject props, Monster monster) {
|
||||
parseStringSet(props, "damage_immunities", monster.damageImmunities);
|
||||
parseStringSet(props, "damage_resistances", monster.damageResistances);
|
||||
parseStringSet(props, "damage_vulnerabilities", monster.damageVulnerabilities);
|
||||
parseStringSet(props, "condition_immunities", monster.conditionImmunities);
|
||||
}
|
||||
|
||||
private void parseStringSet(JsonObject props, String key, java.util.Set<String> set) {
|
||||
if (!props.has(key)) return;
|
||||
JsonElement el = props.get(key);
|
||||
if (el.isJsonArray()) {
|
||||
JsonArray arr = el.getAsJsonArray();
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
String val = arr.get(i).getAsString();
|
||||
if (!val.isEmpty()) set.add(capitalize(val));
|
||||
}
|
||||
} else if (el.isJsonPrimitive()) {
|
||||
for (String val : el.getAsString().split(",")) {
|
||||
if (!val.trim().isEmpty()) set.add(capitalize(val.trim()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseTraitsAndActions(JsonObject props, Monster monster) {
|
||||
parseTraitList(props, "traits", monster.abilities);
|
||||
parseTraitList(props, "actions", monster.actions);
|
||||
parseTraitList(props, "bonus_actions", monster.bonusActions);
|
||||
parseTraitList(props, "reactions", monster.reactions);
|
||||
parseTraitList(props, "legendary_actions", monster.legendaryActions);
|
||||
parseTraitList(props, "lair_actions", monster.lairActions);
|
||||
parseTraitList(props, "regional_effects", monster.regionalActions);
|
||||
parseTraitList(props, "mythic_actions", monster.mythicActions);
|
||||
|
||||
if (props.has("legendary_desc")) monster.legendaryActionsDescription = getString(props, "legendary_desc");
|
||||
if (props.has("lair_desc")) monster.lairActionsDescription = getString(props, "lair_desc");
|
||||
if (props.has("lair_end_note")) monster.lairActionsEndNote = getString(props, "lair_end_note");
|
||||
if (props.has("regional_desc")) monster.regionalActionsDescription = getString(props, "regional_desc");
|
||||
if (props.has("regional_end_note")) monster.regionalActionsEndNote = getString(props, "regional_end_note");
|
||||
if (props.has("mythic_desc")) monster.mythicActionsDescription = getString(props, "mythic_desc");
|
||||
}
|
||||
|
||||
private void parseTraitList(JsonObject props, String key, List<Trait> list) {
|
||||
if (!props.has(key) || !props.get(key).isJsonArray()) return;
|
||||
JsonArray arr = props.getAsJsonArray(key);
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
JsonElement el = arr.get(i);
|
||||
if (el.isJsonObject()) {
|
||||
JsonObject obj = el.getAsJsonObject();
|
||||
String name = getString(obj, "name");
|
||||
String desc = getString(obj, "desc");
|
||||
if (desc.isEmpty()) desc = getString(obj, "description");
|
||||
if (!name.isEmpty()) {
|
||||
list.add(new Trait(name, desc));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getString(JsonObject obj, String key) {
|
||||
return getString(obj, key, "");
|
||||
}
|
||||
|
||||
private static String getString(JsonObject obj, String key, String defaultVal) {
|
||||
if (obj.has(key) && !obj.get(key).isJsonNull()) {
|
||||
return obj.get(key).getAsString();
|
||||
}
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
private static int getInt(JsonObject obj, String key, int defaultVal) {
|
||||
if (obj.has(key) && !obj.get(key).isJsonNull()) {
|
||||
try {
|
||||
return obj.get(key).getAsInt();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
private static String capitalize(String str) {
|
||||
if (str == null || str.isEmpty()) return str;
|
||||
String[] words = str.split("\\s+");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String w : words) {
|
||||
if (!w.isEmpty()) {
|
||||
if (sb.length() > 0) sb.append(" ");
|
||||
sb.append(Character.toUpperCase(w.charAt(0))).append(w.substring(1).toLowerCase(Locale.ROOT));
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -233,6 +233,66 @@ public class Monster {
|
||||
@ColumnInfo(name = "mythic_actions_description", defaultValue = "")
|
||||
public String mythicActionsDescription;
|
||||
|
||||
@NonNull
|
||||
@ColumnInfo(name = "player_name", defaultValue = "")
|
||||
public String playerName;
|
||||
|
||||
@NonNull
|
||||
@ColumnInfo(name = "background", defaultValue = "")
|
||||
public String background;
|
||||
|
||||
@NonNull
|
||||
@ColumnInfo(name = "personality_traits", defaultValue = "")
|
||||
public String personalityTraits;
|
||||
|
||||
@NonNull
|
||||
@ColumnInfo(name = "ideals", defaultValue = "")
|
||||
public String ideals;
|
||||
|
||||
@NonNull
|
||||
@ColumnInfo(name = "bonds", defaultValue = "")
|
||||
public String bonds;
|
||||
|
||||
@NonNull
|
||||
@ColumnInfo(name = "flaws", defaultValue = "")
|
||||
public String flaws;
|
||||
|
||||
@NonNull
|
||||
@ColumnInfo(name = "age", defaultValue = "")
|
||||
public String age;
|
||||
|
||||
@NonNull
|
||||
@ColumnInfo(name = "height", defaultValue = "")
|
||||
public String height;
|
||||
|
||||
@NonNull
|
||||
@ColumnInfo(name = "weight", defaultValue = "")
|
||||
public String weight;
|
||||
|
||||
@NonNull
|
||||
@ColumnInfo(name = "eyes", defaultValue = "")
|
||||
public String eyes;
|
||||
|
||||
@NonNull
|
||||
@ColumnInfo(name = "skin", defaultValue = "")
|
||||
public String skin;
|
||||
|
||||
@NonNull
|
||||
@ColumnInfo(name = "hair", defaultValue = "")
|
||||
public String hair;
|
||||
|
||||
@NonNull
|
||||
@ColumnInfo(name = "appearance", defaultValue = "")
|
||||
public String appearance;
|
||||
|
||||
@NonNull
|
||||
@ColumnInfo(name = "backstory", defaultValue = "")
|
||||
public String backstory;
|
||||
|
||||
@NonNull
|
||||
@ColumnInfo(name = "allies_and_organizations", defaultValue = "")
|
||||
public String alliesAndOrganizations;
|
||||
|
||||
public Monster() {
|
||||
id = UUID.randomUUID();
|
||||
name = "";
|
||||
@@ -278,6 +338,21 @@ public class Monster {
|
||||
wisdomSavingThrowProficiency = ProficiencyType.NONE;
|
||||
charismaSavingThrowAdvantage = AdvantageType.NONE;
|
||||
charismaSavingThrowProficiency = ProficiencyType.NONE;
|
||||
playerName = "";
|
||||
background = "";
|
||||
personalityTraits = "";
|
||||
ideals = "";
|
||||
bonds = "";
|
||||
flaws = "";
|
||||
age = "";
|
||||
height = "";
|
||||
weight = "";
|
||||
eyes = "";
|
||||
skin = "";
|
||||
hair = "";
|
||||
appearance = "";
|
||||
backstory = "";
|
||||
alliesAndOrganizations = "";
|
||||
|
||||
|
||||
skills = new HashSet<>();
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.majinnaibu.monstercards.MainActivity;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
import com.majinnaibu.monstercards.importers.DnDBeyondImporter;
|
||||
import com.majinnaibu.monstercards.importers.Open5eImporter;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.ui.monster.MonsterDetailFragmentDirections;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
@@ -88,9 +89,12 @@ public class LibraryFragment extends MCFragment {
|
||||
ClipData clip = clipboard.getPrimaryClip();
|
||||
if (clip != null && clip.getItemCount() > 0) {
|
||||
CharSequence clipText = clip.getItemAt(0).getText();
|
||||
if (clipText != null && new DnDBeyondImporter().canImport(clipText.toString())) {
|
||||
input.setText(clipText.toString().trim());
|
||||
input.selectAll();
|
||||
if (clipText != null) {
|
||||
String clipStr = clipText.toString().trim();
|
||||
if (new DnDBeyondImporter().canImport(clipStr) || new Open5eImporter().canImport(clipStr)) {
|
||||
input.setText(clipStr);
|
||||
input.selectAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,11 +162,11 @@
|
||||
<string name="snackbar_collection_added_to_dashboard">Added collection %1$s to Dashboard</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="action_import_from_url">Import from URL...</string>
|
||||
<string name="dialog_import_url_title">Import from D&D Beyond</string>
|
||||
<string name="dialog_import_url_message">Enter or paste a D&D Beyond character URL or Character ID:</string>
|
||||
<string name="dialog_import_url_hint">e.g. https://www.dndbeyond.com/characters/49074997/s7sLyX</string>
|
||||
<string name="action_import_from_url">Import Entity / Character...</string>
|
||||
<string name="dialog_import_url_title">Import Entity or 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_hint">e.g. D&D Beyond URL or Open5e JSON</string>
|
||||
<string name="dialog_import">Import</string>
|
||||
<string name="toast_importing_url">Importing character from D&D Beyond...</string>
|
||||
<string name="failed_to_import_url">Failed to import character. Please check the URL or character privacy settings.</string>
|
||||
<string name="toast_importing_url">Importing entity...</string>
|
||||
<string name="failed_to_import_url">Failed to import entity. Please check the URL, ID, or JSON format.</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user