Skip to content

Commit

Permalink
feat: import csv (#62)
Browse files Browse the repository at this point in the history
* chore: create CustomBestiary class

* chore: add CustomBestiaries table

* chore: update settings page layout to use tabs

* chore: update database

* feat: create custom bestiary

* feat: delete custom bestiary

* chore: add translate methods to enums

* feat: add notification when importing a bestiary

* feat: display custom bestiaries to pick combatants from

* feat: allow users to download the CSV template

* chore: display danger zone on app settings

* chore: l10n

* chore: add empty state for My Bestiaries section

* chore: limit bestiary import to custom engine only

* feat: import 5e from json

* chore: update import dialog description

* chore: use tabs in AddCombatant widget

* style: format code

* chore: conver AddCombatant widget to stateless
  • Loading branch information
VytorCalixto authored Nov 24, 2024
1 parent e6f9ce6 commit c860e16
Show file tree
Hide file tree
Showing 36 changed files with 2,868 additions and 310 deletions.
310 changes: 310 additions & 0 deletions assets/templates/5e.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
[
{
"slug": "ancient-bad-dragon",
"desc": "",
"name": "Ancient Bad Dragon",
"size": "Gargantuan",
"type": "Dragon",
"subtype": "",
"group": "Bad Dragon",
"alignment": "chaotic evil",
"armor_class": 22,
"armor_desc": "natural armor",
"hit_points": 546,
"hit_dice": "28d20+252",
"speed": {
"walk": 40,
"climb": 40,
"fly": 80
},
"strength": 30,
"dexterity": 10,
"constitution": 29,
"intelligence": 18,
"wisdom": 15,
"charisma": 23,
"strength_save": null,
"dexterity_save": 7,
"constitution_save": 16,
"intelligence_save": null,
"wisdom_save": 9,
"charisma_save": 13,
"perception": 16,
"skills": {
"perception": 16,
"stealth": 7
},
"damage_vulnerabilities": "",
"damage_resistances": "",
"damage_immunities": "fire",
"condition_immunities": "",
"senses": "blindsight 60 ft., darkvision 120 ft., passive Perception 26",
"languages": "Common, Draconic",
"challenge_rating": "24",
"cr": 24.0,
"actions": [
{
"name": "Multiattack",
"desc": "The dragon can use its Frightful Presence. It then makes three attacks: one with its bite and two with its claws."
},
{
"name": "Bite",
"desc": "Melee Weapon Attack: +17 to hit, reach 15 ft., one target. Hit: 21 (2d10 + 10) piercing damage plus 14 (4d6) fire damage.",
"attack_bonus": 17,
"damage_dice": "2d10+4d6",
"damage_bonus": 10
},
{
"name": "Claw",
"desc": "Melee Weapon Attack: +17 to hit, reach 10 ft., one target. Hit: 17 (2d6 + 10) slashing damage.",
"attack_bonus": 17,
"damage_dice": "2d6",
"damage_bonus": 10
},
{
"name": "Tail",
"desc": "Melee Weapon Attack: +17 to hit, reach 20 ft., one target. Hit: 19 (2d8 + 10) bludgeoning damage.",
"attack_bonus": 17,
"damage_dice": "2d8",
"damage_bonus": 10
},
{
"name": "Frightful Presence",
"desc": "Each creature of the dragon's choice that is within 120 feet of the dragon and aware of it must succeed on a DC 21 Wisdom saving throw or become frightened for 1 minute. A creature can repeat the saving throw at the end of each of its turns, ending the effect on itself on a success. If a creature's saving throw is successful or the effect ends for it, the creature is immune to the dragon's Frightful Presence for the next 24 hours."
},
{
"name": "Bad Breath (Recharge 5-6)",
"desc": "The dragon exhales fire in a 90-foot cone. Each creature in that area must make a DC 24 Dexterity saving throw, taking 91 (26d6) fire damage on a failed save, or half as much damage on a successful one.",
"attack_bonus": 0,
"damage_dice": "26d6"
}
],
"bonus_actions": null,
"reactions": null,
"legendary_desc": "The dragon can take 3 legendary actions, choosing from the options below. Only one legendary action option can be used at a time and only at the end of another creature's turn. The dragon regains spent legendary actions at the start of its turn.",
"legendary_actions": [
{
"name": "Detect",
"desc": "The dragon makes a Wisdom (Perception) check."
},
{
"name": "Tail Attack",
"desc": "The dragon makes a tail attack."
},
{
"name": "Wing Attack (Costs 2 Actions)",
"desc": "The dragon beats its wings. Each creature within 15 ft. of the dragon must succeed on a DC 25 Dexterity saving throw or take 17 (2d6 + 10) bludgeoning damage and be knocked prone. The dragon can then fly up to half its flying speed."
}
],
"special_abilities": [
{
"name": "Legendary Resistance (3/Day)",
"desc": "If the dragon fails a saving throw, it can choose to succeed instead."
}
],
"spell_list": [],
"page_no": 286,
"environments": [
"Hill",
"Mountains",
"Mountain"
],
"document__slug": "custom-bestiary",
"document__title": "Custom Bestiary"
},
{
"slug": "baboleth",
"desc": "",
"name": "Baboleth",
"size": "Large",
"type": "Baberration",
"subtype": "",
"group": null,
"alignment": "lawful evil",
"armor_class": 17,
"armor_desc": "natural armor",
"hit_points": 135,
"hit_dice": "18d10+36",
"speed": {
"walk": 10,
"swim": 40
},
"strength": 21,
"dexterity": 9,
"constitution": 15,
"intelligence": 18,
"wisdom": 15,
"charisma": 18,
"strength_save": null,
"dexterity_save": null,
"constitution_save": 6,
"intelligence_save": 8,
"wisdom_save": 6,
"charisma_save": null,
"perception": 10,
"skills": {
"history": 12,
"perception": 10
},
"damage_vulnerabilities": "",
"damage_resistances": "",
"damage_immunities": "",
"condition_immunities": "",
"senses": "darkvision 120 ft., passive Perception 20",
"languages": "Deep Speech, telepathy 120 ft.",
"challenge_rating": "10",
"cr": 10.0,
"actions": [
{
"name": "Multiattack",
"desc": "The baboleth makes three tentacle attacks."
},
{
"name": "Tentacle",
"desc": "Melee Weapon Attack: +9 to hit, reach 10 ft., one target. Hit: 12 (2d6 + 5) bludgeoning damage. If the target is a creature, it must succeed on a DC 14 Constitution saving throw or become diseased. The disease has no effect for 1 minute and can be removed by any magic that cures disease. After 1 minute, the diseased creature's skin becomes translucent and slimy, the creature can't regain hit points unless it is underwater, and the disease can be removed only by heal or another disease-curing spell of 6th level or higher. When the creature is outside a body of water, it takes 6 (1d12) acid damage every 10 minutes unless moisture is applied to the skin before 10 minutes have passed.",
"attack_bonus": 9,
"damage_dice": "2d6",
"damage_bonus": 5
},
{
"name": "Tail",
"desc": "Melee Weapon Attack: +9 to hit, reach 10 ft., one target. Hit: 15 (3d6 + 5) bludgeoning damage.",
"attack_bonus": 9,
"damage_dice": "3d6",
"damage_bonus": 5
},
{
"name": "Enslave (3/day)",
"desc": "The baboleth targets one creature it can see within 30 ft. of it. The target must succeed on a DC 14 Wisdom saving throw or be magically charmed by the baboleth until the baboleth dies or until it is on a different plane of existence from the target. The charmed target is under the baboleth's control and can't take reactions, and the baboleth and the target can communicate telepathically with each other over any distance.\nWhenever the charmed target takes damage, the target can repeat the saving throw. On a success, the effect ends. No more than once every 24 hours, the target can also repeat the saving throw when it is at least 1 mile away from the baboleth."
}
],
"bonus_actions": null,
"reactions": null,
"legendary_desc": "The baboleth can take 3 legendary actions, choosing from the options below. Only one legendary action option can be used at a time and only at the end of another creature's turn. The baboleth regains spent legendary actions at the start of its turn.",
"legendary_actions": [
{
"name": "Detect",
"desc": "The baboleth makes a Wisdom (Perception) check."
},
{
"name": "Tail Swipe",
"desc": "The baboleth makes one tail attack."
},
{
"name": "Psychic Drain (Costs 2 Actions)",
"desc": "One creature charmed by the baboleth takes 10 (3d6) psychic damage, and the baboleth regains hit points equal to the damage the creature takes."
}
],
"special_abilities": [
{
"name": "Amphibious",
"desc": "The baboleth can breathe air and water."
},
{
"name": "Mucous Cloud",
"desc": "While underwater, the baboleth is surrounded by transformative mucus. A creature that touches the baboleth or that hits it with a melee attack while within 5 ft. of it must make a DC 14 Constitution saving throw. On a failure, the creature is diseased for 1d4 hours. The diseased creature can breathe only underwater."
},
{
"name": "Probing Telepathy",
"desc": "If a creature communicates telepathically with the baboleth, the baboleth learns the creature's greatest desires if the baboleth can see the creature."
}
],
"spell_list": [],
"environments": [
"Underdark",
"Sewer",
"Caverns",
"Plane Of Water",
"Water"
],
"document__slug": "custom-bestiary",
"document__title": "Custom Bestiary"
},
{
"slug": "boblin",
"desc": "",
"name": "Boblin",
"size": "Small",
"type": "Humanoid",
"subtype": "boblinoid",
"group": null,
"alignment": "neutral evil",
"armor_class": 15,
"armor_desc": "leather armor, shield",
"hit_points": 7,
"hit_dice": "2d6",
"speed": {
"walk": 30
},
"strength": 8,
"dexterity": 14,
"constitution": 10,
"intelligence": 10,
"wisdom": 8,
"charisma": 8,
"strength_save": null,
"dexterity_save": null,
"constitution_save": null,
"intelligence_save": null,
"wisdom_save": null,
"charisma_save": null,
"perception": null,
"skills": {
"stealth": 6
},
"damage_vulnerabilities": "",
"damage_resistances": "",
"damage_immunities": "",
"condition_immunities": "",
"senses": "darkvision 60 ft., passive Perception 9",
"languages": "Common, Goblin",
"challenge_rating": "1/4",
"cr": 0.25,
"actions": [
{
"name": "Scimitar",
"desc": "Melee Weapon Attack: +4 to hit, reach 5 ft., one target. Hit: 5 (1d6 + 2) slashing damage.",
"attack_bonus": 4,
"damage_dice": "1d6",
"damage_bonus": 2
},
{
"name": "Shortbow",
"desc": "Ranged Weapon Attack: +4 to hit, range 80/320 ft., one target. Hit: 5 (1d6 + 2) piercing damage.",
"attack_bonus": 4,
"damage_dice": "1d6",
"damage_bonus": 2
}
],
"bonus_actions": null,
"reactions": null,
"legendary_desc": "",
"legendary_actions": null,
"special_abilities": [
{
"name": "Nimble Escape",
"desc": "The goblin can take the Disengage or Hide action as a bonus action on each of its turns."
}
],
"spell_list": [],
"environments": [
"Hill",
"Desert",
"Underdark",
"Mountains",
"Grassland",
"Tundra",
"Ruin",
"Feywild",
"Swamp",
"Settlement",
"Sewer",
"Forest",
"Jungle",
"Hills",
"Caverns"
],
"document__slug": "custom-bestiary",
"document__title": "Custom Bestiary"
}
]
3 changes: 3 additions & 0 deletions assets/templates/custom.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name,hp,armor_class,initiative_mod,level,type
Fighter,25,17,3,2,player
Scary monster,38,17,5,3,monster
Binary file added assets/templates/templates.zip
Binary file not shown.
1 change: 1 addition & 0 deletions drift_schemas/battlemaster/drift_schema_v4.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"encounter_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter<EncounterType>(EncounterType.values)","dart_type_name":"EncounterType"}},{"name":"combatants","getter_name":"combatants","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const CombatantsConverter()","dart_type_name":"List<Combatant>"}},{"name":"engine","getter_name":"engine","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"round","getter_name":"round","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const Constant(1)","default_client_dart":null,"dsl_features":[]},{"name":"turn","getter_name":"turn","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const Constant(0)","default_client_dart":null,"dsl_features":[]},{"name":"logs","getter_name":"logs","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant('[]')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EncounterLogConverter()","dart_type_name":"List<EncounterLog>"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[],"type":"table","data":{"name":"custom_conditions","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"description","getter_name":"description","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"engine","getter_name":"engine","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter<GameEngineType>(GameEngineType.values)","dart_type_name":"GameEngineType"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":2,"references":[],"type":"table","data":{"name":"custom_bestiaries","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"combatants","getter_name":"combatants","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const CombatantsConverter()","dart_type_name":"List<Combatant>"}},{"name":"engine","getter_name":"engine","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter<GameEngineType>(GameEngineType.values)","dart_type_name":"GameEngineType"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}}]}
43 changes: 34 additions & 9 deletions lib/database/database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import '../features/encounters/models/encounter_type.dart';

part 'database.g.dart';

@DriftDatabase(tables: [EncounterTable, CustomConditions])
@DriftDatabase(tables: [EncounterTable, CustomConditions, CustomBestiaries])
class AppDatabase extends _$AppDatabase {
AppDatabase({QueryExecutor? executor})
: super(
Expand All @@ -35,18 +35,24 @@ class AppDatabase extends _$AppDatabase {
);

@override
int get schemaVersion => 3;
int get schemaVersion => 4;

@override
MigrationStrategy get migration {
return MigrationStrategy(
onUpgrade: stepByStep(from1To2: (m, schema) async {
await m.createTable(customConditions);
}, from2To3: (m, schema) async {
await m.addColumn(encounterTable, encounterTable.round);
await m.addColumn(encounterTable, encounterTable.turn);
await m.addColumn(encounterTable, encounterTable.logs);
}),
onUpgrade: stepByStep(
from1To2: (m, schema) async {
await m.createTable(customConditions);
},
from2To3: (m, schema) async {
await m.addColumn(encounterTable, encounterTable.round);
await m.addColumn(encounterTable, encounterTable.turn);
await m.addColumn(encounterTable, encounterTable.logs);
},
from3To4: (m, schema) async {
await m.createTable(customBestiaries);
},
),
);
}

Expand Down Expand Up @@ -136,8 +142,27 @@ class AppDatabase extends _$AppDatabase {
.go();
}

Stream<List<CustomBestiary>> watchCustomBestiaries() {
return select(customBestiaries).watch();
}

Future<void> insertBestiary(CustomBestiary bestiary) async {
await into(customBestiaries).insert(
CustomBestiariesCompanion.insert(
name: bestiary.name,
combatants: bestiary.combatants,
engine: bestiary.engine,
),
);
}

Future<void> deleteBestiary(int id) async {
await (delete(customBestiaries)..where((b) => b.id.equals(id))).go();
}

Future<void> eraseDb() async {
await delete(encounterTable).go();
await delete(customConditions).go();
await delete(customBestiaries).go();
}
}
Loading

0 comments on commit c860e16

Please sign in to comment.