Refactors monster importers to a common interface.
This commit is contained in:
@@ -2,310 +2,33 @@ package com.majinnaibu.monstercards.helpers;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.google.gson.JsonArray;
|
import com.majinnaibu.monstercards.importers.EntityImporter;
|
||||||
import com.google.gson.JsonElement;
|
import com.majinnaibu.monstercards.importers.TetraCubeMonsterImporter;
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.JsonParser;
|
|
||||||
import com.google.gson.JsonPrimitive;
|
|
||||||
import com.majinnaibu.monstercards.data.converters.ArmorTypeConverter;
|
|
||||||
import com.majinnaibu.monstercards.data.converters.ChallengeRatingConverter;
|
|
||||||
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
|
||||||
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
|
||||||
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
|
||||||
import com.majinnaibu.monstercards.models.Language;
|
|
||||||
import com.majinnaibu.monstercards.models.Monster;
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
import com.majinnaibu.monstercards.models.Skill;
|
|
||||||
import com.majinnaibu.monstercards.models.Trait;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class MonsterImportHelper {
|
public class MonsterImportHelper {
|
||||||
|
private static final List<EntityImporter<Monster>> IMPORTERS = new ArrayList<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
IMPORTERS.add(new TetraCubeMonsterImporter());
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static Monster fromJSON(String json) {
|
public static Monster fromJSON(String json) {
|
||||||
JsonObject rootDict = JsonParser.parseString(json).getAsJsonObject();
|
if (json != null) {
|
||||||
|
for (EntityImporter<Monster> importer : IMPORTERS) {
|
||||||
Monster monster = new Monster();
|
if (importer.canImport(json)) {
|
||||||
monster.name = Helpers.getString(rootDict, "name");
|
try {
|
||||||
monster.size = Helpers.getString(rootDict, "size");
|
return importer.parse(json);
|
||||||
monster.type = Helpers.getString(rootDict, "type");
|
} catch (Exception e) {
|
||||||
monster.subtype = Helpers.getString(rootDict, "tag");
|
throw new IllegalArgumentException("Failed to parse monster JSON", e);
|
||||||
monster.alignment = Helpers.getString(rootDict, "alignment");
|
|
||||||
monster.hitDice = Helpers.getInt(rootDict, "hitDice");
|
|
||||||
monster.armorType = ArmorTypeConverter.armorTypeFromStringValue(Helpers.getString(rootDict, "armorName"));
|
|
||||||
monster.shieldBonus = Helpers.getInt(rootDict, "shieldBonus");
|
|
||||||
monster.naturalArmorBonus = Helpers.getInt(rootDict, "natArmorBonus");
|
|
||||||
monster.otherArmorDescription = Helpers.getString(rootDict, "otherArmorDesc");
|
|
||||||
monster.walkSpeed = Helpers.getInt(rootDict, "speed");
|
|
||||||
monster.burrowSpeed = Helpers.getInt(rootDict, "burrowSpeed");
|
|
||||||
monster.climbSpeed = Helpers.getInt(rootDict, "climbSpeed");
|
|
||||||
monster.flySpeed = Helpers.getInt(rootDict, "flySpeed");
|
|
||||||
monster.canHover = Helpers.getBool(rootDict, "hover");
|
|
||||||
monster.swimSpeed = Helpers.getInt(rootDict, "swimSpeed");
|
|
||||||
monster.hasCustomHP = Helpers.getBool(rootDict, "customHP");
|
|
||||||
monster.hasCustomSpeed = Helpers.getBool(rootDict, "customSpeed");
|
|
||||||
monster.customHPDescription = Helpers.getString(rootDict, "hpText");
|
|
||||||
monster.customSpeedDescription = Helpers.getString(rootDict, "speedDesc");
|
|
||||||
monster.strengthScore = Helpers.getInt(rootDict, "strPoints");
|
|
||||||
monster.dexterityScore = Helpers.getInt(rootDict, "dexPoints");
|
|
||||||
monster.constitutionScore = Helpers.getInt(rootDict, "conPoints");
|
|
||||||
monster.intelligenceScore = Helpers.getInt(rootDict, "intPoints");
|
|
||||||
monster.wisdomScore = Helpers.getInt(rootDict, "wisPoints");
|
|
||||||
monster.charismaScore = Helpers.getInt(rootDict, "chaPoints");
|
|
||||||
Helpers.addSense(monster, rootDict, "blindsight");
|
|
||||||
// Helpers.getBool(rootDict, "blind");
|
|
||||||
Helpers.addSense(monster, rootDict, "darkvision");
|
|
||||||
Helpers.addSense(monster, rootDict, "tremorsense");
|
|
||||||
Helpers.addSense(monster, rootDict, "truesight");
|
|
||||||
monster.telepathyRange = Helpers.getInt(rootDict, "telepathy");
|
|
||||||
monster.challengeRating = ChallengeRatingConverter.challengeRatingFromStringValue(Helpers.getString(rootDict, "cr"));
|
|
||||||
monster.customChallengeRatingDescription = Helpers.getString(rootDict, "customCr");
|
|
||||||
monster.customProficiencyBonus = Helpers.getInt(rootDict, "customProf");
|
|
||||||
// Helpers.getBool(rootDict, "isLegendary");
|
|
||||||
// Helpers.getString(rootDict, "legendariesDescription");
|
|
||||||
// Helpers.getBool(rootDict, "isLair");
|
|
||||||
// Helpers.getString(rootDict, "lairDescription");
|
|
||||||
// Helpers.getString(rootDict, "lairDescriptionEnd");
|
|
||||||
// Helpers.getBool(rootDict, "isRegional");
|
|
||||||
// Helpers.getString(rootDict, "regionalDescription");
|
|
||||||
// Helpers.getString(rootDict, "regionalDescriptionEnd");
|
|
||||||
// properties: []
|
|
||||||
monster.abilities = Helpers.getListOfTraits(rootDict, "abilities");
|
|
||||||
monster.actions = Helpers.getListOfTraits(rootDict, "actions");
|
|
||||||
monster.reactions = Helpers.getListOfTraits(rootDict, "reactions");
|
|
||||||
monster.legendaryActions = Helpers.getListOfTraits(rootDict, "legendaries");
|
|
||||||
monster.lairActions = Helpers.getListOfTraits(rootDict, "lairs");
|
|
||||||
monster.regionalActions = Helpers.getListOfTraits(rootDict, "regionals");
|
|
||||||
Helpers.addSavingThrows(monster, rootDict);
|
|
||||||
// skills: []
|
|
||||||
monster.skills = Helpers.getSetOfSkills(rootDict);
|
|
||||||
// damagetypes: []
|
|
||||||
// specialdamage: []
|
|
||||||
monster.damageImmunities = Helpers.getSetOfDamageTypes(rootDict, "damageTypes", "i");
|
|
||||||
monster.damageImmunities.addAll(Helpers.getSetOfDamageTypes(rootDict, "specialdamage", "i"));
|
|
||||||
monster.damageResistances = Helpers.getSetOfDamageTypes(rootDict, "damageTypes", "r");
|
|
||||||
monster.damageResistances.addAll(Helpers.getSetOfDamageTypes(rootDict, "specialdamage", "r"));
|
|
||||||
monster.damageVulnerabilities = Helpers.getSetOfDamageTypes(rootDict, "damageTypes", "v");
|
|
||||||
monster.damageVulnerabilities.addAll(Helpers.getSetOfDamageTypes(rootDict, "specialdamage", "v"));
|
|
||||||
// conditions: []
|
|
||||||
monster.conditionImmunities = Helpers.getSetOfDamageTypes(rootDict, "conditions");
|
|
||||||
// languages: []
|
|
||||||
monster.languages = Helpers.getSetOfLanguages(rootDict, "languages");
|
|
||||||
// understandsBut: ""
|
|
||||||
monster.understandsButDescription = Helpers.getString(rootDict, "understandsBut");
|
|
||||||
// shortName: ""
|
|
||||||
// doubleColumns: true
|
|
||||||
// separationPoint: -1
|
|
||||||
// damage: []
|
|
||||||
// pluralName: ""
|
|
||||||
|
|
||||||
return monster;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class Helpers {
|
|
||||||
public static String getString(JsonObject dict, String name) {
|
|
||||||
return getString(dict, name, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getString(@NonNull JsonObject dict, String name, String defaultValue) {
|
|
||||||
if (dict.has(name)) {
|
|
||||||
return dict.get(name).getAsString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getInt(JsonObject dict, String name) {
|
|
||||||
return getInt(dict, name, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getInt(@NonNull JsonObject dict, String name, int defaultValue) {
|
|
||||||
if (dict.has(name)) {
|
|
||||||
JsonElement element = dict.get(name);
|
|
||||||
if (element.isJsonPrimitive()) {
|
|
||||||
JsonPrimitive rawValue = element.getAsJsonPrimitive();
|
|
||||||
if (rawValue.isNumber()) {
|
|
||||||
return rawValue.getAsInt();
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
return rawValue.getAsInt();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean getBool(JsonObject dict, String name) {
|
|
||||||
return getBool(dict, name, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean getBool(@NonNull JsonObject dict, String name, boolean defaultValue) {
|
|
||||||
if (dict.has(name)) {
|
|
||||||
JsonElement element = dict.get(name);
|
|
||||||
if (element.isJsonPrimitive()) {
|
|
||||||
JsonPrimitive rawValue = element.getAsJsonPrimitive();
|
|
||||||
if (rawValue.isBoolean()) {
|
|
||||||
return rawValue.getAsBoolean();
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
return rawValue.getAsBoolean();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static String formatDistance(String name, int distance) {
|
|
||||||
// TODO: consider moving this to a string resource so it can be localized
|
|
||||||
return String.format(Locale.getDefault(), "%s %d ft.", name, distance);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void addSense(Monster monster, JsonObject root, String name) {
|
|
||||||
int distance = Helpers.getInt(root, name);
|
|
||||||
if (distance > 0) {
|
|
||||||
monster.senses.add(Helpers.formatDistance(name, distance));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static List<Trait> getListOfTraits(@NonNull JsonObject dict, String name) {
|
|
||||||
ArrayList<Trait> traits = new ArrayList<>();
|
|
||||||
if (dict.has(name)) {
|
|
||||||
JsonElement arrayElement = dict.get(name);
|
|
||||||
if (arrayElement.isJsonArray()) {
|
|
||||||
JsonArray array = arrayElement.getAsJsonArray();
|
|
||||||
int size = array.size();
|
|
||||||
for (int index = 0; index < size; index++) {
|
|
||||||
JsonElement jsonElement = array.get(index);
|
|
||||||
if (jsonElement.isJsonObject()) {
|
|
||||||
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
|
||||||
String traitName = Helpers.getString(jsonObject, "name");
|
|
||||||
String description = Helpers.getString(jsonObject, "desc");
|
|
||||||
Trait trait = new Trait(traitName, description);
|
|
||||||
traits.add(trait);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return traits;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void addSavingThrows(Monster monster, @NonNull JsonObject root) {
|
|
||||||
if (root.has("sthrows")) {
|
|
||||||
JsonElement arrayElement = root.get("sthrows");
|
|
||||||
if (arrayElement.isJsonArray()) {
|
|
||||||
JsonArray array = arrayElement.getAsJsonArray();
|
|
||||||
int size = array.size();
|
|
||||||
for (int index = 0; index < size; index++) {
|
|
||||||
JsonElement jsonElement = array.get(index);
|
|
||||||
if (jsonElement.isJsonObject()) {
|
|
||||||
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
|
||||||
String name = Helpers.getString(jsonObject, "name");
|
|
||||||
if ("str".equals(name)) {
|
|
||||||
monster.strengthSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
|
||||||
} else if ("dex".equals(name)) {
|
|
||||||
monster.dexteritySavingThrowProficiency = ProficiencyType.PROFICIENT;
|
|
||||||
} else if ("con".equals(name)) {
|
|
||||||
monster.constitutionSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
|
||||||
} else if ("int".equals(name)) {
|
|
||||||
monster.intelligenceSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
|
||||||
} else if ("wis".equals(name)) {
|
|
||||||
monster.wisdomSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
|
||||||
} else if ("cha".equals(name)) {
|
|
||||||
monster.charismaSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
throw new IllegalArgumentException("No importer found capable of parsing the given JSON payload.");
|
||||||
@NonNull
|
|
||||||
public static Set<Skill> getSetOfSkills(@NonNull JsonObject root) {
|
|
||||||
HashSet<Skill> skills = new HashSet<>();
|
|
||||||
if (root.has("skills")) {
|
|
||||||
JsonElement arrayElement = root.get("skills");
|
|
||||||
if (arrayElement.isJsonArray()) {
|
|
||||||
JsonArray array = arrayElement.getAsJsonArray();
|
|
||||||
int size = array.size();
|
|
||||||
for (int index = 0; index < size; index++) {
|
|
||||||
JsonElement jsonElement = array.get(index);
|
|
||||||
if (jsonElement.isJsonObject()) {
|
|
||||||
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
|
||||||
String name = Helpers.getString(jsonObject, "name");
|
|
||||||
String stat = Helpers.getString(jsonObject, "stat");
|
|
||||||
String note = Helpers.getString(jsonObject, "note");
|
|
||||||
|
|
||||||
Skill skill = new Skill(name, AbilityScore.valueOfString(stat), AdvantageType.NONE, " (ex)".equals(note) ? ProficiencyType.EXPERTISE : ProficiencyType.PROFICIENT);
|
|
||||||
skills.add(skill);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return skills;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static Set<String> getSetOfDamageTypes(JsonObject rootDict, String name) {
|
|
||||||
return getSetOfDamageTypes(rootDict, name, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static Set<String> getSetOfDamageTypes(@NonNull JsonObject root, String name, String type) {
|
|
||||||
HashSet<String> damageTypes = new HashSet<>();
|
|
||||||
if (root.has(name)) {
|
|
||||||
JsonElement arrayElement = root.get(name);
|
|
||||||
if (arrayElement.isJsonArray()) {
|
|
||||||
JsonArray array = arrayElement.getAsJsonArray();
|
|
||||||
int size = array.size();
|
|
||||||
for (int index = 0; index < size; index++) {
|
|
||||||
JsonElement jsonElement = array.get(index);
|
|
||||||
if (jsonElement.isJsonObject()) {
|
|
||||||
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
|
||||||
String dtName = Helpers.getString(jsonObject, "name");
|
|
||||||
String dtType = Helpers.getString(jsonObject, "type");
|
|
||||||
if (type == null || type.equals(dtType)) {
|
|
||||||
damageTypes.add(dtName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return damageTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static Set<Language> getSetOfLanguages(@NonNull JsonObject root, String name) {
|
|
||||||
HashSet<Language> languages = new HashSet<>();
|
|
||||||
if (root.has(name)) {
|
|
||||||
JsonElement arrayElement = root.get(name);
|
|
||||||
if (arrayElement.isJsonArray()) {
|
|
||||||
JsonArray array = arrayElement.getAsJsonArray();
|
|
||||||
int size = array.size();
|
|
||||||
for (int index = 0; index < size; index++) {
|
|
||||||
JsonElement jsonElement = array.get(index);
|
|
||||||
if (jsonElement.isJsonObject()) {
|
|
||||||
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
|
||||||
String languageName = Helpers.getString(jsonObject, "name");
|
|
||||||
boolean canSpeak = Helpers.getBool(jsonObject, "speaks");
|
|
||||||
Language language = new Language(languageName, canSpeak);
|
|
||||||
languages.add(language);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return languages;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.majinnaibu.monstercards.importers;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public interface EntityImporter<T> {
|
||||||
|
/**
|
||||||
|
* Determines whether the given input string or payload can be handled by this importer.
|
||||||
|
*/
|
||||||
|
boolean canImport(@NonNull String input);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the raw input string into internal domain memory objects (e.g., Monster).
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
T parse(@NonNull String input) throws Exception;
|
||||||
|
}
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
package com.majinnaibu.monstercards.importers;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.google.gson.JsonPrimitive;
|
||||||
|
import com.majinnaibu.monstercards.data.converters.ArmorTypeConverter;
|
||||||
|
import com.majinnaibu.monstercards.data.converters.ChallengeRatingConverter;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
||||||
|
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||||
|
import com.majinnaibu.monstercards.models.Language;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
import com.majinnaibu.monstercards.models.Skill;
|
||||||
|
import com.majinnaibu.monstercards.models.Trait;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class TetraCubeMonsterImporter implements EntityImporter<Monster> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canImport(@NonNull String input) {
|
||||||
|
if (input.trim().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JsonElement element = JsonParser.parseString(input);
|
||||||
|
if (!element.isJsonObject()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
JsonObject rootDict = element.getAsJsonObject();
|
||||||
|
return rootDict.has("hitDice") || rootDict.has("armorName") || rootDict.has("strPoints");
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Monster parse(@NonNull String input) throws Exception {
|
||||||
|
JsonObject rootDict = JsonParser.parseString(input).getAsJsonObject();
|
||||||
|
|
||||||
|
Monster monster = new Monster();
|
||||||
|
monster.name = getString(rootDict, "name");
|
||||||
|
monster.size = getString(rootDict, "size");
|
||||||
|
monster.type = getString(rootDict, "type");
|
||||||
|
monster.subtype = getString(rootDict, "tag");
|
||||||
|
monster.alignment = getString(rootDict, "alignment");
|
||||||
|
monster.hitDice = getInt(rootDict, "hitDice");
|
||||||
|
monster.armorType = ArmorTypeConverter.armorTypeFromStringValue(getString(rootDict, "armorName"));
|
||||||
|
monster.shieldBonus = getInt(rootDict, "shieldBonus");
|
||||||
|
monster.naturalArmorBonus = getInt(rootDict, "natArmorBonus");
|
||||||
|
monster.otherArmorDescription = getString(rootDict, "otherArmorDesc");
|
||||||
|
monster.walkSpeed = getInt(rootDict, "speed");
|
||||||
|
monster.burrowSpeed = getInt(rootDict, "burrowSpeed");
|
||||||
|
monster.climbSpeed = getInt(rootDict, "climbSpeed");
|
||||||
|
monster.flySpeed = getInt(rootDict, "flySpeed");
|
||||||
|
monster.canHover = getBool(rootDict, "hover");
|
||||||
|
monster.swimSpeed = getInt(rootDict, "swimSpeed");
|
||||||
|
monster.hasCustomHP = getBool(rootDict, "customHP");
|
||||||
|
monster.hasCustomSpeed = getBool(rootDict, "customSpeed");
|
||||||
|
monster.customHPDescription = getString(rootDict, "hpText");
|
||||||
|
monster.customSpeedDescription = getString(rootDict, "speedDesc");
|
||||||
|
monster.strengthScore = getInt(rootDict, "strPoints");
|
||||||
|
monster.dexterityScore = getInt(rootDict, "dexPoints");
|
||||||
|
monster.constitutionScore = getInt(rootDict, "conPoints");
|
||||||
|
monster.intelligenceScore = getInt(rootDict, "intPoints");
|
||||||
|
monster.wisdomScore = getInt(rootDict, "wisPoints");
|
||||||
|
monster.charismaScore = getInt(rootDict, "chaPoints");
|
||||||
|
addSense(monster, rootDict, "blindsight");
|
||||||
|
addSense(monster, rootDict, "darkvision");
|
||||||
|
addSense(monster, rootDict, "tremorsense");
|
||||||
|
addSense(monster, rootDict, "truesight");
|
||||||
|
monster.telepathyRange = getInt(rootDict, "telepathy");
|
||||||
|
monster.challengeRating = ChallengeRatingConverter.challengeRatingFromStringValue(getString(rootDict, "cr"));
|
||||||
|
monster.customChallengeRatingDescription = getString(rootDict, "customCr");
|
||||||
|
monster.customProficiencyBonus = getInt(rootDict, "customProf");
|
||||||
|
|
||||||
|
monster.abilities = getListOfTraits(rootDict, "abilities");
|
||||||
|
monster.actions = getListOfTraits(rootDict, "actions");
|
||||||
|
monster.reactions = getListOfTraits(rootDict, "reactions");
|
||||||
|
monster.legendaryActions = getListOfTraits(rootDict, "legendaries");
|
||||||
|
monster.lairActions = getListOfTraits(rootDict, "lairs");
|
||||||
|
monster.regionalActions = getListOfTraits(rootDict, "regionals");
|
||||||
|
addSavingThrows(monster, rootDict);
|
||||||
|
monster.skills = getSetOfSkills(rootDict);
|
||||||
|
monster.damageImmunities = getSetOfDamageTypes(rootDict, "damageTypes", "i");
|
||||||
|
monster.damageImmunities.addAll(getSetOfDamageTypes(rootDict, "specialdamage", "i"));
|
||||||
|
monster.damageResistances = getSetOfDamageTypes(rootDict, "damageTypes", "r");
|
||||||
|
monster.damageResistances.addAll(getSetOfDamageTypes(rootDict, "specialdamage", "r"));
|
||||||
|
monster.damageVulnerabilities = getSetOfDamageTypes(rootDict, "damageTypes", "v");
|
||||||
|
monster.damageVulnerabilities.addAll(getSetOfDamageTypes(rootDict, "specialdamage", "v"));
|
||||||
|
monster.conditionImmunities = getSetOfDamageTypes(rootDict, "conditions");
|
||||||
|
monster.languages = getSetOfLanguages(rootDict, "languages");
|
||||||
|
monster.understandsButDescription = getString(rootDict, "understandsBut");
|
||||||
|
|
||||||
|
return monster;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getString(JsonObject dict, String name) {
|
||||||
|
return getString(dict, name, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getString(@NonNull JsonObject dict, String name, String defaultValue) {
|
||||||
|
if (dict.has(name)) {
|
||||||
|
return dict.get(name).getAsString();
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getInt(JsonObject dict, String name) {
|
||||||
|
return getInt(dict, name, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getInt(@NonNull JsonObject dict, String name, int defaultValue) {
|
||||||
|
if (dict.has(name)) {
|
||||||
|
JsonElement element = dict.get(name);
|
||||||
|
if (element.isJsonPrimitive()) {
|
||||||
|
JsonPrimitive rawValue = element.getAsJsonPrimitive();
|
||||||
|
if (rawValue.isNumber()) {
|
||||||
|
return rawValue.getAsInt();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return rawValue.getAsInt();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean getBool(JsonObject dict, String name) {
|
||||||
|
return getBool(dict, name, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean getBool(@NonNull JsonObject dict, String name, boolean defaultValue) {
|
||||||
|
if (dict.has(name)) {
|
||||||
|
JsonElement element = dict.get(name);
|
||||||
|
if (element.isJsonPrimitive()) {
|
||||||
|
JsonPrimitive rawValue = element.getAsJsonPrimitive();
|
||||||
|
if (rawValue.isBoolean()) {
|
||||||
|
return rawValue.getAsBoolean();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return rawValue.getAsBoolean();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static String formatDistance(String name, int distance) {
|
||||||
|
return String.format(Locale.getDefault(), "%s %d ft.", name, distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addSense(Monster monster, JsonObject root, String name) {
|
||||||
|
int distance = getInt(root, name);
|
||||||
|
if (distance > 0) {
|
||||||
|
monster.senses.add(formatDistance(name, distance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static List<Trait> getListOfTraits(@NonNull JsonObject dict, String name) {
|
||||||
|
ArrayList<Trait> traits = new ArrayList<>();
|
||||||
|
if (dict.has(name)) {
|
||||||
|
JsonElement arrayElement = dict.get(name);
|
||||||
|
if (arrayElement.isJsonArray()) {
|
||||||
|
JsonArray array = arrayElement.getAsJsonArray();
|
||||||
|
int size = array.size();
|
||||||
|
for (int index = 0; index < size; index++) {
|
||||||
|
JsonElement jsonElement = array.get(index);
|
||||||
|
if (jsonElement.isJsonObject()) {
|
||||||
|
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
||||||
|
String traitName = getString(jsonObject, "name");
|
||||||
|
String description = getString(jsonObject, "desc");
|
||||||
|
Trait trait = new Trait(traitName, description);
|
||||||
|
traits.add(trait);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return traits;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addSavingThrows(Monster monster, @NonNull JsonObject root) {
|
||||||
|
if (root.has("sthrows")) {
|
||||||
|
JsonElement arrayElement = root.get("sthrows");
|
||||||
|
if (arrayElement.isJsonArray()) {
|
||||||
|
JsonArray array = arrayElement.getAsJsonArray();
|
||||||
|
int size = array.size();
|
||||||
|
for (int index = 0; index < size; index++) {
|
||||||
|
JsonElement jsonElement = array.get(index);
|
||||||
|
if (jsonElement.isJsonObject()) {
|
||||||
|
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
||||||
|
String name = getString(jsonObject, "name");
|
||||||
|
if ("str".equals(name)) {
|
||||||
|
monster.strengthSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
} else if ("dex".equals(name)) {
|
||||||
|
monster.dexteritySavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
} else if ("con".equals(name)) {
|
||||||
|
monster.constitutionSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
} else if ("int".equals(name)) {
|
||||||
|
monster.intelligenceSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
} else if ("wis".equals(name)) {
|
||||||
|
monster.wisdomSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
} else if ("cha".equals(name)) {
|
||||||
|
monster.charismaSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static Set<Skill> getSetOfSkills(@NonNull JsonObject root) {
|
||||||
|
HashSet<Skill> skills = new HashSet<>();
|
||||||
|
if (root.has("skills")) {
|
||||||
|
JsonElement arrayElement = root.get("skills");
|
||||||
|
if (arrayElement.isJsonArray()) {
|
||||||
|
JsonArray array = arrayElement.getAsJsonArray();
|
||||||
|
int size = array.size();
|
||||||
|
for (int index = 0; index < size; index++) {
|
||||||
|
JsonElement jsonElement = array.get(index);
|
||||||
|
if (jsonElement.isJsonObject()) {
|
||||||
|
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
||||||
|
String name = getString(jsonObject, "name");
|
||||||
|
String stat = getString(jsonObject, "stat");
|
||||||
|
String note = getString(jsonObject, "note");
|
||||||
|
|
||||||
|
Skill skill = new Skill(name, AbilityScore.valueOfString(stat), AdvantageType.NONE, " (ex)".equals(note) ? ProficiencyType.EXPERTISE : ProficiencyType.PROFICIENT);
|
||||||
|
skills.add(skill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return skills;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static Set<String> getSetOfDamageTypes(JsonObject rootDict, String name) {
|
||||||
|
return getSetOfDamageTypes(rootDict, name, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static Set<String> getSetOfDamageTypes(JsonObject rootDict, String name, String type) {
|
||||||
|
HashSet<String> damageTypes = new HashSet<>();
|
||||||
|
if (rootDict.has(name)) {
|
||||||
|
JsonElement arrayElement = rootDict.get(name);
|
||||||
|
if (arrayElement.isJsonArray()) {
|
||||||
|
JsonArray array = arrayElement.getAsJsonArray();
|
||||||
|
int size = array.size();
|
||||||
|
for (int index = 0; index < size; index++) {
|
||||||
|
JsonElement jsonElement = array.get(index);
|
||||||
|
if (jsonElement.isJsonObject()) {
|
||||||
|
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
||||||
|
String dtName = getString(jsonObject, "name");
|
||||||
|
String dtType = getString(jsonObject, "type");
|
||||||
|
if (type == null || type.equals(dtType)) {
|
||||||
|
damageTypes.add(dtName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return damageTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static Set<Language> getSetOfLanguages(@NonNull JsonObject root, String name) {
|
||||||
|
HashSet<Language> languages = new HashSet<>();
|
||||||
|
if (root.has(name)) {
|
||||||
|
JsonElement arrayElement = root.get(name);
|
||||||
|
if (arrayElement.isJsonArray()) {
|
||||||
|
JsonArray array = arrayElement.getAsJsonArray();
|
||||||
|
int size = array.size();
|
||||||
|
for (int index = 0; index < size; index++) {
|
||||||
|
JsonElement jsonElement = array.get(index);
|
||||||
|
if (jsonElement.isJsonObject()) {
|
||||||
|
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
||||||
|
String languageName = getString(jsonObject, "name");
|
||||||
|
boolean canSpeak = getBool(jsonObject, "speaks");
|
||||||
|
Language language = new Language(languageName, canSpeak);
|
||||||
|
languages.add(language);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return languages;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package com.majinnaibu.monstercards.importers;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import com.majinnaibu.monstercards.helpers.MonsterImportHelper;
|
||||||
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class TetraCubeMonsterImporterTest {
|
||||||
|
|
||||||
|
private final TetraCubeMonsterImporter importer = new TetraCubeMonsterImporter();
|
||||||
|
|
||||||
|
private static final String SAMPLE_TETRACUBE_JSON = "{"
|
||||||
|
+ "\"name\": \"Goblin\","
|
||||||
|
+ "\"size\": \"Small\","
|
||||||
|
+ "\"type\": \"humanoid\","
|
||||||
|
+ "\"tag\": \"goblinoid\","
|
||||||
|
+ "\"alignment\": \"neutral evil\","
|
||||||
|
+ "\"hitDice\": 2,"
|
||||||
|
+ "\"armorName\": \"leather\","
|
||||||
|
+ "\"shieldBonus\": 1,"
|
||||||
|
+ "\"speed\": 30,"
|
||||||
|
+ "\"strPoints\": 8,"
|
||||||
|
+ "\"dexPoints\": 14,"
|
||||||
|
+ "\"conPoints\": 10,"
|
||||||
|
+ "\"intPoints\": 10,"
|
||||||
|
+ "\"wisPoints\": 8,"
|
||||||
|
+ "\"chaPoints\": 8,"
|
||||||
|
+ "\"cr\": \"1/4\""
|
||||||
|
+ "}";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void canImport_validTetraCubeJson_returnsTrue() {
|
||||||
|
assertTrue(importer.canImport(SAMPLE_TETRACUBE_JSON));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void canImport_invalidJson_returnsFalse() {
|
||||||
|
assertFalse(importer.canImport("not a json string"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void canImport_emptyString_returnsFalse() {
|
||||||
|
assertFalse(importer.canImport(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void canImport_otherJson_returnsFalse() {
|
||||||
|
assertFalse(importer.canImport("{\"foo\": \"bar\"}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parse_validTetraCubeJson_createsMonster() throws Exception {
|
||||||
|
Monster monster = importer.parse(SAMPLE_TETRACUBE_JSON);
|
||||||
|
assertNotNull(monster);
|
||||||
|
assertEquals("Goblin", monster.name);
|
||||||
|
assertEquals("Small", monster.size);
|
||||||
|
assertEquals("humanoid", monster.type);
|
||||||
|
assertEquals("goblinoid", monster.subtype);
|
||||||
|
assertEquals("neutral evil", monster.alignment);
|
||||||
|
assertEquals(2, monster.hitDice);
|
||||||
|
assertEquals(30, monster.walkSpeed);
|
||||||
|
assertEquals(8, monster.strengthScore);
|
||||||
|
assertEquals(14, monster.dexterityScore);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void monsterImportHelper_fromJSON_delegatesToImporter() {
|
||||||
|
Monster monster = MonsterImportHelper.fromJSON(SAMPLE_TETRACUBE_JSON);
|
||||||
|
assertNotNull(monster);
|
||||||
|
assertEquals("Goblin", monster.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -168,7 +168,7 @@ Extend the generic sharing feature with specialized, direct sharing channels (to
|
|||||||
## 3. Implementation Checklist & Status
|
## 3. Implementation Checklist & Status
|
||||||
|
|
||||||
- [x] **Step 0**: Restrict app intent filters in `AndroidManifest.xml` (`.monster` & `.monster.txt`) and add runtime filename validation in `MainActivity.java`.
|
- [x] **Step 0**: Restrict app intent filters in `AndroidManifest.xml` (`.monster` & `.monster.txt`) and add runtime filename validation in `MainActivity.java`.
|
||||||
- [ ] **Step 1**: Refactor import & conversion code into a shared `EntityImporter<T>` interface and `TetraCubeMonsterImporter` class.
|
- [x] **Step 1**: Refactor import & conversion code into a shared `EntityImporter<T>` interface and `TetraCubeMonsterImporter` class.
|
||||||
- [ ] **Step 2**: Update Tetra-cube importer class to support the newest Tetra-cube format (`bonusActions`, `mythics`, `blind`, intro descriptions).
|
- [ ] **Step 2**: Update Tetra-cube importer class to support the newest Tetra-cube format (`bonusActions`, `mythics`, `blind`, intro descriptions).
|
||||||
- [ ] **Step 3**: Import from D&D Beyond URL (`https://www.dndbeyond.com/characters/49074997` fetching from character service endpoint `character/v2/character/49074997`).
|
- [ ] **Step 3**: Import from D&D Beyond URL (`https://www.dndbeyond.com/characters/49074997` fetching from character service endpoint `character/v2/character/49074997`).
|
||||||
- [ ] **Step 4**: Export to internal format described by Open5e document (`Open5eExporter`).
|
- [ ] **Step 4**: Export to internal format described by Open5e document (`Open5eExporter`).
|
||||||
|
|||||||
Reference in New Issue
Block a user