Adds plan to support other import / export formats on android.
This commit is contained in:
243
docs/ImportFromMonster.md
Normal file
243
docs/ImportFromMonster.md
Normal file
@@ -0,0 +1,243 @@
|
||||
# Import Process & Schema Documentation
|
||||
|
||||
> **Platform Note**: This documentation describes the import process and internal database schema for the **Android Application** (`com.majinnaibu.monstercards`).
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview of the Import Process
|
||||
|
||||
The Monster Cards Android application can import monster stat blocks formatted in JSON from `.monster` or `.monster.txt` files shared or opened from other applications (such as file managers, messaging apps, or browsers).
|
||||
|
||||
```
|
||||
+-------------------------------------------------------+
|
||||
| External .monster / .monster.txt file (JSON Payload) |
|
||||
+-------------------------------------------------------+
|
||||
|
|
||||
v
|
||||
+-------------------------------------------------------+
|
||||
| MainActivity (Intent Handling: ACTION_VIEW / SEND) |
|
||||
| - Extracts raw JSON string from Content/File URI |
|
||||
+-------------------------------------------------------+
|
||||
|
|
||||
v
|
||||
+-------------------------------------------------------+
|
||||
| MonsterImportFragment & MonsterImportViewModel |
|
||||
| - MonsterImportHelper.fromJSON(json) parses JSON |
|
||||
| - Maps JSON fields to internal Monster domain model |
|
||||
| - Renders UI preview with calculated AC, HP, & Markdown|
|
||||
+-------------------------------------------------------+
|
||||
|
|
||||
v
|
||||
+-------------------------------------------------------+
|
||||
| User clicks "Import" Menu Action |
|
||||
| - Generates new UUID |
|
||||
| - Persists Monster to Room Database (monsters table) |
|
||||
| - Rebuilds FTS index (monsters_fts) |
|
||||
+-------------------------------------------------------+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Source Format (.monster / .monster.txt) & File Extension Behavior
|
||||
|
||||
The `.monster` file format is generated by Tetra-cube's D&D 5e Statblock Generator (`statblock-script.js`).
|
||||
|
||||
### File Saving Behavior (`SavedData.SaveToFile`)
|
||||
```javascript
|
||||
SaveToFile: () => saveAs(new Blob([JSON.stringify(mon)], {
|
||||
type: "text/plain;charset=utf-8"
|
||||
}), mon.name.toLowerCase() + ".monster")
|
||||
```
|
||||
|
||||
- **Desktop Browsers**: Save the file with the specified `.monster` filename.
|
||||
- **Android Mobile Browsers & DownloadManager**: Because the `Blob` uses MIME type `text/plain;charset=utf-8`, Android file handlers append a `.txt` extension, creating `.monster.txt` files.
|
||||
|
||||
---
|
||||
|
||||
## 3. JSON Parsing & Field Mapping (`MonsterImportHelper.java`)
|
||||
|
||||
`MonsterImportHelper.fromJSON(String json)` uses Gson's `JsonParser` to parse the root JSON object and populate a `Monster` instance.
|
||||
|
||||
### Complete Field Mapping Reference
|
||||
|
||||
| JSON Field Name | Target `Monster` Field | Type / Default | Description / Conversion Logic |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `name` | `monster.name` | `String` | Monster name |
|
||||
| `size` | `monster.size` | `String` | Size string (e.g., `"medium"`, `"large"`) |
|
||||
| `type` | `monster.type` | `String` | Creature type (e.g., `"humanoid"`, `"dragon"`) |
|
||||
| `tag` | `monster.subtype` | `String` | Subtype / tag (e.g., `"elf"`, `"shapechanger"`) |
|
||||
| `alignment` | `monster.alignment` | `String` | Alignment string (e.g., `"neutral good"`) |
|
||||
| `hitDice` | `monster.hitDice` | `int` (default 1) | Number of hit dice |
|
||||
| `armorName` | `monster.armorType` | `ArmorType` | Converted via `ArmorTypeConverter.armorTypeFromStringValue()` |
|
||||
| `shieldBonus` | `monster.shieldBonus` | `int` | Shield AC bonus (+2 if present) |
|
||||
| `natArmorBonus` | `monster.naturalArmorBonus` | `int` | Additional natural armor AC bonus |
|
||||
| `otherArmorDesc` | `monster.otherArmorDescription` | `String` | Custom armor description string |
|
||||
| `speed` | `monster.walkSpeed` | `int` | Walking speed in feet |
|
||||
| `burrowSpeed` | `monster.burrowSpeed` | `int` | Burrowing speed in feet |
|
||||
| `climbSpeed` | `monster.climbSpeed` | `int` | Climbing speed in feet |
|
||||
| `flySpeed` | `monster.flySpeed` | `int` | Flying speed in feet |
|
||||
| `hover` | `monster.canHover` | `boolean` | Whether flying creature can hover |
|
||||
| `swimSpeed` | `monster.swimSpeed` | `int` | Swimming speed in feet |
|
||||
| `customHP` | `monster.hasCustomHP` | `boolean` | Flag for override custom HP string |
|
||||
| `customSpeed` | `monster.hasCustomSpeed` | `boolean` | Flag for override custom speed string |
|
||||
| `hpText` | `monster.customHPDescription` | `String` | Custom HP text description |
|
||||
| `speedDesc` | `monster.customSpeedDescription` | `String` | Custom speed text description |
|
||||
| `strPoints` | `monster.strengthScore` | `int` (default 10) | Strength ability score |
|
||||
| `dexPoints` | `monster.dexterityScore` | `int` (default 10) | Dexterity ability score |
|
||||
| `conPoints` | `monster.constitutionScore` | `int` (default 10) | Constitution ability score |
|
||||
| `intPoints` | `monster.intelligenceScore` | `int` (default 10) | Intelligence ability score |
|
||||
| `wisPoints` | `monster.wisdomScore` | `int` (default 10) | Wisdom ability score |
|
||||
| `chaPoints` | `monster.charismaScore` | `int` (default 10) | Charisma ability score |
|
||||
| `blindsight` | `monster.senses` | `Set<String>` | Added as `"blindsight <N> ft."` if `N > 0` |
|
||||
| `darkvision` | `monster.senses` | `Set<String>` | Added as `"darkvision <N> ft."` if `N > 0` |
|
||||
| `tremorsense` | `monster.senses` | `Set<String>` | Added as `"tremorsense <N> ft."` if `N > 0` |
|
||||
| `truesight` | `monster.senses` | `Set<String>` | Added as `"truesight <N> ft."` if `N > 0` |
|
||||
| `telepathy` | `monster.telepathyRange` | `int` | Telepathy range in feet |
|
||||
| `cr` | `monster.challengeRating` | `ChallengeRating` | Converted via `ChallengeRatingConverter` |
|
||||
| `customCr` | `monster.customChallengeRatingDescription` | `String` | Custom CR text |
|
||||
| `customProf` | `monster.customProficiencyBonus` | `int` | Custom proficiency bonus override |
|
||||
| `abilities` | `monster.abilities` | `List<Trait>` | List of JSON objects `{ "name": "...", "desc": "..." }` |
|
||||
| `actions` | `monster.actions` | `List<Trait>` | List of JSON objects `{ "name": "...", "desc": "..." }` |
|
||||
| `bonusActions` | `monster.actions` *(Future)* | `List<Trait>` | Bonus actions array |
|
||||
| `reactions` | `monster.reactions` | `List<Trait>` | List of JSON objects `{ "name": "...", "desc": "..." }` |
|
||||
| `legendaries` | `monster.legendaryActions` | `List<Trait>` | List of JSON objects `{ "name": "...", "desc": "..." }` |
|
||||
| `mythics` | `monster.legendaryActions` *(Future)* | `List<Trait>` | Mythic actions array |
|
||||
| `lairs` | `monster.lairActions` | `List<Trait>` | List of JSON objects `{ "name": "...", "desc": "..." }` |
|
||||
| `regionals` | `monster.regionalActions` | `List<Trait>` | List of JSON objects `{ "name": "...", "desc": "..." }` |
|
||||
| `sthrows` | Saving throw proficiencies | `ProficiencyType` | Array of objects `{ "name": "str"\|"dex"\|"con"\|"int"\|"wis"\|"cha" }`. Sets proficiency to `PROFICIENT`. |
|
||||
| `skills` | `monster.skills` | `Set<Skill>` | Array of `{ "name": "...", "stat": "...", "note": " (ex)" }`. `note == " (ex)"` sets `EXPERTISE`. |
|
||||
| `damageTypes`, `specialdamage` | Immunities / Resistances / Vulnerabilities | `Set<String>` | Array of `{ "name": "...", "type": "i"\|"r"\|"v" }`. Sorted into immunities (`"i"`), resistances (`"r"`), or vulnerabilities (`"v"`). |
|
||||
| `conditions` | `monster.conditionImmunities` | `Set<String>` | Array of `{ "name": "..." }` |
|
||||
| `languages` | `monster.languages` | `Set<Language>` | Array of `{ "name": "...", "speaks": true\|false }` |
|
||||
| `understandsBut` | `monster.understandsButDescription` | `String` | Language qualifier text |
|
||||
|
||||
---
|
||||
|
||||
## 4. Preview & Database Persistence
|
||||
|
||||
1. **ViewModel Computation (`MonsterImportViewModel.java`)**:
|
||||
- Computes calculated stat block values:
|
||||
- **AC**: Calculated based on `armorType`, dexterity modifier, shield bonus, natural armor bonus, or custom description.
|
||||
- **HP**: Calculated as `floor(hitDice * ((dieSize + 1) / 2 + conModifier))` where `dieSize` is derived from size (e.g., Medium = d8, Large = d10).
|
||||
- **Dynamic Placeholders**: Replaces placeholders in trait descriptions (e.g., `[STR ATK]`, `[WIS SAVE]`) with computed attack bonuses and spell save DCs based on ability scores and proficiency bonuses.
|
||||
- **Markdown Rendering**: Trait and action descriptions are parsed through `CommonMarkHelper` to format inline HTML/Markdown for display.
|
||||
|
||||
2. **Persistence (`MonsterImportFragment.java`)**:
|
||||
- When the user taps the **Import** action menu button (`R.id.menu_action_import_monster`):
|
||||
1. Assigns a new `UUID` to `monster.id`.
|
||||
2. Calls `MonsterRepository.addMonster(monster)` which executes an `INSERT` into the Room database asynchronously on an IO thread via RxJava3.
|
||||
3. Shows a `Snackbar` confirming import success and navigates to the library/detail view for the imported monster.
|
||||
|
||||
---
|
||||
|
||||
## 5. Internal Database Schema (`monsters` Table)
|
||||
|
||||
The app uses **Room Database** (Database Name: `monsters`, Version: `5`).
|
||||
|
||||
### Table: `monsters`
|
||||
|
||||
| Column Name | SQLite Data Type | Default Value | Description / Room TypeConverter |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `id` | `TEXT` | Primary Key | UUID string (`UUIDConverter`) |
|
||||
| `name` | `TEXT` | `""` | Monster name |
|
||||
| `size` | `TEXT` | `""` | Size category (e.g. `"medium"`) |
|
||||
| `type` | `TEXT` | `""` | Creature type |
|
||||
| `subtype` | `TEXT` | `""` | Creature tag / subtype |
|
||||
| `alignment` | `TEXT` | `""` | Alignment |
|
||||
| `strength_score` | `INTEGER` | `10` | Strength score |
|
||||
| `strength_saving_throw_advantage` | `TEXT` | `"none"` | Advantage type (`"none"`, `"advantage"`, `"disadvantage"`) |
|
||||
| `strength_saving_throw_proficiency` | `TEXT` | `"none"` | Proficiency type (`"none"`, `"proficient"`, `"expertise"`) |
|
||||
| `dexterity_score` | `INTEGER` | `10` | Dexterity score |
|
||||
| `dexterity_saving_throw_advantage` | `TEXT` | `"none"` | Dexterity saving throw advantage |
|
||||
| `dexterity_saving_throw_proficiency` | `TEXT` | `"none"` | Dexterity saving throw proficiency |
|
||||
| `constitution_score` | `INTEGER` | `10` | Constitution score |
|
||||
| `constitution_saving_throw_advantage` | `TEXT` | `"none"` | Constitution saving throw advantage |
|
||||
| `constitution_saving_throw_proficiency` | `TEXT` | `"none"` | Constitution saving throw proficiency |
|
||||
| `intelligence_score` | `INTEGER` | `10` | Intelligence score |
|
||||
| `intelligence_saving_throw_advantage` | `TEXT` | `"none"` | Intelligence saving throw advantage |
|
||||
| `intelligence_saving_throw_proficiency` | `TEXT` | `"none"` | Intelligence saving throw proficiency |
|
||||
| `wisdom_score` | `INTEGER` | `10` | Wisdom score |
|
||||
| `wisdom_saving_throw_advantage` | `TEXT` | `"none"` | Wisdom saving throw advantage |
|
||||
| `wisdom_saving_throw_proficiency` | `TEXT` | `"none"` | Wisdom saving throw proficiency |
|
||||
| `charisma_score` | `INTEGER` | `10` | Charisma score |
|
||||
| `charisma_saving_throw_advantage` | `TEXT` | `"none"` | Charisma saving throw advantage |
|
||||
| `charisma_saving_throw_proficiency` | `TEXT` | `"none"` | Charisma saving throw proficiency |
|
||||
| `armor_type` | `TEXT` | `"none"` | String representation of `ArmorType` (`ArmorTypeConverter`) |
|
||||
| `shield_bonus` | `INTEGER` | `0` | Shield AC bonus |
|
||||
| `natural_armor_bonus` | `INTEGER` | `0` | Natural armor bonus |
|
||||
| `other_armor_description` | `TEXT` | `""` | Custom armor description |
|
||||
| `hit_dice` | `INTEGER` | `1` | Hit dice count |
|
||||
| `has_custom_hit_points` | `INTEGER` | `0` | Boolean flag (0 = false, 1 = true) |
|
||||
| `custom_hit_points_description` | `TEXT` | `""` | Override HP string |
|
||||
| `walk_speed` | `INTEGER` | `0` | Walk speed in ft |
|
||||
| `burrow_speed` | `INTEGER` | `0` | Burrow speed in ft |
|
||||
| `climb_speed` | `INTEGER` | `0` | Climb speed in ft |
|
||||
| `fly_speed` | `INTEGER` | `0` | Fly speed in ft |
|
||||
| `can_hover` | `INTEGER` | `0` | Boolean flag |
|
||||
| `swim_speed` | `INTEGER` | `0` | Swim speed in ft |
|
||||
| `has_custom_speed` | `INTEGER` | `0` | Boolean flag |
|
||||
| `custom_speed_description` | `TEXT` | NULL | Override speed string |
|
||||
| `challenge_rating` | `TEXT` | `"1"` | String representation of `ChallengeRating` (`ChallengeRatingConverter`) |
|
||||
| `custom_challenge_rating_description` | `TEXT` | `""` | Custom CR text |
|
||||
| `custom_proficiency_bonus` | `INTEGER` | `0` | Custom proficiency bonus override |
|
||||
| `telepathy_range` | `INTEGER` | `0` | Telepathy range in ft |
|
||||
| `understands_but_description` | `TEXT` | `""` | Language qualifier description |
|
||||
| `senses` | `TEXT` | `"[]"` | JSON array of strings (`SetOfStringConverter`) |
|
||||
| `skills` | `TEXT` | `"[]"` | JSON array of Skill objects (`SetOfSkillConverter`) |
|
||||
| `damage_immunities` | `TEXT` | `"[]"` | JSON array of strings (`SetOfStringConverter`) |
|
||||
| `damage_resistances` | `TEXT` | `"[]"` | JSON array of strings (`SetOfStringConverter`) |
|
||||
| `damage_vulnerabilities` | `TEXT` | `"[]"` | JSON array of strings (`SetOfStringConverter`) |
|
||||
| `condition_immunities` | `TEXT` | `"[]"` | JSON array of strings (`SetOfStringConverter`) |
|
||||
| `languages` | `TEXT` | `"[]"` | JSON array of Language objects (`SetOfLanguageConverter`) |
|
||||
| `abilities` | `TEXT` | `"[]"` | JSON array of Trait objects (`ListOfTraitsConverter`) |
|
||||
| `actions` | `TEXT` | `"[]"` | JSON array of Trait objects (`ListOfTraitsConverter`) |
|
||||
| `reactions` | `TEXT` | `"[]"` | JSON array of Trait objects (`ListOfTraitsConverter`) |
|
||||
| `lair_actions` | `TEXT` | `"[]"` | JSON array of Trait objects (`ListOfTraitsConverter`) |
|
||||
| `legendary_actions` | `TEXT` | `"[]"` | JSON array of Trait objects (`ListOfTraitsConverter`) |
|
||||
| `regional_actions` | `TEXT` | `"[]"` | JSON array of Trait objects (`ListOfTraitsConverter`) |
|
||||
|
||||
### Virtual FTS Table: `monsters_fts`
|
||||
|
||||
Used for full-text search indexing:
|
||||
```sql
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS `monsters_fts`
|
||||
USING FTS4(`name` TEXT, `size` TEXT, `type` TEXT, `subtype` TEXT, `alignment` TEXT, content=`monsters`);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Component Objects Schema (JSON Serialized in DB)
|
||||
|
||||
### 1. `Trait`
|
||||
Used in `abilities`, `actions`, `reactions`, `lair_actions`, `legendary_actions`, and `regional_actions`:
|
||||
```json
|
||||
{
|
||||
"name": "Keen Smell",
|
||||
"description": "The monster has advantage on Wisdom (Perception) checks that rely on smell."
|
||||
}
|
||||
```
|
||||
|
||||
### 2. `Skill`
|
||||
Used in `skills`:
|
||||
```json
|
||||
{
|
||||
"name": "Perception",
|
||||
"abilityScore": "WISDOM",
|
||||
"advantageType": "NONE",
|
||||
"proficiencyType": "PROFICIENT"
|
||||
}
|
||||
```
|
||||
- **`abilityScore`**: `"STRENGTH"`, `"DEXTERITY"`, `"CONSTITUTION"`, `"INTELLIGENCE"`, `"WISDOM"`, `"CHARISMA"`.
|
||||
- **`advantageType`**: `"NONE"`, `"ADVANTAGE"`, `"DISADVANTAGE"`.
|
||||
- **`proficiencyType`**: `"NONE"`, `"PROFICIENT"`, `"EXPERTISE"`.
|
||||
|
||||
### 3. `Language`
|
||||
Used in `languages`:
|
||||
```json
|
||||
{
|
||||
"mName": "Common",
|
||||
"mSpeaks": true
|
||||
}
|
||||
```
|
||||
- **`mName`**: Name of the language (e.g., `"Elvish"`).
|
||||
- **`mSpeaks`**: `true` if the creature can speak it; `false` if it only understands it.
|
||||
Reference in New Issue
Block a user