Adds import support from our other project.

This commit is contained in:
2026-09-07 21:57:59 -07:00
parent a3d1a139e9
commit a3dace36de
150 changed files with 17716 additions and 21 deletions

View File

@@ -0,0 +1,768 @@
{
"formatVersion": 1,
"database": {
"version": 7,
"identityHash": "38f6bbd6f1e298b20e17b3a08fbc7f70",
"entities": [
{
"tableName": "monsters",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`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`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "subtype",
"columnName": "subtype",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "alignment",
"columnName": "alignment",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "strengthScore",
"columnName": "strength_score",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "10"
},
{
"fieldPath": "strengthSavingThrowAdvantage",
"columnName": "strength_saving_throw_advantage",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "strengthSavingThrowProficiency",
"columnName": "strength_saving_throw_proficiency",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "dexterityScore",
"columnName": "dexterity_score",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "10"
},
{
"fieldPath": "dexteritySavingThrowAdvantage",
"columnName": "dexterity_saving_throw_advantage",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "dexteritySavingThrowProficiency",
"columnName": "dexterity_saving_throw_proficiency",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "constitutionScore",
"columnName": "constitution_score",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "10"
},
{
"fieldPath": "constitutionSavingThrowAdvantage",
"columnName": "constitution_saving_throw_advantage",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "constitutionSavingThrowProficiency",
"columnName": "constitution_saving_throw_proficiency",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "intelligenceScore",
"columnName": "intelligence_score",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "10"
},
{
"fieldPath": "intelligenceSavingThrowAdvantage",
"columnName": "intelligence_saving_throw_advantage",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "intelligenceSavingThrowProficiency",
"columnName": "intelligence_saving_throw_proficiency",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "wisdomScore",
"columnName": "wisdom_score",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "10"
},
{
"fieldPath": "wisdomSavingThrowAdvantage",
"columnName": "wisdom_saving_throw_advantage",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "wisdomSavingThrowProficiency",
"columnName": "wisdom_saving_throw_proficiency",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "charismaScore",
"columnName": "charisma_score",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "10"
},
{
"fieldPath": "charismaSavingThrowAdvantage",
"columnName": "charisma_saving_throw_advantage",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "charismaSavingThrowProficiency",
"columnName": "charisma_saving_throw_proficiency",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "armorType",
"columnName": "armor_type",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "shieldBonus",
"columnName": "shield_bonus",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "naturalArmorBonus",
"columnName": "natural_armor_bonus",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "otherArmorDescription",
"columnName": "other_armor_description",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "hitDice",
"columnName": "hit_dice",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
},
{
"fieldPath": "hasCustomHP",
"columnName": "has_custom_hit_points",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "customHPDescription",
"columnName": "custom_hit_points_description",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "walkSpeed",
"columnName": "walk_speed",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "burrowSpeed",
"columnName": "burrow_speed",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "climbSpeed",
"columnName": "climb_speed",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "flySpeed",
"columnName": "fly_speed",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "canHover",
"columnName": "can_hover",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "swimSpeed",
"columnName": "swim_speed",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "hasCustomSpeed",
"columnName": "has_custom_speed",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "customSpeedDescription",
"columnName": "custom_speed_description",
"affinity": "TEXT"
},
{
"fieldPath": "challengeRating",
"columnName": "challenge_rating",
"affinity": "TEXT",
"defaultValue": "'1'"
},
{
"fieldPath": "customChallengeRatingDescription",
"columnName": "custom_challenge_rating_description",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "customProficiencyBonus",
"columnName": "custom_proficiency_bonus",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "telepathyRange",
"columnName": "telepathy_range",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "understandsButDescription",
"columnName": "understands_but_description",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "senses",
"columnName": "senses",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "skills",
"columnName": "skills",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "damageImmunities",
"columnName": "damage_immunities",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "damageResistances",
"columnName": "damage_resistances",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "damageVulnerabilities",
"columnName": "damage_vulnerabilities",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "conditionImmunities",
"columnName": "condition_immunities",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "languages",
"columnName": "languages",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "abilities",
"columnName": "abilities",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "actions",
"columnName": "actions",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "reactions",
"columnName": "reactions",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "lairActions",
"columnName": "lair_actions",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "legendaryActions",
"columnName": "legendary_actions",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "regionalActions",
"columnName": "regional_actions",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "sourceUrl",
"columnName": "source_url",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "bonusActions",
"columnName": "bonus_actions",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "mythicActions",
"columnName": "mythic_actions",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "legendaryActionsDescription",
"columnName": "legendary_actions_description",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "lairActionsDescription",
"columnName": "lair_actions_description",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "lairActionsEndNote",
"columnName": "lair_actions_end_note",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "regionalActionsDescription",
"columnName": "regional_actions_description",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "regionalActionsEndNote",
"columnName": "regional_actions_end_note",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "mythicActionsDescription",
"columnName": "mythic_actions_description",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "playerName",
"columnName": "player_name",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "background",
"columnName": "background",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "personalityTraits",
"columnName": "personality_traits",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "ideals",
"columnName": "ideals",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "bonds",
"columnName": "bonds",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "flaws",
"columnName": "flaws",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "age",
"columnName": "age",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "height",
"columnName": "height",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "weight",
"columnName": "weight",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "eyes",
"columnName": "eyes",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "skin",
"columnName": "skin",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "hair",
"columnName": "hair",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "appearance",
"columnName": "appearance",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "backstory",
"columnName": "backstory",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "alliesAndOrganizations",
"columnName": "allies_and_organizations",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "monsters_fts",
"createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`name` TEXT, `size` TEXT, `type` TEXT, `subtype` TEXT, `alignment` TEXT, content=`monsters`)",
"fields": [
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT"
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "TEXT"
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT"
},
{
"fieldPath": "subtype",
"columnName": "subtype",
"affinity": "TEXT"
},
{
"fieldPath": "alignment",
"columnName": "alignment",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": []
},
"ftsVersion": "FTS4",
"ftsOptions": {
"tokenizer": "simple",
"tokenizerArgs": [],
"contentTable": "monsters",
"languageIdColumnName": "",
"matchInfo": "FTS4",
"notIndexedColumns": [],
"prefixSizes": [],
"preferredOrder": "ASC"
},
"contentSyncTriggers": [
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_UPDATE BEFORE UPDATE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_DELETE BEFORE DELETE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_UPDATE AFTER UPDATE ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END",
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_INSERT AFTER INSERT ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END"
]
},
{
"tableName": "collections",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `description` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "collection_monsters",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `collection_id` TEXT NOT NULL, `monster_id` TEXT NOT NULL, `ordinal` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`collection_id`) REFERENCES `collections`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`monster_id`) REFERENCES `monsters`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "collectionId",
"columnName": "collection_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "monsterId",
"columnName": "monster_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "ordinal",
"columnName": "ordinal",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_collection_monsters_collection_id",
"unique": false,
"columnNames": [
"collection_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_monsters_collection_id` ON `${TABLE_NAME}` (`collection_id`)"
},
{
"name": "index_collection_monsters_monster_id",
"unique": false,
"columnNames": [
"monster_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_monsters_monster_id` ON `${TABLE_NAME}` (`monster_id`)"
}
],
"foreignKeys": [
{
"table": "collections",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"collection_id"
],
"referencedColumns": [
"id"
]
},
{
"table": "monsters",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"monster_id"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "dashboard_monsters",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `monster_id` TEXT NOT NULL, `ordinal` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`monster_id`) REFERENCES `monsters`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "monsterId",
"columnName": "monster_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "ordinal",
"columnName": "ordinal",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_dashboard_monsters_monster_id",
"unique": false,
"columnNames": [
"monster_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_dashboard_monsters_monster_id` ON `${TABLE_NAME}` (`monster_id`)"
}
],
"foreignKeys": [
{
"table": "monsters",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"monster_id"
],
"referencedColumns": [
"id"
]
}
]
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '38f6bbd6f1e298b20e17b3a08fbc7f70')"
]
}
}

View File

@@ -0,0 +1,768 @@
{
"formatVersion": 1,
"database": {
"version": 8,
"identityHash": "38f6bbd6f1e298b20e17b3a08fbc7f70",
"entities": [
{
"tableName": "monsters",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`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`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "subtype",
"columnName": "subtype",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "alignment",
"columnName": "alignment",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "strengthScore",
"columnName": "strength_score",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "10"
},
{
"fieldPath": "strengthSavingThrowAdvantage",
"columnName": "strength_saving_throw_advantage",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "strengthSavingThrowProficiency",
"columnName": "strength_saving_throw_proficiency",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "dexterityScore",
"columnName": "dexterity_score",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "10"
},
{
"fieldPath": "dexteritySavingThrowAdvantage",
"columnName": "dexterity_saving_throw_advantage",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "dexteritySavingThrowProficiency",
"columnName": "dexterity_saving_throw_proficiency",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "constitutionScore",
"columnName": "constitution_score",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "10"
},
{
"fieldPath": "constitutionSavingThrowAdvantage",
"columnName": "constitution_saving_throw_advantage",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "constitutionSavingThrowProficiency",
"columnName": "constitution_saving_throw_proficiency",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "intelligenceScore",
"columnName": "intelligence_score",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "10"
},
{
"fieldPath": "intelligenceSavingThrowAdvantage",
"columnName": "intelligence_saving_throw_advantage",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "intelligenceSavingThrowProficiency",
"columnName": "intelligence_saving_throw_proficiency",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "wisdomScore",
"columnName": "wisdom_score",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "10"
},
{
"fieldPath": "wisdomSavingThrowAdvantage",
"columnName": "wisdom_saving_throw_advantage",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "wisdomSavingThrowProficiency",
"columnName": "wisdom_saving_throw_proficiency",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "charismaScore",
"columnName": "charisma_score",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "10"
},
{
"fieldPath": "charismaSavingThrowAdvantage",
"columnName": "charisma_saving_throw_advantage",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "charismaSavingThrowProficiency",
"columnName": "charisma_saving_throw_proficiency",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "armorType",
"columnName": "armor_type",
"affinity": "TEXT",
"defaultValue": "'none'"
},
{
"fieldPath": "shieldBonus",
"columnName": "shield_bonus",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "naturalArmorBonus",
"columnName": "natural_armor_bonus",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "otherArmorDescription",
"columnName": "other_armor_description",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "hitDice",
"columnName": "hit_dice",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
},
{
"fieldPath": "hasCustomHP",
"columnName": "has_custom_hit_points",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "customHPDescription",
"columnName": "custom_hit_points_description",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "walkSpeed",
"columnName": "walk_speed",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "burrowSpeed",
"columnName": "burrow_speed",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "climbSpeed",
"columnName": "climb_speed",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "flySpeed",
"columnName": "fly_speed",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "canHover",
"columnName": "can_hover",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "swimSpeed",
"columnName": "swim_speed",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "hasCustomSpeed",
"columnName": "has_custom_speed",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "customSpeedDescription",
"columnName": "custom_speed_description",
"affinity": "TEXT"
},
{
"fieldPath": "challengeRating",
"columnName": "challenge_rating",
"affinity": "TEXT",
"defaultValue": "'1'"
},
{
"fieldPath": "customChallengeRatingDescription",
"columnName": "custom_challenge_rating_description",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "customProficiencyBonus",
"columnName": "custom_proficiency_bonus",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "telepathyRange",
"columnName": "telepathy_range",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "understandsButDescription",
"columnName": "understands_but_description",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "senses",
"columnName": "senses",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "skills",
"columnName": "skills",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "damageImmunities",
"columnName": "damage_immunities",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "damageResistances",
"columnName": "damage_resistances",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "damageVulnerabilities",
"columnName": "damage_vulnerabilities",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "conditionImmunities",
"columnName": "condition_immunities",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "languages",
"columnName": "languages",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "abilities",
"columnName": "abilities",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "actions",
"columnName": "actions",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "reactions",
"columnName": "reactions",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "lairActions",
"columnName": "lair_actions",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "legendaryActions",
"columnName": "legendary_actions",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "regionalActions",
"columnName": "regional_actions",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "sourceUrl",
"columnName": "source_url",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "bonusActions",
"columnName": "bonus_actions",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "mythicActions",
"columnName": "mythic_actions",
"affinity": "TEXT",
"defaultValue": "'[]'"
},
{
"fieldPath": "legendaryActionsDescription",
"columnName": "legendary_actions_description",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "lairActionsDescription",
"columnName": "lair_actions_description",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "lairActionsEndNote",
"columnName": "lair_actions_end_note",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "regionalActionsDescription",
"columnName": "regional_actions_description",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "regionalActionsEndNote",
"columnName": "regional_actions_end_note",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "mythicActionsDescription",
"columnName": "mythic_actions_description",
"affinity": "TEXT",
"defaultValue": "''"
},
{
"fieldPath": "playerName",
"columnName": "player_name",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "background",
"columnName": "background",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "personalityTraits",
"columnName": "personality_traits",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "ideals",
"columnName": "ideals",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "bonds",
"columnName": "bonds",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "flaws",
"columnName": "flaws",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "age",
"columnName": "age",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "height",
"columnName": "height",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "weight",
"columnName": "weight",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "eyes",
"columnName": "eyes",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "skin",
"columnName": "skin",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "hair",
"columnName": "hair",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "appearance",
"columnName": "appearance",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "backstory",
"columnName": "backstory",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "alliesAndOrganizations",
"columnName": "allies_and_organizations",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "monsters_fts",
"createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`name` TEXT, `size` TEXT, `type` TEXT, `subtype` TEXT, `alignment` TEXT, content=`monsters`)",
"fields": [
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT"
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "TEXT"
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT"
},
{
"fieldPath": "subtype",
"columnName": "subtype",
"affinity": "TEXT"
},
{
"fieldPath": "alignment",
"columnName": "alignment",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": []
},
"ftsVersion": "FTS4",
"ftsOptions": {
"tokenizer": "simple",
"tokenizerArgs": [],
"contentTable": "monsters",
"languageIdColumnName": "",
"matchInfo": "FTS4",
"notIndexedColumns": [],
"prefixSizes": [],
"preferredOrder": "ASC"
},
"contentSyncTriggers": [
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_UPDATE BEFORE UPDATE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_DELETE BEFORE DELETE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_UPDATE AFTER UPDATE ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END",
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_INSERT AFTER INSERT ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END"
]
},
{
"tableName": "collections",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `description` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "collection_monsters",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `collection_id` TEXT NOT NULL, `monster_id` TEXT NOT NULL, `ordinal` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`collection_id`) REFERENCES `collections`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`monster_id`) REFERENCES `monsters`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "collectionId",
"columnName": "collection_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "monsterId",
"columnName": "monster_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "ordinal",
"columnName": "ordinal",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_collection_monsters_collection_id",
"unique": false,
"columnNames": [
"collection_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_monsters_collection_id` ON `${TABLE_NAME}` (`collection_id`)"
},
{
"name": "index_collection_monsters_monster_id",
"unique": false,
"columnNames": [
"monster_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_monsters_monster_id` ON `${TABLE_NAME}` (`monster_id`)"
}
],
"foreignKeys": [
{
"table": "collections",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"collection_id"
],
"referencedColumns": [
"id"
]
},
{
"table": "monsters",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"monster_id"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "dashboard_monsters",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `monster_id` TEXT NOT NULL, `ordinal` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`monster_id`) REFERENCES `monsters`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "monsterId",
"columnName": "monster_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "ordinal",
"columnName": "ordinal",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_dashboard_monsters_monster_id",
"unique": false,
"columnNames": [
"monster_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_dashboard_monsters_monster_id` ON `${TABLE_NAME}` (`monster_id`)"
}
],
"foreignKeys": [
{
"table": "monsters",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"monster_id"
],
"referencedColumns": [
"id"
]
}
]
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '38f6bbd6f1e298b20e17b3a08fbc7f70')"
]
}
}

View File

@@ -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 -->

View File

@@ -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,

View File

@@ -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

View File

@@ -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();

View File

@@ -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;
}
}

View File

@@ -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());
}

View File

@@ -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();
}
}

View File

@@ -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<>();

View File

@@ -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();
}
}
}
}

View File

@@ -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&amp;D Beyond</string>
<string name="dialog_import_url_message">Enter or paste a D&amp;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&amp;D Beyond URL, Character ID, or Open5e JSON payload:</string>
<string name="dialog_import_url_hint">e.g. D&amp;D Beyond URL or Open5e JSON</string>
<string name="dialog_import">Import</string>
<string name="toast_importing_url">Importing character from D&amp;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>

View File

@@ -0,0 +1,83 @@
package com.majinnaibu.monstercards.exporters;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.majinnaibu.monstercards.data.enums.AbilityScore;
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 org.junit.Before;
import org.junit.Test;
import java.util.UUID;
public class Open5eExporterTest {
private Open5eExporter exporter;
@Before
public void setUp() {
exporter = new Open5eExporter();
}
@Test
public void testExportMonster_BasicFieldsAndEnvelope() {
Monster monster = new Monster();
monster.id = UUID.fromString("e5b72e61-a83d-4c3d-b4b1-e28a5a41c101");
monster.name = "Goblin";
monster.size = "Small";
monster.type = "Humanoid";
monster.subtype = "goblinoid";
monster.alignment = "neutral evil";
monster.strengthScore = 8;
monster.dexterityScore = 14;
monster.constitutionScore = 10;
monster.intelligenceScore = 10;
monster.wisdomScore = 8;
monster.charismaScore = 8;
monster.walkSpeed = 30;
monster.playerName = "Player One";
monster.background = "Criminal";
monster.personalityTraits = "Always cautious";
monster.languages.add(new Language("Common", true));
monster.languages.add(new Language("Goblin", true));
monster.skills.add(new Skill("Stealth", AbilityScore.DEXTERITY));
monster.abilities.add(new Trait("Nimble Escape", "Disengage or Hide as bonus action."));
monster.actions.add(new Trait("Scimitar", "+4 to hit, 1d6+2 slashing."));
String json = exporter.exportMonster(monster);
assertNotNull(json);
JsonObject root = JsonParser.parseString(json).getAsJsonObject();
assertEquals("open5e", root.get("ruleset_id").getAsString());
assertEquals("character", root.get("entity_type").getAsString());
assertEquals("Goblin", root.get("display_name").getAsString());
assertEquals("e5b72e61-a83d-4c3d-b4b1-e28a5a41c101", root.get("uuid").getAsString());
assertTrue(root.has("properties"));
JsonObject props = root.getAsJsonObject("properties");
assertEquals("Goblin", props.get("name").getAsString());
assertEquals("Small", props.get("size").getAsString());
assertEquals("Player One", props.get("player_name").getAsString());
assertEquals("Criminal", props.get("background").getAsString());
JsonObject abilities = props.getAsJsonObject("abilities");
assertEquals(8, abilities.get("strength").getAsInt());
assertEquals(14, abilities.get("dexterity").getAsInt());
assertTrue(props.has("skills"));
assertTrue(props.has("languages"));
assertTrue(props.has("traits"));
assertTrue(props.has("actions"));
}
}

View File

@@ -0,0 +1,100 @@
package com.majinnaibu.monstercards.importers;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import com.majinnaibu.monstercards.exporters.Open5eExporter;
import com.majinnaibu.monstercards.helpers.MonsterImportHelper;
import com.majinnaibu.monstercards.models.Monster;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.nio.file.Files;
public class Open5eImporterTest {
private Open5eImporter importer;
private Open5eExporter exporter;
@Before
public void setUp() {
importer = new Open5eImporter();
exporter = new Open5eExporter();
}
@Test
public void testParseGoblinSampleJson() throws Exception {
File sampleFile = new File("/Users/tom/Projects/TTRPG/CharacterDataFiles/rulesets/open5e/examples/character/goblin.json");
if (!sampleFile.exists()) {
sampleFile = new File("../../../CharacterDataFiles/rulesets/open5e/examples/character/goblin.json");
}
if (!sampleFile.exists()) {
sampleFile = new File("../../CharacterDataFiles/rulesets/open5e/examples/character/goblin.json");
}
assertTrue("Goblin sample JSON file should exist", sampleFile.exists());
String json = new String(Files.readAllBytes(sampleFile.toPath()));
assertTrue(importer.canImport(json));
Monster monster = importer.parse(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(8, monster.strengthScore);
assertEquals(14, monster.dexterityScore);
assertEquals(10, monster.constitutionScore);
assertEquals(10, monster.intelligenceScore);
assertEquals(8, monster.wisdomScore);
assertEquals(8, monster.charismaScore);
assertEquals(30, monster.walkSpeed);
assertEquals(2, monster.hitDice);
assertEquals(1, monster.abilities.size());
assertEquals("Nimble Escape", monster.abilities.get(0).name);
assertEquals(2, monster.actions.size());
assertEquals("Scimitar", monster.actions.get(0).name);
assertEquals("Shortbow", monster.actions.get(1).name);
Monster delegatedMonster = MonsterImportHelper.fromJSON(json);
assertNotNull(delegatedMonster);
assertEquals("Goblin", delegatedMonster.name);
}
@Test
public void testRoundTrip_ExportAndImport() throws Exception {
Monster monster = new Monster();
monster.name = "Custom Dragon";
monster.size = "Huge";
monster.type = "Dragon";
monster.strengthScore = 23;
monster.dexterityScore = 10;
monster.constitutionScore = 21;
monster.intelligenceScore = 14;
monster.wisdomScore = 13;
monster.charismaScore = 17;
monster.playerName = "DM";
monster.background = "Ancient";
String open5eJson = exporter.exportMonster(monster);
assertTrue(importer.canImport(open5eJson));
Monster importedMonster = importer.parse(open5eJson);
assertNotNull(importedMonster);
assertEquals("Custom Dragon", importedMonster.name);
assertEquals("Huge", importedMonster.size);
assertEquals("Dragon", importedMonster.type);
assertEquals(23, importedMonster.strengthScore);
assertEquals(21, importedMonster.constitutionScore);
assertEquals("DM", importedMonster.playerName);
assertEquals("Ancient", importedMonster.background);
}
}

View File

@@ -4,9 +4,19 @@ This document outlines the modular architecture and step-by-step technical roadm
---
## 1. Modular Importer Architecture
## 1. File Formats & Importer Architecture
To keep import/export code clean and maintainable, all format parsers implement a shared interface:
Monster Cards separates canonical native formats from third-party import sources:
1. **Universal Native Format (`.card` / `.card.txt`)**:
- Our native file format utilizing the **Universal 2-Tier Envelope Specification** (`$schema`, `uuid`, `ruleset_id`, `entity_type`, `display_name`, `template_id`, `properties`).
- Uses the **Open5e Ruleset** (`ruleset_id: "open5e"`) which matches our app's 5e monster and character needs ([`entities/character.json`](file:///Users/tom/Projects/TTRPG/CharacterDataFiles/rulesets/open5e/entities/character.json)).
- Supported for both **Export** and **Native Import**.
2. **Third-Party Import-Only Formats**:
- `.monster` / `.monster.txt`: Tetra-cube generator stat block JSON format (Import-only).
- D&D Beyond URLs / API payloads: D&D Beyond character web service format (Import-only).
All format parsers implement a shared interface:
```java
public interface EntityImporter<T> {
@@ -72,12 +82,12 @@ The output of any `EntityImporter` is an in-memory domain model (such as `Monste
---
### Step 0: Restrict File Extension Intent Filters
### Step 0: File Extension Intent Filters (.monster & .card)
Restrict the Android app so it reacts **only** to `.monster` and `.monster.txt` files instead of all generic `.txt` files.
Restrict the Android app so it reacts to supported stat block file extensions (`.monster`, `.monster.txt` for Tetra-cube format; `.card`, `.card.txt` for internal Open5e card format) instead of all generic `.txt` files.
1. **`AndroidManifest.xml`**:
Add `android:pathPattern` and `android:pathSuffix` constraints (`.monster` and `.monster.txt`) to the `<intent-filter>` for `MainActivity`.
Add `android:pathPattern` constraints (`.monster`, `.monster.txt`, `.card`, `.card.txt`) to the `<intent-filter>` for `MainActivity`.
2. **`MainActivity.java`**:
Implement runtime display name validation querying `OpenableColumns.DISPLAY_NAME` via `ContentResolver` to filter out non-monster files passed via `content://` URIs.
@@ -193,6 +203,13 @@ The Open5e ruleset is part of a modular, versioned ruleset framework located at
- **Payload Mapping**:
- Maps `Monster` fields to Open5e character properties in `properties` object conforming to [`entities/character.json`](file:///Users/tom/Projects/TTRPG/CharacterDataFiles/rulesets/open5e/entities/character.json).
#### Open5e vs. Monster Cards Database Schema Comparison & Final Mappings
- **Database Storage Architecture (Room SQLite vs. Open5e JSON)**:
- **Ability Scores**: Kept as flat integer columns in Room (`strength_score`, `dexterity_score`, etc.) for maximum query performance. When exporting to Open5e JSON, `Open5eExporter` packages these into a `properties.abilities` JSON object `{ "strength": 12, ... }`. Modifiers are calculated dynamically using `floor((score - 10) / 2)`.
- **Variable-Length Lists (`traits`, `actions`, `skills`, `languages`)**: Stored as text columns using Room `@TypeConverter` JSON converters. In Open5e JSON export, traits and actions are formatted as `{ "name": "...", "desc": "..." }` arrays.
- **PC Identity Fields**: Added PC identity columns (`player_name`, `background`, `personality_traits`, `ideals`, `bonds`, `flaws`, `age`, `height`, `weight`, `eyes`, `skin`, `hair`, `appearance`, `backstory`, `allies_and_organizations`) directly to `Monster.java` (`Database version 7`, `MIGRATION_6_7`).
- **Omitted PC Fields**: `currency`, `inventory`, `death_saves`, and `spellcasting.slots` are ignored/omitted in monster stat block exports.
---
### Step 5: Import from Custom Internal Format (Open5e Specification)
@@ -230,9 +247,9 @@ Extend the generic sharing feature with specialized, direct sharing channels (to
- [x] **Step 0**: Restrict app intent filters in `AndroidManifest.xml` (`.monster` & `.monster.txt`) and add runtime filename validation in `MainActivity.java`.
- [x] **Step 1**: Refactor import & conversion code into a shared `EntityImporter<T>` interface and `TetraCubeMonsterImporter` class.
- [x] **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 4**: Export to internal format described by Open5e document (`Open5eExporter`).
- [ ] **Step 5**: Import from internal format described by Open5e document (`Open5eImporter`).
- [x] **Step 3**: Import from D&D Beyond URL (`https://www.dndbeyond.com/characters/49074997` fetching from v5 character service endpoint).
- [x] **Step 4**: Export to internal format described by Open5e document (`Open5eExporter`).
- [x] **Step 5**: Import from internal format described by Open5e document (`Open5eImporter`).
- [ ] **Step 6**: Generic Android Share button feature (`ACTION_SEND`).
- [ ] **Step 7**: Specific share targets (7.1 NFC, 7.2 Bluetooth, 7.3 Embedded Web URL).

View File

@@ -0,0 +1 @@
{"name":"Acolyte","size":"medium","type":"Humanoid","tag":"any race","alignment":"any alignment","hitDice":2,"armorName":"none","shieldBonus":0,"natArmorBonus":7,"otherArmorDesc":"10","speed":30,"burrowSpeed":0,"climbSpeed":0,"flySpeed":0,"hover":false,"swimSpeed":0,"customHP":false,"customSpeed":false,"hpText":"2","speedDesc":"30 ft.","strPoints":10,"dexPoints":10,"conPoints":10,"intPoints":10,"wisPoints":14,"chaPoints":11,"blindsight":0,"blind":false,"darkvision":0,"tremorsense":0,"truesight":0,"telepathy":0,"cr":"1/4","customCr":"1/4 (50 XP)","customProf":2,"isLegendary":false,"legendariesDescription":"The acolyte 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 acolyte regains spent legendary actions at the start of its turn.","isLair":false,"lairDescription":"When fighting inside its lair, the acolyte can invoke the ambient magic to take lair actions. On initiative count 20 (losing initiative ties), the acolyte can take one lair action to cause one of the following effects:","lairDescriptionEnd":"The acolyte can't repeat an effect until they have all been used, and it can't use the same effect two rounds in a row.","isMythic":false,"mythicDescription":"If the acolyte's mythic trait is active, it can use the options below as legendary actions for 1 hour after using {Some Ability}.","isRegional":false,"regionalDescription":"The region containing the acolyte's lair is warped by the creature's presence, which creates one or more of the following effects:","regionalDescriptionEnd":"If the acolyte dies, the first two effects fade over the course of 3d10 days.","properties":[],"abilities":[{"name":"Spellcasting","desc":"The acolyte is a 1st-level spellcaster. Its spellcasting ability is Wisdom (spell save DC [WIS SAVE], [WIS ATK] to hit with spell attacks). The acolyte has following cleric spells prepared:\n\n\n> * Cantrips (at will): _light, sacred flame, thaumaturgy_\n> * 1st level (3 slots): _bless, cure wounds, sanctuary_"}],"actions":[{"name":"Club","desc":"_Melee Weapon Attack:_ +2 to hit, reach 5 ft., one target. _Hit:_ 2 (1d4) bludgeoning damage."}],"bonusActions":[],"reactions":[],"legendaries":[],"mythics":[],"lairs":[],"regionals":[],"sthrows":[],"skills":[{"name":"medicine","stat":"wis"},{"name":"religion","stat":"int"}],"damagetypes":[],"specialdamage":[],"conditions":[],"languages":[{"name":"any one language (usually Common)","speaks":true}],"understandsBut":"","shortName":"","pluralName":"","doubleColumns":false,"separationPoint":0,"damage":[]}

View File

@@ -0,0 +1,49 @@
Homebrewery V3
{{monster,frame
## Acolyte
*Medium Humanoid (any race), any alignment*
___
**Armor Class** :: 10
**Hit Points** :: 9 (2d8)
**Speed** :: 30 ft.
___
|STR|DEX|CON|INT|WIS|CHA|
|:---:|:---:|:---:|:---:|:---:|:---:|
|10 (+0)|10 (+0)|10 (+0)|10 (+0)|14 (+2)|11 (+0)|
___
**Skills** :: Medicine +4, Religion +2
**Senses** :: passive Perception 12
**Languages** :: any one language (usually Common)
**Challenge** :: 1/4 (50 XP)
___
***Spellcasting.*** The acolyte is a 1st-level spellcaster. Its spellcasting ability is Wisdom (spell save DC 12, +4 to hit with spell attacks). The acolyte has following cleric spells prepared:<br>
<br>
* Cantrips (at will): _light, sacred flame, thaumaturgy_<br>
* 1st level (3 slots): _bless, cure wounds, sanctuary_
### Actions
***Club.*** _Melee Weapon Attack:_ +2 to hit, reach 5 ft., one target. _Hit:_ 2 (1d4) bludgeoning damage.
}}
Homebrewery (Legacy)/GM Binder Markdown
___
> ## Acolyte
> *Medium Humanoid (any race), any alignment*
> ___
> - **Armor Class** 10
> - **Hit Points** 9 (2d8)
> - **Speed** 30 ft.
> ___
> |STR|DEX|CON|INT|WIS|CHA|
> |:---:|:---:|:---:|:---:|:---:|:---:|
> |10 (+0)|10 (+0)|10 (+0)|10 (+0)|14 (+2)|11 (+0)|
> ___
> - **Skills** Medicine +4, Religion +2
> - **Senses** passive Perception 12
> - **Languages** any one language (usually Common)
> - **Challenge** 1/4 (50 XP)
> ___
> ***Spellcasting.*** The acolyte is a 1st-level spellcaster. Its spellcasting ability is Wisdom (spell save DC 12, +4 to hit with spell attacks). The acolyte has following cleric spells prepared:<br>
> <br>
> * Cantrips (at will): _light, sacred flame, thaumaturgy_<br>
> * 1st level (3 slots): _bless, cure wounds, sanctuary_
> ### Actions
> ***Club.*** _Melee Weapon Attack:_ +2 to hit, reach 5 ft., one target. _Hit:_ 2 (1d4) bludgeoning damage.

View File

@@ -0,0 +1 @@
{"name":"Acolyte","size":"medium","type":"Humanoid","tag":"any race","alignment":"any alignment","hitDice":2,"armorName":"none","shieldBonus":0,"natArmorBonus":7,"otherArmorDesc":"10","speed":30,"burrowSpeed":0,"climbSpeed":0,"flySpeed":0,"hover":false,"swimSpeed":0,"customHP":false,"customSpeed":false,"hpText":"2","speedDesc":"30 ft.","strPoints":10,"dexPoints":10,"conPoints":10,"intPoints":10,"wisPoints":14,"chaPoints":11,"blindsight":0,"blind":false,"darkvision":0,"tremorsense":0,"truesight":0,"telepathy":0,"cr":"1/4","customCr":"1/4 (50 XP)","customProf":2,"isLegendary":false,"legendariesDescription":"The acolyte 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 acolyte regains spent legendary actions at the start of its turn.","isLair":false,"lairDescription":"When fighting inside its lair, the acolyte can invoke the ambient magic to take lair actions. On initiative count 20 (losing initiative ties), the acolyte can take one lair action to cause one of the following effects:","lairDescriptionEnd":"The acolyte can't repeat an effect until they have all been used, and it can't use the same effect two rounds in a row.","isMythic":false,"mythicDescription":"If the acolyte's mythic trait is active, it can use the options below as legendary actions for 1 hour after using {Some Ability}.","isRegional":false,"regionalDescription":"The region containing the acolyte's lair is warped by the creature's presence, which creates one or more of the following effects:","regionalDescriptionEnd":"If the acolyte dies, the first two effects fade over the course of 3d10 days.","properties":[],"abilities":[{"name":"Spellcasting","desc":"The acolyte is a 1st-level spellcaster. Its spellcasting ability is Wisdom (spell save DC [WIS SAVE], [WIS ATK] to hit with spell attacks). The acolyte has following cleric spells prepared:\n\n\n> * Cantrips (at will): _light, sacred flame, thaumaturgy_\n> * 1st level (3 slots): _bless, cure wounds, sanctuary_"}],"actions":[{"name":"Club","desc":"_Melee Weapon Attack:_ +2 to hit, reach 5 ft., one target. _Hit:_ 2 (1d4) bludgeoning damage."}],"bonusActions":[],"reactions":[],"legendaries":[],"mythics":[],"lairs":[],"regionals":[],"sthrows":[],"skills":[{"name":"medicine","stat":"wis"},{"name":"religion","stat":"int"}],"damagetypes":[],"specialdamage":[],"conditions":[],"languages":[{"name":"any one language (usually Common)","speaks":true}],"understandsBut":"","shortName":"","pluralName":"","doubleColumns":false,"separationPoint":0,"damage":[]}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

View File

@@ -0,0 +1 @@
{"name":"Adult Black Dragon","size":"huge","type":"Dragon","tag":"","alignment":"chaotic evil","hitDice":17,"armorName":"natural armor","shieldBonus":0,"natArmorBonus":7,"otherArmorDesc":"19 (natural armor)","speed":40,"burrowSpeed":0,"climbSpeed":0,"flySpeed":80,"hover":false,"swimSpeed":40,"customHP":false,"customSpeed":false,"hpText":"17","speedDesc":"40 ft., fly 80 ft., swim 40 ft.","strPoints":23,"dexPoints":14,"conPoints":21,"intPoints":14,"wisPoints":13,"chaPoints":17,"blindsight":60,"blind":false,"darkvision":120,"tremorsense":0,"truesight":0,"telepathy":0,"cr":"14","customCr":"14 (11,500 XP)","customProf":5,"isLegendary":true,"legendariesDescription":"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.","isLair":false,"lairDescription":"When fighting inside its lair, the adult black dragon can invoke the ambient magic to take lair actions. On initiative count 20 (losing initiative ties), the adult black dragon can take one lair action to cause one of the following effects:","lairDescriptionEnd":"The adult black dragon can't repeat an effect until they have all been used, and it can't use the same effect two rounds in a row.","isMythic":false,"mythicDescription":"If the adult black dragon's mythic trait is active, it can use the options below as legendary actions for 1 hour after using {Some Ability}.","isRegional":false,"regionalDescription":"The region containing the adult black dragon's lair is warped by the creature's presence, which creates one or more of the following effects:","regionalDescriptionEnd":"If the adult black dragon dies, the first two effects fade over the course of 3d10 days.","properties":[],"abilities":[{"name":"Amphibious","desc":"The dragon can breathe air and water."},{"name":"Legendary Resistance (3/Day)","desc":"If the dragon fails a saving throw, it can choose to succeed instead."}],"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:_ +11 to hit, reach 10 ft., one target. _Hit:_ 17 (2d10 + 6) piercing damage plus 4 (1d8) acid damage."},{"name":"Claw","desc":"_Melee Weapon Attack:_ +11 to hit, reach 5 ft., one target. _Hit:_ 13 (2d6 + 6) slashing damage."},{"name":"Tail","desc":"_Melee Weapon Attack:_ +11 to hit, reach 15 ft., one target. _Hit:_ 15 (2d8 + 6) bludgeoning damage."},{"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 16 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":"Acid Breath (Recharge 5-6)","desc":"The dragon exhales acid in a 60-foot line that is 5 feet wide. Each creature in that line must make a DC 18 Dexterity saving throw, taking 54 (12d8) acid damage on a failed save, or half as much damage on a successful one."}],"bonusActions":[],"reactions":[],"legendaries":[{"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 10 ft. of the dragon must succeed on a DC 19 Dexterity saving throw or take 13 (2d6 + 6) bludgeoning damage and be knocked prone. The dragon can then fly up to half its flying speed."}],"mythics":[],"lairs":[],"regionals":[],"sthrows":[{"name":"dex","order":1},{"name":"con","order":2},{"name":"wis","order":4},{"name":"cha","order":5}],"skills":[{"name":"perception","stat":"wis","note":" (ex)"},{"name":"stealth","stat":"dex"}],"damagetypes":[{"name":"acid","note":" (Immune)","type":"i"}],"specialdamage":[],"conditions":[],"languages":[{"name":"Common","speaks":true},{"name":"Draconic","speaks":true}],"understandsBut":"","shortName":"","pluralName":"","doubleColumns":false,"separationPoint":5,"damage":[]}

View File

@@ -0,0 +1,87 @@
Homebrewery V3
{{monster,frame
## Adult Black Dragon
*Huge Dragon, chaotic evil*
___
**Armor Class** :: 19 (natural armor)
**Hit Points** :: 195 (17d12 + 85)
**Speed** :: 40 ft., fly 80 ft., swim 40 ft.
___
|STR|DEX|CON|INT|WIS|CHA|
|:---:|:---:|:---:|:---:|:---:|:---:|
|23 (+6)|14 (+2)|21 (+5)|14 (+2)|13 (+1)|17 (+3)|
___
**Saving Throws** :: Dex +7, Con +10, Wis +6, Cha +8
**Skills** :: Perception +11, Stealth +7
**Damage Immunities** :: acid
**Senses** :: blindsight 60 ft., darkvision 120 ft., passive Perception 21
**Languages** :: Common, Draconic
**Challenge** :: 14 (11,500 XP)
___
***Amphibious.*** The dragon can breathe air and water.
:
***Legendary Resistance (3/Day).*** If the dragon fails a saving throw, it can choose to succeed instead.
### Actions
***Multiattack.*** The dragon can use its Frightful Presence. It then makes three attacks: one with its bite and two with its claws.
:
***Bite.*** _Melee Weapon Attack:_ +11 to hit, reach 10 ft., one target. _Hit:_ 17 (2d10 + 6) piercing damage plus 4 (1d8) acid damage.
:
***Claw.*** _Melee Weapon Attack:_ +11 to hit, reach 5 ft., one target. _Hit:_ 13 (2d6 + 6) slashing damage.
:
***Tail.*** _Melee Weapon Attack:_ +11 to hit, reach 15 ft., one target. _Hit:_ 15 (2d8 + 6) bludgeoning damage.
:
***Frightful Presence.*** Each creature of the dragon's choice that is within 120 feet of the dragon and aware of it must succeed on a DC 16 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.
:
***Acid Breath (Recharge 5-6).*** The dragon exhales acid in a 60-foot line that is 5 feet wide. Each creature in that line must make a DC 18 Dexterity saving throw, taking 54 (12d8) acid damage on a failed save, or half as much damage on a successful one.
### Legendary Actions
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.
:
**Detect.** The dragon makes a Wisdom (Perception) check.
:
**Tail Attack.** The dragon makes a tail attack.
:
**Wing Attack (Costs 2 Actions).** The dragon beats its wings. Each creature within 10 ft. of the dragon must succeed on a DC 19 Dexterity saving throw or take 13 (2d6 + 6) bludgeoning damage and be knocked prone. The dragon can then fly up to half its flying speed.
}}
Homebrewery (Legacy)/GM Binder Markdown
___
> ## Adult Black Dragon
> *Huge Dragon, chaotic evil*
> ___
> - **Armor Class** 19 (natural armor)
> - **Hit Points** 195 (17d12 + 85)
> - **Speed** 40 ft., fly 80 ft., swim 40 ft.
> ___
> |STR|DEX|CON|INT|WIS|CHA|
> |:---:|:---:|:---:|:---:|:---:|:---:|
> |23 (+6)|14 (+2)|21 (+5)|14 (+2)|13 (+1)|17 (+3)|
> ___
> - **Saving Throws** Dex +7, Con +10, Wis +6, Cha +8
> - **Skills** Perception +11, Stealth +7
> - **Damage Immunities** acid
> - **Senses** blindsight 60 ft., darkvision 120 ft., passive Perception 21
> - **Languages** Common, Draconic
> - **Challenge** 14 (11,500 XP)
> ___
> ***Amphibious.*** The dragon can breathe air and water.
>
> ***Legendary Resistance (3/Day).*** If the dragon fails a saving throw, it can choose to succeed instead.
> ### Actions
> ***Multiattack.*** The dragon can use its Frightful Presence. It then makes three attacks: one with its bite and two with its claws.
>
> ***Bite.*** _Melee Weapon Attack:_ +11 to hit, reach 10 ft., one target. _Hit:_ 17 (2d10 + 6) piercing damage plus 4 (1d8) acid damage.
>
> ***Claw.*** _Melee Weapon Attack:_ +11 to hit, reach 5 ft., one target. _Hit:_ 13 (2d6 + 6) slashing damage.
>
> ***Tail.*** _Melee Weapon Attack:_ +11 to hit, reach 15 ft., one target. _Hit:_ 15 (2d8 + 6) bludgeoning damage.
>
> ***Frightful Presence.*** Each creature of the dragon's choice that is within 120 feet of the dragon and aware of it must succeed on a DC 16 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.
>
> ***Acid Breath (Recharge 5-6).*** The dragon exhales acid in a 60-foot line that is 5 feet wide. Each creature in that line must make a DC 18 Dexterity saving throw, taking 54 (12d8) acid damage on a failed save, or half as much damage on a successful one.
> ### Legendary Actions
> 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.
>
> **Detect.** The dragon makes a Wisdom (Perception) check.
>
> **Tail Attack.** The dragon makes a tail attack.
>
> **Wing Attack (Costs 2 Actions).** The dragon beats its wings. Each creature within 10 ft. of the dragon must succeed on a DC 19 Dexterity saving throw or take 13 (2d6 + 6) bludgeoning damage and be knocked prone. The dragon can then fly up to half its flying speed.

View File

@@ -0,0 +1 @@
{"name":"Adult Black Dragon","size":"huge","type":"Dragon","tag":"","alignment":"chaotic evil","hitDice":17,"armorName":"natural armor","shieldBonus":0,"natArmorBonus":7,"otherArmorDesc":"19 (natural armor)","speed":40,"burrowSpeed":0,"climbSpeed":0,"flySpeed":80,"hover":false,"swimSpeed":40,"customHP":false,"customSpeed":false,"hpText":"17","speedDesc":"40 ft., fly 80 ft., swim 40 ft.","strPoints":23,"dexPoints":14,"conPoints":21,"intPoints":14,"wisPoints":13,"chaPoints":17,"blindsight":60,"blind":false,"darkvision":120,"tremorsense":0,"truesight":0,"telepathy":0,"cr":"14","customCr":"14 (11,500 XP)","customProf":5,"isLegendary":true,"legendariesDescription":"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.","isLair":false,"lairDescription":"When fighting inside its lair, the adult black dragon can invoke the ambient magic to take lair actions. On initiative count 20 (losing initiative ties), the adult black dragon can take one lair action to cause one of the following effects:","lairDescriptionEnd":"The adult black dragon can't repeat an effect until they have all been used, and it can't use the same effect two rounds in a row.","isMythic":false,"mythicDescription":"If the adult black dragon's mythic trait is active, it can use the options below as legendary actions for 1 hour after using {Some Ability}.","isRegional":false,"regionalDescription":"The region containing the adult black dragon's lair is warped by the creature's presence, which creates one or more of the following effects:","regionalDescriptionEnd":"If the adult black dragon dies, the first two effects fade over the course of 3d10 days.","properties":[],"abilities":[{"name":"Amphibious","desc":"The dragon can breathe air and water."},{"name":"Legendary Resistance (3/Day)","desc":"If the dragon fails a saving throw, it can choose to succeed instead."}],"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:_ +11 to hit, reach 10 ft., one target. _Hit:_ 17 (2d10 + 6) piercing damage plus 4 (1d8) acid damage."},{"name":"Claw","desc":"_Melee Weapon Attack:_ +11 to hit, reach 5 ft., one target. _Hit:_ 13 (2d6 + 6) slashing damage."},{"name":"Tail","desc":"_Melee Weapon Attack:_ +11 to hit, reach 15 ft., one target. _Hit:_ 15 (2d8 + 6) bludgeoning damage."},{"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 16 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":"Acid Breath (Recharge 5-6)","desc":"The dragon exhales acid in a 60-foot line that is 5 feet wide. Each creature in that line must make a DC 18 Dexterity saving throw, taking 54 (12d8) acid damage on a failed save, or half as much damage on a successful one."}],"bonusActions":[],"reactions":[],"legendaries":[{"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 10 ft. of the dragon must succeed on a DC 19 Dexterity saving throw or take 13 (2d6 + 6) bludgeoning damage and be knocked prone. The dragon can then fly up to half its flying speed."}],"mythics":[],"lairs":[],"regionals":[],"sthrows":[{"name":"dex","order":1},{"name":"con","order":2},{"name":"wis","order":4},{"name":"cha","order":5}],"skills":[{"name":"perception","stat":"wis","note":" (ex)"},{"name":"stealth","stat":"dex"}],"damagetypes":[{"name":"acid","note":" (Immune)","type":"i"}],"specialdamage":[],"conditions":[],"languages":[{"name":"Common","speaks":true},{"name":"Draconic","speaks":true}],"understandsBut":"","shortName":"","pluralName":"","doubleColumns":false,"separationPoint":5,"damage":[]}

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 KiB

View File

@@ -0,0 +1 @@
{"name":"Monster Goblin","size":"small","type":"Humanoid","tag":"goblinoid","alignment":"neutral evil","hitDice":2,"armorName":"leather armor","shieldBonus":2,"natArmorBonus":7,"otherArmorDesc":"15 (leather armor, shield)","speed":30,"burrowSpeed":0,"climbSpeed":0,"flySpeed":0,"hover":false,"swimSpeed":0,"customHP":false,"customSpeed":false,"hpText":"2","speedDesc":"30 ft.","strPoints":8,"dexPoints":14,"conPoints":10,"intPoints":10,"wisPoints":8,"chaPoints":8,"blindsight":0,"blind":false,"darkvision":60,"tremorsense":0,"truesight":0,"telepathy":0,"cr":"1/4","customCr":"1/4 (50 XP)","customProf":2,"isLegendary":false,"legendariesDescription":"The goblin 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 goblin regains spent legendary actions at the start of its turn.","isLair":false,"lairDescription":"When fighting inside its lair, the goblin can invoke the ambient magic to take lair actions. On initiative count 20 (losing initiative ties), the goblin can take one lair action to cause one of the following effects:","lairDescriptionEnd":"The goblin can't repeat an effect until they have all been used, and it can't use the same effect two rounds in a row.","isMythic":false,"mythicDescription":"If the goblin's mythic trait is active, it can use the options below as legendary actions for 1 hour after using {Some Ability}.","isRegional":false,"regionalDescription":"The region containing the goblin's lair is warped by the creature's presence, which creates one or more of the following effects:","regionalDescriptionEnd":"If the goblin dies, the first two effects fade over the course of 3d10 days.","properties":[],"abilities":[{"name":"Nimble Escape","desc":"The goblin can take the Disengage or Hide action as a bonus action on each of its turns."}],"actions":[{"name":"Scimitar","desc":"_Melee Weapon Attack:_ +4 to hit, reach 5 ft., one target. _Hit:_ 5 (1d6 + 2) slashing damage."},{"name":"Shortbow","desc":"_Ranged Weapon Attack:_ +4 to hit, range 80/320 ft., one target. _Hit:_ 5 (1d6 + 2) piercing damage."}],"bonusActions":[],"reactions":[],"legendaries":[],"mythics":[],"lairs":[],"regionals":[],"sthrows":[],"skills":[{"name":"stealth","stat":"dex","note":" (ex)"}],"damagetypes":[],"specialdamage":[],"conditions":[],"languages":[{"name":"Common","speaks":true},{"name":"Goblin","speaks":true}],"understandsBut":"","shortName":"","pluralName":"","doubleColumns":false,"separationPoint":1,"damage":[]}

View File

@@ -0,0 +1 @@
{"name":"Text Monster Goblin","size":"small","type":"Humanoid","tag":"goblinoid","alignment":"neutral evil","hitDice":2,"armorName":"leather armor","shieldBonus":2,"natArmorBonus":7,"otherArmorDesc":"15 (leather armor, shield)","speed":30,"burrowSpeed":0,"climbSpeed":0,"flySpeed":0,"hover":false,"swimSpeed":0,"customHP":false,"customSpeed":false,"hpText":"2","speedDesc":"30 ft.","strPoints":8,"dexPoints":14,"conPoints":10,"intPoints":10,"wisPoints":8,"chaPoints":8,"blindsight":0,"blind":false,"darkvision":60,"tremorsense":0,"truesight":0,"telepathy":0,"cr":"1/4","customCr":"1/4 (50 XP)","customProf":2,"isLegendary":false,"legendariesDescription":"The goblin 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 goblin regains spent legendary actions at the start of its turn.","isLair":false,"lairDescription":"When fighting inside its lair, the goblin can invoke the ambient magic to take lair actions. On initiative count 20 (losing initiative ties), the goblin can take one lair action to cause one of the following effects:","lairDescriptionEnd":"The goblin can't repeat an effect until they have all been used, and it can't use the same effect two rounds in a row.","isMythic":false,"mythicDescription":"If the goblin's mythic trait is active, it can use the options below as legendary actions for 1 hour after using {Some Ability}.","isRegional":false,"regionalDescription":"The region containing the goblin's lair is warped by the creature's presence, which creates one or more of the following effects:","regionalDescriptionEnd":"If the goblin dies, the first two effects fade over the course of 3d10 days.","properties":[],"abilities":[{"name":"Nimble Escape","desc":"The goblin can take the Disengage or Hide action as a bonus action on each of its turns."}],"actions":[{"name":"Scimitar","desc":"_Melee Weapon Attack:_ +4 to hit, reach 5 ft., one target. _Hit:_ 5 (1d6 + 2) slashing damage."},{"name":"Shortbow","desc":"_Ranged Weapon Attack:_ +4 to hit, range 80/320 ft., one target. _Hit:_ 5 (1d6 + 2) piercing damage."}],"bonusActions":[],"reactions":[],"legendaries":[],"mythics":[],"lairs":[],"regionals":[],"sthrows":[],"skills":[{"name":"stealth","stat":"dex","note":" (ex)"}],"damagetypes":[],"specialdamage":[],"conditions":[],"languages":[{"name":"Common","speaks":true},{"name":"Goblin","speaks":true}],"understandsBut":"","shortName":"","pluralName":"","doubleColumns":false,"separationPoint":1,"damage":[]}

View File

@@ -0,0 +1 @@
{"name":"Text Goblin","size":"small","type":"Humanoid","tag":"goblinoid","alignment":"neutral evil","hitDice":2,"armorName":"leather armor","shieldBonus":2,"natArmorBonus":7,"otherArmorDesc":"15 (leather armor, shield)","speed":30,"burrowSpeed":0,"climbSpeed":0,"flySpeed":0,"hover":false,"swimSpeed":0,"customHP":false,"customSpeed":false,"hpText":"2","speedDesc":"30 ft.","strPoints":8,"dexPoints":14,"conPoints":10,"intPoints":10,"wisPoints":8,"chaPoints":8,"blindsight":0,"blind":false,"darkvision":60,"tremorsense":0,"truesight":0,"telepathy":0,"cr":"1/4","customCr":"1/4 (50 XP)","customProf":2,"isLegendary":false,"legendariesDescription":"The goblin 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 goblin regains spent legendary actions at the start of its turn.","isLair":false,"lairDescription":"When fighting inside its lair, the goblin can invoke the ambient magic to take lair actions. On initiative count 20 (losing initiative ties), the goblin can take one lair action to cause one of the following effects:","lairDescriptionEnd":"The goblin can't repeat an effect until they have all been used, and it can't use the same effect two rounds in a row.","isMythic":false,"mythicDescription":"If the goblin's mythic trait is active, it can use the options below as legendary actions for 1 hour after using {Some Ability}.","isRegional":false,"regionalDescription":"The region containing the goblin's lair is warped by the creature's presence, which creates one or more of the following effects:","regionalDescriptionEnd":"If the goblin dies, the first two effects fade over the course of 3d10 days.","properties":[],"abilities":[{"name":"Nimble Escape","desc":"The goblin can take the Disengage or Hide action as a bonus action on each of its turns."}],"actions":[{"name":"Scimitar","desc":"_Melee Weapon Attack:_ +4 to hit, reach 5 ft., one target. _Hit:_ 5 (1d6 + 2) slashing damage."},{"name":"Shortbow","desc":"_Ranged Weapon Attack:_ +4 to hit, range 80/320 ft., one target. _Hit:_ 5 (1d6 + 2) piercing damage."}],"bonusActions":[],"reactions":[],"legendaries":[],"mythics":[],"lairs":[],"regionals":[],"sthrows":[],"skills":[{"name":"stealth","stat":"dex","note":" (ex)"}],"damagetypes":[],"specialdamage":[],"conditions":[],"languages":[{"name":"Common","speaks":true},{"name":"Goblin","speaks":true}],"understandsBut":"","shortName":"","pluralName":"","doubleColumns":false,"separationPoint":1,"damage":[]}

View File

@@ -0,0 +1,47 @@
Homebrewery V3
{{monster,frame
## Goblin
*Small Humanoid (goblinoid), neutral evil*
___
**Armor Class** :: 15 (leather armor, shield)
**Hit Points** :: 7 (2d6)
**Speed** :: 30 ft.
___
|STR|DEX|CON|INT|WIS|CHA|
|:---:|:---:|:---:|:---:|:---:|:---:|
|8 (-1)|14 (+2)|10 (+0)|10 (+0)|8 (-1)|8 (-1)|
___
**Skills** :: Stealth +6
**Senses** :: darkvision 60 ft., passive Perception 9
**Languages** :: Common, Goblin
**Challenge** :: 1/4 (50 XP)
___
***Nimble Escape.*** The goblin can take the Disengage or Hide action as a bonus action on each of its turns.
### Actions
***Scimitar.*** _Melee Weapon Attack:_ +4 to hit, reach 5 ft., one target. _Hit:_ 5 (1d6 + 2) slashing damage.
:
***Shortbow.*** _Ranged Weapon Attack:_ +4 to hit, range 80/320 ft., one target. _Hit:_ 5 (1d6 + 2) piercing damage.
}}
Homebrewery (Legacy)/GM Binder Markdown
___
> ## Goblin
> *Small Humanoid (goblinoid), neutral evil*
> ___
> - **Armor Class** 15 (leather armor, shield)
> - **Hit Points** 7 (2d6)
> - **Speed** 30 ft.
> ___
> |STR|DEX|CON|INT|WIS|CHA|
> |:---:|:---:|:---:|:---:|:---:|:---:|
> |8 (-1)|14 (+2)|10 (+0)|10 (+0)|8 (-1)|8 (-1)|
> ___
> - **Skills** Stealth +6
> - **Senses** darkvision 60 ft., passive Perception 9
> - **Languages** Common, Goblin
> - **Challenge** 1/4 (50 XP)
> ___
> ***Nimble Escape.*** The goblin can take the Disengage or Hide action as a bonus action on each of its turns.
> ### Actions
> ***Scimitar.*** _Melee Weapon Attack:_ +4 to hit, reach 5 ft., one target. _Hit:_ 5 (1d6 + 2) slashing damage.
>
> ***Shortbow.*** _Ranged Weapon Attack:_ +4 to hit, range 80/320 ft., one target. _Hit:_ 5 (1d6 + 2) piercing damage.

View File

@@ -0,0 +1 @@
{"name":"Goblin","size":"small","type":"Humanoid","tag":"goblinoid","alignment":"neutral evil","hitDice":2,"armorName":"leather armor","shieldBonus":2,"natArmorBonus":7,"otherArmorDesc":"15 (leather armor, shield)","speed":30,"burrowSpeed":0,"climbSpeed":0,"flySpeed":0,"hover":false,"swimSpeed":0,"customHP":false,"customSpeed":false,"hpText":"2","speedDesc":"30 ft.","strPoints":8,"dexPoints":14,"conPoints":10,"intPoints":10,"wisPoints":8,"chaPoints":8,"blindsight":0,"blind":false,"darkvision":60,"tremorsense":0,"truesight":0,"telepathy":0,"cr":"1/4","customCr":"1/4 (50 XP)","customProf":2,"isLegendary":false,"legendariesDescription":"The goblin 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 goblin regains spent legendary actions at the start of its turn.","isLair":false,"lairDescription":"When fighting inside its lair, the goblin can invoke the ambient magic to take lair actions. On initiative count 20 (losing initiative ties), the goblin can take one lair action to cause one of the following effects:","lairDescriptionEnd":"The goblin can't repeat an effect until they have all been used, and it can't use the same effect two rounds in a row.","isMythic":false,"mythicDescription":"If the goblin's mythic trait is active, it can use the options below as legendary actions for 1 hour after using {Some Ability}.","isRegional":false,"regionalDescription":"The region containing the goblin's lair is warped by the creature's presence, which creates one or more of the following effects:","regionalDescriptionEnd":"If the goblin dies, the first two effects fade over the course of 3d10 days.","properties":[],"abilities":[{"name":"Nimble Escape","desc":"The goblin can take the Disengage or Hide action as a bonus action on each of its turns."}],"actions":[{"name":"Scimitar","desc":"_Melee Weapon Attack:_ +4 to hit, reach 5 ft., one target. _Hit:_ 5 (1d6 + 2) slashing damage."},{"name":"Shortbow","desc":"_Ranged Weapon Attack:_ +4 to hit, range 80/320 ft., one target. _Hit:_ 5 (1d6 + 2) piercing damage."}],"bonusActions":[],"reactions":[],"legendaries":[],"mythics":[],"lairs":[],"regionals":[],"sthrows":[],"skills":[{"name":"stealth","stat":"dex","note":" (ex)"}],"damagetypes":[],"specialdamage":[],"conditions":[],"languages":[{"name":"Common","speaks":true},{"name":"Goblin","speaks":true}],"understandsBut":"","shortName":"","pluralName":"","doubleColumns":false,"separationPoint":1,"damage":[]}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

View File

@@ -0,0 +1,51 @@
{
"$schema": "../../../../schema/entity.json",
"uuid": "f6a83d72-b94e-4e5c-a5c2-f39b6b52d202",
"ruleset_id": "open5e",
"entity_type": "character",
"display_name": "Acolyte",
"description": "Medium humanoid (any race), any alignment.",
"version": "1.0.0",
"template_id": "stat_block",
"tags": ["npc", "humanoid", "spellcaster", "cr-1/4"],
"properties": {
"size": "medium",
"type": "Humanoid",
"subtype": "any race",
"alignment": "any alignment",
"armor_class": 10,
"hit_points": {
"current": 9,
"max": 9,
"formula": "2d8 + 0",
"hit_dice": "2d8"
},
"speed": 30,
"speed_desc": "30 ft.",
"challenge_rating": "1/4 (50 XP)",
"cr": "1/4",
"abilities": {
"strength": 10,
"dexterity": 10,
"constitution": 10,
"intelligence": 10,
"wisdom": 14,
"charisma": 11
},
"skills": ["medicine +4", "religion +2"],
"senses": "passive Perception 12",
"languages": ["any one language (usually Common)"],
"traits": [
{
"name": "Spellcasting",
"desc": "The acolyte is a 1st-level spellcaster. Its spellcasting ability is Wisdom (spell save DC 12, +4 to hit with spell attacks). The acolyte has following cleric spells prepared:\n\n* Cantrips (at will): _light, sacred flame, thaumaturgy_\n* 1st level (3 slots): _bless, cure wounds, sanctuary_"
}
],
"actions": [
{
"name": "Club",
"desc": "_Melee Weapon Attack:_ +2 to hit, reach 5 ft., one target. _Hit:_ 2 (1d4) bludgeoning damage."
}
]
}
}

View File

@@ -0,0 +1,99 @@
{
"$schema": "../../../../schema/entity.json",
"uuid": "b8a91c34-d07f-4f8a-92c1-d41a8a91e404",
"ruleset_id": "open5e",
"entity_type": "character",
"display_name": "Adult Black Dragon",
"description": "Huge dragon, chaotic evil.",
"version": "1.0.0",
"template_id": "stat_block",
"tags": ["monster", "dragon", "legendary", "cr-14"],
"properties": {
"size": "huge",
"type": "Dragon",
"alignment": "chaotic evil",
"armor_class": 19,
"armor_description": "natural armor",
"hit_points": {
"current": 196,
"max": 196,
"formula": "17d12 + 85",
"hit_dice": "17d12"
},
"speed": 40,
"speed_desc": "40 ft., fly 80 ft., swim 40 ft.",
"challenge_rating": "14 (11,500 XP)",
"cr": "14",
"abilities": {
"strength": 23,
"dexterity": 14,
"constitution": 21,
"intelligence": 14,
"wisdom": 13,
"charisma": 17
},
"saving_throws": [
"Dexterity +7",
"Constitution +10",
"Wisdom +6",
"Charisma +8"
],
"skills": ["perception +16", "stealth +11"],
"damage_immunities": ["acid"],
"senses": "darkvision 120 ft., blindsight 60 ft., passive Perception 11",
"languages": ["Common", "Draconic"],
"traits": [
{
"name": "Amphibious",
"desc": "The dragon can breathe air and water."
},
{
"name": "Legendary Resistance (3/Day)",
"desc": "If the dragon fails a saving throw, it can choose to succeed instead."
}
],
"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:_ +11 to hit, reach 10 ft., one target. _Hit:_ 17 (2d10 + 6) piercing damage plus 4 (1d8) acid damage."
},
{
"name": "Claw",
"desc": "_Melee Weapon Attack:_ +11 to hit, reach 5 ft., one target. _Hit:_ 13 (2d6 + 6) slashing damage."
},
{
"name": "Tail",
"desc": "_Melee Weapon Attack:_ +11 to hit, reach 15 ft., one target. _Hit:_ 15 (2d8 + 6) bludgeoning damage."
},
{
"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 16 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": "Acid Breath (Recharge 5-6)",
"desc": "The dragon exhales acid in a 60-foot line that is 5 feet wide. Each creature in that line must make a DC 18 Dexterity saving throw, taking 54 (12d8) acid damage on a failed save, or half as much damage on a successful one."
}
],
"legendary_actions": {
"description": "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.",
"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 10 ft. of the dragon must succeed on a DC 19 Dexterity saving throw or take 13 (2d6 + 6) bludgeoning damage and be knocked prone. The dragon can then fly up to half its flying speed."
}
]
}
}
}

View File

@@ -0,0 +1,306 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "4c8f3824-3453-48bf-9a0e-a4c336b3bcf2",
"ruleset_id": "open5e",
"entity_type": "character",
"display_name": "Arannis",
"description": "Elven Ranger exploring ancient ruins.",
"version": "1.0.0",
"created_at": "2026-09-06T14:00:00Z",
"updated_at": "2026-09-06T18:00:00Z",
"tags": ["pc", "ranger", "elf"],
"properties": {
"player_name": "Tom",
"class_levels": {
"ranger": {
"name": "Ranger",
"level": 3,
"subclass": "Hunter",
"description": "Hunter archetype with Colossus Slayer"
}
},
"race": "Elf",
"size": "medium",
"type": "Humanoid",
"subtype": "Elf",
"background": "Outlander",
"alignment": "Chaotic Good",
"experience_points": 900,
"inspiration": true,
"proficiency_bonus": 2,
"initiative": 3,
"speed": 35,
"speed_desc": "35 ft.",
"hit_points": {
"current": 24,
"max": 24,
"formula": "3d10 + 6",
"temp": 0
},
"hit_dice": {
"total": 3,
"current": 3,
"dice": "1d10"
},
"death_saves": {
"successes": 0,
"failures": 0
},
"saving_throws": ["Strength +3", "Dexterity +5"],
"advantages": ["wis"],
"skills": ["Athletics +3", "Perception +5", "Stealth +5", "Survival +5"],
"passive_perception": 15,
"senses": "darkvision 60 ft., passive Perception 15",
"languages": ["Common", "Elvish", "Sylvan"],
"other_proficiencies_and_languages": {
"armor": ["Light armor", "Medium armor", "Shields"],
"weapons": ["Simple weapons", "Martial weapons"],
"tools": ["Herbalism kit", "Flute"],
"languages": ["Common", "Elvish", "Sylvan"]
},
"attacks": [
{
"name": "Longbow",
"atk_bonus": "+7",
"damage": "1d8 + 3",
"damage_type": "Piercing",
"range": "150/600 ft."
},
{
"name": "Shortsword",
"atk_bonus": "+5",
"damage": "1d6 + 3",
"damage_type": "Piercing",
"range": "5 ft."
}
],
"currency": {
"cp": 15,
"sp": 40,
"ep": 0,
"gp": 25,
"pp": 0
},
"personality_traits": "I feel far more comfortable among the ancient trees and wild beasts than in bustling cities.",
"ideals": "Nature: The natural world is more important than the constructs of civilization.",
"bonds": "An ancient ruined shrine in the northern forest holds a secret I have sworn to protect.",
"flaws": "I am slow to trust others and prefer solitude even in times of danger.",
"features_and_traits": [
{
"name": "Favored Enemy (Undead)",
"source": "Ranger 1",
"desc": "You have advantage on Wisdom (Survival) checks to track your favored enemies, as well as on Intelligence checks to recall information about them."
},
{
"name": "Natural Explorer (Forest)",
"source": "Ranger 1",
"desc": "When making an Intelligence or Wisdom check related to your favored terrain, your proficiency bonus is doubled if you are using a skill you're proficient in."
},
{
"name": "Colossus Slayer",
"source": "Hunter Archetype",
"desc": "Your tenacity can wear down the most potent foes. Once per turn when you hit a creature with a weapon attack, the creature takes an extra 1d8 damage if it's below its hit point maximum."
}
],
"spellcasting": {
"class": "Ranger",
"ability": "WISDOM",
"save_dc": 13,
"attack_bonus": 5,
"cantrips": [],
"slots": {
"1": {
"total": 3,
"expended": 1
}
},
"spells": [
{
"name": "Hunter's Mark",
"level": 1,
"prepared": true,
"desc": "You choose a creature you can see within range and mystically mark it as your quarry."
},
{
"name": "Cure Wounds",
"level": 1,
"prepared": true,
"desc": "A creature you touch regains a number of hit points equal to 1d8 + your spellcasting ability modifier."
}
]
},
"traits": [
{
"name": "Fey Ancestry",
"desc": "Arannis has advantage on saving throws against being charmed, and magic cannot put her to sleep."
},
{
"name": "Natural Explorer",
"desc": "Difficult terrain does not slow Arannis's group while traveling."
}
],
"actions": [
{
"name": "Longbow",
"desc": "_Ranged Weapon Attack:_ +7 to hit, range 150/600 ft., one target. _Hit:_ 7 (1d8 + 3) piercing damage."
},
{
"name": "Shortsword",
"desc": "_Melee Weapon Attack:_ +5 to hit, reach 5 ft., one target. _Hit:_ 6 (1d6 + 3) piercing damage."
}
],
"equipped_armor": {
"id": "armor_leather",
"name": "Leather Armor",
"armor_category": "light",
"ac_base": 11,
"ac_modifier": 1,
"dex_cap": null,
"stealth_disadvantage": false,
"strength_requirement": null
},
"equipped_shield": null,
"armor_class": {
"value": 15,
"base": 10,
"expr": "base + sum(modifiers)",
"modifiers": [
{
"source": "Leather Armor",
"value": 1,
"type": "armor"
},
{
"source": "Dexterity Modifier",
"value": 3,
"type": "ability"
},
{
"source": "Ring of Protection +1",
"value": 1,
"type": "bonus",
"active": true
}
]
},
"abilities": {
"strength": {
"display_name": "Strength",
"short_name": "Str",
"value": 12,
"modifier": 1,
"proficiency": true,
"saving_throw": 3
},
"dexterity": {
"display_name": "Dexterity",
"short_name": "Dex",
"value": 16,
"base": 14,
"modifier": 3,
"proficiency": true,
"saving_throw": 5,
"modifiers": [
{
"source": "Elven Agility",
"value": 2,
"type": "bonus"
}
]
},
"constitution": {
"display_name": "Constitution",
"short_name": "Con",
"value": 14,
"modifier": 2,
"proficiency": false,
"saving_throw": 2
},
"intelligence": {
"display_name": "Intelligence",
"short_name": "Int",
"value": 10,
"modifier": 0,
"proficiency": false,
"saving_throw": 0
},
"wisdom": {
"display_name": "Wisdom",
"short_name": "Wis",
"value": 15,
"modifier": 2,
"proficiency": false,
"saving_throw": 2,
"advantage": "Fey Ancestry (charm saves)"
},
"charisma": {
"display_name": "Charisma",
"short_name": "Cha",
"value": 8,
"modifier": -1,
"proficiency": false,
"saving_throw": -1
}
},
"inventory": [
{
"id": "armor_leather",
"entity_ref": "entities/armor.json",
"name": "Leather Armor",
"quantity": 1,
"weight": 10.0,
"equipped": true,
"ac_base": 11,
"dex_cap": null
},
{
"id": "ring_protection",
"entity_ref": "entities/item.json",
"name": "Ring of Protection +1",
"quantity": 1,
"weight": 0.1,
"equipped": true,
"grants_modifiers": [
{
"target": "armor_class",
"value": 1,
"type": "bonus",
"source": "Ring of Protection +1"
}
]
},
{
"id": "longbow",
"entity_ref": "entities/weapon.json",
"name": "Longbow",
"quantity": 1,
"weight": 2.0,
"equipped": true
},
{
"id": "shortsword",
"entity_ref": "entities/weapon.json",
"name": "Shortsword",
"quantity": 1,
"weight": 2.0,
"equipped": true
},
{
"id": "arrow",
"entity_ref": "entities/item.json",
"name": "Arrow",
"quantity": 20,
"weight": 1.0,
"equipped": false
},
{
"id": "potion_healing",
"entity_ref": "entities/item.json",
"name": "Potion of Healing",
"quantity": 2,
"weight": 1.0,
"equipped": false
}
]
}
}

View File

@@ -0,0 +1,56 @@
{
"$schema": "../../../../schema/entity.json",
"uuid": "e5b72e61-a83d-4c3d-b4b1-e28a5a41c101",
"ruleset_id": "open5e",
"entity_type": "character",
"display_name": "Goblin",
"description": "Small humanoid (goblinoid), neutral evil.",
"version": "1.0.0",
"template_id": "stat_block",
"tags": ["monster", "goblinoid", "humanoid", "cr-1/4"],
"properties": {
"size": "small",
"type": "Humanoid",
"subtype": "goblinoid",
"alignment": "neutral evil",
"armor_class": 15,
"armor_description": "leather armor, shield",
"hit_points": {
"current": 7,
"max": 7,
"formula": "2d6 + 0",
"hit_dice": "2d6"
},
"speed": 30,
"speed_desc": "30 ft.",
"challenge_rating": "1/4 (50 XP)",
"cr": "1/4",
"abilities": {
"strength": 8,
"dexterity": 14,
"constitution": 10,
"intelligence": 10,
"wisdom": 8,
"charisma": 8
},
"skills": ["stealth +6"],
"senses": "darkvision 60 ft., passive Perception 9",
"languages": ["Common", "Goblin"],
"traits": [
{
"name": "Nimble Escape",
"desc": "The goblin can take the Disengage or Hide action as a bonus action on each of its turns."
}
],
"actions": [
{
"name": "Scimitar",
"desc": "_Melee Weapon Attack:_ +4 to hit, reach 5 ft., one target. _Hit:_ 5 (1d6 + 2) slashing damage."
},
{
"name": "Shortbow",
"desc": "_Ranged Weapon Attack:_ +4 to hit, range 80/320 ft., one target. _Hit:_ 5 (1d6 + 2) piercing damage."
}
]
}
}

View File

@@ -0,0 +1,56 @@
{
"$schema": "../../../../schema/entity.json",
"uuid": "e5b72e61-a83d-4c3d-b4b1-e28a5a41c101",
"ruleset_id": "open5e",
"entity_type": "character",
"display_name": "Goblin",
"description": "Small humanoid (goblinoid), neutral evil.",
"version": "1.0.0",
"template_id": "stat_block",
"tags": ["monster", "goblinoid", "humanoid", "cr-1/4"],
"properties": {
"size": "small",
"type": "Humanoid",
"subtype": "goblinoid",
"alignment": "neutral evil",
"armor_class": 15,
"armor_description": "leather armor, shield",
"hit_points": {
"current": 7,
"max": 7,
"formula": "2d6 + 0",
"hit_dice": "2d6"
},
"speed": 30,
"speed_desc": "30 ft.",
"challenge_rating": "1/4 (50 XP)",
"cr": "1/4",
"abilities": {
"strength": 8,
"dexterity": 14,
"constitution": 10,
"intelligence": 10,
"wisdom": 8,
"charisma": 8
},
"skills": ["stealth +6"],
"senses": "darkvision 60 ft., passive Perception 9",
"languages": ["Common", "Goblin"],
"traits": [
{
"name": "Nimble Escape",
"desc": "The goblin can take the Disengage or Hide action as a bonus action on each of its turns."
}
],
"actions": [
{
"name": "Scimitar",
"desc": "_Melee Weapon Attack:_ +4 to hit, reach 5 ft., one target. _Hit:_ 5 (1d6 + 2) slashing damage."
},
{
"name": "Shortbow",
"desc": "_Ranged Weapon Attack:_ +4 to hit, range 80/320 ft., one target. _Hit:_ 5 (1d6 + 2) piercing damage."
}
]
}
}

View File

@@ -0,0 +1,187 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "8a7231de-4411-4f0e-b81b-520e1189ac33",
"ruleset_id": "open5e",
"entity_type": "character",
"display_name": "Krag Ironspark",
"description": "Dwarven Fighter / Mage (Fireballer) multiclass battlemage.",
"version": "1.0.0",
"created_at": "2026-09-07T12:00:00Z",
"updated_at": "2026-09-07T18:00:00Z",
"tags": ["pc", "fighter", "mage", "multiclass", "dwarf"],
"properties": {
"player_name": "Alex",
"class_levels": {
"fighter": {
"name": "Fighter",
"level": 2,
"description": "Defense fighting style (+1 AC) and Action Surge"
},
"mage": {
"name": "Mage",
"level": 10,
"subclass": "Fireballer",
"description": "School of Evocation specialist (Sculpt Spells, Potent Cantrip, Empowered Evocation)"
}
},
"race": "Dwarf",
"size": "medium",
"type": "Humanoid",
"subtype": "Dwarf",
"background": "Sage",
"alignment": "Neutral Good",
"experience_points": 100000,
"inspiration": false,
"speed": 25,
"speed_desc": "25 ft.",
"hit_points": {
"current": 98,
"max": 98,
"formula": "2d10 + 10d6 + 36",
"temp": 0
},
"hit_dice": {
"total": 12,
"current": 12,
"dice": "2d10 + 10d6"
},
"death_saves": {
"successes": 0,
"failures": 0
},
"abilities": {
"strength": {
"display_name": "Strength",
"short_name": "Str",
"value": 16,
"modifier": 3,
"proficiency": true,
"saving_throw": 7
},
"dexterity": {
"display_name": "Dexterity",
"short_name": "Dex",
"value": 12,
"modifier": 1,
"proficiency": false,
"saving_throw": 1
},
"constitution": {
"display_name": "Constitution",
"short_name": "Con",
"value": 16,
"modifier": 3,
"proficiency": true,
"saving_throw": 7,
"advantage": "Dwarven Resilience (poison saves)"
},
"intelligence": {
"display_name": "Intelligence",
"short_name": "Int",
"value": 18,
"modifier": 4,
"proficiency": false,
"saving_throw": 4
},
"wisdom": {
"display_name": "Wisdom",
"short_name": "Wis",
"value": 12,
"modifier": 1,
"proficiency": false,
"saving_throw": 1
},
"charisma": {
"display_name": "Charisma",
"short_name": "Cha",
"value": 8,
"modifier": -1,
"proficiency": false,
"saving_throw": -1
}
},
"armor_class": 19,
"equipped_armor": {
"id": "plate_armor",
"name": "Plate Armor",
"armor_category": "heavy",
"ac_base": 18,
"dex_cap": 0,
"stealth_disadvantage": true,
"strength_requirement": 15
},
"equipped_shield": null,
"skills": ["Athletics +7", "Arcana +8", "History +8", "Investigation +8"],
"passive_perception": 11,
"senses": "darkvision 60 ft., passive Perception 11",
"languages": ["Common", "Dwarvish", "Draconic", "Ignan"],
"attacks": [
{
"name": "Warhammer",
"atk_bonus": "+7",
"damage": "1d8 + 3",
"damage_type": "Bludgeoning",
"range": "5 ft."
},
{
"name": "Fire Bolt",
"atk_bonus": "+8",
"damage": "3d10 + 4",
"damage_type": "Fire",
"range": "120 ft."
}
],
"spellcasting": {
"class": "Mage",
"ability": "Intelligence",
"save_dc": 16,
"attack_bonus": 8,
"cantrips": [
"Fire Bolt",
"Light",
"Mage Hand",
"Prestidigitation",
"Shocking Grasp"
],
"slots": {
"1": { "total": 4, "expended": 0 },
"2": { "total": 3, "expended": 1 },
"3": { "total": 3, "expended": 0 },
"4": { "total": 3, "expended": 1 },
"5": { "total": 2, "expended": 0 }
},
"spells": [
{ "name": "Shield", "level": 1, "prepared": true },
{ "name": "Misty Step", "level": 2, "prepared": true },
{ "name": "Fireball", "level": 3, "prepared": true },
{ "name": "Counterspell", "level": 3, "prepared": true },
{ "name": "Wall of Fire", "level": 4, "prepared": true },
{ "name": "Cone of Cold", "level": 5, "prepared": true }
]
},
"features_and_traits": [
{
"name": "Action Surge",
"source": "Fighter 2",
"desc": "On your turn, you can take one additional action on top of your regular action and possible bonus action."
},
{
"name": "Sculpt Spells",
"source": "Mage (Evocation)",
"desc": "When you cast an evocation spell that affects other creatures you can see, you can choose a number of them equal to 1 + the spell's level to automatically succeed on their saving throws."
},
{
"name": "Empowered Evocation",
"source": "Mage (Evocation)",
"desc": "Beginning at 10th level, you can add your Intelligence modifier (+4) to one damage roll of any wizard evocation spell you cast."
}
],
"currency": {
"cp": 20,
"sp": 50,
"ep": 0,
"gp": 450,
"pp": 5
}
}
}

View File

@@ -0,0 +1,57 @@
{
"$schema": "../../../schema/entity-definition.json",
"id": "armor",
"name": "Armor",
"description": "Armor entity extending base item schema with AC base, dex cap, strength requirement, and stealth penalties.",
"ruleset_id": "open5e",
"version": "1.0.0",
"schema": {
"type": "object",
"required": ["name", "armor_category", "ac_base"],
"allOf": [
{
"$ref": "item.json#/schema"
},
{
"type": "object",
"properties": {
"armor_category": {
"type": "string",
"enum": ["light", "medium", "heavy"],
"description": "Classification of armor determining weight class and dexterity application."
},
"ac_base": {
"type": "integer",
"minimum": 0,
"description": "Base Armor Class provided by this armor (e.g. 11 for Leather, 14 for Breastplate, 18 for Plate)."
},
"ac_modifier": {
"type": "integer",
"description": "Armor Class modifier over the base 10 (e.g. +1 for Leather, +4 for Breastplate, +8 for Plate)."
},
"dex_cap": {
"type": ["integer", "null"],
"description": "Maximum dexterity modifier bonus allowed. null = uncapped (light armor), 2 = medium armor cap (+2), 0 = heavy armor (no dex bonus)."
},
"strength_requirement": {
"type": ["integer", "null"],
"description": "Minimum Strength score needed to wear without speed penalty. null or 0 if none."
},
"stealth_disadvantage": {
"type": "boolean",
"default": false,
"description": "Whether wearing this armor imposes disadvantage on Dexterity (Stealth) checks."
}
}
}
]
},
"default_values": {
"weight": 10,
"quantity": 1,
"equipped": false,
"rarity": "common",
"stealth_disadvantage": false,
"strength_requirement": null
}
}

View File

@@ -0,0 +1,375 @@
{
"$schema": "../../../schema/entity-definition.json",
"id": "character",
"name": "Player Character",
"description": "5e Player Character entity with ability scores, proficiencies, hit points, and inventory.",
"ruleset_id": "open5e",
"version": "1.0.0",
"default_template": "character_sheet",
"allowed_templates": [
"character_sheet",
"character_sheet_alt",
"spellcasting_sheet",
"stat_block",
"character_card",
"character_list_item"
],
"schema": {
"type": "object",
"required": ["level", "abilities", "hit_points"],
"properties": {
"level": {
"type": "integer",
"minimum": 1,
"maximum": 20,
"description": "Total character level."
},
"class": {
"type": "string",
"description": "Primary class of the character."
},
"race": {
"type": "string",
"description": "Character race / species."
},
"background": {
"type": "string",
"description": "Character background."
},
"alignment": {
"type": "string",
"description": "Ethical and moral alignment."
},
"experience_points": {
"type": "integer",
"minimum": 0
},
"armor_class": {
"type": ["integer", "object"],
"description": "Armor class value or extended property with shield/dex/item modifiers."
},
"equipped_armor": {
"type": ["object", "null"],
"description": "Currently equipped body armor item snapshot or reference. null if unarmored.",
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"armor_category": {
"type": "string",
"enum": ["light", "medium", "heavy"]
},
"ac_base": { "type": "integer" },
"dex_cap": { "type": ["integer", "null"] },
"stealth_disadvantage": { "type": "boolean" },
"strength_requirement": { "type": ["integer", "null"] }
}
},
"equipped_shield": {
"type": ["object", "null"],
"description": "Currently equipped shield item snapshot or reference. null if no shield is equipped.",
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"ac_bonus": { "type": "integer", "default": 2 },
"stealth_disadvantage": { "type": "boolean" },
"strength_requirement": { "type": ["integer", "null"] }
}
},
"speed": {
"type": "integer",
"description": "Base walking speed in feet."
},
"hit_points": {
"type": "object",
"required": ["current", "max"],
"properties": {
"current": { "type": "integer" },
"max": { "type": "integer" },
"temp": { "type": "integer", "default": 0 }
}
},
"abilities": {
"type": "object",
"description": "Core 5e ability scores.",
"required": [
"strength",
"dexterity",
"constitution",
"intelligence",
"wisdom",
"charisma"
],
"properties": {
"strength": { "type": ["integer", "object"] },
"dexterity": { "type": ["integer", "object"] },
"constitution": { "type": ["integer", "object"] },
"intelligence": { "type": ["integer", "object"] },
"wisdom": { "type": ["integer", "object"] },
"charisma": { "type": ["integer", "object"] }
}
},
"player_name": {
"type": "string",
"description": "Name of the player controlling this character."
},
"subclass": {
"type": "string",
"description": "Character archetype or subclass (e.g. Hunter, Champion, Evoker)."
},
"inspiration": {
"type": ["boolean", "integer"],
"default": false,
"description": "Heroic inspiration state or count."
},
"initiative": {
"type": ["integer", "object"],
"description": "Initiative modifier (typically dexterity modifier plus bonuses)."
},
"hit_dice": {
"type": "object",
"properties": {
"total": { "type": ["integer", "string"] },
"current": { "type": ["integer", "string"] },
"dice": { "type": "string" }
}
},
"death_saves": {
"type": "object",
"properties": {
"successes": {
"type": "integer",
"minimum": 0,
"maximum": 3,
"default": 0
},
"failures": {
"type": "integer",
"minimum": 0,
"maximum": 3,
"default": 0
}
}
},
"saving_throws": {
"type": ["array", "object"],
"description": "Saving throw proficiencies and bonuses."
},
"skills": {
"type": ["array", "object"],
"description": "Skill proficiencies and bonuses across the 18 standard 5e skills."
},
"passive_perception": {
"type": ["integer", "object"],
"description": "Passive Wisdom (Perception) score: typically 10 + perception bonus."
},
"other_proficiencies_and_languages": {
"type": "object",
"properties": {
"armor": { "type": "array", "items": { "type": "string" } },
"weapons": { "type": "array", "items": { "type": "string" } },
"tools": { "type": "array", "items": { "type": "string" } },
"languages": { "type": "array", "items": { "type": "string" } }
}
},
"attacks": {
"type": "array",
"description": "Weapons and spell attacks for the Attacks & Spellcasting section.",
"items": {
"type": "object",
"required": ["name"],
"properties": {
"name": { "type": "string" },
"atk_bonus": { "type": ["string", "integer"] },
"damage": { "type": "string" },
"damage_type": { "type": "string" },
"range": { "type": "string" },
"notes": { "type": "string" }
}
}
},
"currency": {
"type": "object",
"properties": {
"cp": { "type": "integer", "default": 0 },
"sp": { "type": "integer", "default": 0 },
"ep": { "type": "integer", "default": 0 },
"gp": { "type": "integer", "default": 0 },
"pp": { "type": "integer", "default": 0 }
}
},
"personality_traits": { "type": ["string", "array"] },
"ideals": { "type": ["string", "array"] },
"bonds": { "type": ["string", "array"] },
"flaws": { "type": ["string", "array"] },
"features_and_traits": {
"type": "array",
"description": "Class, racial, and background features.",
"items": {
"type": "object",
"required": ["name"],
"properties": {
"name": { "type": "string" },
"source": { "type": "string" },
"desc": { "type": "string" }
}
}
},
"character_details": {
"type": "object",
"properties": {
"age": { "type": ["integer", "string"] },
"height": { "type": "string" },
"weight": { "type": "string" },
"eyes": { "type": "string" },
"skin": { "type": "string" },
"hair": { "type": "string" },
"appearance": { "type": "string" },
"backstory": { "type": "string" },
"allies_and_organizations": { "type": ["string", "array"] },
"treasure": { "type": ["string", "array"] }
}
},
"spellcasting": {
"type": "object",
"properties": {
"class": { "type": "string" },
"ability": { "type": "string" },
"save_dc": { "type": ["integer", "object"] },
"attack_bonus": { "type": ["integer", "object"] },
"cantrips": {
"type": "array",
"items": { "type": ["string", "object"] }
},
"slots": {
"type": "object",
"description": "Spell slot counts per spell level 1-9.",
"additionalProperties": {
"type": "object",
"properties": {
"total": { "type": "integer" },
"expended": { "type": "integer", "default": 0 }
}
}
},
"spells": {
"type": "array",
"items": {
"type": "object",
"required": ["name"],
"properties": {
"name": { "type": "string" },
"level": { "type": "integer", "minimum": 0, "maximum": 9 },
"prepared": { "type": "boolean", "default": false },
"desc": { "type": "string" }
}
}
}
}
},
"inventory": {
"type": "array",
"description": "List of carried items.",
"items": {
"type": "object",
"required": ["id", "name"],
"properties": {
"id": { "type": "string" },
"entity_ref": {
"type": "string",
"description": "Optional relative path or identifier of the base item entity."
},
"name": { "type": "string" },
"quantity": { "type": "integer", "default": 1 },
"weight": { "type": "number", "default": 0 },
"equipped": { "type": "boolean", "default": false },
"ac_base": { "type": "integer" },
"dex_cap": { "type": ["integer", "null"] },
"ac_bonus": { "type": "integer" },
"grants_modifiers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"target": { "type": "string" },
"value": { "type": ["number", "string"] },
"type": { "type": "string" },
"source": { "type": "string" }
}
}
}
},
"additionalProperties": true
}
}
}
},
"calculated_properties": {
"armor_class": {
"expr": "10 + (equipped_armor ? (equipped_armor.ac_modifier !== undefined ? equipped_armor.ac_modifier : equipped_armor.ac_base - 10) : 0) + (equipped_armor && equipped_armor.dex_cap !== null ? (equipped_armor.dex_cap === 0 ? min(0, abilities.dexterity.modifier) : min(equipped_armor.dex_cap, abilities.dexterity.modifier)) : abilities.dexterity.modifier) + (equipped_shield ? equipped_shield.ac_bonus : 0) + sum(filter(armor_class.modifiers, m => m.active && m.type !== 'armor' && m.type !== 'ability'))",
"type": "integer",
"dependencies": [
"equipped_armor",
"equipped_shield",
"abilities.dexterity.modifier",
"armor_class.modifiers"
],
"description": "5e Armor Class calculation: base 10 + armor modifier (e.g. +1 for leather, +8 for plate) + dexterity modifier (capped by armor; heavy armor caps positive dex to 0 but allows negative penalties) + shield bonus + active external modifiers."
},
"proficiency_bonus": {
"expr": "floor((level - 1) / 4) + 2",
"type": "integer",
"dependencies": ["level"],
"description": "Standard 5e proficiency bonus calculation."
},
"ability_modifiers.strength": {
"expr": "floor((abilities.strength - 10) / 2)",
"type": "integer",
"dependencies": ["abilities.strength"]
},
"ability_modifiers.dexterity": {
"expr": "floor((abilities.dexterity - 10) / 2)",
"type": "integer",
"dependencies": ["abilities.dexterity"]
},
"ability_modifiers.constitution": {
"expr": "floor((abilities.constitution - 10) / 2)",
"type": "integer",
"dependencies": ["abilities.constitution"]
},
"ability_modifiers.intelligence": {
"expr": "floor((abilities.intelligence - 10) / 2)",
"type": "integer",
"dependencies": ["abilities.intelligence"]
},
"ability_modifiers.wisdom": {
"expr": "floor((abilities.wisdom - 10) / 2)",
"type": "integer",
"dependencies": ["abilities.wisdom"]
},
"ability_modifiers.charisma": {
"expr": "floor((abilities.charisma - 10) / 2)",
"type": "integer",
"dependencies": ["abilities.charisma"]
}
},
"default_values": {
"level": 1,
"equipped_armor": null,
"equipped_shield": null,
"abilities": {
"strength": 10,
"dexterity": 10,
"constitution": 10,
"intelligence": 10,
"wisdom": 10,
"charisma": 10
},
"hit_points": {
"current": 10,
"max": 10,
"temp": 0
},
"speed": 30,
"inventory": []
}
}

View File

@@ -0,0 +1,100 @@
{
"$schema": "../../../schema/entity-definition.json",
"id": "item",
"name": "Item / Gear",
"description": "Base entity schema for generic items, equipment, and gear in 5e.",
"ruleset_id": "open5e",
"version": "1.0.0",
"schema": {
"type": "object",
"required": ["name"],
"properties": {
"name": {
"type": "string",
"description": "The name of the item."
},
"description": {
"type": "string",
"description": "A description of the item."
},
"cost": {
"description": "The market value or cost of the item.",
"type": "object",
"properties": {
"quantity": { "type": "number", "minimum": 0 },
"unit": {
"type": "string",
"enum": ["cp", "sp", "ep", "gp", "pp"],
"default": "gp"
}
}
},
"weight": {
"type": "number",
"minimum": 0,
"description": "Weight in pounds (lbs)."
},
"rarity": {
"type": "string",
"enum": [
"common",
"uncommon",
"rare",
"very rare",
"legendary",
"artifact"
],
"default": "common"
},
"attunement": {
"type": "boolean",
"default": false,
"description": "Whether this item requires attunement."
},
"equipped": {
"type": "boolean",
"default": false,
"description": "Whether this item is currently equipped or worn."
},
"quantity": {
"type": "integer",
"minimum": 1,
"default": 1,
"description": "Number of copies carried in inventory."
},
"grants_modifiers": {
"type": "array",
"description": "Modifiers applied to the character when this item is equipped.",
"items": {
"type": "object",
"required": ["target", "value"],
"properties": {
"target": {
"type": "string",
"description": "The property path modified on the wearer (e.g. 'armor_class', 'saving_throws.all')."
},
"value": {
"type": ["number", "string"],
"description": "Modifier bonus value or expression."
},
"type": {
"type": "string",
"description": "Modifier type (e.g. 'bonus', 'base', 'override', 'advantage')."
},
"source": {
"type": "string",
"description": "Name or origin of the bonus."
}
}
}
}
}
},
"default_values": {
"weight": 0,
"quantity": 1,
"equipped": false,
"rarity": "common",
"attunement": false
}
}

View File

@@ -0,0 +1,45 @@
{
"$schema": "../../../schema/entity-definition.json",
"id": "shield",
"name": "Shield",
"description": "Shield entity extending base item schema with AC bonus and strength/stealth properties.",
"ruleset_id": "open5e",
"version": "1.0.0",
"schema": {
"type": "object",
"required": ["name", "ac_bonus"],
"allOf": [
{
"$ref": "item.json#/schema"
},
{
"type": "object",
"properties": {
"ac_bonus": {
"type": "integer",
"default": 2,
"description": "Armor Class bonus granted by wielding this shield (standard 5e shield is +2)."
},
"strength_requirement": {
"type": ["integer", "null"],
"description": "Minimum Strength score required to use without penalty."
},
"stealth_disadvantage": {
"type": "boolean",
"default": false,
"description": "Whether wielding this shield imposes disadvantage on Dexterity (Stealth) checks."
}
}
}
]
},
"default_values": {
"ac_bonus": 2,
"weight": 6,
"quantity": 1,
"equipped": false,
"rarity": "common",
"stealth_disadvantage": false,
"strength_requirement": null
}
}

View File

@@ -0,0 +1,92 @@
{
"$schema": "../../../schema/entity-definition.json",
"id": "spell",
"name": "Spell",
"description": "5e Spell entity definition declaring level, school, casting time, range, components, duration, and spell description.",
"ruleset_id": "open5e",
"version": "1.0.0",
"schema": {
"type": "object",
"required": [
"level",
"school",
"casting_time",
"range",
"duration",
"description"
],
"properties": {
"level": {
"type": "integer",
"minimum": 0,
"maximum": 9,
"description": "Spell level from 0 (cantrip) up to 9."
},
"school": {
"type": "string",
"enum": [
"abjuration",
"conjuration",
"divination",
"enchantment",
"evocation",
"illusion",
"necromancy",
"transmutation"
]
},
"casting_time": {
"type": "string",
"description": "e.g. 1 action, 1 bonus action, 1 reaction, 10 minutes."
},
"range": {
"type": "string",
"description": "e.g. Self, Touch, 30 feet, 60 feet, 120 feet."
},
"components": {
"type": "object",
"properties": {
"verbal": { "type": "boolean", "default": true },
"somatic": { "type": "boolean", "default": true },
"material": { "type": "boolean", "default": false },
"material_specified": { "type": "string" }
}
},
"duration": {
"type": "string",
"description": "e.g. Instantaneous, 1 round, 1 hour, Concentration up to 1 minute."
},
"concentration": {
"type": "boolean",
"default": false
},
"ritual": {
"type": "boolean",
"default": false
},
"description": {
"type": "string",
"description": "Rules text of the spell effect."
},
"higher_levels": {
"type": "string",
"description": "Rules text for casting at higher slot levels."
}
}
},
"default_values": {
"level": 0,
"school": "evocation",
"casting_time": "1 action",
"range": "60 feet",
"components": {
"verbal": true,
"somatic": true,
"material": false
},
"duration": "Instantaneous",
"concentration": false,
"ritual": false,
"description": ""
}
}

View File

@@ -0,0 +1,82 @@
{
"$schema": "../../../schema/entity-definition.json",
"id": "weapon",
"name": "Weapon",
"description": "5e Weapon entity definition inheriting from base item, specifying damage dice, damage type, and weapon properties.",
"ruleset_id": "open5e",
"version": "1.0.0",
"schema": {
"allOf": [
{
"$ref": "item.json#/schema"
},
{
"type": "object",
"required": ["damage_dice", "damage_type"],
"properties": {
"weapon_category": {
"type": "string",
"enum": ["simple", "martial"]
},
"weapon_range": {
"type": "string",
"enum": ["melee", "ranged"],
"default": "melee"
},
"damage_dice": {
"type": "string",
"description": "Damage formula e.g. 1d6, 1d8, 2d6."
},
"damage_type": {
"type": "string",
"enum": ["bludgeoning", "piercing", "slashing"]
},
"properties": {
"type": "array",
"description": "Standard 5e weapon properties.",
"items": {
"type": "string",
"enum": [
"ammunition",
"finesse",
"heavy",
"light",
"loading",
"range",
"reach",
"special",
"thrown",
"two-handed",
"versatile"
]
}
},
"range_normal": {
"type": ["integer", "null"],
"description": "Standard effective range in feet."
},
"range_long": {
"type": ["integer", "null"],
"description": "Long range with disadvantage in feet."
},
"versatile_dice": {
"type": ["string", "null"],
"description": "Two-handed damage dice when used versatile (e.g. 1d10 for a longsword)."
}
}
}
]
},
"default_values": {
"cost": {
"quantity": 10,
"unit": "gp"
},
"weight": 2.0,
"weapon_category": "simple",
"weapon_range": "melee",
"damage_dice": "1d6",
"damage_type": "slashing",
"properties": []
}
}

View File

@@ -0,0 +1,51 @@
{
"$schema": "../../../../schema/entity.json",
"uuid": "f6a83d72-b94e-4e5c-a5c2-f39b6b52d202",
"ruleset_id": "open5e",
"entity_type": "character",
"display_name": "Acolyte",
"description": "Medium humanoid (any race), any alignment.",
"version": "1.0.0",
"template_id": "stat_block",
"tags": ["npc", "humanoid", "spellcaster", "cr-1/4"],
"properties": {
"size": "medium",
"type": "Humanoid",
"subtype": "any race",
"alignment": "any alignment",
"armor_class": 10,
"hit_points": {
"current": 9,
"max": 9,
"formula": "2d8 + 0",
"hit_dice": "2d8"
},
"speed": 30,
"speed_desc": "30 ft.",
"challenge_rating": "1/4 (50 XP)",
"cr": "1/4",
"abilities": {
"strength": 10,
"dexterity": 10,
"constitution": 10,
"intelligence": 10,
"wisdom": 14,
"charisma": 11
},
"skills": ["medicine +4", "religion +2"],
"senses": "passive Perception 12",
"languages": ["any one language (usually Common)"],
"traits": [
{
"name": "Spellcasting",
"desc": "The acolyte is a 1st-level spellcaster. Its spellcasting ability is Wisdom (spell save DC 12, +4 to hit with spell attacks). The acolyte has following cleric spells prepared:\n\n* Cantrips (at will): _light, sacred flame, thaumaturgy_\n* 1st level (3 slots): _bless, cure wounds, sanctuary_"
}
],
"actions": [
{
"name": "Club",
"desc": "_Melee Weapon Attack:_ +2 to hit, reach 5 ft., one target. _Hit:_ 2 (1d4) bludgeoning damage."
}
]
}
}

View File

@@ -0,0 +1,99 @@
{
"$schema": "../../../../schema/entity.json",
"uuid": "b8a91c34-d07f-4f8a-92c1-d41a8a91e404",
"ruleset_id": "open5e",
"entity_type": "character",
"display_name": "Adult Black Dragon",
"description": "Huge dragon, chaotic evil.",
"version": "1.0.0",
"template_id": "stat_block",
"tags": ["monster", "dragon", "legendary", "cr-14"],
"properties": {
"size": "huge",
"type": "Dragon",
"alignment": "chaotic evil",
"armor_class": 19,
"armor_description": "natural armor",
"hit_points": {
"current": 196,
"max": 196,
"formula": "17d12 + 85",
"hit_dice": "17d12"
},
"speed": 40,
"speed_desc": "40 ft., fly 80 ft., swim 40 ft.",
"challenge_rating": "14 (11,500 XP)",
"cr": "14",
"abilities": {
"strength": 23,
"dexterity": 14,
"constitution": 21,
"intelligence": 14,
"wisdom": 13,
"charisma": 17
},
"saving_throws": [
"Dexterity +7",
"Constitution +10",
"Wisdom +6",
"Charisma +8"
],
"skills": ["perception +16", "stealth +11"],
"damage_immunities": ["acid"],
"senses": "darkvision 120 ft., blindsight 60 ft., passive Perception 11",
"languages": ["Common", "Draconic"],
"traits": [
{
"name": "Amphibious",
"desc": "The dragon can breathe air and water."
},
{
"name": "Legendary Resistance (3/Day)",
"desc": "If the dragon fails a saving throw, it can choose to succeed instead."
}
],
"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:_ +11 to hit, reach 10 ft., one target. _Hit:_ 17 (2d10 + 6) piercing damage plus 4 (1d8) acid damage."
},
{
"name": "Claw",
"desc": "_Melee Weapon Attack:_ +11 to hit, reach 5 ft., one target. _Hit:_ 13 (2d6 + 6) slashing damage."
},
{
"name": "Tail",
"desc": "_Melee Weapon Attack:_ +11 to hit, reach 15 ft., one target. _Hit:_ 15 (2d8 + 6) bludgeoning damage."
},
{
"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 16 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": "Acid Breath (Recharge 5-6)",
"desc": "The dragon exhales acid in a 60-foot line that is 5 feet wide. Each creature in that line must make a DC 18 Dexterity saving throw, taking 54 (12d8) acid damage on a failed save, or half as much damage on a successful one."
}
],
"legendary_actions": {
"description": "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.",
"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 10 ft. of the dragon must succeed on a DC 19 Dexterity saving throw or take 13 (2d6 + 6) bludgeoning damage and be knocked prone. The dragon can then fly up to half its flying speed."
}
]
}
}
}

View File

@@ -0,0 +1,261 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "4c8f3824-3453-48bf-9a0e-a4c336b3bcf2",
"ruleset_id": "open5e",
"entity_type": "character",
"display_name": "Arannis",
"description": "Elven Ranger exploring ancient ruins.",
"version": "1.0.0",
"created_at": "2026-09-06T14:00:00Z",
"updated_at": "2026-09-06T18:00:00Z",
"tags": ["pc", "ranger", "elf"],
"properties": {
"player_name": "Tom",
"level": 3,
"class": "Ranger",
"subclass": "Hunter",
"race": "Elf",
"size": "medium",
"type": "Humanoid",
"subtype": "Elf",
"background": "Outlander",
"alignment": "Chaotic Good",
"experience_points": 900,
"inspiration": true,
"proficiency_bonus": 2,
"initiative": 3,
"speed": 35,
"speed_desc": "35 ft.",
"hit_points": {
"current": 24,
"max": 24,
"formula": "3d10 + 6",
"temp": 0
},
"hit_dice": {
"total": 3,
"current": 3,
"dice": "1d10"
},
"death_saves": {
"successes": 0,
"failures": 0
},
"saving_throws": ["Strength +3", "Dexterity +5"],
"advantages": ["wis"],
"skills": ["Athletics +3", "Perception +5", "Stealth +5", "Survival +5"],
"passive_perception": 15,
"senses": "darkvision 60 ft., passive Perception 15",
"languages": ["Common", "Elvish", "Sylvan"],
"other_proficiencies_and_languages": {
"armor": ["Light armor", "Medium armor", "Shields"],
"weapons": ["Simple weapons", "Martial weapons"],
"tools": ["Herbalism kit", "Flute"],
"languages": ["Common", "Elvish", "Sylvan"]
},
"attacks": [
{
"name": "Longbow",
"atk_bonus": "+7",
"damage": "1d8 + 3",
"damage_type": "Piercing",
"range": "150/600 ft."
},
{
"name": "Shortsword",
"atk_bonus": "+5",
"damage": "1d6 + 3",
"damage_type": "Piercing",
"range": "5 ft."
}
],
"currency": {
"cp": 15,
"sp": 40,
"ep": 0,
"gp": 25,
"pp": 0
},
"personality_traits": "I feel far more comfortable among the ancient trees and wild beasts than in bustling cities.",
"ideals": "Nature: The natural world is more important than the constructs of civilization.",
"bonds": "An ancient ruined shrine in the northern forest holds a secret I have sworn to protect.",
"flaws": "I am slow to trust others and prefer solitude even in times of danger.",
"features_and_traits": [
{
"name": "Favored Enemy (Undead)",
"source": "Ranger 1",
"desc": "You have advantage on Wisdom (Survival) checks to track your favored enemies, as well as on Intelligence checks to recall information about them."
},
{
"name": "Natural Explorer (Forest)",
"source": "Ranger 1",
"desc": "When making an Intelligence or Wisdom check related to your favored terrain, your proficiency bonus is doubled if you are using a skill you're proficient in."
},
{
"name": "Colossus Slayer",
"source": "Hunter Archetype",
"desc": "Your tenacity can wear down the most potent foes. Once per turn when you hit a creature with a weapon attack, the creature takes an extra 1d8 damage if it's below its hit point maximum."
}
],
"spellcasting": {
"class": "Ranger",
"ability": "WISDOM",
"save_dc": 13,
"attack_bonus": 5,
"cantrips": [],
"slots": {
"1": {
"total": 3,
"expended": 1
}
},
"spells": [
{
"name": "Hunter's Mark",
"level": 1,
"prepared": true,
"desc": "You choose a creature you can see within range and mystically mark it as your quarry."
},
{
"name": "Cure Wounds",
"level": 1,
"prepared": true,
"desc": "A creature you touch regains a number of hit points equal to 1d8 + your spellcasting ability modifier."
}
]
},
"traits": [
{
"name": "Fey Ancestry",
"desc": "Arannis has advantage on saving throws against being charmed, and magic cannot put her to sleep."
},
{
"name": "Natural Explorer",
"desc": "Difficult terrain does not slow Arannis's group while traveling."
}
],
"actions": [
{
"name": "Longbow",
"desc": "_Ranged Weapon Attack:_ +7 to hit, range 150/600 ft., one target. _Hit:_ 7 (1d8 + 3) piercing damage."
},
{
"name": "Shortsword",
"desc": "_Melee Weapon Attack:_ +5 to hit, reach 5 ft., one target. _Hit:_ 6 (1d6 + 3) piercing damage."
}
],
"equipped_armor": {
"id": "armor_leather",
"name": "Leather Armor",
"armor_category": "light",
"ac_base": 11,
"ac_modifier": 1,
"dex_cap": null,
"stealth_disadvantage": false,
"strength_requirement": null
},
"equipped_shield": null,
"armor_class": {
"value": 15,
"base": 10,
"expr": "base + sum(modifiers)",
"modifiers": [
{
"source": "Leather Armor",
"value": 1,
"type": "armor"
},
{
"source": "Dexterity Modifier",
"value": 3,
"type": "ability"
},
{
"source": "Ring of Protection +1",
"value": 1,
"type": "bonus",
"active": true
}
]
},
"abilities": {
"strength": 12,
"dexterity": {
"value": 16,
"base": 14,
"modifier": 3,
"modifiers": [
{
"source": "Elven Agility",
"value": 2,
"type": "bonus"
}
]
},
"constitution": 14,
"intelligence": 10,
"wisdom": 15,
"charisma": 8
},
"inventory": [
{
"id": "armor_leather",
"entity_ref": "entities/armor.json",
"name": "Leather Armor",
"quantity": 1,
"weight": 10.0,
"equipped": true,
"ac_base": 11,
"dex_cap": null
},
{
"id": "ring_protection",
"entity_ref": "entities/item.json",
"name": "Ring of Protection +1",
"quantity": 1,
"weight": 0.1,
"equipped": true,
"grants_modifiers": [
{
"target": "armor_class",
"value": 1,
"type": "bonus",
"source": "Ring of Protection +1"
}
]
},
{
"id": "longbow",
"entity_ref": "entities/weapon.json",
"name": "Longbow",
"quantity": 1,
"weight": 2.0,
"equipped": true
},
{
"id": "shortsword",
"entity_ref": "entities/weapon.json",
"name": "Shortsword",
"quantity": 1,
"weight": 2.0,
"equipped": true
},
{
"id": "arrow",
"entity_ref": "entities/item.json",
"name": "Arrow",
"quantity": 20,
"weight": 1.0,
"equipped": false
},
{
"id": "potion_healing",
"entity_ref": "entities/item.json",
"name": "Potion of Healing",
"quantity": 2,
"weight": 1.0,
"equipped": false
}
]
}
}

View File

@@ -0,0 +1,56 @@
{
"$schema": "../../../../schema/entity.json",
"uuid": "e5b72e61-a83d-4c3d-b4b1-e28a5a41c101",
"ruleset_id": "open5e",
"entity_type": "character",
"display_name": "Goblin",
"description": "Small humanoid (goblinoid), neutral evil.",
"version": "1.0.0",
"template_id": "stat_block",
"tags": ["monster", "goblinoid", "humanoid", "cr-1/4"],
"properties": {
"size": "small",
"type": "Humanoid",
"subtype": "goblinoid",
"alignment": "neutral evil",
"armor_class": 15,
"armor_description": "leather armor, shield",
"hit_points": {
"current": 7,
"max": 7,
"formula": "2d6 + 0",
"hit_dice": "2d6"
},
"speed": 30,
"speed_desc": "30 ft.",
"challenge_rating": "1/4 (50 XP)",
"cr": "1/4",
"abilities": {
"strength": 8,
"dexterity": 14,
"constitution": 10,
"intelligence": 10,
"wisdom": 8,
"charisma": 8
},
"skills": ["stealth +6"],
"senses": "darkvision 60 ft., passive Perception 9",
"languages": ["Common", "Goblin"],
"traits": [
{
"name": "Nimble Escape",
"desc": "The goblin can take the Disengage or Hide action as a bonus action on each of its turns."
}
],
"actions": [
{
"name": "Scimitar",
"desc": "_Melee Weapon Attack:_ +4 to hit, reach 5 ft., one target. _Hit:_ 5 (1d6 + 2) slashing damage."
},
{
"name": "Shortbow",
"desc": "_Ranged Weapon Attack:_ +4 to hit, range 80/320 ft., one target. _Hit:_ 5 (1d6 + 2) piercing damage."
}
]
}
}

View File

@@ -0,0 +1,492 @@
# Open5e Ruleset — Import & Export Specification Guide
This document is the technical integration and schema guide for third-party applications, services, and developers who want to **import** or **export** Open5e (D&D 5th Edition SRD) entities—including **Player Characters / Creatures**, **Weapons**, **Armor**, **Shields**, **Spells**, and **Generic Items**.
---
## Table of Contents
1. [Overview & Architecture](#1-overview--architecture)
2. [What Files to Distribute to External Apps](#2-what-files-to-distribute-to-external-apps)
3. [The Universal Entity Envelope (`entity.json`)](#3-the-universal-entity-envelope-entityjson)
4. [Entity Specifications & Payloads](#4-entity-specifications--payloads)
- [4.1 Character (`character.json`)](#41-character-entity-characterjson)
- [4.2 Base Item (`item.json`)](#42-base-item-entity-itemjson)
- [4.3 Weapon (`weapon.json`)](#43-weapon-entity-weaponjson)
- [4.4 Armor (`armor.json`)](#44-armor-entity-armorjson)
- [4.5 Shield (`shield.json`)](#45-shield-entity-shieldjson)
- [4.6 Spell (`spell.json`)](#46-spell-entity-spelljson)
5. [Validation & Conformance Rules](#5-validation--conformance-rules)
6. [Import & Export Implementation Checklist](#6-import--export-implementation-checklist)
---
## 1. Overview & Architecture
In our system, all data entities (characters, items, spells, etc.) follow a two-tier JSON architecture:
1. **The Universal Envelope (`entity.json`)**: Provides standard metadata required across all rulesets and systems (`uuid`, `ruleset_id`, `entity_type`, `display_name`, `version`, `tags`, etc.).
2. **The Entity Properties Payload (`properties`)**: Houses ruleset-specific and entity-specific fields defined by the entity schema (e.g. ability scores, spell slots, armor classes, damage dice).
```
┌─────────────────────────────────────────────────────────────┐
│ Universal Envelope (schema/entity.json) │
│ ├─ uuid: "4c8f3824-3453-48bf-9a0e-a4c336b3bcf2" │
│ ├─ ruleset_id: "open5e" │
│ ├─ entity_type: "character" | "weapon" | "spell" | ... │
│ ├─ display_name: "Arannis" │
│ └─ properties: { ... } <── Validated by Entity Schema │
└─────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ Entity-Specific Schema (entities/{type}.json) │
│ ├─ character.json │
│ ├─ weapon.json (inherits from item.json) │
│ ├─ armor.json (inherits from item.json) │
│ ├─ shield.json (inherits from item.json) │
│ ├─ spell.json │
│ └─ item.json │
└──────────────────────────────────────────────────┘
```
---
## 2. What Files to Distribute to External Apps
To give an external application complete import and export capability, package and provide the following files:
### A. Root Schemas (`schema/`)
1. **[`schema/entity.json`](../../schema/entity.json)**: The universal envelope schema. Every exported file must validate against this schema.
2. **[`schema/entity-definition.json`](../../schema/entity-definition.json)**: The meta-schema describing how entity types and schemas are declared.
### B. Ruleset Manifest (`rulesets/open5e/`)
3. **[`rulesets/open5e/manifest.json`](manifest.json)**: Identifies the ruleset (`open5e`), version, author, and all registered entities and templates.
### C. Entity Property Schemas (`rulesets/open5e/entities/`)
4. **[`character.json`](entities/character.json)**: 5e Character and creature schema (attributes, vitals, proficiencies, attacks, spells, inventory).
5. **[`item.json`](entities/item.json)**: Base schema for equipment, gear, and magic items.
6. **[`weapon.json`](entities/weapon.json)**: Weapon schema (damage dice, damage types, ranges, weapon properties; inherits from `item.json`).
7. **[`armor.json`](entities/armor.json)**: Armor schema (AC base, Dex caps, stealth penalty, strength requirement; inherits from `item.json`).
8. **[`shield.json`](entities/shield.json)**: Shield schema (AC bonus, stealth penalty; inherits from `item.json`).
9. **[`spell.json`](entities/spell.json)**: Spell schema (spell level 0–9, school, casting time, range, components, duration).
> **Integration Note on `$ref` Resolution**:
> `weapon.json`, `armor.json`, and `shield.json` use JSON Schema `$ref: "item.json#/schema"`. When integrating with schema validators (such as Ajv, jsonschema, or Newtonsoft.Json), ensure all schema files are preloaded in the schema registry or resolved relative to their folder.
---
## 3. The Universal Entity Envelope (`entity.json`)
Every exported file **must** include these top-level fields:
| Field | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `$schema` | `string` | Recommended | URI or relative path to schema (`"../../../schema/entity.json"`). |
| `uuid` | `string` | **Yes** | Unique identifier (standard UUID v4 format recommended). |
| `ruleset_id` | `string` | **Yes** | Must be `"open5e"` for this ruleset. |
| `entity_type` | `string` | **Yes** | One of: `"character"`, `"item"`, `"weapon"`, `"armor"`, `"shield"`, `"spell"`. |
| `display_name` | `string` | **Yes** | Human-readable primary label (e.g. `"Arannis"`, `"Longsword +1"`). |
| `description` | `string` | No | Short synopsis or narrative summary. |
| `version` | `string` | No | Data version format (default `"1.0.0"`). |
| `created_at` | `string` | No | ISO 8601 timestamp (e.g. `"2026-09-07T12:00:00Z"`). |
| `updated_at` | `string` | No | ISO 8601 timestamp of last modification. |
| `tags` | `array<string>`| No | Filter and categorization tags (e.g. `["pc", "ranger", "elf"]`). |
| `template_id` | `string` | No | Optional UI template override (e.g. `"character_sheet"`). |
| `properties` | `object` | **Yes** | The entity's payload data (detailed below). |
---
## 4. Entity Specifications & Payloads
### 4.1 Character Entity (`character.json`)
Used for **Player Characters (PCs)**, **Non-Player Characters (NPCs)**, and **Monsters / Creatures**.
#### Required Fields in `properties`:
- `level` (`integer`, 1–20)
- `abilities` (`object` containing `strength`, `dexterity`, `constitution`, `intelligence`, `wisdom`, `charisma`)
- `hit_points` (`object` containing `current` and `max`)
#### Full Property Breakdown:
| Group | Field | Type | Description / Example |
| :--- | :--- | :--- | :--- |
| **Identity** | `player_name` | `string` | Player name (optional for NPCs/monsters). |
| | `class` | `string` | Primary class (e.g. `"Ranger"`). |
| | `subclass` | `string` | Archetype / subclass (e.g. `"Hunter"`). |
| | `race` | `string` | Character race (e.g. `"Elf"`). |
| | `background` | `string` | Background (e.g. `"Outlander"`). |
| | `alignment` | `string` | Moral alignment (e.g. `"Chaotic Good"`). |
| | `experience_points` | `integer` | Current total XP. |
| | `inspiration` | `boolean` | Inspiration point toggle. |
| **Abilities** | `abilities` | `object` | 6 scores. Can be raw integers or score objects: `{"strength": 12, "dexterity": 16, "constitution": 14, "intelligence": 10, "wisdom": 15, "charisma": 8}`. |
| | `proficiency_bonus` | `integer` | Bonus added to proficient rolls (auto-calculated from level or explicit). |
| | `saving_throws` | `array<string>` | Formatted saves (e.g. `["Strength +3", "Dexterity +5"]`). |
| | `skills` | `array<string>` | Formatted skill proficiencies (e.g. `["Athletics +3", "Stealth +5"]`). |
| | `passive_perception` | `integer` | Passive Wisdom (Perception) score (e.g. `15`). |
| **Combat** | `hit_points` | `object` | `{"current": 24, "max": 24, "temp": 0, "formula": "3d10 + 6"}`. |
| | `hit_dice` | `object` | `{"total": 3, "current": 3, "dice": "1d10"}`. |
| | `death_saves` | `object` | `{"successes": 0, "failures": 0}` (0–3 each). |
| | `speed` | `integer` | Walking speed in feet (e.g. `35`). |
| | `speed_desc` | `string` | Display string including flight/swim (e.g. `"30 ft., fly 60 ft."`). |
| | `initiative` | `integer` | Initiative modifier (usually Dex modifier). |
| | `armor_class` | `integer\|object` | Total AC (e.g. `15`). |
| | `equipped_armor` | `object\|null` | Snapshot: `{"name": "Studded Leather", "ac_base": 12, "dex_cap": null}`. |
| | `equipped_shield` | `object\|null` | Snapshot: `{"name": "Shield", "ac_bonus": 2}`. |
| | `attacks` | `array<object>` | Weapon/spell attack rows: `[{"name": "Longbow", "atk_bonus": "+7", "damage": "1d8 + 3", "damage_type": "Piercing", "range": "150/600 ft."}]`. |
| **Inventory & Wealth** | `currency` | `object` | `{"cp": 15, "sp": 40, "ep": 0, "gp": 25, "pp": 0}`. |
| | `inventory` | `array<object>` | Carried items matching `item.json` schema. |
| **Roleplay** | `personality_traits` | `string` | Personality trait text. |
| | `ideals` | `string` | Character ideals. |
| | `bonds` | `string` | Character bonds. |
| | `flaws` | `string` | Character flaws. |
| | `features_and_traits` | `array<object>` | Class/racial features: `[{"name": "Colossus Slayer", "source": "Hunter Archetype", "desc": "..."}]`. |
| **Spellcasting** | `spellcasting` | `object` | `{"class": "Ranger", "ability": "Wisdom", "save_dc": 13, "attack_bonus": 5, "cantrips": [], "slots": {"1": {"total": 3, "expended": 1}}, "spells": [...]}`. |
| **NPC / Monster** | `challenge_rating` | `number\|string` | CR (e.g. `0.25`, `"1/4"`, `14`). |
| | `actions` | `array<object>` | Monster stat block actions: `[{"name": "Scimitar", "desc": "Melee Weapon Attack..."}]`. |
| | `reactions` | `array<object>` | Creature reactions. |
| | `legendary_actions` | `array<object>` | Creature legendary actions. |
#### Complete Export/Import Example (`character`):
```json
{
"$schema": "../../../schema/entity.json",
"uuid": "4c8f3824-3453-48bf-9a0e-a4c336b3bcf2",
"ruleset_id": "open5e",
"entity_type": "character",
"display_name": "Arannis",
"description": "Elven Ranger exploring ancient ruins.",
"version": "1.0.0",
"tags": ["pc", "ranger", "elf"],
"properties": {
"player_name": "Tom",
"level": 3,
"class": "Ranger",
"subclass": "Hunter",
"race": "Elf",
"background": "Outlander",
"alignment": "Chaotic Good",
"experience_points": 900,
"inspiration": true,
"proficiency_bonus": 2,
"initiative": 3,
"speed": 35,
"hit_points": {
"current": 24,
"max": 24,
"temp": 0,
"formula": "3d10 + 6"
},
"hit_dice": {
"total": 3,
"current": 3,
"dice": "1d10"
},
"death_saves": {
"successes": 0,
"failures": 0
},
"armor_class": 15,
"abilities": {
"strength": 12,
"dexterity": 16,
"constitution": 14,
"intelligence": 10,
"wisdom": 15,
"charisma": 8
},
"saving_throws": ["Strength +3", "Dexterity +5"],
"skills": ["Athletics +3", "Perception +5", "Stealth +5", "Survival +5"],
"passive_perception": 15,
"languages": ["Common", "Elvish", "Sylvan"],
"attacks": [
{
"name": "Longbow",
"atk_bonus": "+7",
"damage": "1d8 + 3",
"damage_type": "Piercing",
"range": "150/600 ft."
}
],
"currency": {
"cp": 15,
"sp": 40,
"ep": 0,
"gp": 25,
"pp": 0
}
}
}
```
---
### 4.2 Base Item Entity (`item.json`)
Used for general adventuring gear, tools, containers, consumables, and magic items.
#### Required Fields in `properties`:
- `name` (`string`)
#### Property Fields:
| Field | Type | Default | Description / Enum |
| :--- | :--- | :--- | :--- |
| `name` | `string` | — | Item name (e.g. `"Rope, Hempen (50 feet)"`). |
| `description` | `string` | `""` | Detailed description or rules text. |
| `cost` | `object` | `{"quantity": 1, "unit": "gp"}` | Value (`quantity`: number, `unit`: `"cp"`, `"sp"`, `"ep"`, `"gp"`, `"pp"`). |
| `weight` | `number` | `0` | Weight in pounds (lbs). |
| `rarity` | `string` | `"common"` | `"common"`, `"uncommon"`, `"rare"`, `"very rare"`, `"legendary"`, `"artifact"`. |
| `attunement` | `boolean` | `false` | Whether the item requires attunement. |
| `equipped` | `boolean` | `false` | Equipped/worn status. |
| `quantity` | `integer` | `1` | Count carried. |
| `grants_modifiers`| `array<object>` | `[]` | Stat bonuses granted when equipped (e.g. `[{"target": "armor_class", "value": 1, "type": "bonus", "source": "Cloak of Protection"}]`). |
#### Example Payload (`item`):
```json
{
"$schema": "../../../schema/entity.json",
"uuid": "8b5123d1-419b-4351-92b0-9b34e56711aa",
"ruleset_id": "open5e",
"entity_type": "item",
"display_name": "Potion of Healing",
"description": "A magical red fluid that restores hit points when consumed.",
"properties": {
"name": "Potion of Healing",
"description": "You regain 2d4 + 2 hit points when you drink this potion.",
"cost": { "quantity": 50, "unit": "gp" },
"weight": 0.5,
"rarity": "common",
"attunement": false,
"equipped": false,
"quantity": 2
}
}
```
---
### 4.3 Weapon Entity (`weapon.json`)
Inherits all fields from `item.json` plus combat damage specifications.
#### Required Fields in `properties`:
- `name` (`string`)
- `damage_dice` (`string`, e.g. `"1d8"`, `"2d6"`)
- `damage_type` (`string`: `"bludgeoning"`, `"piercing"`, or `"slashing"`)
#### Additional Property Fields:
| Field | Type | Default | Description / Enum |
| :--- | :--- | :--- | :--- |
| `weapon_category` | `string` | `"simple"` | `"simple"` or `"martial"`. |
| `weapon_range` | `string` | `"melee"` | `"melee"` or `"ranged"`. |
| `damage_dice` | `string` | `"1d6"` | Damage roll formula (e.g. `"1d8"`, `"2d6"`). |
| `damage_type` | `string` | `"slashing"` | `"bludgeoning"`, `"piercing"`, `"slashing"`. |
| `properties` | `array<string>` | `[]` | 5e weapon properties: `"ammunition"`, `"finesse"`, `"heavy"`, `"light"`, `"loading"`, `"range"`, `"reach"`, `"special"`, `"thrown"`, `"two-handed"`, `"versatile"`. |
| `range_normal` | `integer\|null` | `null` | Normal range in feet (e.g. `20` for dagger, `150` for longbow). |
| `range_long` | `integer\|null` | `null` | Disadvantage range in feet (e.g. `60` for dagger, `600` for longbow). |
| `versatile_dice`| `string\|null` | `null` | Alternate two-handed damage die (e.g. `"1d10"` for longsword/battleaxe). |
#### Example Payload (`weapon`):
```json
{
"$schema": "../../../schema/entity.json",
"uuid": "7f139d42-26cb-4029-a1b7-6ec7e34ef399",
"ruleset_id": "open5e",
"entity_type": "weapon",
"display_name": "Longsword",
"description": "A versatile martial melee blade.",
"properties": {
"name": "Longsword",
"description": "A classic double-edged straight blade with a cruciform hilt.",
"cost": { "quantity": 15, "unit": "gp" },
"weight": 3.0,
"rarity": "common",
"weapon_category": "martial",
"weapon_range": "melee",
"damage_dice": "1d8",
"damage_type": "slashing",
"properties": ["versatile"],
"versatile_dice": "1d10",
"equipped": true,
"quantity": 1
}
}
```
---
### 4.4 Armor Entity (`armor.json`)
Inherits all fields from `item.json` plus protection and armor class modifiers.
#### Required Fields in `properties`:
- `name` (`string`)
- `armor_category` (`string`: `"light"`, `"medium"`, or `"heavy"`)
- `ac_base` (`integer`, minimum `0`)
#### Additional Property Fields:
| Field | Type | Description |
| :--- | :--- | :--- |
| `armor_category` | `string` | `"light"`, `"medium"`, or `"heavy"`. |
| `ac_base` | `integer` | Base AC provided (e.g. `11` for Leather, `14` for Scale Mail, `18` for Plate). |
| `ac_modifier` | `integer` | Bonus over base 10 (e.g. `+1` for Leather, `+4` for Scale Mail, `+8` for Plate). |
| `dex_cap` | `integer\|null` | Max Dex modifier allowed: `null` (uncapped, light armor), `2` (medium armor cap), `0` (heavy armor, no Dex). |
| `strength_requirement` | `integer\|null` | Minimum Strength score required without -10 speed penalty (e.g. `13` or `15`). `null` if none. |
| `stealth_disadvantage` | `boolean` | `true` if wearing this armor imposes disadvantage on Stealth checks. |
#### Example Payload (`armor`):
```json
{
"$schema": "../../../schema/entity.json",
"uuid": "21950d81-817b-40fa-9861-bbd51381aa01",
"ruleset_id": "open5e",
"entity_type": "armor",
"display_name": "Breastplate",
"description": "Fitted metal chest armor with leather backing.",
"properties": {
"name": "Breastplate",
"description": "Protects the wearer's torso while leaving limbs free.",
"cost": { "quantity": 400, "unit": "gp" },
"weight": 20.0,
"rarity": "common",
"armor_category": "medium",
"ac_base": 14,
"dex_cap": 2,
"strength_requirement": null,
"stealth_disadvantage": false,
"equipped": true,
"quantity": 1
}
}
```
---
### 4.5 Shield Entity (`shield.json`)
Inherits all fields from `item.json` plus shield-specific AC bonus and penalties.
#### Required Fields in `properties`:
- `name` (`string`)
- `ac_bonus` (`integer`, standard 5e value is `2`)
#### Additional Property Fields:
| Field | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `ac_bonus` | `integer` | `2` | Armor Class bonus added when wielded. |
| `strength_requirement` | `integer\|null` | `null` | Minimum Strength required to wield without penalty. |
| `stealth_disadvantage` | `boolean` | `false` | Imposes disadvantage on Stealth rolls if `true`. |
#### Example Payload (`shield`):
```json
{
"$schema": "../../../schema/entity.json",
"uuid": "43e2f901-b841-47fa-89fa-948123da6711",
"ruleset_id": "open5e",
"entity_type": "shield",
"display_name": "Shield",
"description": "A wooden or metal shield carried in one hand.",
"properties": {
"name": "Shield",
"description": "Wielding a shield increases your Armor Class by 2.",
"cost": { "quantity": 10, "unit": "gp" },
"weight": 6.0,
"rarity": "common",
"ac_bonus": 2,
"strength_requirement": null,
"stealth_disadvantage": false,
"equipped": true,
"quantity": 1
}
}
```
---
### 4.6 Spell Entity (`spell.json`)
Defines complete 5e spells, cantrips, and rituals.
#### Required Fields in `properties`:
- `level` (`integer`, 0–9; 0 represents cantrips)
- `school` (`string`: abjuration, conjuration, divination, enchantment, evocation, illusion, necromancy, transmutation)
- `casting_time` (`string`, e.g. `"1 action"`, `"1 bonus action"`, `"1 reaction"`)
- `range` (`string`, e.g. `"Self"`, `"Touch"`, `"60 feet"`, `"120 feet"`)
- `duration` (`string`, e.g. `"Instantaneous"`, `"Concentration, up to 1 minute"`)
- `description` (`string`, the full rules text)
#### Additional Property Fields:
| Field | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `components` | `object` | `{"verbal": true, "somatic": true, "material": false}` | Component breakdown: `verbal` (`bool`), `somatic` (`bool`), `material` (`bool`), `material_specified` (`string`). |
| `concentration` | `boolean` | `false` | Requires spellcaster concentration. |
| `ritual` | `boolean` | `false` | Can be cast as a 10-minute ritual without expending a spell slot. |
| `higher_levels` | `string` | `""` | Additional damage or target effects when upcast with higher level slots. |
#### Example Payload (`spell`):
```json
{
"$schema": "../../../schema/entity.json",
"uuid": "e58123f0-49a1-4322-9fa1-01f4218aef82",
"ruleset_id": "open5e",
"entity_type": "spell",
"display_name": "Fireball",
"description": "A bright streak that blossoms with a low roar into an explosion of flame.",
"properties": {
"level": 3,
"school": "evocation",
"casting_time": "1 action",
"range": "150 feet",
"duration": "Instantaneous",
"components": {
"verbal": true,
"somatic": true,
"material": true,
"material_specified": "a tiny ball of bat guano and sulfur"
},
"concentration": false,
"ritual": false,
"description": "Each creature in a 20-foot-radius sphere centered on that point must make a Dexterity saving throw. A target takes 8d6 fire damage on a failed save, or half as much damage on a successful one.",
"higher_levels": "When you cast this spell using a spell slot of 4th level or higher, the damage increases by 1d6 for each slot level above 3rd."
}
}
```
---
## 5. Validation & Conformance Rules
When parsing or validating imported entities, external apps should follow these rules:
1. **Top-Level Envelope Validation**:
- Verify `ruleset_id === "open5e"`.
- Ensure `entity_type` matches one of the 6 registered entity types (`character`, `item`, `weapon`, `armor`, `shield`, `spell`).
- Ensure `uuid` is a valid unique string or UUID v4.
2. **Sub-Schema Dispatch**:
- Extract `properties` and validate against the corresponding `entities/{entity_type}.json` schema.
3. **Graceful Defaults**:
- If an optional field is missing (such as `temp` HP or `currency.ep`), assume default 0 or empty structures rather than throwing a validation exception.
4. **Extensibility**:
- Extra custom fields in `properties` are permitted (`additionalProperties: true` in base schemas) to allow apps to preserve their own proprietary metadata (e.g. inventory IDs, campaign notes, asset URLs) without breaking import/export compatibility.
---
## 6. Import & Export Implementation Checklist
When implementing an integration adapter in another application:
- [ ] **Export Checklist**:
- [ ] Generate or preserve a stable `uuid` (UUID v4).
- [ ] Set `"ruleset_id": "open5e"`.
- [ ] Set `"entity_type"` to the exact entity type slug.
- [ ] Populate `"display_name"` with the character/item name.
- [ ] Place all game attributes inside the `"properties"` root object.
- [ ] Export numbers as JSON numbers (not stringified quotes, except for dice formulas like `"1d8 + 3"`).
- [ ] Format ISO 8601 dates for `created_at` and `updated_at`.
- [ ] **Import Checklist**:
- [ ] Check `"ruleset_id" === "open5e"`.
- [ ] Inspect `"entity_type"` to determine character vs. item vs. spell importer.
- [ ] Read `properties.abilities` (handle both raw numeric maps and modifier objects).
- [ ] Safely read `properties.hit_points` (`current`, `max`, `temp`).
- [ ] Safely import or map `properties.attacks` and `properties.currency`.
- [ ] If importing items or weapons into a character inventory, unpack nested items into the character's equipment list.

View File

@@ -0,0 +1,127 @@
{
"$schema": "../../schema/manifest.json",
"id": "open5e",
"uuid": "7a35e4d2-f67b-4890-a292-6a7593c72b21",
"name": "Open5e (D&D 5th Edition SRD)",
"version": "1.0.0",
"description": "5th Edition System Reference Document ruleset providing entity schemas and sheet templates.",
"license": "OGL 1.0a",
"author": {
"name": "Open5e Community",
"url": "https://open5e.com"
},
"entities": [
{
"id": "character",
"name": "Player Character",
"description": "A player character with attributes, proficiencies, classes, equipment, and spells.",
"schema": "entities/character.json",
"default_template": "character_sheet",
"templates": [
"character_sheet",
"character_sheet_alt",
"spellcasting_sheet",
"stat_block",
"character_card",
"character_list_item"
]
},
{
"id": "item",
"name": "Item / Gear",
"description": "Generic items, equipment, adventuring gear, and magical items.",
"schema": "entities/item.json"
},
{
"id": "weapon",
"name": "Weapon",
"description": "Simple and martial melee and ranged weapons.",
"schema": "entities/weapon.json"
},
{
"id": "armor",
"name": "Armor",
"description": "Wearable armor with base AC, dex caps, and strength/stealth rules.",
"schema": "entities/armor.json"
},
{
"id": "shield",
"name": "Shield",
"description": "Equippable shields providing armor class bonuses.",
"schema": "entities/shield.json"
},
{
"id": "spell",
"name": "Spell",
"description": "5e Spells with level, school, casting time, range, and components.",
"schema": "entities/spell.json"
}
],
"templates": [
{
"id": "character_sheet",
"name": "Standard Character Sheet",
"description": "Full interactive 5e character sheet matching the official 3-column print layout.",
"entity_types": ["character"],
"mode": "sheet",
"engine": "html",
"html": "templates/character_sheet.html",
"css": ["templates/character_sheet.css"],
"js": ["templates/character_sheet.js"]
},
{
"id": "character_sheet_alt",
"name": "Alternative Character Sheet",
"description": "5e character sheet with saving throws and skills grouped by ability score.",
"entity_types": ["character"],
"mode": "sheet",
"engine": "html",
"html": "templates/character_sheet_alt.html",
"css": ["templates/character_sheet_alt.css"]
},
{
"id": "spellcasting_sheet",
"name": "Spellcasting Sheet",
"description": "Dedicated 5e spellcasting sheet with cantrips, spell slots 1-9, and spell save DC.",
"entity_types": ["character"],
"mode": "sheet",
"engine": "html",
"html": "templates/spellcasting_sheet.html",
"css": ["templates/spellcasting_sheet.css"]
},
{
"id": "stat_block",
"name": "Creature Stat Block",
"description": "Monster manual style stat block with full actions, traits, and combat statistics.",
"entity_types": ["character"],
"mode": "view",
"engine": "html",
"html": "templates/stat_block.html",
"css": ["templates/stat_block.css"]
},
{
"id": "character_card",
"name": "NPC Character Card",
"description": "Compact NPC card with 8 stat boxes and quick action summaries.",
"entity_types": ["character"],
"mode": "card",
"engine": "html",
"html": "templates/character_card.html",
"css": ["templates/character_card.css"]
},
{
"id": "character_list_item",
"name": "Character List Item",
"description": "Uniform fixed-height quick-reference list item card with non-wrapping horizontal action row.",
"entity_types": ["character"],
"mode": "card",
"engine": "html",
"html": "templates/character_list_item.html",
"css": ["templates/character_list_item.css"]
}
],
"assets": {
"path": "assets",
"images": []
}
}

View File

@@ -0,0 +1,155 @@
.card-page-wrapper {
margin: 0;
padding: 20px;
background-color: #f4f6f8;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
display: flex;
justify-content: center;
}
.npc-card {
width: 100%;
max-width: 580px;
background: #ffffff;
border: 1.5px solid #922610;
border-radius: 4px;
padding: 16px 18px 18px;
box-sizing: border-box;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
}
.card-top {
display: flex;
justify-content: space-between;
align-items: baseline;
}
.card-name {
margin: 0;
font-size: 22px;
font-weight: 700;
color: #922610;
line-height: 1.15;
}
.card-cr {
font-size: 18px;
font-weight: 500;
color: #555;
white-space: nowrap;
}
.card-subtitle {
font-style: italic;
font-size: 13.5px;
color: #666;
margin: 4px 0 14px 0;
}
.stat-boxes-row {
display: flex;
gap: 6px;
justify-content: space-between;
margin-bottom: 14px;
}
.stat-box {
flex: 1;
position: relative;
border: 1.5px solid #222;
border-radius: 2px;
padding: 6px 2px 4px;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 48px;
background: #ffffff;
box-sizing: border-box;
}
.badge-advantage {
position: absolute;
top: 1.5px;
left: 2.5px;
font-size: 8.5px;
font-weight: 800;
color: #111;
line-height: 1;
}
.badge-proficiency {
position: absolute;
top: 1.5px;
right: 2.5px;
font-size: 8.5px;
font-weight: 800;
color: #111;
line-height: 1;
}
.stat-mod {
font-size: 15px;
font-weight: 700;
color: #111;
line-height: 1.1;
}
.stat-code {
font-size: 9.5px;
font-weight: 600;
color: #333;
margin-top: 3px;
text-transform: uppercase;
}
.action-boxes-grid {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.action-card-box {
flex: 1 1 calc(50% - 8px);
min-width: 140px;
border: 1px solid #333;
border-radius: 2px;
padding: 8px 10px;
background: #ffffff;
box-sizing: border-box;
}
.action-box-title {
font-size: 13.5px;
font-weight: 700;
color: #222;
margin-bottom: 4px;
}
.action-box-desc {
font-size: 11.5px;
line-height: 1.35;
color: #444;
}
/* Single action takes full width */
.action-boxes-grid > .action-card-box:only-child {
flex: 1 1 100%;
}
@media (max-width: 440px) {
.card-page-wrapper {
padding: 8px;
}
.stat-boxes-row {
gap: 3px;
}
.stat-mod {
font-size: 13px;
}
.action-card-box {
flex: 1 1 100%;
}
}

View File

@@ -0,0 +1,118 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{display_name}} - Character Card</title>
<link rel="stylesheet" href="character_card.css">
</head>
<body class="card-page-wrapper">
<div class="npc-card">
<!-- Top Bar: Name & Challenge/Level -->
<div class="card-top">
<h2 class="card-name">{{display_name}}</h2>
<div class="card-cr">
{{#if properties.challenge_rating}}
CR {{properties.challenge_rating}}
{{else if properties.cr}}
CR {{properties.cr}}
{{else if properties.level}}
LVL {{properties.level}}
{{else}}
CR 1
{{/if}}
</div>
</div>
<!-- Subtitle: Size, Type, Subtype, Alignment -->
<div class="card-subtitle">
{{#if properties.size}}{{properties.size}}{{else}}medium{{/if}}
{{#if properties.type}}{{properties.type}}{{else}}Humanoid{{/if}}{{#if properties.subtype}} ({{properties.subtype}}){{else if properties.race}} ({{properties.race}}){{/if}},
{{#if properties.alignment}}{{properties.alignment}}{{else}}unaligned{{/if}}
</div>
<!-- 8 Stat Boxes Row: S, D, C, I, W, Ch, AC, HP -->
<div class="stat-boxes-row">
<!-- STR -->
<div class="stat-box">
<span class="badge-advantage">{{advBadge properties "str"}}</span>
<span class="badge-proficiency">{{profBadge properties.saving_throws "str"}}</span>
<span class="stat-mod">{{modText properties.abilities.strength}}</span>
<span class="stat-code">S</span>
</div>
<!-- DEX -->
<div class="stat-box">
<span class="badge-advantage">{{advBadge properties "dex"}}</span>
<span class="badge-proficiency">{{profBadge properties.saving_throws "dex"}}</span>
<span class="stat-mod">{{modText properties.abilities.dexterity}}</span>
<span class="stat-code">D</span>
</div>
<!-- CON -->
<div class="stat-box">
<span class="badge-advantage">{{advBadge properties "con"}}</span>
<span class="badge-proficiency">{{profBadge properties.saving_throws "con"}}</span>
<span class="stat-mod">{{modText properties.abilities.constitution}}</span>
<span class="stat-code">C</span>
</div>
<!-- INT -->
<div class="stat-box">
<span class="badge-advantage">{{advBadge properties "int"}}</span>
<span class="badge-proficiency">{{profBadge properties.saving_throws "int"}}</span>
<span class="stat-mod">{{modText properties.abilities.intelligence}}</span>
<span class="stat-code">I</span>
</div>
<!-- WIS -->
<div class="stat-box">
<span class="badge-advantage">{{advBadge properties "wis"}}</span>
<span class="badge-proficiency">{{profBadge properties.saving_throws "wis"}}</span>
<span class="stat-mod">{{modText properties.abilities.wisdom}}</span>
<span class="stat-code">W</span>
</div>
<!-- CHA -->
<div class="stat-box">
<span class="badge-advantage">{{advBadge properties "cha"}}</span>
<span class="badge-proficiency">{{profBadge properties.saving_throws "cha"}}</span>
<span class="stat-mod">{{modText properties.abilities.charisma}}</span>
<span class="stat-code">Ch</span>
</div>
<!-- AC -->
<div class="stat-box">
<span class="stat-mod">{{val properties.armor_class}}</span>
<span class="stat-code">AC</span>
</div>
<!-- HP -->
<div class="stat-box">
<span class="stat-mod">
{{#if properties.hit_points.current}}
{{properties.hit_points.current}}
{{else if properties.hit_points.max}}
{{properties.hit_points.max}}
{{else}}
{{val properties.hit_points}}
{{/if}}
</span>
<span class="stat-code">HP</span>
</div>
</div>
<!-- Action Summary Boxes -->
{{#if properties.actions}}
<div class="action-boxes-grid">
{{#each properties.actions}}
<div class="action-card-box">
<div class="action-box-title">{{this.name}}</div>
<div class="action-box-desc">{{formatText this.desc}}</div>
</div>
{{/each}}
</div>
{{/if}}
</div>
</body>
</html>

View File

@@ -0,0 +1,245 @@
/* Character List Item Template - Uniform Fixed Height with Non-Wrapping Horizontal Actions */
:root {
--card-border: #922610;
--card-title: #922610;
--box-border: #222222;
--bg-page: #f4f6f8;
--bg-card: #ffffff;
--bg-box: #ffffff;
--text-main: #111111;
--text-muted: #666666;
--font-sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
body.list-item-page-wrapper {
margin: 0;
padding: 16px;
background-color: var(--bg-page);
font-family: var(--font-sans);
display: flex;
justify-content: center;
}
/* Card has fixed height so all list cards are identical height */
.npc-list-card {
width: 100%;
max-width: 580px;
height: 236px;
background: var(--bg-card);
border: 1.5px solid var(--card-border);
border-radius: 4px;
padding: 12px 14px;
box-sizing: border-box;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
display: flex;
flex-direction: column;
overflow: hidden;
}
/* Header */
.card-top {
display: flex;
justify-content: space-between;
align-items: baseline;
flex-shrink: 0;
}
.card-name {
margin: 0;
font-size: 20px;
font-weight: 700;
color: var(--card-title);
line-height: 1.15;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.card-cr {
font-size: 16px;
font-weight: 500;
color: #555;
white-space: nowrap;
margin-left: 8px;
}
.card-subtitle {
font-style: italic;
font-size: 12px;
color: var(--text-muted);
margin: 2px 0 10px 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex-shrink: 0;
}
/* 8 Stat Boxes */
.stat-boxes-row {
display: flex;
gap: 5px;
justify-content: space-between;
margin-bottom: 10px;
flex-shrink: 0;
}
.stat-box {
flex: 1;
position: relative;
border: 1.2px solid var(--box-border);
border-radius: 2px;
padding: 4px 1px 3px;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 44px;
background: var(--bg-box);
box-sizing: border-box;
}
.badge-advantage {
position: absolute;
top: 1px;
left: 2px;
font-size: 8px;
font-weight: 800;
color: #111;
line-height: 1;
}
.badge-proficiency {
position: absolute;
top: 1px;
right: 2px;
font-size: 8px;
font-weight: 800;
color: #111;
line-height: 1;
}
.stat-mod {
font-size: 14px;
font-weight: 700;
color: var(--text-main);
line-height: 1.1;
}
.stat-code {
font-size: 9px;
font-weight: 600;
color: #333;
margin-top: 2px;
text-transform: uppercase;
}
/* Actions Row: Single Non-Wrapping Row with Horizontal Scrolling */
.action-row-container {
flex: 1;
display: flex;
min-height: 0; /* Allows container to shrink inside flex parent */
overflow: hidden;
}
.action-boxes-row {
display: flex;
flex-wrap: nowrap;
gap: 8px;
width: 100%;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: thin;
scrollbar-color: #ccc transparent;
-webkit-overflow-scrolling: touch;
padding-bottom: 2px;
}
.action-boxes-row::-webkit-scrollbar {
height: 4px;
}
.action-boxes-row::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 2px;
}
.action-boxes-row::-webkit-scrollbar-track {
background: transparent;
}
/* Individual Action Card */
.action-card-box {
flex: 0 0 170px;
max-width: 240px;
border: 1px solid #333;
border-radius: 2px;
padding: 6px 8px;
background: #ffffff;
box-sizing: border-box;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* When only 1 action exists (e.g. Acolyte Club), expand full width */
.action-boxes-row > .action-card-box:only-child {
flex: 1 0 100%;
max-width: 100%;
}
.action-box-title {
font-size: 12.5px;
font-weight: 700;
color: #222;
margin-bottom: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex-shrink: 0;
}
.action-box-desc {
font-size: 10.5px;
line-height: 1.25;
color: #444;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
}
/* Empty State (e.g. Unnamed Monster) */
.no-actions-box {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
border: 1px dashed #ccc;
border-radius: 2px;
background: #fafafa;
}
.no-actions-text {
font-size: 11px;
color: #999;
font-style: italic;
}
/* Responsive */
@media (max-width: 440px) {
.list-item-page-wrapper {
padding: 6px;
}
.stat-boxes-row {
gap: 3px;
}
.stat-mod {
font-size: 12px;
}
.action-card-box {
flex: 0 0 145px;
}
}

View File

@@ -0,0 +1,127 @@
<!DOCTYPE html>
<html lang="en">
<head>
<base href="/rulesets/open5e/templates/">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{display_name}} - List Item</title>
<link rel="stylesheet" href="character_list_item.css">
</head>
<body class="list-item-page-wrapper">
<div class="npc-list-card">
<!-- Header: Name & Challenge/Level -->
<div class="card-top">
<h2 class="card-name">{{display_name}}</h2>
<div class="card-cr">
{{#if properties.challenge_rating}}
CR {{properties.challenge_rating}}
{{else if properties.cr}}
CR {{properties.cr}}
{{else if properties.level}}
LVL {{properties.level}}
{{else}}
CR 1
{{/if}}
</div>
</div>
<!-- Subtitle: Size, Type, Subtype, Alignment -->
<div class="card-subtitle">
{{#if properties.size}}{{properties.size}}{{else}}medium{{/if}}
{{#if properties.type}}{{properties.type}}{{else}}Humanoid{{/if}}{{#if properties.subtype}} ({{properties.subtype}}){{else if properties.race}} ({{properties.race}}){{/if}},
{{#if properties.alignment}}{{properties.alignment}}{{else}}unaligned{{/if}}
</div>
<!-- 8 Stat Boxes Row: S, D, C, I, W, Ch, AC, HP -->
<div class="stat-boxes-row">
<!-- STR -->
<div class="stat-box">
<span class="badge-advantage">{{advBadge properties "str"}}</span>
<span class="badge-proficiency">{{profBadge properties.saving_throws "str"}}</span>
<span class="stat-mod">{{modText properties.abilities.strength}}</span>
<span class="stat-code">S</span>
</div>
<!-- DEX -->
<div class="stat-box">
<span class="badge-advantage">{{advBadge properties "dex"}}</span>
<span class="badge-proficiency">{{profBadge properties.saving_throws "dex"}}</span>
<span class="stat-mod">{{modText properties.abilities.dexterity}}</span>
<span class="stat-code">D</span>
</div>
<!-- CON -->
<div class="stat-box">
<span class="badge-advantage">{{advBadge properties "con"}}</span>
<span class="badge-proficiency">{{profBadge properties.saving_throws "con"}}</span>
<span class="stat-mod">{{modText properties.abilities.constitution}}</span>
<span class="stat-code">C</span>
</div>
<!-- INT -->
<div class="stat-box">
<span class="badge-advantage">{{advBadge properties "int"}}</span>
<span class="badge-proficiency">{{profBadge properties.saving_throws "int"}}</span>
<span class="stat-mod">{{modText properties.abilities.intelligence}}</span>
<span class="stat-code">I</span>
</div>
<!-- WIS -->
<div class="stat-box">
<span class="badge-advantage">{{advBadge properties "wis"}}</span>
<span class="badge-proficiency">{{profBadge properties.saving_throws "wis"}}</span>
<span class="stat-mod">{{modText properties.abilities.wisdom}}</span>
<span class="stat-code">W</span>
</div>
<!-- CHA -->
<div class="stat-box">
<span class="badge-advantage">{{advBadge properties "cha"}}</span>
<span class="badge-proficiency">{{profBadge properties.saving_throws "cha"}}</span>
<span class="stat-mod">{{modText properties.abilities.charisma}}</span>
<span class="stat-code">Ch</span>
</div>
<!-- AC -->
<div class="stat-box">
<span class="stat-mod">{{val properties.armor_class}}</span>
<span class="stat-code">AC</span>
</div>
<!-- HP -->
<div class="stat-box">
<span class="stat-mod">
{{#if properties.hit_points.current}}
{{properties.hit_points.current}}
{{else if properties.hit_points.max}}
{{properties.hit_points.max}}
{{else}}
{{val properties.hit_points}}
{{/if}}
</span>
<span class="stat-code">HP</span>
</div>
</div>
<!-- Actions Row: Single Non-Wrapping Row with Horizontal Scroll -->
<div class="action-row-container">
{{#if properties.actions}}
<div class="action-boxes-row">
{{#each properties.actions}}
<div class="action-card-box">
<div class="action-box-title">{{this.name}}</div>
<div class="action-box-desc">{{formatText this.desc}}</div>
</div>
{{/each}}
</div>
{{else}}
<div class="no-actions-box">
<span class="no-actions-text">&mdash; No listed actions &mdash;</span>
</div>
{{/if}}
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,585 @@
/* D&D 5e Standard Character Sheet Styles */
:root {
--border-color: #2b2b2b;
--accent-red: #922610;
--bg-page: #eaedf0;
--bg-sheet: #ffffff;
--bg-box: #f9fbfd;
--text-main: #1e1e1e;
--text-muted: #666666;
--font-sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
--font-serif: "Libre Baskerville", "Georgia", serif;
}
body.sheet-page-wrapper {
margin: 0;
padding: 24px;
background-color: var(--bg-page);
font-family: var(--font-sans);
color: var(--text-main);
display: flex;
justify-content: center;
box-sizing: border-box;
}
.character-sheet-container {
width: 100%;
max-width: 1050px;
background: var(--bg-sheet);
border: 2px solid var(--border-color);
border-radius: 6px;
padding: 20px 24px 24px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
box-sizing: border-box;
}
/* ================= HEADER ================= */
.sheet-header {
display: grid;
grid-template-columns: 320px 1fr;
gap: 16px;
align-items: stretch;
border-bottom: 2px solid var(--border-color);
padding-bottom: 16px;
margin-bottom: 20px;
}
.header-name-box {
border: 1.5px solid var(--border-color);
border-radius: 4px;
padding: 12px 16px;
display: flex;
flex-direction: column;
justify-content: flex-end;
background: var(--bg-box);
}
.character-name {
font-family: var(--font-serif);
font-size: 26px;
color: var(--accent-red);
margin: 0 0 6px 0;
line-height: 1.1;
word-break: break-word;
}
.header-label {
font-size: 9.5px;
font-weight: bold;
color: var(--text-muted);
letter-spacing: 0.5px;
}
.header-details-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px 12px;
border: 1.5px solid var(--border-color);
border-radius: 4px;
padding: 10px 14px;
background: var(--bg-box);
}
.detail-cell {
display: flex;
flex-direction: column;
justify-content: flex-end;
border-bottom: 1px solid #ccc;
padding-bottom: 3px;
}
.detail-val {
font-size: 13.5px;
font-weight: 600;
color: var(--text-main);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 2px;
}
/* ================= 3-COLUMN MAIN GRID ================= */
.sheet-main-grid {
display: grid;
grid-template-columns: 320px 340px 1fr;
gap: 16px;
}
.sheet-col {
display: flex;
flex-direction: column;
gap: 12px;
}
/* Generic Section Box */
.section-box {
border: 1.5px solid var(--border-color);
border-radius: 4px;
padding: 10px 12px;
background: #ffffff;
position: relative;
box-sizing: border-box;
}
.box-bottom-label {
text-align: center;
font-size: 9.5px;
font-weight: bold;
color: var(--text-muted);
letter-spacing: 0.5px;
margin-top: 6px;
padding-top: 4px;
border-top: 1px solid #eee;
}
/* ================= LEFT COLUMN ================= */
.abilities-and-skills-row {
display: grid;
grid-template-columns: 88px 1fr;
gap: 12px;
}
/* 6 Abilities */
.abilities-stack {
display: flex;
flex-direction: column;
gap: 10px;
}
.ability-box {
border: 1.5px solid var(--border-color);
border-radius: 6px;
padding: 6px 4px 4px;
display: flex;
flex-direction: column;
align-items: center;
background: var(--bg-box);
text-align: center;
}
.ab-title {
font-size: 8.5px;
font-weight: bold;
color: var(--accent-red);
letter-spacing: 0.5px;
}
.ab-modifier {
font-size: 20px;
font-weight: bold;
color: var(--text-main);
margin: 2px 0;
line-height: 1;
}
.ab-score-pill {
font-size: 11px;
font-weight: 600;
color: #333;
border: 1px solid #999;
border-radius: 10px;
padding: 1px 8px;
background: #ffffff;
}
/* Skills & Saves Stack */
.skills-and-saves-stack {
display: flex;
flex-direction: column;
gap: 10px;
}
.top-stat-pill {
border: 1.5px solid var(--border-color);
border-radius: 16px;
padding: 4px 10px;
display: flex;
align-items: center;
gap: 8px;
background: var(--bg-box);
}
.pill-checkbox {
width: 14px;
height: 14px;
border: 1.5px solid var(--border-color);
border-radius: 50%;
display: inline-block;
}
.pill-checkbox.checked {
background-color: var(--accent-red);
}
.pill-value {
font-size: 13px;
font-weight: bold;
color: var(--text-main);
border-right: 1px solid #ccc;
padding-right: 8px;
}
.pill-label {
font-size: 9px;
font-weight: bold;
color: var(--text-muted);
}
/* Proficiency Lists (Saves & Skills) */
.prof-list {
list-style: none;
margin: 0;
padding: 0;
}
.prof-item {
display: flex;
align-items: center;
font-size: 11.5px;
margin-bottom: 3.5px;
gap: 6px;
line-height: 1.2;
}
.prof-item .dot {
width: 8px;
height: 8px;
border: 1.2px solid var(--border-color);
border-radius: 50%;
display: inline-block;
flex-shrink: 0;
}
.prof-item .dot.filled {
background-color: var(--accent-red);
border-color: var(--accent-red);
}
.prof-bonus {
font-weight: 700;
width: 22px;
text-align: right;
flex-shrink: 0;
}
.prof-name {
color: #333;
}
.skill-attr {
color: #888;
font-size: 10px;
}
/* Passive Perception */
.passive-perception-box {
border: 1.5px solid var(--border-color);
border-radius: 18px;
padding: 6px 12px;
display: flex;
align-items: center;
gap: 10px;
background: var(--bg-box);
}
.passive-score {
font-size: 15px;
font-weight: bold;
color: var(--text-main);
border: 1px solid var(--border-color);
border-radius: 50%;
width: 26px;
height: 26px;
display: flex;
align-items: center;
justify-content: center;
background: #ffffff;
flex-shrink: 0;
}
.passive-label {
font-size: 9.5px;
font-weight: bold;
color: var(--text-muted);
}
.prof-block-content {
font-size: 11.5px;
line-height: 1.45;
}
.prof-line {
margin-bottom: 4px;
}
/* ================= MIDDLE COLUMN ================= */
.combat-top-row {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.combat-badge {
border: 1.5px solid var(--border-color);
border-radius: 6px;
padding: 8px 4px;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: var(--bg-box);
}
.badge-ac {
border-color: var(--accent-red);
}
.badge-value {
font-size: 22px;
font-weight: bold;
color: var(--text-main);
line-height: 1.1;
}
.badge-label {
font-size: 8.5px;
font-weight: bold;
color: var(--text-muted);
margin-top: 3px;
}
/* HP Boxes */
.hp-max-line {
font-size: 10.5px;
color: #666;
border-bottom: 1px solid #eee;
padding-bottom: 4px;
}
.hp-current-val, .temp-hp-val {
font-size: 28px;
font-weight: bold;
text-align: center;
padding: 8px 0;
color: var(--text-main);
}
.hd-and-death-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.half-box {
padding: 6px 8px;
}
.hd-total-line {
font-size: 9.5px;
color: #666;
}
.hd-current-val {
font-size: 18px;
font-weight: bold;
text-align: center;
padding: 4px 0;
}
.death-save-line {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 9px;
font-weight: bold;
margin: 3px 0;
}
.ds-bubbles {
display: flex;
gap: 4px;
}
.ds-bubbles .bubble {
width: 9px;
height: 9px;
border: 1.2px solid var(--border-color);
border-radius: 50%;
display: inline-block;
}
.ds-bubbles .bubble.filled {
background-color: var(--accent-red);
}
/* Attacks Table */
.attacks-table {
width: 100%;
border-collapse: collapse;
font-size: 11px;
margin-bottom: 8px;
}
.attacks-table th {
font-size: 8.5px;
font-weight: bold;
color: var(--text-muted);
border-bottom: 1px solid #ccc;
padding: 2px 4px;
text-align: left;
}
.attacks-table td {
padding: 5px 4px;
border-bottom: 1px solid #eee;
}
.atk-name {
font-weight: 600;
}
.atk-bonus {
font-weight: 700;
color: var(--accent-red);
}
.attack-notes {
font-size: 10.5px;
line-height: 1.35;
color: #444;
margin-top: 6px;
max-height: 120px;
overflow-y: auto;
}
.attack-note-item {
margin: 4px 0;
}
/* Equipment */
.equipment-layout {
display: grid;
grid-template-columns: 56px 1fr;
gap: 8px;
}
.coins-stack {
display: flex;
flex-direction: column;
gap: 4px;
}
.coin-row {
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 2px 4px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 10px;
background: var(--bg-box);
}
.coin-tag {
font-weight: bold;
font-size: 8.5px;
color: var(--accent-red);
}
.coin-val {
font-weight: 600;
}
.items-list-container {
max-height: 180px;
overflow-y: auto;
}
.items-list {
list-style: none;
margin: 0;
padding: 0;
font-size: 11px;
}
.item-row {
display: flex;
justify-content: space-between;
padding: 3px 0;
border-bottom: 1px solid #eee;
}
.equipped-tag {
color: var(--accent-red);
font-weight: bold;
}
.item-meta {
color: #777;
font-size: 10px;
}
/* ================= RIGHT COLUMN ================= */
.roleplay-box {
min-height: 64px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.box-content {
font-size: 11.5px;
line-height: 1.4;
color: #333;
}
.box-content p {
margin: 0;
}
.empty-hint {
color: #999;
font-style: italic;
font-size: 11px;
}
.features-box {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: 250px;
}
.features-content {
font-size: 11.5px;
line-height: 1.4;
}
.feature-item {
margin-bottom: 8px;
}
.feature-name {
color: var(--accent-red);
display: block;
margin-bottom: 2px;
}
.feature-desc {
margin: 0;
}
/* Responsive Styles */
@media (max-width: 900px) {
.sheet-header {
grid-template-columns: 1fr;
}
.sheet-main-grid {
grid-template-columns: 1fr;
}
.abilities-and-skills-row {
grid-template-columns: 90px 1fr;
}
}

View File

@@ -0,0 +1,511 @@
<!DOCTYPE html>
<html lang="en">
<head>
<base href="/rulesets/open5e/templates/">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{display_name}} - Character Sheet</title>
<link rel="stylesheet" href="character_sheet.css">
</head>
<body class="sheet-page-wrapper">
<div class="character-sheet-container">
<!-- Top Header Banner -->
<header class="sheet-header">
<div class="header-name-box">
<h1 class="character-name">{{display_name}}</h1>
<label class="header-label">CHARACTER NAME</label>
</div>
<div class="header-details-grid">
<div class="detail-cell">
<div class="detail-val">{{properties.class}}{{#if properties.subclass}} ({{properties.subclass}}){{/if}} {{properties.level}}</div>
<label class="header-label">CLASS &amp; LEVEL</label>
</div>
<div class="detail-cell">
<div class="detail-val">{{#if properties.background}}{{properties.background}}{{else}}&mdash;{{/if}}</div>
<label class="header-label">BACKGROUND</label>
</div>
<div class="detail-cell">
<div class="detail-val">{{#if properties.player_name}}{{properties.player_name}}{{else}}&mdash;{{/if}}</div>
<label class="header-label">PLAYER NAME</label>
</div>
<div class="detail-cell">
<div class="detail-val">{{properties.race}}</div>
<label class="header-label">RACE</label>
</div>
<div class="detail-cell">
<div class="detail-val">{{properties.alignment}}</div>
<label class="header-label">ALIGNMENT</label>
</div>
<div class="detail-cell">
<div class="detail-val">{{#if properties.experience_points}}{{properties.experience_points}}{{else}}0{{/if}} XP</div>
<label class="header-label">EXPERIENCE POINTS</label>
</div>
</div>
</header>
<!-- Main 3-Column Body -->
<main class="sheet-main-grid">
<!-- ================= LEFT COLUMN ================= -->
<section class="sheet-col col-left">
<div class="abilities-and-skills-row">
<!-- 6 Ability Score Boxes -->
<div class="abilities-stack">
<!-- STR -->
<div class="ability-box">
<span class="ab-title">STRENGTH</span>
<span class="ab-modifier">{{modText properties.abilities.strength}}</span>
<div class="ab-score-pill">{{val properties.abilities.strength}}</div>
</div>
<!-- DEX -->
<div class="ability-box">
<span class="ab-title">DEXTERITY</span>
<span class="ab-modifier">{{modText properties.abilities.dexterity}}</span>
<div class="ab-score-pill">{{val properties.abilities.dexterity}}</div>
</div>
<!-- CON -->
<div class="ability-box">
<span class="ab-title">CONSTITUTION</span>
<span class="ab-modifier">{{modText properties.abilities.constitution}}</span>
<div class="ab-score-pill">{{val properties.abilities.constitution}}</div>
</div>
<!-- INT -->
<div class="ability-box">
<span class="ab-title">INTELLIGENCE</span>
<span class="ab-modifier">{{modText properties.abilities.intelligence}}</span>
<div class="ab-score-pill">{{val properties.abilities.intelligence}}</div>
</div>
<!-- WIS -->
<div class="ability-box">
<span class="ab-title">WISDOM</span>
<span class="ab-modifier">{{modText properties.abilities.wisdom}}</span>
<div class="ab-score-pill">{{val properties.abilities.wisdom}}</div>
</div>
<!-- CHA -->
<div class="ability-box">
<span class="ab-title">CHARISMA</span>
<span class="ab-modifier">{{modText properties.abilities.charisma}}</span>
<div class="ab-score-pill">{{val properties.abilities.charisma}}</div>
</div>
</div>
<!-- Proficiency, Inspiration, Saves & Skills -->
<div class="skills-and-saves-stack">
<!-- Inspiration & Proficiency Bonus -->
<div class="top-stat-pill">
<span class="pill-checkbox {{#if properties.inspiration}}checked{{/if}}"></span>
<span class="pill-label">INSPIRATION</span>
</div>
<div class="top-stat-pill">
<span class="pill-value">+{{calcProf properties.level properties.cr}}</span>
<span class="pill-label">PROFICIENCY BONUS</span>
</div>
<!-- Saving Throws Box -->
<div class="section-box saves-box">
<ul class="prof-list">
<li class="prof-item">
<span class="dot {{#if (hasSave properties.saving_throws 'str')}}filled{{/if}}"></span>
<span class="prof-bonus">{{saveBonus properties 'strength'}}</span>
<span class="prof-name">Strength</span>
</li>
<li class="prof-item">
<span class="dot {{#if (hasSave properties.saving_throws 'dex')}}filled{{/if}}"></span>
<span class="prof-bonus">{{saveBonus properties 'dexterity'}}</span>
<span class="prof-name">Dexterity</span>
</li>
<li class="prof-item">
<span class="dot {{#if (hasSave properties.saving_throws 'con')}}filled{{/if}}"></span>
<span class="prof-bonus">{{saveBonus properties 'constitution'}}</span>
<span class="prof-name">Constitution</span>
</li>
<li class="prof-item">
<span class="dot {{#if (hasSave properties.saving_throws 'int')}}filled{{/if}}"></span>
<span class="prof-bonus">{{saveBonus properties 'intelligence'}}</span>
<span class="prof-name">Intelligence</span>
</li>
<li class="prof-item">
<span class="dot {{#if (hasSave properties.saving_throws 'wis')}}filled{{/if}}"></span>
<span class="prof-bonus">{{saveBonus properties 'wisdom'}}</span>
<span class="prof-name">Wisdom</span>
</li>
<li class="prof-item">
<span class="dot {{#if (hasSave properties.saving_throws 'cha')}}filled{{/if}}"></span>
<span class="prof-bonus">{{saveBonus properties 'charisma'}}</span>
<span class="prof-name">Charisma</span>
</li>
</ul>
<div class="box-bottom-label">SAVING THROWS</div>
</div>
<!-- Skills Box (Alphabetical 18 Skills) -->
<div class="section-box skills-box">
<ul class="prof-list">
<li class="prof-item">
<span class="dot {{#if (isSkillProf properties.skills 'acrobatics')}}filled{{/if}}"></span>
<span class="prof-bonus">{{skillBonus properties 'acrobatics' 'dexterity'}}</span>
<span class="prof-name">Acrobatics <small class="skill-attr">(Dex)</small></span>
</li>
<li class="prof-item">
<span class="dot {{#if (isSkillProf properties.skills 'animal_handling')}}filled{{/if}}"></span>
<span class="prof-bonus">{{skillBonus properties 'animal_handling' 'wisdom'}}</span>
<span class="prof-name">Animal Handling <small class="skill-attr">(Wis)</small></span>
</li>
<li class="prof-item">
<span class="dot {{#if (isSkillProf properties.skills 'arcana')}}filled{{/if}}"></span>
<span class="prof-bonus">{{skillBonus properties 'arcana' 'intelligence'}}</span>
<span class="prof-name">Arcana <small class="skill-attr">(Int)</small></span>
</li>
<li class="prof-item">
<span class="dot {{#if (isSkillProf properties.skills 'athletics')}}filled{{/if}}"></span>
<span class="prof-bonus">{{skillBonus properties 'athletics' 'strength'}}</span>
<span class="prof-name">Athletics <small class="skill-attr">(Str)</small></span>
</li>
<li class="prof-item">
<span class="dot {{#if (isSkillProf properties.skills 'deception')}}filled{{/if}}"></span>
<span class="prof-bonus">{{skillBonus properties 'deception' 'charisma'}}</span>
<span class="prof-name">Deception <small class="skill-attr">(Cha)</small></span>
</li>
<li class="prof-item">
<span class="dot {{#if (isSkillProf properties.skills 'history')}}filled{{/if}}"></span>
<span class="prof-bonus">{{skillBonus properties 'history' 'intelligence'}}</span>
<span class="prof-name">History <small class="skill-attr">(Int)</small></span>
</li>
<li class="prof-item">
<span class="dot {{#if (isSkillProf properties.skills 'insight')}}filled{{/if}}"></span>
<span class="prof-bonus">{{skillBonus properties 'insight' 'wisdom'}}</span>
<span class="prof-name">Insight <small class="skill-attr">(Wis)</small></span>
</li>
<li class="prof-item">
<span class="dot {{#if (isSkillProf properties.skills 'intimidation')}}filled{{/if}}"></span>
<span class="prof-bonus">{{skillBonus properties 'intimidation' 'charisma'}}</span>
<span class="prof-name">Intimidation <small class="skill-attr">(Cha)</small></span>
</li>
<li class="prof-item">
<span class="dot {{#if (isSkillProf properties.skills 'investigation')}}filled{{/if}}"></span>
<span class="prof-bonus">{{skillBonus properties 'investigation' 'intelligence'}}</span>
<span class="prof-name">Investigation <small class="skill-attr">(Int)</small></span>
</li>
<li class="prof-item">
<span class="dot {{#if (isSkillProf properties.skills 'medicine')}}filled{{/if}}"></span>
<span class="prof-bonus">{{skillBonus properties 'medicine' 'wisdom'}}</span>
<span class="prof-name">Medicine <small class="skill-attr">(Wis)</small></span>
</li>
<li class="prof-item">
<span class="dot {{#if (isSkillProf properties.skills 'nature')}}filled{{/if}}"></span>
<span class="prof-bonus">{{skillBonus properties 'nature' 'intelligence'}}</span>
<span class="prof-name">Nature <small class="skill-attr">(Int)</small></span>
</li>
<li class="prof-item">
<span class="dot {{#if (isSkillProf properties.skills 'perception')}}filled{{/if}}"></span>
<span class="prof-bonus">{{skillBonus properties 'perception' 'wisdom'}}</span>
<span class="prof-name">Perception <small class="skill-attr">(Wis)</small></span>
</li>
<li class="prof-item">
<span class="dot {{#if (isSkillProf properties.skills 'performance')}}filled{{/if}}"></span>
<span class="prof-bonus">{{skillBonus properties 'performance' 'charisma'}}</span>
<span class="prof-name">Performance <small class="skill-attr">(Cha)</small></span>
</li>
<li class="prof-item">
<span class="dot {{#if (isSkillProf properties.skills 'persuasion')}}filled{{/if}}"></span>
<span class="prof-bonus">{{skillBonus properties 'persuasion' 'charisma'}}</span>
<span class="prof-name">Persuasion <small class="skill-attr">(Cha)</small></span>
</li>
<li class="prof-item">
<span class="dot {{#if (isSkillProf properties.skills 'religion')}}filled{{/if}}"></span>
<span class="prof-bonus">{{skillBonus properties 'religion' 'intelligence'}}</span>
<span class="prof-name">Religion <small class="skill-attr">(Int)</small></span>
</li>
<li class="prof-item">
<span class="dot {{#if (isSkillProf properties.skills 'sleight_of_hand')}}filled{{/if}}"></span>
<span class="prof-bonus">{{skillBonus properties 'sleight_of_hand' 'dexterity'}}</span>
<span class="prof-name">Sleight of Hand <small class="skill-attr">(Dex)</small></span>
</li>
<li class="prof-item">
<span class="dot {{#if (isSkillProf properties.skills 'stealth')}}filled{{/if}}"></span>
<span class="prof-bonus">{{skillBonus properties 'stealth' 'dexterity'}}</span>
<span class="prof-name">Stealth <small class="skill-attr">(Dex)</small></span>
</li>
<li class="prof-item">
<span class="dot {{#if (isSkillProf properties.skills 'survival')}}filled{{/if}}"></span>
<span class="prof-bonus">{{skillBonus properties 'survival' 'wisdom'}}</span>
<span class="prof-name">Survival <small class="skill-attr">(Wis)</small></span>
</li>
</ul>
<div class="box-bottom-label">SKILLS</div>
</div>
</div>
</div>
<!-- Passive Perception Box -->
<div class="passive-perception-box">
<span class="passive-score">{{passivePerception properties}}</span>
<span class="passive-label">PASSIVE WISDOM (PERCEPTION)</span>
</div>
<!-- Other Proficiencies & Languages Box -->
<div class="section-box proficiencies-box">
<div class="prof-block-content">
{{#if properties.other_proficiencies_and_languages.armor}}
<div class="prof-line"><strong>Armor:</strong> {{join properties.other_proficiencies_and_languages.armor}}</div>
{{/if}}
{{#if properties.other_proficiencies_and_languages.weapons}}
<div class="prof-line"><strong>Weapons:</strong> {{join properties.other_proficiencies_and_languages.weapons}}</div>
{{/if}}
{{#if properties.other_proficiencies_and_languages.tools}}
<div class="prof-line"><strong>Tools:</strong> {{join properties.other_proficiencies_and_languages.tools}}</div>
{{/if}}
{{#if properties.languages}}
<div class="prof-line"><strong>Languages:</strong> {{join properties.languages}}</div>
{{else if properties.other_proficiencies_and_languages.languages}}
<div class="prof-line"><strong>Languages:</strong> {{join properties.other_proficiencies_and_languages.languages}}</div>
{{/if}}
</div>
<div class="box-bottom-label">OTHER PROFICIENCIES &amp; LANGUAGES</div>
</div>
</section>
<!-- ================= MIDDLE COLUMN ================= -->
<section class="sheet-col col-middle">
<!-- Combat Top Row: AC, Initiative, Speed -->
<div class="combat-top-row">
<div class="combat-badge badge-ac">
<span class="badge-value">{{val properties.armor_class}}</span>
<span class="badge-label">ARMOR CLASS</span>
</div>
<div class="combat-badge badge-init">
<span class="badge-value">{{#if properties.initiative}}{{modSign properties.initiative}}{{else}}{{modText properties.abilities.dexterity}}{{/if}}</span>
<span class="badge-label">INITIATIVE</span>
</div>
<div class="combat-badge badge-speed">
<span class="badge-value">{{#if properties.speed}}{{properties.speed}} ft.{{else}}30 ft.{{/if}}</span>
<span class="badge-label">SPEED</span>
</div>
</div>
<!-- Current Hit Points -->
<div class="section-box hp-box">
<div class="hp-max-line">
Hit Point Maximum: <strong>{{properties.hit_points.max}}</strong>
</div>
<div class="hp-current-val">
{{properties.hit_points.current}}
</div>
<div class="box-bottom-label">CURRENT HIT POINTS</div>
</div>
<!-- Temporary Hit Points -->
<div class="section-box temp-hp-box">
<div class="temp-hp-val">
{{#if properties.hit_points.temp}}{{properties.hit_points.temp}}{{else}}--{{/if}}
</div>
<div class="box-bottom-label">TEMPORARY HIT POINTS</div>
</div>
<!-- Hit Dice & Death Saves Row -->
<div class="hd-and-death-row">
<!-- Hit Dice -->
<div class="section-box half-box hd-box">
<div class="hd-total-line">Total: {{#if properties.hit_dice.total}}{{properties.hit_dice.total}}{{else}}{{properties.level}}{{/if}}</div>
<div class="hd-current-val">
{{#if properties.hit_dice.dice}}{{properties.hit_dice.dice}}{{else if properties.hit_points.hit_dice}}{{properties.hit_points.hit_dice}}{{else}}1d10{{/if}}
</div>
<div class="box-bottom-label">HIT DICE</div>
</div>
<!-- Death Saves -->
<div class="section-box half-box death-saves-box">
<div class="death-save-line">
<span class="ds-title">SUCCESSES</span>
<span class="ds-bubbles">
<span class="bubble {{#if (gte properties.death_saves.successes 1)}}filled{{/if}}"></span>
<span class="bubble {{#if (gte properties.death_saves.successes 2)}}filled{{/if}}"></span>
<span class="bubble {{#if (gte properties.death_saves.successes 3)}}filled{{/if}}"></span>
</span>
</div>
<div class="death-save-line">
<span class="ds-title">FAILURES</span>
<span class="ds-bubbles">
<span class="bubble {{#if (gte properties.death_saves.failures 1)}}filled{{/if}}"></span>
<span class="bubble {{#if (gte properties.death_saves.failures 2)}}filled{{/if}}"></span>
<span class="bubble {{#if (gte properties.death_saves.failures 3)}}filled{{/if}}"></span>
</span>
</div>
<div class="box-bottom-label">DEATH SAVES</div>
</div>
</div>
<!-- Attacks & Spellcasting -->
<div class="section-box attacks-box">
<table class="attacks-table">
<thead>
<tr>
<th>NAME</th>
<th>ATK BONUS</th>
<th>DAMAGE/TYPE</th>
</tr>
</thead>
<tbody>
{{#if properties.attacks}}
{{#each properties.attacks}}
<tr>
<td class="atk-name">{{this.name}}</td>
<td class="atk-bonus">{{#if this.atk_bonus}}{{this.atk_bonus}}{{else}}+0{{/if}}</td>
<td class="atk-damage">{{this.damage}} {{this.damage_type}}</td>
</tr>
{{/each}}
{{else if properties.actions}}
{{#each properties.actions}}
<tr>
<td class="atk-name">{{this.name}}</td>
<td class="atk-bonus">+5</td>
<td class="atk-damage">{{this.desc}}</td>
</tr>
{{/each}}
{{else}}
<tr><td colspan="3" class="empty-hint">No attacks equipped</td></tr>
{{/if}}
</tbody>
</table>
{{#if properties.actions}}
<div class="attack-notes">
{{#each properties.actions}}
<p class="attack-note-item"><em><strong>{{this.name}}:</strong></em> {{formatText this.desc}}</p>
{{/each}}
</div>
{{/if}}
<div class="box-bottom-label">ATTACKS &amp; SPELLCASTING</div>
</div>
<!-- Equipment & Coins -->
<div class="section-box equipment-box">
<div class="equipment-layout">
<!-- Coins Column -->
<div class="coins-stack">
<div class="coin-row">
<span class="coin-tag">CP</span>
<span class="coin-val">{{#if properties.currency.cp}}{{properties.currency.cp}}{{else}}0{{/if}}</span>
</div>
<div class="coin-row">
<span class="coin-tag">SP</span>
<span class="coin-val">{{#if properties.currency.sp}}{{properties.currency.sp}}{{else}}0{{/if}}</span>
</div>
<div class="coin-row">
<span class="coin-tag">EP</span>
<span class="coin-val">{{#if properties.currency.ep}}{{properties.currency.ep}}{{else}}0{{/if}}</span>
</div>
<div class="coin-row">
<span class="coin-tag">GP</span>
<span class="coin-val">{{#if properties.currency.gp}}{{properties.currency.gp}}{{else}}0{{/if}}</span>
</div>
<div class="coin-row">
<span class="coin-tag">PP</span>
<span class="coin-val">{{#if properties.currency.pp}}{{properties.currency.pp}}{{else}}0{{/if}}</span>
</div>
</div>
<!-- Equipment List -->
<div class="items-list-container">
<ul class="items-list">
{{#each properties.inventory}}
<li class="item-row">
<span class="item-name">{{this.name}}{{#if this.equipped}} <small class="equipped-tag">(E)</small>{{/if}}</span>
<span class="item-meta">x{{this.quantity}}{{#if this.weight}} &bull; {{this.weight}} lb{{/if}}</span>
</li>
{{/each}}
</ul>
</div>
</div>
<div class="box-bottom-label">EQUIPMENT</div>
</div>
</section>
<!-- ================= RIGHT COLUMN ================= -->
<section class="sheet-col col-right">
<!-- Personality Traits -->
<div class="section-box roleplay-box">
<div class="box-content">
{{#if properties.personality_traits}}
<p>{{formatText properties.personality_traits}}</p>
{{else}}
<span class="empty-hint">None</span>
{{/if}}
</div>
<div class="box-bottom-label">PERSONALITY TRAITS</div>
</div>
<!-- Ideals -->
<div class="section-box roleplay-box">
<div class="box-content">
{{#if properties.ideals}}
<p>{{formatText properties.ideals}}</p>
{{else}}
<span class="empty-hint">None</span>
{{/if}}
</div>
<div class="box-bottom-label">IDEALS</div>
</div>
<!-- Bonds -->
<div class="section-box roleplay-box">
<div class="box-content">
{{#if properties.bonds}}
<p>{{formatText properties.bonds}}</p>
{{else}}
<span class="empty-hint">None</span>
{{/if}}
</div>
<div class="box-bottom-label">BONDS</div>
</div>
<!-- Flaws -->
<div class="section-box roleplay-box">
<div class="box-content">
{{#if properties.flaws}}
<p>{{formatText properties.flaws}}</p>
{{else}}
<span class="empty-hint">None</span>
{{/if}}
</div>
<div class="box-bottom-label">FLAWS</div>
</div>
<!-- Features & Traits (Large Box) -->
<div class="section-box features-box">
<div class="features-content">
{{#if properties.features_and_traits}}
{{#each properties.features_and_traits}}
<div class="feature-item">
<strong class="feature-name">{{this.name}}</strong>
<p class="feature-desc">{{formatText this.desc}}</p>
</div>
{{/each}}
{{else if properties.traits}}
{{#each properties.traits}}
<div class="feature-item">
<strong class="feature-name">{{this.name}}</strong>
<p class="feature-desc">{{formatText this.desc}}</p>
</div>
{{/each}}
{{/if}}
</div>
<div class="box-bottom-label">FEATURES &amp; TRAITS</div>
</div>
</section>
</main>
</div>
</body>
</html>

View File

@@ -0,0 +1,5 @@
// Starter client-side script for open5e character sheet
window.addEventListener('DOMContentLoaded', () => {
console.log('Open5e Character Sheet template initialized.');
});

View File

@@ -0,0 +1,487 @@
/* D&D 5e Alternative (Ability-Grouped) Character Sheet Styles */
:root {
--border-color: #2b2b2b;
--accent-red: #922610;
--bg-page: #eaedf0;
--bg-sheet: #ffffff;
--bg-box: #f9fbfd;
--text-main: #1e1e1e;
--text-muted: #666666;
--font-sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
--font-serif: "Libre Baskerville", "Georgia", serif;
}
body.sheet-page-wrapper {
margin: 0;
padding: 24px;
background-color: var(--bg-page);
font-family: var(--font-sans);
color: var(--text-main);
display: flex;
justify-content: center;
box-sizing: border-box;
}
.character-sheet-container {
width: 100%;
max-width: 1050px;
background: var(--bg-sheet);
border: 2px solid var(--border-color);
border-radius: 6px;
padding: 20px 24px 24px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
box-sizing: border-box;
}
/* Header */
.sheet-header {
display: grid;
grid-template-columns: 320px 1fr;
gap: 16px;
align-items: stretch;
border-bottom: 2px solid var(--border-color);
padding-bottom: 16px;
margin-bottom: 20px;
}
.header-name-box {
border: 1.5px solid var(--border-color);
border-radius: 4px;
padding: 12px 16px;
display: flex;
flex-direction: column;
justify-content: flex-end;
background: var(--bg-box);
}
.character-name {
font-family: var(--font-serif);
font-size: 26px;
color: var(--accent-red);
margin: 0 0 6px 0;
line-height: 1.1;
word-break: break-word;
}
.header-label {
font-size: 9.5px;
font-weight: bold;
color: var(--text-muted);
letter-spacing: 0.5px;
}
.header-details-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px 12px;
border: 1.5px solid var(--border-color);
border-radius: 4px;
padding: 10px 14px;
background: var(--bg-box);
}
.detail-cell {
display: flex;
flex-direction: column;
justify-content: flex-end;
border-bottom: 1px solid #ccc;
padding-bottom: 3px;
}
.detail-val {
font-size: 13.5px;
font-weight: 600;
color: var(--text-main);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 2px;
}
/* 3-Column Grid */
.sheet-main-grid {
display: grid;
grid-template-columns: 320px 340px 1fr;
gap: 16px;
margin-bottom: 16px;
}
.sheet-col {
display: flex;
flex-direction: column;
gap: 12px;
}
.section-box {
border: 1.5px solid var(--border-color);
border-radius: 4px;
padding: 10px 12px;
background: #ffffff;
position: relative;
box-sizing: border-box;
}
.box-bottom-label {
text-align: center;
font-size: 9.5px;
font-weight: bold;
color: var(--text-muted);
letter-spacing: 0.5px;
margin-top: 6px;
padding-top: 4px;
border-top: 1px solid #eee;
}
/* Ability-Grouped Box Styles */
.alt-top-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.alt-stat-pill {
border: 1.5px solid var(--border-color);
border-radius: 16px;
padding: 5px 10px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
background: var(--bg-box);
}
.pill-value {
font-size: 14px;
font-weight: bold;
color: var(--accent-red);
}
.pill-checkbox {
width: 14px;
height: 14px;
border: 1.5px solid var(--border-color);
border-radius: 50%;
display: inline-block;
}
.pill-checkbox.checked {
background-color: var(--accent-red);
}
.pill-label {
font-size: 8.5px;
font-weight: bold;
color: var(--text-muted);
}
.ability-group-box {
border: 1.5px solid var(--border-color);
border-radius: 6px;
padding: 8px 10px;
display: grid;
grid-template-columns: 78px 1fr;
gap: 10px;
align-items: center;
background: #ffffff;
}
.ag-score-badge {
border: 1.2px solid var(--border-color);
border-radius: 4px;
padding: 4px 2px;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
background: var(--bg-box);
}
.ag-title {
font-size: 8px;
font-weight: bold;
color: var(--accent-red);
}
.ag-mod {
font-size: 18px;
font-weight: bold;
color: var(--text-main);
line-height: 1.1;
}
.ag-score {
font-size: 10px;
color: #555;
border: 1px solid #ccc;
border-radius: 8px;
padding: 0 6px;
background: #ffffff;
}
.ag-sublist {
list-style: none;
margin: 0;
padding: 0;
}
.ag-subitem {
display: flex;
align-items: center;
font-size: 11px;
gap: 6px;
margin: 2px 0;
line-height: 1.2;
}
.ag-subitem .dot {
width: 8px;
height: 8px;
border: 1.2px solid var(--border-color);
border-radius: 50%;
display: inline-block;
flex-shrink: 0;
}
.ag-subitem .dot.filled {
background-color: var(--accent-red);
border-color: var(--accent-red);
}
.ag-bonus {
font-weight: 700;
width: 20px;
text-align: right;
flex-shrink: 0;
}
.ag-name {
color: #333;
}
/* Passive Perception */
.passive-perception-box {
border: 1.5px solid var(--border-color);
border-radius: 18px;
padding: 6px 12px;
display: flex;
align-items: center;
gap: 10px;
background: var(--bg-box);
}
.passive-score {
font-size: 15px;
font-weight: bold;
color: var(--text-main);
border: 1px solid var(--border-color);
border-radius: 50%;
width: 26px;
height: 26px;
display: flex;
align-items: center;
justify-content: center;
background: #ffffff;
flex-shrink: 0;
}
.passive-label {
font-size: 9.5px;
font-weight: bold;
color: var(--text-muted);
}
/* Combat Stats */
.combat-top-row {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.combat-badge {
border: 1.5px solid var(--border-color);
border-radius: 6px;
padding: 8px 4px;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: var(--bg-box);
}
.badge-ac { border-color: var(--accent-red); }
.badge-value {
font-size: 22px;
font-weight: bold;
color: var(--text-main);
line-height: 1.1;
}
.badge-label {
font-size: 8.5px;
font-weight: bold;
color: var(--text-muted);
margin-top: 3px;
}
.hp-max-line {
font-size: 10.5px;
color: #666;
border-bottom: 1px solid #eee;
padding-bottom: 4px;
}
.hp-current-val, .temp-hp-val {
font-size: 28px;
font-weight: bold;
text-align: center;
padding: 8px 0;
color: var(--text-main);
}
.hd-and-death-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.half-box { padding: 6px 8px; }
.hd-total-line { font-size: 9.5px; color: #666; }
.hd-current-val {
font-size: 18px;
font-weight: bold;
text-align: center;
padding: 4px 0;
}
.death-save-line {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 9px;
font-weight: bold;
margin: 3px 0;
}
.ds-bubbles { display: flex; gap: 4px; }
.ds-bubbles .bubble {
width: 9px;
height: 9px;
border: 1.2px solid var(--border-color);
border-radius: 50%;
display: inline-block;
}
.ds-bubbles .bubble.filled { background-color: var(--accent-red); }
/* Attacks Table */
.attacks-table {
width: 100%;
border-collapse: collapse;
font-size: 11px;
margin-bottom: 8px;
}
.attacks-table th {
font-size: 8.5px;
font-weight: bold;
color: var(--text-muted);
border-bottom: 1px solid #ccc;
padding: 2px 4px;
text-align: left;
}
.attacks-table td {
padding: 5px 4px;
border-bottom: 1px solid #eee;
}
.atk-name { font-weight: 600; }
.atk-bonus { font-weight: 700; color: var(--accent-red); }
.attack-notes {
font-size: 10.5px;
line-height: 1.35;
color: #444;
margin-top: 6px;
max-height: 120px;
overflow-y: auto;
}
.attack-note-item { margin: 4px 0; }
/* Right Column Roleplay Boxes */
.roleplay-box {
min-height: 64px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.box-content { font-size: 11.5px; line-height: 1.4; color: #333; }
.box-content p { margin: 0; }
.empty-hint { color: #999; font-style: italic; font-size: 11px; }
.features-box {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: 250px;
}
.features-content { font-size: 11.5px; line-height: 1.4; }
.feature-item { margin-bottom: 8px; }
.feature-name { color: var(--accent-red); display: block; margin-bottom: 2px; }
.feature-desc { margin: 0; }
/* Bottom Wide Row */
.sheet-bottom-row {
display: grid;
grid-template-columns: 320px 1fr;
gap: 16px;
}
.prof-block-content { font-size: 11.5px; line-height: 1.45; }
.prof-line { margin-bottom: 4px; }
.equipment-layout {
display: grid;
grid-template-columns: 56px 1fr;
gap: 12px;
}
.coins-stack { display: flex; flex-direction: column; gap: 4px; }
.coin-row {
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 2px 4px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 10px;
background: var(--bg-box);
}
.coin-tag { font-weight: bold; font-size: 8.5px; color: var(--accent-red); }
.coin-val { font-weight: 600; }
.items-list-container { max-height: 180px; overflow-y: auto; }
.items-list { list-style: none; margin: 0; padding: 0; font-size: 11px; }
.item-row { display: flex; justify-content: space-between; padding: 3px 0; border-bottom: 1px solid #eee; }
.equipped-tag { color: var(--accent-red); font-weight: bold; }
.item-meta { color: #777; font-size: 10px; }
/* Responsive Styles */
@media (max-width: 900px) {
.sheet-header { grid-template-columns: 1fr; }
.sheet-main-grid { grid-template-columns: 1fr; }
.sheet-bottom-row { grid-template-columns: 1fr; }
}

View File

@@ -0,0 +1,472 @@
<!DOCTYPE html>
<html lang="en">
<head>
<base href="/rulesets/open5e/templates/">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{display_name}} - Alternative Character Sheet</title>
<link rel="stylesheet" href="character_sheet_alt.css">
</head>
<body class="sheet-page-wrapper">
<div class="character-sheet-container">
<!-- Top Header Banner -->
<header class="sheet-header">
<div class="header-name-box">
<h1 class="character-name">{{display_name}}</h1>
<label class="header-label">CHARACTER NAME</label>
</div>
<div class="header-details-grid">
<div class="detail-cell">
<div class="detail-val">{{properties.class}}{{#if properties.subclass}} ({{properties.subclass}}){{/if}} {{properties.level}}</div>
<label class="header-label">CLASS &amp; LEVEL</label>
</div>
<div class="detail-cell">
<div class="detail-val">{{#if properties.background}}{{properties.background}}{{else}}&mdash;{{/if}}</div>
<label class="header-label">BACKGROUND</label>
</div>
<div class="detail-cell">
<div class="detail-val">{{#if properties.player_name}}{{properties.player_name}}{{else}}&mdash;{{/if}}</div>
<label class="header-label">PLAYER NAME</label>
</div>
<div class="detail-cell">
<div class="detail-val">{{properties.race}}</div>
<label class="header-label">RACE</label>
</div>
<div class="detail-cell">
<div class="detail-val">{{properties.alignment}}</div>
<label class="header-label">ALIGNMENT</label>
</div>
<div class="detail-cell">
<div class="detail-val">{{#if properties.experience_points}}{{properties.experience_points}}{{else}}0{{/if}} XP</div>
<label class="header-label">EXPERIENCE POINTS</label>
</div>
</div>
</header>
<!-- Main 3-Column Layout -->
<main class="sheet-main-grid">
<!-- ================= LEFT COLUMN: ABILITY-GROUPED STATS ================= -->
<section class="sheet-col col-left">
<!-- Top Controls: Proficiency Bonus & Inspiration -->
<div class="alt-top-row">
<div class="alt-stat-pill">
<span class="pill-value">+{{calcProf properties.level properties.cr}}</span>
<span class="pill-label">PROFICIENCY BONUS</span>
</div>
<div class="alt-stat-pill">
<span class="pill-checkbox {{#if properties.inspiration}}checked{{/if}}"></span>
<span class="pill-label">INSPIRATION</span>
</div>
</div>
<!-- STRENGTH GROUP -->
<div class="ability-group-box">
<div class="ag-score-badge">
<span class="ag-title">STRENGTH</span>
<span class="ag-mod">{{modText properties.abilities.strength}}</span>
<span class="ag-score">{{val properties.abilities.strength}}</span>
</div>
<ul class="ag-sublist">
<li class="ag-subitem">
<span class="dot {{#if (hasSave properties.saving_throws 'str')}}filled{{/if}}"></span>
<span class="ag-bonus">{{saveBonus properties 'strength'}}</span>
<span class="ag-name">SAVING THROWS</span>
</li>
<li class="ag-subitem">
<span class="dot {{#if (isSkillProf properties.skills 'athletics')}}filled{{/if}}"></span>
<span class="ag-bonus">{{skillBonus properties 'athletics' 'strength'}}</span>
<span class="ag-name">Athletics</span>
</li>
</ul>
</div>
<!-- DEXTERITY GROUP -->
<div class="ability-group-box">
<div class="ag-score-badge">
<span class="ag-title">DEXTERITY</span>
<span class="ag-mod">{{modText properties.abilities.dexterity}}</span>
<span class="ag-score">{{val properties.abilities.dexterity}}</span>
</div>
<ul class="ag-sublist">
<li class="ag-subitem">
<span class="dot {{#if (hasSave properties.saving_throws 'dex')}}filled{{/if}}"></span>
<span class="ag-bonus">{{saveBonus properties 'dexterity'}}</span>
<span class="ag-name">SAVING THROWS</span>
</li>
<li class="ag-subitem">
<span class="dot {{#if (isSkillProf properties.skills 'acrobatics')}}filled{{/if}}"></span>
<span class="ag-bonus">{{skillBonus properties 'acrobatics' 'dexterity'}}</span>
<span class="ag-name">Acrobatics</span>
</li>
<li class="ag-subitem">
<span class="dot {{#if (isSkillProf properties.skills 'sleight_of_hand')}}filled{{/if}}"></span>
<span class="ag-bonus">{{skillBonus properties 'sleight_of_hand' 'dexterity'}}</span>
<span class="ag-name">Sleight of Hand</span>
</li>
<li class="ag-subitem">
<span class="dot {{#if (isSkillProf properties.skills 'stealth')}}filled{{/if}}"></span>
<span class="ag-bonus">{{skillBonus properties 'stealth' 'dexterity'}}</span>
<span class="ag-name">Stealth</span>
</li>
</ul>
</div>
<!-- CONSTITUTION GROUP -->
<div class="ability-group-box">
<div class="ag-score-badge">
<span class="ag-title">CONSTITUTION</span>
<span class="ag-mod">{{modText properties.abilities.constitution}}</span>
<span class="ag-score">{{val properties.abilities.constitution}}</span>
</div>
<ul class="ag-sublist">
<li class="ag-subitem">
<span class="dot {{#if (hasSave properties.saving_throws 'con')}}filled{{/if}}"></span>
<span class="ag-bonus">{{saveBonus properties 'constitution'}}</span>
<span class="ag-name">SAVING THROWS</span>
</li>
</ul>
</div>
<!-- INTELLIGENCE GROUP -->
<div class="ability-group-box">
<div class="ag-score-badge">
<span class="ag-title">INTELLIGENCE</span>
<span class="ag-mod">{{modText properties.abilities.intelligence}}</span>
<span class="ag-score">{{val properties.abilities.intelligence}}</span>
</div>
<ul class="ag-sublist">
<li class="ag-subitem">
<span class="dot {{#if (hasSave properties.saving_throws 'int')}}filled{{/if}}"></span>
<span class="ag-bonus">{{saveBonus properties 'intelligence'}}</span>
<span class="ag-name">SAVING THROWS</span>
</li>
<li class="ag-subitem">
<span class="dot {{#if (isSkillProf properties.skills 'arcana')}}filled{{/if}}"></span>
<span class="ag-bonus">{{skillBonus properties 'arcana' 'intelligence'}}</span>
<span class="ag-name">Arcana</span>
</li>
<li class="ag-subitem">
<span class="dot {{#if (isSkillProf properties.skills 'history')}}filled{{/if}}"></span>
<span class="ag-bonus">{{skillBonus properties 'history' 'intelligence'}}</span>
<span class="ag-name">History</span>
</li>
<li class="ag-subitem">
<span class="dot {{#if (isSkillProf properties.skills 'investigation')}}filled{{/if}}"></span>
<span class="ag-bonus">{{skillBonus properties 'investigation' 'intelligence'}}</span>
<span class="ag-name">Investigation</span>
</li>
<li class="ag-subitem">
<span class="dot {{#if (isSkillProf properties.skills 'nature')}}filled{{/if}}"></span>
<span class="ag-bonus">{{skillBonus properties 'nature' 'intelligence'}}</span>
<span class="ag-name">Nature</span>
</li>
<li class="ag-subitem">
<span class="dot {{#if (isSkillProf properties.skills 'religion')}}filled{{/if}}"></span>
<span class="ag-bonus">{{skillBonus properties 'religion' 'intelligence'}}</span>
<span class="ag-name">Religion</span>
</li>
</ul>
</div>
<!-- WISDOM GROUP -->
<div class="ability-group-box">
<div class="ag-score-badge">
<span class="ag-title">WISDOM</span>
<span class="ag-mod">{{modText properties.abilities.wisdom}}</span>
<span class="ag-score">{{val properties.abilities.wisdom}}</span>
</div>
<ul class="ag-sublist">
<li class="ag-subitem">
<span class="dot {{#if (hasSave properties.saving_throws 'wis')}}filled{{/if}}"></span>
<span class="ag-bonus">{{saveBonus properties 'wisdom'}}</span>
<span class="ag-name">SAVING THROWS</span>
</li>
<li class="ag-subitem">
<span class="dot {{#if (isSkillProf properties.skills 'animal_handling')}}filled{{/if}}"></span>
<span class="ag-bonus">{{skillBonus properties 'animal_handling' 'wisdom'}}</span>
<span class="ag-name">Animal Handling</span>
</li>
<li class="ag-subitem">
<span class="dot {{#if (isSkillProf properties.skills 'insight')}}filled{{/if}}"></span>
<span class="ag-bonus">{{skillBonus properties 'insight' 'wisdom'}}</span>
<span class="ag-name">Insight</span>
</li>
<li class="ag-subitem">
<span class="dot {{#if (isSkillProf properties.skills 'medicine')}}filled{{/if}}"></span>
<span class="ag-bonus">{{skillBonus properties 'medicine' 'wisdom'}}</span>
<span class="ag-name">Medicine</span>
</li>
<li class="ag-subitem">
<span class="dot {{#if (isSkillProf properties.skills 'perception')}}filled{{/if}}"></span>
<span class="ag-bonus">{{skillBonus properties 'perception' 'wisdom'}}</span>
<span class="ag-name">Perception</span>
</li>
<li class="ag-subitem">
<span class="dot {{#if (isSkillProf properties.skills 'survival')}}filled{{/if}}"></span>
<span class="ag-bonus">{{skillBonus properties 'survival' 'wisdom'}}</span>
<span class="ag-name">Survival</span>
</li>
</ul>
</div>
<!-- CHARISMA GROUP -->
<div class="ability-group-box">
<div class="ag-score-badge">
<span class="ag-title">CHARISMA</span>
<span class="ag-mod">{{modText properties.abilities.charisma}}</span>
<span class="ag-score">{{val properties.abilities.charisma}}</span>
</div>
<ul class="ag-sublist">
<li class="ag-subitem">
<span class="dot {{#if (hasSave properties.saving_throws 'cha')}}filled{{/if}}"></span>
<span class="ag-bonus">{{saveBonus properties 'charisma'}}</span>
<span class="ag-name">SAVING THROWS</span>
</li>
<li class="ag-subitem">
<span class="dot {{#if (isSkillProf properties.skills 'deception')}}filled{{/if}}"></span>
<span class="ag-bonus">{{skillBonus properties 'deception' 'charisma'}}</span>
<span class="ag-name">Deception</span>
</li>
<li class="ag-subitem">
<span class="dot {{#if (isSkillProf properties.skills 'intimidation')}}filled{{/if}}"></span>
<span class="ag-bonus">{{skillBonus properties 'intimidation' 'charisma'}}</span>
<span class="ag-name">Intimidation</span>
</li>
<li class="ag-subitem">
<span class="dot {{#if (isSkillProf properties.skills 'performance')}}filled{{/if}}"></span>
<span class="ag-bonus">{{skillBonus properties 'performance' 'charisma'}}</span>
<span class="ag-name">Performance</span>
</li>
<li class="ag-subitem">
<span class="dot {{#if (isSkillProf properties.skills 'persuasion')}}filled{{/if}}"></span>
<span class="ag-bonus">{{skillBonus properties 'persuasion' 'charisma'}}</span>
<span class="ag-name">Persuasion</span>
</li>
</ul>
</div>
<!-- Passive Perception Box -->
<div class="passive-perception-box">
<span class="passive-score">{{passivePerception properties}}</span>
<span class="passive-label">PASSIVE WISDOM (PERCEPTION)</span>
</div>
</section>
<!-- ================= MIDDLE COLUMN ================= -->
<section class="sheet-col col-middle">
<!-- Combat Top Row -->
<div class="combat-top-row">
<div class="combat-badge badge-ac">
<span class="badge-value">{{val properties.armor_class}}</span>
<span class="badge-label">ARMOR CLASS</span>
</div>
<div class="combat-badge badge-init">
<span class="badge-value">{{#if properties.initiative}}{{modSign properties.initiative}}{{else}}{{modText properties.abilities.dexterity}}{{/if}}</span>
<span class="badge-label">INITIATIVE</span>
</div>
<div class="combat-badge badge-speed">
<span class="badge-value">{{#if properties.speed}}{{properties.speed}} ft.{{else}}30 ft.{{/if}}</span>
<span class="badge-label">SPEED</span>
</div>
</div>
<!-- HP Box -->
<div class="section-box hp-box">
<div class="hp-max-line">
Hit Point Maximum: <strong>{{properties.hit_points.max}}</strong>
</div>
<div class="hp-current-val">{{properties.hit_points.current}}</div>
<div class="box-bottom-label">CURRENT HIT POINTS</div>
</div>
<!-- Temp HP -->
<div class="section-box temp-hp-box">
<div class="temp-hp-val">{{#if properties.hit_points.temp}}{{properties.hit_points.temp}}{{else}}--{{/if}}</div>
<div class="box-bottom-label">TEMPORARY HIT POINTS</div>
</div>
<!-- Hit Dice & Death Saves -->
<div class="hd-and-death-row">
<div class="section-box half-box hd-box">
<div class="hd-total-line">Total: {{#if properties.hit_dice.total}}{{properties.hit_dice.total}}{{else}}{{properties.level}}{{/if}}</div>
<div class="hd-current-val">
{{#if properties.hit_dice.dice}}{{properties.hit_dice.dice}}{{else if properties.hit_points.hit_dice}}{{properties.hit_points.hit_dice}}{{else}}1d10{{/if}}
</div>
<div class="box-bottom-label">HIT DICE</div>
</div>
<div class="section-box half-box death-saves-box">
<div class="death-save-line">
<span class="ds-title">SUCCESSES</span>
<span class="ds-bubbles">
<span class="bubble {{#if (gte properties.death_saves.successes 1)}}filled{{/if}}"></span>
<span class="bubble {{#if (gte properties.death_saves.successes 2)}}filled{{/if}}"></span>
<span class="bubble {{#if (gte properties.death_saves.successes 3)}}filled{{/if}}"></span>
</span>
</div>
<div class="death-save-line">
<span class="ds-title">FAILURES</span>
<span class="ds-bubbles">
<span class="bubble {{#if (gte properties.death_saves.failures 1)}}filled{{/if}}"></span>
<span class="bubble {{#if (gte properties.death_saves.failures 2)}}filled{{/if}}"></span>
<span class="bubble {{#if (gte properties.death_saves.failures 3)}}filled{{/if}}"></span>
</span>
</div>
<div class="box-bottom-label">DEATH SAVES</div>
</div>
</div>
<!-- Attacks & Spellcasting -->
<div class="section-box attacks-box">
<table class="attacks-table">
<thead>
<tr>
<th>NAME</th>
<th>ATK BONUS</th>
<th>DAMAGE/TYPE</th>
</tr>
</thead>
<tbody>
{{#if properties.attacks}}
{{#each properties.attacks}}
<tr>
<td class="atk-name">{{this.name}}</td>
<td class="atk-bonus">{{#if this.atk_bonus}}{{this.atk_bonus}}{{else}}+0{{/if}}</td>
<td class="atk-damage">{{this.damage}} {{this.damage_type}}</td>
</tr>
{{/each}}
{{else if properties.actions}}
{{#each properties.actions}}
<tr>
<td class="atk-name">{{this.name}}</td>
<td class="atk-bonus">+5</td>
<td class="atk-damage">{{this.desc}}</td>
</tr>
{{/each}}
{{else}}
<tr><td colspan="3" class="empty-hint">No attacks equipped</td></tr>
{{/if}}
</tbody>
</table>
{{#if properties.actions}}
<div class="attack-notes">
{{#each properties.actions}}
<p class="attack-note-item"><em><strong>{{this.name}}:</strong></em> {{formatText this.desc}}</p>
{{/each}}
</div>
{{/if}}
<div class="box-bottom-label">ATTACKS &amp; SPELLCASTING</div>
</div>
</section>
<!-- ================= RIGHT COLUMN ================= -->
<section class="sheet-col col-right">
<!-- Personality Traits -->
<div class="section-box roleplay-box">
<div class="box-content">{{#if properties.personality_traits}}<p>{{formatText properties.personality_traits}}</p>{{else}}<span class="empty-hint">None</span>{{/if}}</div>
<div class="box-bottom-label">PERSONALITY TRAITS</div>
</div>
<!-- Ideals -->
<div class="section-box roleplay-box">
<div class="box-content">{{#if properties.ideals}}<p>{{formatText properties.ideals}}</p>{{else}}<span class="empty-hint">None</span>{{/if}}</div>
<div class="box-bottom-label">IDEALS</div>
</div>
<!-- Bonds -->
<div class="section-box roleplay-box">
<div class="box-content">{{#if properties.bonds}}<p>{{formatText properties.bonds}}</p>{{else}}<span class="empty-hint">None</span>{{/if}}</div>
<div class="box-bottom-label">BONDS</div>
</div>
<!-- Flaws -->
<div class="section-box roleplay-box">
<div class="box-content">{{#if properties.flaws}}<p>{{formatText properties.flaws}}</p>{{else}}<span class="empty-hint">None</span>{{/if}}</div>
<div class="box-bottom-label">FLAWS</div>
</div>
<!-- Features & Traits -->
<div class="section-box features-box">
<div class="features-content">
{{#if properties.features_and_traits}}
{{#each properties.features_and_traits}}
<div class="feature-item">
<strong class="feature-name">{{this.name}}</strong>
<p class="feature-desc">{{formatText this.desc}}</p>
</div>
{{/each}}
{{else if properties.traits}}
{{#each properties.traits}}
<div class="feature-item">
<strong class="feature-name">{{this.name}}</strong>
<p class="feature-desc">{{formatText this.desc}}</p>
</div>
{{/each}}
{{/if}}
</div>
<div class="box-bottom-label">FEATURES &amp; TRAITS</div>
</div>
</section>
</main>
<!-- Bottom Wide Row: Proficiencies & Equipment/Notes -->
<div class="sheet-bottom-row">
<!-- Other Proficiencies & Languages -->
<div class="section-box proficiencies-box">
<div class="prof-block-content">
{{#if properties.other_proficiencies_and_languages.armor}}
<div class="prof-line"><strong>Armor:</strong> {{join properties.other_proficiencies_and_languages.armor}}</div>
{{/if}}
{{#if properties.other_proficiencies_and_languages.weapons}}
<div class="prof-line"><strong>Weapons:</strong> {{join properties.other_proficiencies_and_languages.weapons}}</div>
{{/if}}
{{#if properties.other_proficiencies_and_languages.tools}}
<div class="prof-line"><strong>Tools:</strong> {{join properties.other_proficiencies_and_languages.tools}}</div>
{{/if}}
{{#if properties.languages}}
<div class="prof-line"><strong>Languages:</strong> {{join properties.languages}}</div>
{{else if properties.other_proficiencies_and_languages.languages}}
<div class="prof-line"><strong>Languages:</strong> {{join properties.other_proficiencies_and_languages.languages}}</div>
{{/if}}
</div>
<div class="box-bottom-label">OTHER PROFICIENCIES &amp; LANGUAGES</div>
</div>
<!-- Equipment & Notes -->
<div class="section-box equipment-box">
<div class="equipment-layout">
<div class="coins-stack">
<div class="coin-row"><span class="coin-tag">CP</span><span class="coin-val">{{#if properties.currency.cp}}{{properties.currency.cp}}{{else}}0{{/if}}</span></div>
<div class="coin-row"><span class="coin-tag">SP</span><span class="coin-val">{{#if properties.currency.sp}}{{properties.currency.sp}}{{else}}0{{/if}}</span></div>
<div class="coin-row"><span class="coin-tag">EP</span><span class="coin-val">{{#if properties.currency.ep}}{{properties.currency.ep}}{{else}}0{{/if}}</span></div>
<div class="coin-row"><span class="coin-tag">GP</span><span class="coin-val">{{#if properties.currency.gp}}{{properties.currency.gp}}{{else}}0{{/if}}</span></div>
<div class="coin-row"><span class="coin-tag">PP</span><span class="coin-val">{{#if properties.currency.pp}}{{properties.currency.pp}}{{else}}0{{/if}}</span></div>
</div>
<div class="items-list-container">
<ul class="items-list">
{{#each properties.inventory}}
<li class="item-row">
<span class="item-name">{{this.name}}{{#if this.equipped}} <small class="equipped-tag">(E)</small>{{/if}}</span>
<span class="item-meta">x{{this.quantity}}{{#if this.weight}} &bull; {{this.weight}} lb{{/if}}</span>
</li>
{{/each}}
</ul>
</div>
</div>
<div class="box-bottom-label">EQUIPMENT &amp; CHARACTER NOTES</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,221 @@
/* D&D 5e Spellcasting Sheet Styles */
:root {
--border-color: #2b2b2b;
--accent-red: #922610;
--bg-page: #eaedf0;
--bg-sheet: #ffffff;
--bg-box: #f9fbfd;
--text-main: #1e1e1e;
--text-muted: #666666;
--font-sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
--font-serif: "Libre Baskerville", "Georgia", serif;
}
body.sheet-page-wrapper {
margin: 0;
padding: 24px;
background-color: var(--bg-page);
font-family: var(--font-sans);
color: var(--text-main);
display: flex;
justify-content: center;
box-sizing: border-box;
}
.spellcasting-sheet-container {
width: 100%;
max-width: 1050px;
background: var(--bg-sheet);
border: 2px solid var(--border-color);
border-radius: 6px;
padding: 20px 24px 24px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
box-sizing: border-box;
}
/* Header */
.spell-header {
display: grid;
grid-template-columns: 320px 1fr;
gap: 16px;
border-bottom: 2px solid var(--border-color);
padding-bottom: 16px;
margin-bottom: 20px;
}
.spell-header-class-box {
border: 1.5px solid var(--border-color);
border-radius: 4px;
padding: 12px 16px;
display: flex;
flex-direction: column;
justify-content: flex-end;
background: var(--bg-box);
}
.spell-class-name {
font-family: var(--font-serif);
font-size: 26px;
color: var(--accent-red);
margin: 0 0 6px 0;
line-height: 1.1;
}
.spell-header-label {
font-size: 9px;
font-weight: bold;
color: var(--text-muted);
letter-spacing: 0.5px;
}
.spell-header-stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
border: 1.5px solid var(--border-color);
border-radius: 4px;
padding: 10px 14px;
background: var(--bg-box);
}
.spell-stat-cell {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-right: 1px solid #ccc;
}
.spell-stat-cell:last-child {
border-right: none;
}
.spell-stat-val {
font-size: 20px;
font-weight: bold;
color: var(--text-main);
margin-bottom: 4px;
}
/* 3-Column Layout */
.spells-main-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
.spells-column {
display: flex;
flex-direction: column;
gap: 14px;
}
.spell-level-box {
border: 1.5px solid var(--border-color);
border-radius: 6px;
background: #ffffff;
overflow: hidden;
}
.spell-level-header {
background: var(--bg-box);
border-bottom: 1.5px solid var(--border-color);
padding: 6px 10px;
display: flex;
align-items: center;
gap: 8px;
}
.level-num {
font-size: 16px;
font-weight: bold;
color: #ffffff;
background: var(--accent-red);
border-radius: 50%;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.level-title {
font-size: 11px;
font-weight: bold;
color: var(--text-main);
letter-spacing: 0.5px;
}
.spell-level-header.has-slots {
justify-content: space-between;
}
.slot-counters {
display: flex;
gap: 10px;
font-size: 10.5px;
color: #555;
}
.slot-field strong {
color: var(--text-main);
}
.spell-items-list {
list-style: none;
margin: 0;
padding: 6px 10px;
min-height: 80px;
}
.spell-item-row {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 0;
border-bottom: 1px solid #f0f0f0;
font-size: 12px;
}
.spell-item-row:last-child {
border-bottom: none;
}
.spell-prep-circle {
width: 10px;
height: 10px;
border: 1.2px solid var(--border-color);
border-radius: 50%;
display: inline-block;
flex-shrink: 0;
}
.spell-prep-circle.prepared {
background: var(--accent-red);
border-color: var(--accent-red);
}
.spell-prep-circle.invisible {
opacity: 0;
}
.spell-name {
color: var(--text-main);
}
.spell-item-row.empty {
color: #bbb;
justify-content: center;
}
/* Responsive */
@media (max-width: 900px) {
.spell-header {
grid-template-columns: 1fr;
}
.spells-main-grid {
grid-template-columns: 1fr;
}
}

View File

@@ -0,0 +1,277 @@
<!DOCTYPE html>
<html lang="en">
<head>
<base href="/rulesets/open5e/templates/">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{display_name}} - Spellcasting Sheet</title>
<link rel="stylesheet" href="spellcasting_sheet.css">
</head>
<body class="sheet-page-wrapper">
<div class="spellcasting-sheet-container">
<!-- Top Spellcasting Header -->
<header class="spell-header">
<div class="spell-header-class-box">
<h1 class="spell-class-name">
{{#if properties.spellcasting.class}}
{{properties.spellcasting.class}}
{{else if properties.class}}
{{properties.class}}
{{else}}
Spellcaster
{{/if}}
</h1>
<label class="spell-header-label">SPELLCASTING CLASS</label>
</div>
<div class="spell-header-stats-grid">
<div class="spell-stat-cell">
<span class="spell-stat-val">
{{#if properties.spellcasting.ability}}
{{properties.spellcasting.ability}}
{{else}}
WISDOM
{{/if}}
</span>
<label class="spell-header-label">SPELLCASTING ABILITY</label>
</div>
<div class="spell-stat-cell">
<span class="spell-stat-val">
{{#if properties.spellcasting.save_dc}}
{{properties.spellcasting.save_dc}}
{{else}}
13
{{/if}}
</span>
<label class="spell-header-label">SPELL SAVE DC</label>
</div>
<div class="spell-stat-cell">
<span class="spell-stat-val">
{{#if properties.spellcasting.attack_bonus}}
{{modSign properties.spellcasting.attack_bonus}}
{{else}}
+5
{{/if}}
</span>
<label class="spell-header-label">SPELL ATTACK BONUS</label>
</div>
</div>
</header>
<!-- 3-Column Spells Grid -->
<main class="spells-main-grid">
<!-- ================= COLUMN 1: Cantrips, Level 1, Level 2 ================= -->
<section class="spells-column">
<!-- Cantrips (Level 0) -->
<div class="spell-level-box">
<div class="spell-level-header">
<span class="level-num">0</span>
<span class="level-title">CANTRIPS</span>
</div>
<ul class="spell-items-list">
{{#if properties.spellcasting.cantrips}}
{{#each properties.spellcasting.cantrips}}
<li class="spell-item-row">
<span class="spell-prep-circle invisible"></span>
<span class="spell-name">{{#if this.name}}{{this.name}}{{else}}{{this}}{{/if}}</span>
</li>
{{/each}}
{{else}}
<li class="spell-item-row empty"><span class="spell-name">&mdash;</span></li>
{{/if}}
</ul>
</div>
<!-- Level 1 Spells -->
<div class="spell-level-box">
<div class="spell-level-header has-slots">
<span class="level-num">1</span>
<div class="slot-counters">
<span class="slot-field">Total: <strong>{{slotTotal ../properties.spellcasting.slots 1}}</strong></span>
<span class="slot-field">Expended: <strong>{{slotExpended ../properties.spellcasting.slots 1}}</strong></span>
</div>
</div>
<ul class="spell-items-list">
{{#each (filterSpells properties.spellcasting.spells 1)}}
<li class="spell-item-row">
<span class="spell-prep-circle {{#if this.prepared}}prepared{{/if}}"></span>
<span class="spell-name">{{this.name}}</span>
</li>
{{/each}}
</ul>
</div>
<!-- Level 2 Spells -->
<div class="spell-level-box">
<div class="spell-level-header has-slots">
<span class="level-num">2</span>
<div class="slot-counters">
<span class="slot-field">Total: <strong>{{slotTotal ../properties.spellcasting.slots 2}}</strong></span>
<span class="slot-field">Expended: <strong>{{slotExpended ../properties.spellcasting.slots 2}}</strong></span>
</div>
</div>
<ul class="spell-items-list">
{{#each (filterSpells properties.spellcasting.spells 2)}}
<li class="spell-item-row">
<span class="spell-prep-circle {{#if this.prepared}}prepared{{/if}}"></span>
<span class="spell-name">{{this.name}}</span>
</li>
{{/each}}
</ul>
</div>
</section>
<!-- ================= COLUMN 2: Level 3, Level 4, Level 5 ================= -->
<section class="spells-column">
<!-- Level 3 Spells -->
<div class="spell-level-box">
<div class="spell-level-header has-slots">
<span class="level-num">3</span>
<div class="slot-counters">
<span class="slot-field">Total: <strong>{{slotTotal ../properties.spellcasting.slots 3}}</strong></span>
<span class="slot-field">Expended: <strong>{{slotExpended ../properties.spellcasting.slots 3}}</strong></span>
</div>
</div>
<ul class="spell-items-list">
{{#each (filterSpells properties.spellcasting.spells 3)}}
<li class="spell-item-row">
<span class="spell-prep-circle {{#if this.prepared}}prepared{{/if}}"></span>
<span class="spell-name">{{this.name}}</span>
</li>
{{/each}}
</ul>
</div>
<!-- Level 4 Spells -->
<div class="spell-level-box">
<div class="spell-level-header has-slots">
<span class="level-num">4</span>
<div class="slot-counters">
<span class="slot-field">Total: <strong>{{slotTotal ../properties.spellcasting.slots 4}}</strong></span>
<span class="slot-field">Expended: <strong>{{slotExpended ../properties.spellcasting.slots 4}}</strong></span>
</div>
</div>
<ul class="spell-items-list">
{{#each (filterSpells properties.spellcasting.spells 4)}}
<li class="spell-item-row">
<span class="spell-prep-circle {{#if this.prepared}}prepared{{/if}}"></span>
<span class="spell-name">{{this.name}}</span>
</li>
{{/each}}
</ul>
</div>
<!-- Level 5 Spells -->
<div class="spell-level-box">
<div class="spell-level-header has-slots">
<span class="level-num">5</span>
<div class="slot-counters">
<span class="slot-field">Total: <strong>{{slotTotal ../properties.spellcasting.slots 5}}</strong></span>
<span class="slot-field">Expended: <strong>{{slotExpended ../properties.spellcasting.slots 5}}</strong></span>
</div>
</div>
<ul class="spell-items-list">
{{#each (filterSpells properties.spellcasting.spells 5)}}
<li class="spell-item-row">
<span class="spell-prep-circle {{#if this.prepared}}prepared{{/if}}"></span>
<span class="spell-name">{{this.name}}</span>
</li>
{{/each}}
</ul>
</div>
</section>
<!-- ================= COLUMN 3: Level 6, 7, 8, 9 ================= -->
<section class="spells-column">
<!-- Level 6 Spells -->
<div class="spell-level-box">
<div class="spell-level-header has-slots">
<span class="level-num">6</span>
<div class="slot-counters">
<span class="slot-field">Total: <strong>{{slotTotal ../properties.spellcasting.slots 6}}</strong></span>
<span class="slot-field">Expended: <strong>{{slotExpended ../properties.spellcasting.slots 6}}</strong></span>
</div>
</div>
<ul class="spell-items-list">
{{#each (filterSpells properties.spellcasting.spells 6)}}
<li class="spell-item-row">
<span class="spell-prep-circle {{#if this.prepared}}prepared{{/if}}"></span>
<span class="spell-name">{{this.name}}</span>
</li>
{{/each}}
</ul>
</div>
<!-- Level 7 Spells -->
<div class="spell-level-box">
<div class="spell-level-header has-slots">
<span class="level-num">7</span>
<div class="slot-counters">
<span class="slot-field">Total: <strong>{{slotTotal ../properties.spellcasting.slots 7}}</strong></span>
<span class="slot-field">Expended: <strong>{{slotExpended ../properties.spellcasting.slots 7}}</strong></span>
</div>
</div>
<ul class="spell-items-list">
{{#each (filterSpells properties.spellcasting.spells 7)}}
<li class="spell-item-row">
<span class="spell-prep-circle {{#if this.prepared}}prepared{{/if}}"></span>
<span class="spell-name">{{this.name}}</span>
</li>
{{/each}}
</ul>
</div>
<!-- Level 8 Spells -->
<div class="spell-level-box">
<div class="spell-level-header has-slots">
<span class="level-num">8</span>
<div class="slot-counters">
<span class="slot-field">Total: <strong>{{slotTotal ../properties.spellcasting.slots 8}}</strong></span>
<span class="slot-field">Expended: <strong>{{slotExpended ../properties.spellcasting.slots 8}}</strong></span>
</div>
</div>
<ul class="spell-items-list">
{{#each (filterSpells properties.spellcasting.spells 8)}}
<li class="spell-item-row">
<span class="spell-prep-circle {{#if this.prepared}}prepared{{/if}}"></span>
<span class="spell-name">{{this.name}}</span>
</li>
{{/each}}
</ul>
</div>
<!-- Level 9 Spells -->
<div class="spell-level-box">
<div class="spell-level-header has-slots">
<span class="level-num">9</span>
<div class="slot-counters">
<span class="slot-field">Total: <strong>{{slotTotal ../properties.spellcasting.slots 9}}</strong></span>
<span class="slot-field">Expended: <strong>{{slotExpended ../properties.spellcasting.slots 9}}</strong></span>
</div>
</div>
<ul class="spell-items-list">
{{#each (filterSpells properties.spellcasting.spells 9)}}
<li class="spell-item-row">
<span class="spell-prep-circle {{#if this.prepared}}prepared{{/if}}"></span>
<span class="spell-name">{{this.name}}</span>
</li>
{{/each}}
</ul>
</div>
</section>
</main>
</div>
</body>
</html>

View File

@@ -0,0 +1,142 @@
.stat-block-wrapper {
margin: 0;
padding: 16px;
background-color: #f0f2f5;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
color: #222;
display: flex;
justify-content: center;
}
.stat-block {
width: 100%;
max-width: 650px;
background: #ffffff;
padding: 24px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
border-radius: 4px;
border-top: 6px solid #922610;
box-sizing: border-box;
}
.sb-header {
margin-bottom: 8px;
}
.sb-name {
font-family: "Libre Baskerville", "Georgia", serif;
font-size: 28px;
font-weight: bold;
color: #922610;
margin: 0 0 4px 0;
line-height: 1.15;
}
.sb-type {
font-style: italic;
font-size: 14px;
color: #555;
margin-bottom: 6px;
}
.red-bar {
height: 3px;
background-color: #922610;
margin: 10px 0;
border-radius: 1px;
}
.sb-vitals, .sb-meta, .sb-traits {
font-size: 14px;
line-height: 1.45;
color: #333;
}
.vital-row, .meta-row {
margin: 4px 0;
}
.sb-abilities {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 4px;
text-align: center;
padding: 6px 0;
}
.sb-ability {
display: flex;
flex-direction: column;
}
.ab-label {
font-weight: bold;
font-size: 12px;
color: #922610;
}
.ab-val {
font-size: 13.5px;
margin-top: 2px;
color: #222;
}
.sb-traits {
margin-top: 10px;
}
.trait-block, .action-block {
font-size: 14px;
line-height: 1.45;
margin: 8px 0;
padding-bottom: 8px;
border-bottom: 1px solid #eee;
text-align: justify;
}
.trait-block:last-child, .action-block:last-child {
border-bottom: none;
padding-bottom: 0;
}
.sb-section {
margin-top: 16px;
}
.section-title {
font-family: "Libre Baskerville", "Georgia", serif;
font-size: 20px;
font-weight: bold;
color: #922610;
margin: 0 0 4px 0;
}
.section-divider {
height: 1.5px;
background-color: #922610;
margin-bottom: 8px;
}
.legendary-preamble {
font-size: 13.5px;
font-style: italic;
line-height: 1.4;
margin: 6px 0 10px 0;
}
/* Responsive adjustments for phones */
@media (max-width: 480px) {
.stat-block-wrapper {
padding: 8px;
}
.stat-block {
padding: 14px;
}
.sb-name {
font-size: 24px;
}
.ab-val {
font-size: 12px;
}
}

View File

@@ -0,0 +1,234 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{display_name}} - Stat Block</title>
<link rel="stylesheet" href="stat_block.css">
</head>
<body class="stat-block-wrapper">
<div class="stat-block">
<!-- Header -->
<header class="sb-header">
<h1 class="sb-name">{{display_name}}</h1>
<div class="sb-type">
{{#if properties.size}}{{properties.size}}{{else}}Medium{{/if}}
{{#if properties.type}}{{properties.type}}{{else}}Humanoid{{/if}}{{#if properties.subtype}} ({{properties.subtype}}){{/if}},
{{#if properties.alignment}}{{properties.alignment}}{{else}}unaligned{{/if}}
</div>
</header>
<div class="red-bar"></div>
<!-- Core Vitals -->
<div class="sb-vitals">
<div class="vital-row">
<strong>Armor Class</strong> {{val properties.armor_class}}{{#if properties.armor_description}} ({{properties.armor_description}}){{/if}}
</div>
<div class="vital-row">
<strong>Hit Points</strong>
{{#if properties.hit_points.current}}
{{properties.hit_points.current}}
{{else if properties.hit_points.max}}
{{properties.hit_points.max}}
{{else}}
{{val properties.hit_points}}
{{/if}}
{{#if properties.hit_points.formula}}
({{properties.hit_points.formula}})
{{else if properties.hit_points.hit_dice}}
({{properties.hit_points.hit_dice}})
{{/if}}
</div>
<div class="vital-row">
<strong>Speed</strong>
{{#if properties.speed_desc}}
{{properties.speed_desc}}
{{else if properties.speed}}
{{properties.speed}} ft.
{{/if}}
</div>
</div>
<div class="red-bar"></div>
<!-- Ability Scores Table -->
<div class="sb-abilities">
<div class="sb-ability">
<span class="ab-label">STR</span>
<span class="ab-val">{{val properties.abilities.strength}} ({{modText properties.abilities.strength}})</span>
</div>
<div class="sb-ability">
<span class="ab-label">DEX</span>
<span class="ab-val">{{val properties.abilities.dexterity}} ({{modText properties.abilities.dexterity}})</span>
</div>
<div class="sb-ability">
<span class="ab-label">CON</span>
<span class="ab-val">{{val properties.abilities.constitution}} ({{modText properties.abilities.constitution}})</span>
</div>
<div class="sb-ability">
<span class="ab-label">INT</span>
<span class="ab-val">{{val properties.abilities.intelligence}} ({{modText properties.abilities.intelligence}})</span>
</div>
<div class="sb-ability">
<span class="ab-label">WIS</span>
<span class="ab-val">{{val properties.abilities.wisdom}} ({{modText properties.abilities.wisdom}})</span>
</div>
<div class="sb-ability">
<span class="ab-label">CHA</span>
<span class="ab-val">{{val properties.abilities.charisma}} ({{modText properties.abilities.charisma}})</span>
</div>
</div>
<div class="red-bar"></div>
<!-- Optional Monster Meta -->
<div class="sb-meta">
{{#if properties.saving_throws}}
<div class="meta-row">
<strong>Saving Throws</strong> {{join properties.saving_throws}}
</div>
{{/if}}
{{#if properties.skills}}
<div class="meta-row">
<strong>Skills</strong> {{join properties.skills}}
</div>
{{/if}}
{{#if properties.damage_vulnerabilities}}
<div class="meta-row">
<strong>Damage Vulnerabilities</strong> {{join properties.damage_vulnerabilities}}
</div>
{{/if}}
{{#if properties.damage_resistances}}
<div class="meta-row">
<strong>Damage Resistances</strong> {{join properties.damage_resistances}}
</div>
{{/if}}
{{#if properties.damage_immunities}}
<div class="meta-row">
<strong>Damage Immunities</strong> {{join properties.damage_immunities}}
</div>
{{/if}}
{{#if properties.condition_immunities}}
<div class="meta-row">
<strong>Condition Immunities</strong> {{join properties.condition_immunities}}
</div>
{{/if}}
{{#if properties.senses}}
<div class="meta-row">
<strong>Senses</strong> {{properties.senses}}
</div>
{{/if}}
{{#if properties.languages}}
<div class="meta-row">
<strong>Languages</strong> {{join properties.languages}}
</div>
{{/if}}
{{#if properties.challenge_rating}}
<div class="meta-row">
<strong>Challenge</strong> {{properties.challenge_rating}}
</div>
{{else if properties.cr}}
<div class="meta-row">
<strong>Challenge</strong> {{properties.cr}}{{#if properties.xp}} ({{properties.xp}} XP){{/if}}
</div>
{{/if}}
{{#if properties.proficiency_bonus}}
<div class="meta-row">
<strong>Proficiency Bonus</strong> +{{properties.proficiency_bonus}}
</div>
{{else if (calcProf properties.level properties.cr)}}
<div class="meta-row">
<strong>Proficiency Bonus</strong> +{{calcProf properties.level properties.cr}}
</div>
{{/if}}
</div>
<!-- Special Traits / Features -->
{{#if properties.traits}}
<div class="sb-traits">
{{#each properties.traits}}
<div class="trait-block">
<em><strong>{{this.name}}:</strong></em> {{formatText this.desc}}
</div>
{{/each}}
</div>
{{/if}}
<!-- Actions Section -->
{{#if properties.actions}}
<section class="sb-section">
<h2 class="section-title">Actions</h2>
<div class="section-divider"></div>
{{#each properties.actions}}
<div class="action-block">
<em><strong>{{this.name}}:</strong></em> {{formatText this.desc}}
</div>
{{/each}}
</section>
{{/if}}
<!-- Bonus Actions Section -->
{{#if properties.bonus_actions}}
<section class="sb-section">
<h2 class="section-title">Bonus Actions</h2>
<div class="section-divider"></div>
{{#each properties.bonus_actions}}
<div class="action-block">
<em><strong>{{this.name}}:</strong></em> {{formatText this.desc}}
</div>
{{/each}}
</section>
{{/if}}
<!-- Reactions Section -->
{{#if properties.reactions}}
<section class="sb-section">
<h2 class="section-title">Reactions</h2>
<div class="section-divider"></div>
{{#each properties.reactions}}
<div class="action-block">
<em><strong>{{this.name}}:</strong></em> {{formatText this.desc}}
</div>
{{/each}}
</section>
{{/if}}
<!-- Legendary Actions Section -->
{{#if properties.legendary_actions}}
<section class="sb-section">
<h2 class="section-title">Legendary Actions</h2>
<div class="section-divider"></div>
{{#if properties.legendary_actions.description}}
<p class="legendary-preamble">{{formatText properties.legendary_actions.description}}</p>
{{else if properties.legendariesDescription}}
<p class="legendary-preamble">{{formatText properties.legendariesDescription}}</p>
{{/if}}
{{#if properties.legendary_actions.actions}}
{{#each properties.legendary_actions.actions}}
<div class="action-block">
<em><strong>{{this.name}}:</strong></em> {{formatText this.desc}}
</div>
{{/each}}
{{else if properties.legendaries}}
{{#each properties.legendaries}}
<div class="action-block">
<em><strong>{{this.name}}:</strong></em> {{formatText this.desc}}
</div>
{{/each}}
{{/if}}
</section>
{{/if}}
</div>
</body>
</html>

View File

@@ -0,0 +1,19 @@
{
"$schema": "../../../schema/entity-definition.json",
"id": "armor",
"name": "Armor",
"description": "SR6 Wearable armor and protective gear providing Defense Rating bonuses.",
"ruleset_id": "sr6",
"version": "1.0.0",
"schema": {
"type": "object",
"required": ["name", "rating"],
"properties": {
"name": { "type": "string" },
"rating": { "type": ["integer", "string"], "description": "Armor rating bonus added to Defense Rating (e.g. 2, 4)." },
"social_modifier": { "type": "integer", "default": 0 },
"capacity": { "type": "integer", "default": 0 },
"notes": { "type": "string" }
}
}
}

View File

@@ -0,0 +1,21 @@
{
"$schema": "../../../schema/entity-definition.json",
"id": "augmentation",
"name": "Augmentation",
"description": "SR6 Cyberware and Bioware implants with essence cost, grade, and enhancements.",
"ruleset_id": "sr6",
"version": "1.0.0",
"schema": {
"type": "object",
"required": ["name", "essence"],
"properties": {
"name": { "type": "string" },
"category": { "type": "string", "enum": ["Cyberware", "Bioware", "Nanoware", "Geneware"], "default": "Cyberware" },
"grade": { "type": "string", "enum": ["Standard", "Alphaware", "Betaware", "Deltaware", "Used"], "default": "Standard" },
"rating": { "type": ["integer", "string"] },
"essence": { "type": "number", "description": "Essence loss." },
"capacity_cost": { "type": "integer" },
"notes": { "type": "string" }
}
}
}

View File

@@ -0,0 +1,326 @@
{
"$schema": "../../../schema/entity-definition.json",
"id": "character",
"name": "Shadowrunner Character",
"description": "A 6th Edition Shadowrunner character with attributes, skills, condition monitors, qualities, augmentations, and combat statistics.",
"ruleset_id": "sr6",
"version": "1.0.0",
"default_template": "character_sheet",
"allowed_templates": [
"character_sheet",
"dossier_sheet"
],
"schema": {
"type": "object",
"required": ["alias", "metatype", "attributes"],
"properties": {
"alias": {
"type": "string",
"description": "Runner handle / street name."
},
"real_name": {
"type": "string",
"description": "Legal name or character identity."
},
"player": {
"type": "string",
"description": "Player name."
},
"notes": {
"type": "string",
"description": "General character notes or mission memo."
},
"metatype": {
"type": "string",
"enum": ["Human", "Elf", "Dwarf", "Ork", "Troll"],
"description": "Character metatype."
},
"ethnicity": {
"type": "string",
"description": "Ethnic or cultural background."
},
"age": {
"type": ["integer", "string"],
"description": "Character age in years."
},
"sex": {
"type": "string",
"description": "Biological sex / gender."
},
"height": {
"type": "string",
"description": "Height (e.g. '2.4 m' or '6 ft 2 in')."
},
"weight": {
"type": "string",
"description": "Weight (e.g. '310 kg')."
},
"reputation": {
"type": ["integer", "string"],
"default": 0,
"description": "Street reputation."
},
"heat": {
"type": ["integer", "string"],
"default": 0,
"description": "Law enforcement attention level."
},
"karma": {
"type": ["integer", "string"],
"default": 0,
"description": "Current unspent Karma."
},
"total_karma": {
"type": ["integer", "string"],
"default": 0,
"description": "Career total Karma earned."
},
"misc": {
"type": "string",
"description": "Miscellaneous character notes."
},
"attributes": {
"type": "object",
"description": "Character core attributes.",
"properties": {
"body": { "type": ["integer", "object"], "description": "Body attribute." },
"agility": { "type": ["integer", "object"], "description": "Agility attribute." },
"reaction": { "type": ["integer", "object"], "description": "Reaction attribute." },
"strength": { "type": ["integer", "object"], "description": "Strength attribute." },
"willpower": { "type": ["integer", "object"], "description": "Willpower attribute." },
"logic": { "type": ["integer", "object"], "description": "Logic attribute." },
"intuition": { "type": ["integer", "object"], "description": "Intuition attribute." },
"charisma": { "type": ["integer", "object"], "description": "Charisma attribute." },
"edge": { "type": ["integer", "object"], "description": "Edge attribute rating." },
"essence": { "type": ["number", "object"], "default": 6.0, "description": "Remaining Essence." },
"magic": { "type": ["integer", "object"], "description": "Magic rating (if awakened)." },
"resonance": { "type": ["integer", "object"], "description": "Resonance rating (if technomancer)." },
"edge_points": { "type": ["integer", "object"], "default": 0, "description": "Current edge points pool." }
}
},
"combat_info": {
"type": "object",
"description": "Derived combat statistics.",
"properties": {
"initiative": { "type": "string", "description": "Physical initiative formula (e.g. '10 + 1D6')." },
"matrix_initiative": { "type": "string", "description": "Matrix initiative formula." },
"astral_initiative": { "type": "string", "description": "Astral initiative formula." },
"defense_rating": { "type": ["integer", "string", "object"], "description": "Defense Rating (Body + Armor)." },
"unarmed_ar": { "type": ["integer", "string", "object"], "description": "Unarmed Attack Rating." },
"composure": { "type": ["integer", "string", "object"], "description": "Composure (Willpower + Charisma)." },
"judge_intentions": { "type": ["integer", "string", "object"], "description": "Judge Intentions (Intuition + Charisma)." },
"memory": { "type": ["integer", "string", "object"], "description": "Memory (Logic + Willpower)." },
"lift_carry": { "type": ["integer", "string", "object"], "description": "Lift/Carry (Body + Willpower)." },
"movement": { "type": "string", "description": "Movement rates (Walking/Running/Sprint)." }
}
},
"condition_monitor": {
"type": "object",
"description": "Physical and Stun damage tracks.",
"properties": {
"physical_damage": { "type": "integer", "default": 0 },
"physical_boxes": { "type": "integer", "description": "Calculated total physical boxes (8 + ceil(body/2))." },
"stun_damage": { "type": "integer", "default": 0 },
"stun_boxes": { "type": "integer", "description": "Calculated total stun boxes (8 + ceil(willpower/2))." },
"overflow": { "type": "integer", "default": 0 }
}
},
"skills": {
"type": "array",
"description": "List of character skills.",
"items": {
"type": "object",
"required": ["name", "rating", "attribute"],
"properties": {
"name": { "type": "string" },
"rating": { "type": "integer", "minimum": 1, "maximum": 12 },
"attribute": { "type": "string" },
"specialization": { "type": "string" },
"expertise": { "type": "string" },
"type": { "type": "string", "enum": ["Action", "Knowledge", "Language"], "default": "Action" },
"dice_pool": { "type": "integer" }
}
}
},
"qualities": {
"type": "array",
"description": "Positive and negative character qualities.",
"items": {
"type": "object",
"required": ["name"],
"properties": {
"name": { "type": "string" },
"type": { "type": "string", "enum": ["Positive", "Negative"] },
"notes": { "type": "string" }
}
}
},
"currency_and_ids": {
"type": "object",
"description": "Lifestyles, IDs, licenses, and nuyen.",
"properties": {
"nuyen": { "type": ["integer", "string"], "default": 0 },
"primary_lifestyle": { "type": "string" },
"licenses": { "type": "string" },
"fake_ids": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"rating": { "type": "integer" },
"lifestyle": { "type": "string" },
"funds": { "type": "string" },
"licenses": { "type": "string" }
}
}
}
}
},
"contacts": {
"type": "array",
"description": "Shadow contacts network.",
"items": {
"type": "object",
"required": ["name"],
"properties": {
"name": { "type": "string" },
"connection": { "type": "integer", "minimum": 1, "maximum": 6 },
"loyalty": { "type": "integer", "minimum": 1, "maximum": 6 },
"notes": { "type": "string" }
}
}
},
"primary_armor": {
"type": "object",
"properties": {
"name": { "type": "string" },
"rating": { "type": ["integer", "string"] },
"notes": { "type": "string" }
}
},
"primary_ranged_weapon": {
"type": "object",
"properties": {
"name": { "type": "string" },
"dv": { "type": "string" },
"mode": { "type": "string" },
"attack_rating": {
"type": "object",
"properties": {
"close": { "type": ["integer", "string"] },
"near": { "type": ["integer", "string"] },
"far": { "type": ["integer", "string"] },
"extreme": { "type": ["integer", "string"] }
}
},
"ammo": { "type": "string" },
"notes": { "type": "string" }
}
},
"primary_melee_weapon": {
"type": "object",
"properties": {
"name": { "type": "string" },
"dv": { "type": "string" },
"close": { "type": ["integer", "string"] },
"notes": { "type": "string" }
}
},
"ranged_weapons": {
"type": "array",
"items": { "$ref": "#/properties/primary_ranged_weapon" }
},
"melee_weapons": {
"type": "array",
"items": { "$ref": "#/properties/primary_melee_weapon" }
},
"armor": {
"type": "array",
"items": { "$ref": "#/properties/primary_armor" }
},
"matrix_stats": {
"type": "object",
"properties": {
"device_name": { "type": "string" },
"device_rating": { "type": ["integer", "string"] },
"attack": { "type": ["integer", "string"] },
"sleaze": { "type": ["integer", "string"] },
"data_processing": { "type": ["integer", "string"] },
"firewall": { "type": ["integer", "string"] },
"programs": { "type": "array", "items": { "type": "string" } },
"matrix_damage": { "type": "integer", "default": 0 }
}
},
"augmentations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"rating": { "type": ["integer", "string"] },
"notes": { "type": "string" },
"essence": { "type": "number" }
}
}
},
"vehicles": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"handling": { "type": "string" },
"acceleration": { "type": "string" },
"speed_interval": { "type": "string" },
"top_speed": { "type": "string" },
"body": { "type": ["integer", "string"] },
"armor": { "type": ["integer", "string"] },
"pilot": { "type": ["integer", "string"] },
"sensor": { "type": ["integer", "string"] },
"seats": { "type": ["integer", "string"] },
"notes": { "type": "string" }
}
}
},
"gear": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"rating": { "type": ["integer", "string"] },
"quantity": { "type": "integer", "default": 1 },
"notes": { "type": "string" }
}
}
},
"spells": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"category": { "type": "string", "enum": ["S", "P", "R", "CF"], "description": "Spell, Preparation, Ritual, Complex Form" },
"type_target": { "type": "string" },
"range": { "type": "string" },
"duration": { "type": "string" },
"drain": { "type": "string" },
"notes": { "type": "string" }
}
}
},
"adept_powers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"level": { "type": ["integer", "string"] },
"notes": { "type": "string" }
}
}
}
}
}
}

View File

@@ -0,0 +1,33 @@
{
"$schema": "../../../schema/entity-definition.json",
"id": "corporation",
"name": "Megacorporation",
"description": "SR6 Corporate Court ranked megacorporation profile with extraterritorial status, slogans, and leadership.",
"ruleset_id": "sr6",
"version": "1.0.0",
"schema": {
"type": "object",
"required": ["name", "status"],
"properties": {
"name": { "type": "string" },
"short_name": { "type": "string" },
"status": { "type": "string", "enum": ["AAA", "AA", "A"], "default": "AAA" },
"court_ranking_2080": { "type": "integer", "minimum": 1, "maximum": 10 },
"slogan": { "type": "string" },
"corporate_type": { "type": "string", "enum": ["Public Corporation", "Private Corporation"] },
"world_headquarters": { "type": "string" },
"leadership": {
"type": "object",
"properties": {
"title": { "type": "string" },
"name": { "type": "string" }
}
},
"major_divisions": {
"type": "array",
"items": { "type": "string" }
},
"notes": { "type": "string" }
}
}
}

View File

@@ -0,0 +1,97 @@
{
"$schema": "../../../schema/entity-definition.json",
"id": "dossier",
"name": "Shadowrunner Dossier",
"description": "A comprehensive 4-page runner dossier combining full character statistics with narrative background, roleplaying tips, tactical guidelines, and tailored action pools.",
"ruleset_id": "sr6",
"version": "1.0.0",
"default_template": "dossier_sheet",
"allowed_templates": [
"dossier_sheet",
"character_sheet"
],
"schema": {
"type": "object",
"required": ["alias", "metatype", "archetype", "attributes"],
"properties": {
"archetype": {
"type": "string",
"description": "Character archetype (e.g. 'Troll Street Samurai', 'Combat Mage', 'Elven Decker')."
},
"quote": {
"type": "string",
"description": "Introductory runner quote or catchphrase."
},
"pitch": {
"type": "string",
"description": "Summary pitch for playing this runner."
},
"background": {
"type": "string",
"description": "Full narrative life history and background story."
},
"preferred_tactics": {
"type": "string",
"description": "Combat style, weapon preferences, and tactical guidelines."
},
"roleplaying_tips": {
"type": "string",
"description": "Roleplaying advice, personality traits, and interpersonal dynamics."
},
"vital_contacts": {
"type": "array",
"description": "Key narrative contacts with detailed descriptions.",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"role": { "type": "string" },
"description": { "type": "string" }
}
}
},
"favorite_hangouts": {
"type": "array",
"description": "Locations the runner frequents in the sprawl.",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"location": { "type": "string" },
"description": { "type": "string" }
}
}
},
"favorite_downtime_activities": {
"type": "array",
"description": "Hobbies and off-run pastimes.",
"items": { "type": "string" }
},
"sample_actions": {
"type": "array",
"description": "Tailored sample actions with pre-computed dice pools.",
"items": {
"type": "object",
"properties": {
"action": { "type": "string" },
"action_type": { "type": "string", "enum": ["Minor", "Major", "Free"] },
"dice_pool": { "type": "integer" },
"notes": { "type": "string" }
}
}
},
"edge_boosts": {
"type": "array",
"description": "Recommended edge boosts and tactical options.",
"items": {
"type": "object",
"properties": {
"cost": { "type": "integer" },
"name": { "type": "string" },
"description": { "type": "string" }
}
}
}
}
}
}

View File

@@ -0,0 +1,47 @@
{
"$schema": "../../../schema/entity-definition.json",
"id": "gear",
"name": "Gear / Equipment",
"description": "SR6 Adventuring gear, electronics, tools, commlinks, cyberdecks, and programs.",
"ruleset_id": "sr6",
"version": "1.0.0",
"schema": {
"type": "object",
"required": ["name"],
"properties": {
"name": { "type": "string" },
"category": {
"type": "string",
"enum": [
"Gear",
"Commlink",
"Cyberdeck",
"Program",
"Electronics",
"Tool",
"Medical",
"Security",
"Other"
]
},
"rating": { "type": ["integer", "string"] },
"device_rating": { "type": ["integer", "string"] },
"attributes_df": {
"type": "string",
"description": "Data Processing / Firewall ratings (e.g. '2/1')."
},
"active_program_slots": { "type": "integer" },
"attack": { "type": ["integer", "string"] },
"sleaze": { "type": ["integer", "string"] },
"programs": { "type": "array", "items": { "type": "string" } },
"rules": {
"type": "string",
"description": "Game mechanics and rules effect."
},
"quantity": { "type": "integer", "default": 1 },
"availability": { "type": ["integer", "string"] },
"cost": { "type": ["integer", "string"] },
"notes": { "type": "string" }
}
}
}

View File

@@ -0,0 +1,124 @@
{
"$schema": "../../../schema/entity-definition.json",
"id": "npc",
"name": "Non-Player Character / Gang",
"description": "SR6 NPCs, gang members, syndicate leaders, and corporate operatives compatible with character and dossier sheets.",
"ruleset_id": "sr6",
"version": "1.0.0",
"default_template": "character_picker",
"allowed_templates": ["character_picker", "character_sheet"],
"schema": {
"type": "object",
"required": ["alias"],
"properties": {
"alias": {
"type": "string",
"description": "NPC name, handle, or gang designation."
},
"real_name": {
"type": "string",
"description": "Legal name or true identity if known."
},
"archetype": {
"type": "string",
"description": "Role, title, or affiliation (e.g., 'Human Female Government Operative', 'Dwarf Gangers')."
},
"metatype": {
"type": "string",
"description": "Metatype (Human, Elf, Dwarf, Ork, Troll, etc.)."
},
"ethnicity": {
"type": "string",
"description": "Ethnicity or cultural background."
},
"quote": {
"type": "string",
"description": "Characteristic quote or battle cry."
},
"hook": {
"type": "string",
"description": "Gamemaster plot hook or motivation."
},
"notes": {
"type": "string",
"description": "Narrative background and roleplay guidance."
},
"attributes": {
"type": "object",
"description": "Core NPC attributes.",
"properties": {
"body": { "type": ["integer", "object"] },
"agility": { "type": ["integer", "object"] },
"reaction": { "type": ["integer", "object"] },
"strength": { "type": ["integer", "object"] },
"willpower": { "type": ["integer", "object"] },
"logic": { "type": ["integer", "object"] },
"intuition": { "type": ["integer", "object"] },
"charisma": { "type": ["integer", "object"] },
"edge": { "type": ["integer", "string", "object"] },
"essence": { "type": ["number", "object"] },
"magic": { "type": ["integer", "object"] }
}
},
"combat_info": {
"type": "object",
"description": "Quick-reference combat stats.",
"properties": {
"initiative": {
"type": "string",
"description": "Initiative score formula (e.g. '6 + 1D6')."
},
"defense_rating": {
"type": ["integer", "string", "object"],
"description": "Defense rating."
},
"condition_monitor": {
"type": ["integer", "string", "object"],
"description": "Condition monitor boxes."
},
"unarmed_ar": { "type": ["integer", "string", "object"] }
}
},
"skills": {
"type": "array",
"description": "List of active skills and ratings.",
"items": {
"type": "object",
"required": ["name"],
"properties": {
"name": { "type": "string" },
"rating": { "type": ["integer", "string"] },
"attribute": { "type": "string" },
"dice_pool": { "type": ["integer", "string"] }
}
}
},
"gear": {
"type": "array",
"description": "Equipment, armor, and commlinks carried.",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"rating": { "type": ["integer", "string"] },
"notes": { "type": "string" }
}
}
},
"weapons": {
"type": "array",
"description": "Weapons equipped.",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"dv": { "type": "string" },
"mode": { "type": "string" },
"attack_rating": { "type": ["integer", "string", "object"] },
"notes": { "type": "string" }
}
}
}
}
}
}

View File

@@ -0,0 +1,21 @@
{
"$schema": "../../../schema/entity-definition.json",
"id": "spell",
"name": "Spell / Complex Form",
"description": "SR6 Hermetic and shamanic spells, preparations, rituals, and technomancer complex forms.",
"ruleset_id": "sr6",
"version": "1.0.0",
"schema": {
"type": "object",
"required": ["name", "category", "drain"],
"properties": {
"name": { "type": "string" },
"category": { "type": "string", "enum": ["Spell", "Preparation", "Ritual", "Complex Form"], "default": "Spell" },
"type_target": { "type": "string", "description": "Physical, Mana, Device, Persona, etc." },
"range": { "type": "string", "description": "Touch, LOS, Area, etc." },
"duration": { "type": "string", "description": "Instant, Sustained, Permanent." },
"drain": { "type": ["integer", "string"], "description": "Drain or Fading value (e.g. '3' or 'DV 4')." },
"notes": { "type": "string" }
}
}
}

View File

@@ -0,0 +1,28 @@
{
"$schema": "../../../schema/entity-definition.json",
"id": "vehicle",
"name": "Vehicle / Drone",
"description": "SR6 Ground vehicles, aircraft, watercraft, and rigger drones.",
"ruleset_id": "sr6",
"version": "1.0.0",
"schema": {
"type": "object",
"required": ["name", "handling", "body", "armor"],
"properties": {
"name": { "type": "string" },
"category": { "type": "string", "enum": ["Drone", "Groundcraft", "Hovercraft", "Watercraft", "Aircraft"] },
"handling": { "type": "string" },
"acceleration": { "type": "string" },
"speed_interval": { "type": "string" },
"top_speed": { "type": "string" },
"body": { "type": ["integer", "string"] },
"armor": { "type": ["integer", "string"] },
"pilot": { "type": ["integer", "string"] },
"sensor": { "type": ["integer", "string"] },
"seats": { "type": ["integer", "string"] },
"mod_slots": { "type": "integer" },
"sensor_array": { "type": "array", "items": { "type": "string" } },
"notes": { "type": "string" }
}
}
}

View File

@@ -0,0 +1,34 @@
{
"$schema": "../../../schema/entity-definition.json",
"id": "weapon",
"name": "Weapon",
"description": "SR6 Ranged and Melee weapons with DV, firing modes, attack ratings across range bands, and ammo.",
"ruleset_id": "sr6",
"version": "1.0.0",
"schema": {
"type": "object",
"required": ["name", "category", "dv"],
"properties": {
"name": { "type": "string" },
"category": {
"type": "string",
"enum": ["Hold-out", "Light Pistol", "Heavy Pistol", "Machine Pistol", "SMG", "Shotgun", "Rifle", "Sniper Rifle", "Assault Cannon", "Blade", "Club", "Unarmed", "Exotic"]
},
"type": { "type": "string", "enum": ["ranged", "melee"], "default": "ranged" },
"dv": { "type": "string", "description": "Damage Value (e.g. '4P', '3S')." },
"mode": { "type": "string", "description": "Firing mode (SS, SA, BF, FA)." },
"attack_rating": {
"type": "object",
"properties": {
"close": { "type": ["integer", "string"] },
"near": { "type": ["integer", "string"] },
"far": { "type": ["integer", "string"] },
"extreme": { "type": ["integer", "string"] }
}
},
"ammo": { "type": "string", "description": "Capacity and reload type (e.g. '15(c)')." },
"accessories": { "type": "array", "items": { "type": "string" } },
"notes": { "type": "string" }
}
}
}

View File

@@ -0,0 +1,13 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "38836c23-969a-4885-b881-029de2531415",
"ruleset_id": "sr6",
"entity_type": "armor",
"display_name": "Armor Jacket",
"description": "Available in many styles, it offers good protection without catching too much attention.",
"properties": {
"name": "Armor Jacket",
"rating": "+4 DR",
"notes": "Available in many styles, it offers good protection without catching too much attention."
}
}

View File

@@ -0,0 +1,13 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "02fd452c-d12f-483b-b0c8-0d7a3c03d6cd",
"ruleset_id": "sr6",
"entity_type": "armor",
"display_name": "Armor Vest",
"description": "A flexible-wrap vest designed to be worn under regular clothing without displaying bulk.",
"properties": {
"name": "Armor Vest",
"rating": "+3 DR",
"notes": "A flexible-wrap vest designed to be worn under regular clothing without displaying bulk."
}
}

View File

@@ -0,0 +1,13 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "e8f060d0-99eb-4592-81b5-6a6583549cc4",
"ruleset_id": "sr6",
"entity_type": "armor",
"display_name": "Lined Coat",
"description": "This duster has been consistently popular on the mean streets of the world’s sprawls for fifty years.",
"properties": {
"name": "Lined Coat",
"rating": "+3 DR",
"notes": "This duster has been consistently popular on the mean streets of the world’s sprawls for fifty years."
}
}

View File

@@ -0,0 +1,377 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "4c8524d7-0f8c-4f76-9d32-bb9a25032b49",
"ruleset_id": "sr6",
"entity_type": "character",
"display_name": "Emu",
"description": "Human Rigger from the official Shadowrun 6th World Beginner Box.",
"template_id": "character_sheet",
"properties": {
"alias": "Emu",
"real_name": "Emu",
"player": "Catalyst Pregen",
"notes": "Human Rigger with a vehicle control rig, drone fleet, and heavy Mafia debt.",
"metatype": "Human",
"ethnicity": "Aboriginal Australian (Wiradjuri)",
"age": 23,
"sex": "Female",
"height": "1.65 m",
"weight": "57 kg",
"reputation": 2,
"heat": 0,
"karma": 0,
"total_karma": 0,
"misc": "Former merc drone pilot; owes 200,000¥ to the Ciarniello Mafia.",
"attributes": {
"body": 2,
"agility": 2,
"reaction": 4,
"strength": 2,
"willpower": 4,
"logic": 4,
"intuition": 6,
"charisma": 3,
"edge": 5,
"essence": 3.6,
"magic": 0,
"edge_points": 5
},
"combat_info": {
"initiative": "10 + 1D6",
"matrix_initiative": "10 + 1D6",
"astral_initiative": "—",
"defense_rating": 6,
"unarmed_ar": 6,
"composure": 7,
"judge_intentions": 10,
"memory": 10,
"lift_carry": 6,
"movement": "5/10/15"
},
"condition_monitor": {
"physical_damage": 0,
"physical_boxes": 9,
"stun_damage": 0,
"stun_boxes": 10,
"overflow": 0
},
"skills": [
{
"name": "Piloting",
"rating": 7,
"attribute": "Reaction",
"specialization": "Groundcraft",
"dice_pool": 13,
"type": "Action"
},
{
"name": "Engineering",
"rating": 6,
"attribute": "Logic",
"specialization": "Gunnery",
"dice_pool": 12,
"type": "Action"
},
{
"name": "Perception",
"rating": 3,
"attribute": "Intuition",
"dice_pool": 9,
"type": "Action"
},
{
"name": "Firearms",
"rating": 3,
"attribute": "Agility",
"dice_pool": 5,
"type": "Action"
},
{
"name": "Electronics",
"rating": 2,
"attribute": "Logic",
"dice_pool": 6,
"type": "Action"
},
{
"name": "Con",
"rating": 2,
"attribute": "Charisma",
"dice_pool": 5,
"type": "Action"
},
{
"name": "Influence",
"rating": 2,
"attribute": "Charisma",
"dice_pool": 5,
"type": "Action"
},
{
"name": "Stealth",
"rating": 2,
"attribute": "Agility",
"dice_pool": 4,
"type": "Action"
},
{
"name": "Biotech",
"rating": 1,
"attribute": "Logic",
"dice_pool": 5,
"type": "Action"
},
{
"name": "Close Combat",
"rating": 1,
"attribute": "Agility",
"dice_pool": 3,
"type": "Action"
},
{
"name": "Seattle Metroplex Geography",
"rating": 2,
"attribute": "Logic",
"dice_pool": 6,
"type": "Knowledge"
},
{
"name": "Smuggling Routes",
"rating": 2,
"attribute": "Logic",
"dice_pool": 6,
"type": "Knowledge"
}
],
"qualities": [
{
"name": "Addiction (Cigarettes)",
"type": "Negative",
"notes": "Level 3 addiction; penalty if in withdrawal."
},
{
"name": "Gearhead",
"type": "Positive",
"notes": "May spend Edge on repair tests and vehicle handling."
},
{
"name": "In Debt",
"type": "Negative",
"notes": "Ciarniello Mafia: 200,000¥ debt, 8,000¥/month interest."
},
{
"name": "Juryrigger",
"type": "Positive",
"notes": "Gains bonus Edge point on Juryrigging tests."
},
{
"name": "SINner",
"type": "Negative",
"notes": "Standard national/system SIN; extra scrutiny and trace exposure."
}
],
"currency_and_ids": {
"nuyen": 265,
"primary_lifestyle": "Low (3 months prepaid)",
"licenses": "Concealed Carry (Assault Rifle), Drone Rigging, Cyberware",
"fake_ids": [
{
"name": "Kylie Mason",
"rating": 3,
"lifestyle": "Low",
"funds": "265¥",
"licenses": "Firearms, Cyberware, RCC"
}
]
},
"contacts": [
{
"name": "Giovanni Valenti",
"loyalty": 2,
"connection": 3,
"notes": "Drone and vehicle fixer, owner of Gio's Garage in Everett."
},
{
"name": "Lenny",
"loyalty": 3,
"connection": 2,
"notes": "Underground safehouse fixer."
},
{
"name": "Steve Draper",
"loyalty": 1,
"connection": 3,
"notes": "Knight Errant dispatcher on the take."
},
{
"name": "Tomaso di Stefano",
"loyalty": 2,
"connection": 2,
"notes": "Ciarniello Mafia loan shark ('Tommy D') and occasional Mr. Johnson."
}
],
"primary_armor": {
"name": "Armor jacket",
"rating": "+4",
"notes": "+4 Defense Rating bonus"
},
"primary_ranged_weapon": {
"name": "FN HAR Assault Rifle",
"dv": "5P",
"mode": "SA/BF/FA",
"attack_rating": { "close": 3, "near": 11, "far": 6, "extreme": 1 },
"ammo": "35(c)",
"notes": "w/ gas-vent system, laser sight"
},
"primary_melee_weapon": {
"name": "Unarmed Strike",
"dv": "2S",
"close": 2,
"notes": "Natural"
},
"ranged_weapons": [
{
"name": "FN HAR Assault Rifle",
"dv": "5P",
"mode": "SA/BF/FA",
"attack_rating": { "close": 3, "near": 11, "far": 6, "extreme": 1 },
"ammo": "35(c)",
"notes": "w/ gas-vent system, laser sight"
}
],
"melee_weapons": [
{
"name": "Unarmed Strike",
"dv": "2S",
"close": 2,
"notes": "Natural"
}
],
"armor": [
{
"name": "Armor jacket",
"rating": "+4",
"notes": "+4 DR bonus"
}
],
"matrix_stats": {
"device_name": "Maersk Spider RCC",
"device_rating": 4,
"attack": 0,
"sleaze": 0,
"data_processing": 4,
"firewall": 5,
"programs": ["Encryption", "Signal Scrubber", "Toolbox"],
"matrix_damage": 0
},
"augmentations": [
{
"name": "Control Rig",
"rating": 2,
"notes": "Grants +2 dice pool bonus and +1 Edge on vehicle skill tests when jumped in.",
"essence": 2.0
},
{
"name": "Cybereyes",
"rating": 3,
"notes": "w/ flare compensation, image link, low-light vision, smartlink, thermographic vision",
"essence": 0.4
},
{
"name": "Reflex Recorder",
"rating": 1,
"notes": "Piloting skill (+1 bonus rating)",
"essence": 0.1
}
],
"vehicles": [
{
"name": "GMC Commodore",
"handling": "4/3",
"accel": "14",
"speed": "120/160",
"armor": 8,
"body": 10,
"pilot": 2,
"sensor": 2,
"notes": "Sports sedan w/ 2 weapon mounts (Mossberg CMDT shotguns, 250 rds)"
},
{
"name": "GMC Bulldog",
"handling": "3/2",
"accel": "10",
"speed": "90/130",
"armor": 12,
"body": 16,
"pilot": 2,
"sensor": 2,
"notes": "Step-van rigger mobile workshop"
},
{
"name": "Lockheed Optic-X2",
"handling": "4",
"accel": "12",
"speed": "80",
"armor": 2,
"body": 3,
"pilot": 4,
"sensor": 4,
"notes": "Stealth recon drone w/ R6 Clearsight, R6 Stealth autosofts"
},
{
"name": "MCT-Nissan Roto-Drone (x3)",
"handling": "4",
"accel": "8",
"speed": "70",
"armor": 6,
"body": 6,
"pilot": 3,
"sensor": 3,
"notes": "Combat drone w/ weapon mount (Ingram Smartgun XI), R6 Maneuver, R6 Targeting autosofts"
},
{
"name": "MCT Hornet (x4)",
"handling": "3",
"accel": "5",
"speed": "30",
"armor": 0,
"body": 1,
"pilot": 2,
"sensor": 2,
"notes": "Micro indoor recon drone w/ R6 Stealth autosoft"
},
{
"name": "Horizon Flying Eye (x3)",
"handling": "3",
"accel": "6",
"speed": "40",
"armor": 1,
"body": 1,
"pilot": 2,
"sensor": 2,
"notes": "Micro surveillance drone w/ smoke grenade upgrade, R6 Maneuver autosoft"
}
],
"gear": [
{ "name": "Armor jacket", "rating": 4, "quantity": 1 },
{ "name": "Automotive mechanic shop", "quantity": 1 },
{
"name": "Maersk Spider RCC",
"rating": 4,
"quantity": 1,
"notes": "DR 4, D/F 4/5"
},
{
"name": "Renraku Sensei commlink",
"rating": 3,
"quantity": 1,
"notes": "DR 3, D/F 2/0"
},
{ "name": "Rating 3 Targeting Autosoft (HK 227)", "quantity": 1 },
{ "name": "Certified credstick", "quantity": 1, "notes": "265¥" },
{ "name": "FN HAR ammo box", "quantity": 175 }
],
"spells": [],
"adept_powers": []
}
}

View File

@@ -0,0 +1,329 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "8f3b2049-3f04-4c17-91de-84bb41e7399a",
"ruleset_id": "sr6",
"entity_type": "character",
"display_name": "Frostburn",
"description": "Ork Combat Mage from the official Shadowrun 6th World Beginner Box.",
"template_id": "character_sheet",
"properties": {
"alias": "Frostburn",
"real_name": "Kendra Maguire",
"player": "Catalyst Pregen",
"notes": "Ork Combat Mage specializing in fire, ice, protective illusions, and healing.",
"metatype": "Ork",
"ethnicity": "White",
"age": 25,
"sex": "Female",
"height": "2 m",
"weight": "115 kg",
"reputation": 2,
"heat": 0,
"karma": 0,
"total_karma": 0,
"misc": "Former NeoNET corporate security magical operative.",
"attributes": {
"body": 4,
"agility": 4,
"reaction": 2,
"strength": 3,
"willpower": 7,
"logic": 5,
"intuition": 1,
"charisma": 3,
"edge": 2,
"essence": 6.0,
"magic": 6,
"edge_points": 2
},
"combat_info": {
"initiative": "3 + 1D6",
"matrix_initiative": "3 + 1D6",
"astral_initiative": "7 + 2D6",
"defense_rating": 7,
"unarmed_ar": 4,
"composure": 10,
"judge_intentions": 4,
"memory": 12,
"lift_carry": 7,
"movement": "5/10/+1"
},
"condition_monitor": {
"physical_damage": 0,
"physical_boxes": 10,
"stun_damage": 0,
"stun_boxes": 12,
"overflow": 0
},
"skills": [
{
"name": "Sorcery",
"rating": 6,
"attribute": "Magic",
"dice_pool": 12,
"type": "Action"
},
{
"name": "Firearms",
"rating": 3,
"attribute": "Agility",
"dice_pool": 7,
"type": "Action"
},
{
"name": "Close Combat",
"rating": 2,
"attribute": "Agility",
"dice_pool": 6,
"type": "Action"
},
{
"name": "Astral",
"rating": 3,
"attribute": "Intuition",
"dice_pool": 4,
"type": "Action"
},
{
"name": "Perception",
"rating": 3,
"attribute": "Intuition",
"dice_pool": 4,
"type": "Action"
},
{
"name": "Athletics",
"rating": 1,
"attribute": "Agility",
"dice_pool": 5,
"type": "Action"
},
{
"name": "Stealth",
"rating": 1,
"attribute": "Agility",
"dice_pool": 5,
"type": "Action"
},
{
"name": "Magical Security",
"rating": 2,
"attribute": "Logic",
"dice_pool": 7,
"type": "Knowledge"
},
{
"name": "Security Procedures",
"rating": 2,
"attribute": "Logic",
"dice_pool": 7,
"type": "Knowledge"
},
{
"name": "Seattle Underground",
"rating": 2,
"attribute": "Logic",
"dice_pool": 7,
"type": "Knowledge"
}
],
"qualities": [
{
"name": "Built Tough",
"type": "Positive",
"notes": "+1 Physical damage track box."
},
{
"name": "Low-light Vision",
"type": "Positive",
"notes": "Natural ork low-light perception."
},
{
"name": "Dependents",
"type": "Negative",
"notes": "Supports her family in Redmond."
},
{
"name": "SINner",
"type": "Negative",
"notes": "Corporate SIN from former employment with NeoNET."
},
{
"name": "Socially Awkward",
"type": "Negative",
"notes": "Direct, blunt, and exasperated demeanor."
}
],
"currency_and_ids": {
"nuyen": 3500,
"primary_lifestyle": "Low (prepaid)",
"licenses": "Concealed Carry, Registered Awakened Practitioner",
"fake_ids": [
{
"name": "Evelyn Graves",
"rating": 3,
"lifestyle": "Low",
"funds": "3,500¥",
"licenses": "Firearms, Mage License"
}
]
},
"contacts": [
{
"name": "Brimstone",
"loyalty": 3,
"connection": 3,
"notes": "Elf combat mage, former NeoNET coworker and fellow runner."
},
{
"name": "Harrison Kellerman",
"loyalty": 2,
"connection": 4,
"notes": "Talismonger and owner of the Io Pan! lore store in Downtown Seattle."
},
{
"name": "Jules Maguire",
"loyalty": 4,
"connection": 2,
"notes": "Ork rigger, Frostburn's older brother and Cascade Ork smuggler."
}
],
"primary_armor": {
"name": "Lined coat",
"rating": "+3",
"notes": "+3 Defense Rating bonus"
},
"primary_ranged_weapon": {
"name": "Walther Palm Pistol",
"dv": "2P",
"mode": "BF",
"attack_rating": { "close": 12, "near": 7, "far": "—", "extreme": "—" },
"ammo": "2(b)",
"notes": "Hold-out pistol"
},
"primary_melee_weapon": {
"name": "Extendible baton",
"dv": "2S",
"close": 5,
"notes": "Club"
},
"ranged_weapons": [
{
"name": "Walther Palm Pistol",
"dv": "2P",
"mode": "BF",
"attack_rating": { "close": 12, "near": 7, "far": "—", "extreme": "—" },
"ammo": "2(b)",
"notes": "Double-barreled hold-out"
}
],
"melee_weapons": [
{
"name": "Extendible baton",
"dv": "2S",
"close": 5,
"notes": "Concealable club"
}
],
"armor": [
{
"name": "Lined coat",
"rating": "+3",
"notes": "+3 DR bonus"
}
],
"matrix_stats": {
"device_name": "Renraku Sensei commlink",
"device_rating": 3,
"attack": 0,
"sleaze": 0,
"data_processing": 2,
"firewall": 0,
"programs": [],
"matrix_damage": 0
},
"augmentations": [],
"vehicles": [],
"gear": [
{ "name": "Lined coat", "rating": 3, "quantity": 1 },
{ "name": "Renraku Sensei commlink", "rating": 3, "quantity": 1 },
{ "name": "Certified credstick", "quantity": 1, "notes": "3,500¥" },
{ "name": "Walther Palm Pistol ammo box", "quantity": 20 }
],
"spells": [
{
"name": "Fireball",
"category": "S",
"type_target": "Physical",
"range": "LOS (Area)",
"duration": "Instant",
"drain": "5",
"notes": "Elemental fire damage area effect"
},
{
"name": "Flamestrike",
"category": "S",
"type_target": "Physical",
"range": "LOS",
"duration": "Instant",
"drain": "4",
"notes": "Single-target direct flame bolt"
},
{
"name": "Ice Spear",
"category": "S",
"type_target": "Physical",
"range": "LOS",
"duration": "Instant",
"drain": "4",
"notes": "Piercing elemental cold damage"
},
{
"name": "Heal",
"category": "S",
"type_target": "Essence",
"range": "Touch",
"duration": "Permanent",
"drain": "3",
"notes": "Repairs physical damage boxes"
},
{
"name": "Combat Sense",
"category": "S",
"type_target": "Mana",
"range": "Touch",
"duration": "Sustained",
"drain": "3",
"notes": "Adds defense dice and awareness"
},
{
"name": "Improved Invisibility",
"category": "S",
"type_target": "Physical",
"range": "Touch",
"duration": "Sustained",
"drain": "4",
"notes": "Cloaks target from visual & technological detection"
},
{
"name": "Levitate",
"category": "S",
"type_target": "Physical",
"range": "LOS",
"duration": "Sustained",
"drain": "4",
"notes": "Telekinetically lifts objects or persons"
},
{
"name": "Mana Barrier",
"category": "S",
"type_target": "Mana",
"range": "LOS (Area)",
"duration": "Sustained",
"drain": "4",
"notes": "Blocks astral forms and hostile spells"
}
],
"adept_powers": []
}
}

View File

@@ -0,0 +1,204 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "e97b91d2-06c5-4d7a-b5a9-467362974eb1",
"ruleset_id": "sr6",
"entity_type": "character",
"display_name": "Rude",
"description": "Troll Street Samurai from the official Shadowrun 6th World Beginner Box.",
"template_id": "character_sheet",
"properties": {
"alias": "Rude",
"real_name": "Unknown (\"Rudiarius\")",
"player": "Catalyst Pregen",
"notes": "Street Samurai meat shield with amnesia and a vicious combat streak.",
"metatype": "Troll",
"ethnicity": "White",
"age": 29,
"sex": "Male",
"height": "2.4 m",
"weight": "310 kg",
"reputation": 3,
"heat": 1,
"karma": 0,
"total_karma": 0,
"misc": "Carries an ancient wooden gladiator practice sword.",
"attributes": {
"body": 7,
"agility": 5,
"reaction": 6,
"strength": 6,
"willpower": 4,
"logic": 4,
"intuition": 4,
"charisma": 2,
"edge": 5,
"essence": 5.5,
"edge_points": 5
},
"combat_info": {
"initiative": "10 + 1D6",
"matrix_initiative": "10 + 1D6",
"astral_initiative": "—",
"defense_rating": 11,
"unarmed_ar": 7,
"composure": 6,
"judge_intentions": 6,
"memory": 8,
"lift_carry": 11,
"movement": "5/10/+1"
},
"condition_monitor": {
"physical_damage": 0,
"physical_boxes": 12,
"stun_damage": 0,
"stun_boxes": 10,
"overflow": 0
},
"skills": [
{ "name": "Firearms", "rating": 6, "attribute": "Agility", "dice_pool": 12, "type": "Action" },
{ "name": "Close Combat", "rating": 3, "attribute": "Agility", "dice_pool": 9, "type": "Action" },
{ "name": "Athletics", "rating": 3, "attribute": "Agility", "dice_pool": 9, "type": "Action" },
{ "name": "Perception", "rating": 3, "attribute": "Intuition", "dice_pool": 7, "type": "Action" },
{ "name": "Engineering", "rating": 2, "attribute": "Logic", "specialization": "Lockpicking", "dice_pool": 6, "type": "Action" },
{ "name": "Influence", "rating": 2, "attribute": "Charisma", "dice_pool": 4, "type": "Action" },
{ "name": "Exotic Weapons", "rating": 3, "attribute": "Agility", "specialization": "Grapple Gun", "dice_pool": 9, "type": "Action" },
{ "name": "Exotic Weapons", "rating": 4, "attribute": "Agility", "specialization": "Grapple Launcher", "dice_pool": 9, "type": "Action" },
{ "name": "Firearms Manufacturers", "rating": 2, "attribute": "Logic", "dice_pool": 6, "type": "Knowledge" },
{ "name": "Security Systems", "rating": 2, "attribute": "Logic", "dice_pool": 6, "type": "Knowledge" },
{ "name": "Urban Brawl Teams", "rating": 2, "attribute": "Logic", "dice_pool": 6, "type": "Knowledge" }
],
"qualities": [
{ "name": "Built Tough", "type": "Positive", "notes": "+1 Physical condition monitor box." },
{ "name": "Dermal Deposits", "type": "Positive", "notes": "+1 natural Armor/Defense Rating." },
{ "name": "High Pain Tolerance", "type": "Positive", "notes": "Reduces wound modifiers by 1." },
{ "name": "Thermographic Vision", "type": "Positive", "notes": "Natural troll heat-signature vision." },
{ "name": "Socially Awkward", "type": "Negative", "notes": "Blunt and lacking empathy due to amnesia." }
],
"currency_and_ids": {
"nuyen": 3500,
"primary_lifestyle": "Low (1 month prepaid)",
"licenses": "Concealed Carry (Heavy Pistol, Sniper Rifle)",
"fake_ids": [
{
"name": "Marcus Vance",
"rating": 3,
"lifestyle": "Low",
"funds": "3,500¥",
"licenses": "Driver's, Concealed Carry"
}
]
},
"contacts": [
{
"name": "Billy Shen",
"loyalty": 2,
"connection": 4,
"notes": "Octagon Triad lieutenant, enforcer, and arms dealer."
},
{
"name": "Four-Finger Wong",
"loyalty": 3,
"connection": 3,
"notes": "Cyber-surgeon and underground street doc."
},
{
"name": "Mia",
"loyalty": 3,
"connection": 2,
"notes": "Part-time fixer and waitress at Peaceable Kingdom."
}
],
"primary_armor": {
"name": "Armor jacket",
"rating": "+2",
"notes": "+2 Defense Rating bonus"
},
"primary_ranged_weapon": {
"name": "Ares Desert Strike",
"dv": "5P",
"mode": "SA",
"attack_rating": {
"close": 3,
"near": 10,
"far": 10,
"extreme": 10
},
"ammo": "14(c)",
"notes": "Sniper rifle w/ imaging scope, shock pad"
},
"primary_melee_weapon": {
"name": "Sword",
"dv": "3P",
"close": 9,
"notes": "Blade"
},
"ranged_weapons": [
{
"name": "Ares Desert Strike (Sniper Rifle)",
"dv": "5P",
"mode": "SA",
"attack_rating": { "close": 3, "near": 10, "far": 10, "extreme": 10 },
"ammo": "14(c)",
"notes": "w/ imaging scope, shock pad"
},
{
"name": "Ares Predator VI (Heavy Pistol)",
"dv": "4P",
"mode": "SA",
"attack_rating": { "close": 9, "near": 9, "far": 7, "extreme": "—" },
"ammo": "15(c)",
"notes": "w/ smartlink"
}
],
"melee_weapons": [
{
"name": "Sword (Blade)",
"dv": "3P",
"close": 9,
"notes": "Monofilament edge"
}
],
"armor": [
{
"name": "Armor jacket",
"rating": "+2",
"notes": "Provides +2 DR"
}
],
"matrix_stats": {
"device_name": "Sony Emperor commlink",
"device_rating": 2,
"attack": 0,
"sleaze": 0,
"data_processing": 1,
"firewall": 1,
"programs": [],
"matrix_damage": 0
},
"augmentations": [
{
"name": "Cybereyes",
"rating": 2,
"notes": "w/ smartlink, low-light vision",
"essence": 0.2
},
{
"name": "Reaction enhancers",
"rating": 1,
"notes": "+1 REA, +1 Initiative",
"essence": 0.3
}
],
"vehicles": [],
"gear": [
{ "name": "Armor jacket", "rating": 2, "quantity": 1 },
{ "name": "Sony Emperor commlink", "rating": 2, "quantity": 1 },
{ "name": "Grapple gun", "quantity": 1, "notes": "w/ 100m stealth rope" },
{ "name": "Certified credstick", "quantity": 1, "notes": "Contains 3,500¥" },
{ "name": "Spare ammo clips (Ares Predator)", "quantity": 3 },
{ "name": "Spare ammo clips (Ares Desert Strike)", "quantity": 2 }
],
"spells": [],
"adept_powers": []
}
}

View File

@@ -0,0 +1,264 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "7a83d9b1-0943-4e89-a2a1-e40d0f287113",
"ruleset_id": "sr6",
"entity_type": "character",
"display_name": "Yu",
"description": "Elf Covert Ops Specialist and Face from the official Shadowrun 6th World Beginner Box.",
"template_id": "character_sheet",
"properties": {
"alias": "Yu",
"real_name": "Yu",
"player": "Catalyst Pregen",
"notes": "Elf Covert Ops Specialist and Face with high agility, charisma, and stealth skills.",
"metatype": "Elf",
"ethnicity": "Cantonese",
"age": 28,
"sex": "Male",
"height": "1.8 m",
"weight": "77 kg",
"reputation": 2,
"heat": 0,
"karma": 0,
"total_karma": 0,
"misc": "Red Dragon / Octagon Triad affiliate; former thief and enforcer in Hong Kong.",
"attributes": {
"body": 3,
"agility": 6,
"reaction": 4,
"strength": 3,
"willpower": 5,
"logic": 5,
"intuition": 5,
"charisma": 8,
"edge": 2,
"essence": 6.0,
"magic": 0,
"edge_points": 2
},
"combat_info": {
"initiative": "9 + 1D6",
"matrix_initiative": "9 + 1D6",
"astral_initiative": "—",
"defense_rating": 6,
"unarmed_ar": 6,
"composure": 13,
"judge_intentions": 13,
"memory": 10,
"lift_carry": 6,
"movement": "5/10/+1"
},
"condition_monitor": {
"physical_damage": 0,
"physical_boxes": 10,
"stun_damage": 0,
"stun_boxes": 11,
"overflow": 0
},
"skills": [
{
"name": "Con",
"rating": 6,
"attribute": "Charisma",
"dice_pool": 14,
"type": "Action"
},
{
"name": "Influence",
"rating": 6,
"attribute": "Charisma",
"dice_pool": 14,
"type": "Action"
},
{
"name": "Stealth",
"rating": 6,
"attribute": "Agility",
"dice_pool": 12,
"type": "Action"
},
{
"name": "Close Combat",
"rating": 4,
"attribute": "Agility",
"dice_pool": 10,
"type": "Action"
},
{
"name": "Firearms",
"rating": 2,
"attribute": "Agility",
"dice_pool": 8,
"type": "Action"
},
{
"name": "Perception",
"rating": 2,
"attribute": "Intuition",
"dice_pool": 7,
"type": "Action"
},
{
"name": "Piloting",
"rating": 2,
"attribute": "Reaction",
"dice_pool": 6,
"type": "Action"
},
{
"name": "Biotech",
"rating": 1,
"attribute": "Logic",
"dice_pool": 6,
"type": "Action"
},
{
"name": "Outdoors",
"rating": 1,
"attribute": "Intuition",
"dice_pool": 6,
"type": "Action"
},
{
"name": "Architecture",
"rating": 2,
"attribute": "Logic",
"dice_pool": 7,
"type": "Knowledge"
},
{
"name": "Close-up Magic",
"rating": 2,
"attribute": "Logic",
"dice_pool": 7,
"type": "Knowledge"
},
{
"name": "Wildlife",
"rating": 2,
"attribute": "Logic",
"dice_pool": 7,
"type": "Knowledge"
}
],
"qualities": [
{
"name": "Catlike",
"type": "Positive",
"notes": "+1 dice pool bonus on stealth and acrobatics tests."
},
{
"name": "Double-Jointed",
"type": "Positive",
"notes": "+1 dice pool bonus on escape and contortion tests."
},
{
"name": "Low-Light Vision",
"type": "Positive",
"notes": "Natural elf low-light perception."
}
],
"currency_and_ids": {
"nuyen": 3500,
"primary_lifestyle": "Low (1 month prepaid)",
"licenses": "Concealed Carry (Light Pistol)",
"fake_ids": [
{
"name": "Raymond Chang",
"rating": 4,
"lifestyle": "Low",
"funds": "3,500¥",
"licenses": "Firearms, Security Tech"
}
]
},
"contacts": [
{
"name": "Billy Shen",
"loyalty": 2,
"connection": 4,
"notes": "Octagon Triad lieutenant, enforcer, and arms dealer."
},
{
"name": "Four-Finger Wong",
"loyalty": 3,
"connection": 3,
"notes": "Cyber-surgeon and underground street doc."
},
{
"name": "Mia",
"loyalty": 3,
"connection": 2,
"notes": "Part-time fixer and waitress at Peaceable Kingdom."
}
],
"primary_armor": {
"name": "Lined coat",
"rating": "+3",
"notes": "+3 Defense Rating bonus"
},
"primary_ranged_weapon": {
"name": "Ares Light Fire 75",
"dv": "2P",
"mode": "SA",
"attack_rating": { "close": 10, "near": 7, "far": 6, "extreme": "—" },
"ammo": "16(c)",
"notes": "w/ silencer, smartgun"
},
"primary_melee_weapon": {
"name": "Extendible baton",
"dv": "3S",
"close": 6,
"notes": "Club"
},
"ranged_weapons": [
{
"name": "Ares Light Fire 75 (Light Pistol)",
"dv": "2P",
"mode": "SA",
"attack_rating": { "close": 10, "near": 7, "far": 6, "extreme": "—" },
"ammo": "16(c)",
"notes": "w/ silencer, smartgun"
}
],
"melee_weapons": [
{
"name": "Extendible baton",
"dv": "3S",
"close": 6,
"notes": "Club"
}
],
"armor": [
{
"name": "Lined coat",
"rating": "+3",
"notes": "+3 DR bonus"
}
],
"matrix_stats": {
"device_name": "Erika Elite commlink",
"device_rating": 4,
"attack": 0,
"sleaze": 0,
"data_processing": 2,
"firewall": 1,
"programs": [],
"matrix_damage": 0
},
"augmentations": [],
"vehicles": [],
"gear": [
{ "name": "Lined coat", "rating": 3, "quantity": 1 },
{ "name": "Erika Elite commlink", "rating": 4, "quantity": 1 },
{ "name": "Disguise kit", "quantity": 1 },
{ "name": "First Aid Kit", "rating": 3, "quantity": 1 },
{ "name": "Passkey", "rating": 4, "quantity": 1 },
{ "name": "Contacts", "quantity": 1, "notes": "w/ smartlink" },
{ "name": "Certified credstick", "quantity": 1, "notes": "3,500¥" },
{ "name": "Spare ammo clips (Ares Light Fire 75)", "quantity": 3 }
],
"spells": [],
"adept_powers": []
}
}

View File

@@ -0,0 +1,257 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "e812d4a5-671c-4b52-9b2f-3d6118bfa934",
"ruleset_id": "sr6",
"entity_type": "character",
"display_name": "Zipfile",
"description": "Dwarf Decker from the official Shadowrun 6th World Beginner Box.",
"template_id": "character_sheet",
"properties": {
"alias": "Zipfile",
"real_name": "Zipfile",
"player": "Catalyst Pregen",
"notes": "Dwarf Decker specializing in illicit host penetration, device control, and Matrix sabotage.",
"metatype": "Dwarf",
"ethnicity": "Bantu",
"age": 23,
"sex": "Female",
"height": "1.1 m",
"weight": "36 kg",
"reputation": 2,
"heat": 0,
"karma": 0,
"total_karma": 0,
"misc": "Azanian neo-anarchist activist and urban brawl fan.",
"attributes": {
"body": 5,
"agility": 3,
"reaction": 4,
"strength": 4,
"willpower": 6,
"logic": 6,
"intuition": 6,
"charisma": 3,
"edge": 4,
"essence": 3.7,
"magic": 0,
"edge_points": 4
},
"combat_info": {
"initiative": "10 + 1D6",
"matrix_initiative": "12 + 1D6",
"astral_initiative": "—",
"defense_rating": 8,
"unarmed_ar": 4,
"composure": 9,
"judge_intentions": 9,
"memory": 12,
"lift_carry": 9,
"movement": "5/10/+1"
},
"condition_monitor": {
"physical_damage": 0,
"physical_boxes": 11,
"stun_damage": 0,
"stun_boxes": 11,
"overflow": 0
},
"skills": [
{
"name": "Cracking",
"rating": 6,
"attribute": "Logic",
"dice_pool": 12,
"type": "Action"
},
{
"name": "Electronics",
"rating": 6,
"attribute": "Logic",
"dice_pool": 12,
"type": "Action"
},
{
"name": "Engineering",
"rating": 2,
"attribute": "Logic",
"dice_pool": 8,
"type": "Action"
},
{
"name": "Perception",
"rating": 3,
"attribute": "Intuition",
"dice_pool": 9,
"type": "Action"
},
{
"name": "Firearms",
"rating": 2,
"attribute": "Agility",
"dice_pool": 5,
"type": "Action"
},
{
"name": "Close Combat",
"rating": 2,
"attribute": "Agility",
"dice_pool": 5,
"type": "Action"
},
{
"name": "Cyberdeck Models",
"rating": 2,
"attribute": "Logic",
"dice_pool": 8,
"type": "Knowledge"
},
{
"name": "German Expressionist Cinema",
"rating": 2,
"attribute": "Logic",
"dice_pool": 8,
"type": "Knowledge"
},
{
"name": "Matrix Security",
"rating": 2,
"attribute": "Logic",
"dice_pool": 8,
"type": "Knowledge"
}
],
"qualities": [
{
"name": "Gearhead",
"type": "Positive",
"notes": "May spend Edge on Repair tests and during downtime repairs."
},
{
"name": "Guts",
"type": "Positive",
"notes": "+2 dice pool bonus on tests resisting fear and intimidation."
},
{
"name": "Thermographic Vision",
"type": "Positive",
"notes": "Natural dwarf heat-signature vision."
}
],
"currency_and_ids": {
"nuyen": 3500,
"primary_lifestyle": "Low (1 month prepaid)",
"licenses": "Concealed Carry (Heavy Pistol), Cyberdeck Operator License",
"fake_ids": [
{
"name": "Nandi Mandela",
"rating": 3,
"lifestyle": "Low",
"funds": "3,500¥",
"licenses": "Heavy Pistol, Hardware Tech"
}
]
},
"contacts": [
{
"name": "Arclight",
"loyalty": 3,
"connection": 4,
"notes": "Combat mage and coordinator for the Black Star neo-anarchist shadowrunner collective."
},
{
"name": "Casey Connors",
"loyalty": 2,
"connection": 3,
"notes": "Owner of the Cathode Glow e-sports bar and tech museum in Tacoma."
},
{
"name": "Jules Maguire",
"loyalty": 2,
"connection": 2,
"notes": "Cascade Ork smuggler."
}
],
"primary_armor": {
"name": "Armor vest",
"rating": "+3",
"notes": "+3 Defense Rating bonus"
},
"primary_ranged_weapon": {
"name": "Ruger Super Warhawk",
"dv": "4P",
"mode": "SS",
"attack_rating": { "close": 8, "near": 11, "far": 8, "extreme": "—" },
"ammo": "6(cy)",
"notes": "Heavy cylinder revolver"
},
"primary_melee_weapon": {
"name": "Unarmed Strike",
"dv": "2S",
"close": 4,
"notes": "Fist / kick"
},
"ranged_weapons": [
{
"name": "Ruger Super Warhawk (Heavy Pistol)",
"dv": "4P",
"mode": "SS",
"attack_rating": { "close": 8, "near": 11, "far": 8, "extreme": "—" },
"ammo": "6(cy)",
"notes": "Heavy revolver"
}
],
"melee_weapons": [
{
"name": "Unarmed Strike",
"dv": "2S",
"close": 4,
"notes": "Natural"
}
],
"armor": [
{
"name": "Armor vest",
"rating": "+3",
"notes": "+3 DR bonus"
}
],
"matrix_stats": {
"device_name": "Renraku Kitsune cyberdeck",
"device_rating": 4,
"attack": 6,
"sleaze": 7,
"data_processing": 4,
"firewall": 4,
"programs": ["Decryption", "Exploit", "Overclock"],
"matrix_damage": 0
},
"augmentations": [
{
"name": "Cyberjack MPI",
"rating": 4,
"notes": "7/6, +2 Matrix initiative",
"essence": 2.3
}
],
"vehicles": [],
"gear": [
{ "name": "Armor vest", "rating": 3, "quantity": 1 },
{
"name": "Renraku Kitsune cyberdeck",
"rating": 4,
"quantity": 1,
"notes": "DR 4, 7/6, 8 slots"
},
{
"name": "Hermes Ikon commlink",
"rating": 5,
"quantity": 1,
"notes": "DR 5, 3/0"
},
{ "name": "Certified credstick", "quantity": 1, "notes": "3,500¥" },
{ "name": "Heavy pistol ammo box (Ruger)", "quantity": 30 }
],
"spells": [],
"adept_powers": []
}
}

View File

@@ -0,0 +1,514 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "7a353684-2e91-4e78-98e3-0d32f508a8c1",
"ruleset_id": "sr6",
"entity_type": "dossier",
"display_name": "Emu (Human Rigger Dossier)",
"description": "Full 4-page narrative dossier for Emu, Human Rigger from the SR6 Beginner Box.",
"template_id": "dossier_sheet",
"properties": {
"alias": "Emu",
"real_name": "Emu",
"player": "Catalyst Pregen",
"archetype": "Human Rigger",
"quote": "\"(when jumped into her Optic-X2 or Roto-Drones) Flightless bird, my arse.\" / \"Stop shooting my bloody drones!\" / \"You know we don't actually say 'toss a shrimp on the barbie,' right? They're prawns, for frag's sake.\"",
"pitch": "Play this character if you like burned rubber and scorched engine oil, commanding a metal-and-plastic army with your brain, or rushing in and charging out of any sticky situation.",
"metatype": "Human",
"ethnicity": "Aboriginal Australian (Wiradjuri)",
"age": 23,
"sex": "Female",
"height": "1.65 m",
"weight": "57 kg",
"reputation": 2,
"heat": 0,
"karma": 0,
"total_karma": 0,
"misc": "Former mercenary drone pilot with an armed sports sedan, drone fleet, and Ciarniello Mafia debt.",
"notes": "Human Rigger commanding aerial recon drones, combat roto-drones, and a modified GMC Commodore sport sedan.",
"background": "Some are born into shadowrunning, some achieve it, and some have shadowrunning thrust upon them. The middle group often choose to become runners because they refuse to be pushed into the role in which their upbringing cast them, whether as a starving barrens rat, a comfortable-but-oppressed wageslave, or a hidebound traditionalist who follows the old ways just because their elders said they should. If Emu's life had begun in one of those roles, she might still be there today. Her parents were both Wiradjuri, descended from the indigenous peoples of New South Wales. Emu's mother came from a family of vocal indigenous-rights activists, while her father was a Japancorp wageslave and a product of modern Australian society, despite his ethnic heritage. As a result, Emu spent her formative years being pulled between two different lives. One week, she'd be on 'holiday' in the Outback with her mother, stepfather, and half-siblings, eating bush tucker and being told how her exposure to 'colonial culture' had prevented her from Awakening into a koradji like her grandfather. The next, she'd be in an air-conditioned corporate housing complex, being the token 'Indigenous friend' for other kids her age the way her father was to his colleagues. Feeling like an outsider no matter where she went, Emu took refuge in tinkering, whether with the complex electronics her father designed or the beat-up old trucks her mother's family drove whenever they went out woop woop. When Emu was a teenager, she fled both of her parents' homes for a technical school in Queensland, where she started training as a mechanic. She took up racing both cars and drones in her spare time, and she earned the attention of a mercenary recruiter, who offered her a contract as a drone pilot. Emu's time as a merc had definite rough patches—she didn't handle military-style discipline well—but the combat experience was the foundation she needed to become a shadowrunner. When her contract was up, she decided it was time for a change of pace, so she left Australia for Seattle. Emu's early days in the shadows were rocky; she kept a roof over her head, barely, but her lack of a control rig made it hard for her to catch and pull off better-paying jobs. In sheer frustration, she borrowed a pile of nuyen from the Ciarniello Mafia to get the headware she needed. Luckily, she ran into Ms. Myth soon after, and so far she's managed to keep her creditors at bay—but if the work dries up, it might be time for another inter-city 'change of pace.'",
"preferred_tactics": "As a rigger, Emu's responsibilities fall into three primary areas: scouting, ferrying her teammates around, and fire support. She favours using her GMC Commodore sport sedan during runs, since it's fast enough to outrun most pursuit and sports a pair of Mossberg shotguns to deal with (almost) anything that can keep up with it, but she keeps a stereotypical 'rigger van' (a GMC Bulldog) for occasions when she needs to field her entire fleet of drones. The drone that gets the most use by far is Emu's Lockheed Optic-X2, a stealth recon platform she uses to scout locations before runs and keep an eye on events on the ground as they unfold. Her MCT Hornets fill the same role when runs take place indoors. If and when a fight breaks out, Emu turns to her trusty Roto-Drones, usually armed with automatic weapons to lay down suppressive fire while Emu and the rest of the team escape.",
"roleplaying_tips": "On the outside, Emu combines the laid-back attitude that many Australians possess with the self-assuredness of a rigger who knows she's damn good at what she does. The flip side is that because she's used to feeling like an outsider, she's wary of commitment, preferring to keep even her teammates at arm's length. She'll have their backs in a scrap, but she's careful not to let herself get too attached.",
"vital_contacts": [
{
"name": "Giovanni Valenti",
"role": "Vehicle & Drone Fixer",
"description": "Owner of Gio's Garage, drone and vehicle salesman."
},
{
"name": "Steve Draper",
"role": "Knight Errant Dispatcher",
"description": "Knight Errant dispatcher on the take."
},
{
"name": "Tomaso di Stefano",
"role": "Mafia Loan Shark",
"description": "Ciarniello Mafia loan shark ('Tommy D') and occasional Mr. Johnson."
}
],
"favorite_hangouts": [
{
"name": "Black's Junk Yard",
"location": "Puyallup",
"description": "A scrap yard and workshop in Puyallup."
},
{
"name": "Gio's Garage",
"location": "Everett",
"description": "An Everett chop shop owned by the Ciarniello Mafia."
},
{
"name": "Grounds for Appeal",
"location": "University District",
"description": "A cafe near the University of Washington."
}
],
"favorite_downtime_activities": [
"Racing cars and drones all over the 'plex",
"Tinkering with her various vehicles",
"Watching action trids, especially Battlerun and Karl Kombatmage Reloaded"
],
"sample_actions": [
{
"action": "Ground Vehicle Operation (Jumped In)",
"action_type": "Major",
"dice_pool": 13,
"notes": "Piloting 7 + Intuition 6 (+2 Control Rig bonus, -Speed penalty)"
},
{
"action": "Flying Drone Operation (Jumped In)",
"action_type": "Major",
"dice_pool": 13,
"notes": "Piloting 7 + Intuition 6 (Controls Optic-X2 or Roto-Drones directly)"
},
{
"action": "Vehicle Gunnery (Jumped In)",
"action_type": "Major",
"dice_pool": 12,
"notes": "Engineering 6 + Gunnery 2 + Logic 4 (Operates Commodore shotguns / drone mounts)"
},
{
"action": "Ground Vehicle Operation (Manual)",
"action_type": "Major",
"dice_pool": 13,
"notes": "Piloting 7 + Reaction 4 + Reflex Recorder 1 + Spec 2 - 1 = 13"
},
{
"action": "Sensor Perception / Spot Ambush",
"action_type": "Minor",
"dice_pool": 9,
"notes": "Perception 3 + Intuition 6 (Via Cybereyes / drone sensors)"
},
{
"action": "Command Drone (Autopilot Orders)",
"action_type": "Minor",
"dice_pool": 10,
"notes": "Drone Pilot 4 + Autosoft 6 (Autonomous recon or evasive maneuvers)"
},
{
"action": "Juryrig Device / Emergency Repair",
"action_type": "Major",
"dice_pool": 10,
"notes": "Engineering 6 + Logic 4 (Grants 1 bonus Edge)"
},
{
"action": "Assault Rifle Shot (FN HAR)",
"action_type": "Major",
"dice_pool": 5,
"notes": "Firearms 3 + Agility 2 (Gas-vent 2, laser sight)"
}
],
"edge_boosts": [
{
"cost": 1,
"name": "Reroll one die",
"description": "Reroll any one die from your roll that failed to score a hit (Post)."
},
{
"cost": 1,
"name": "Add 3 to Initiative",
"description": "Add +3 to your Initiative Score for the combat turn (Any)."
},
{
"cost": 2,
"name": "+1 to single die",
"description": "Add +1 to any single die result, turning a 4 into a 5 (hit) (Post)."
},
{
"cost": 2,
"name": "Give ally 1 Edge",
"description": "Pass 1 Edge point to an allied runner (Any)."
},
{
"cost": 2,
"name": "Negate 1 Edge of foe",
"description": "Negate 1 point of Edge held by an opponent (Pre)."
},
{
"cost": 3,
"name": "Buy automatic hit",
"description": "Add 1 automatic hit to test result (Any)."
},
{
"cost": 3,
"name": "Heal Stun Damage",
"description": "Immediately clear 1 box from your stun damage track."
},
{
"cost": 4,
"name": "Add Edge to Pool",
"description": "Add full Edge attribute (+5) to dice pool and 6s explode (Pre)."
}
],
"attributes": {
"body": 2,
"agility": 2,
"reaction": 4,
"strength": 2,
"willpower": 4,
"logic": 4,
"intuition": 6,
"charisma": 3,
"edge": 5,
"essence": 3.6,
"magic": 0,
"edge_points": 5
},
"combat_info": {
"initiative": "10 + 1D6",
"matrix_initiative": "10 + 1D6",
"astral_initiative": "—",
"defense_rating": 6,
"unarmed_ar": 6,
"composure": 7,
"judge_intentions": 10,
"memory": 10,
"lift_carry": 6,
"movement": "5/10/15"
},
"condition_monitor": {
"physical_damage": 0,
"physical_boxes": 9,
"stun_damage": 0,
"stun_boxes": 10,
"overflow": 0
},
"skills": [
{
"name": "Piloting",
"rating": 7,
"attribute": "Reaction",
"specialization": "Groundcraft",
"dice_pool": 13,
"type": "Action"
},
{
"name": "Engineering",
"rating": 6,
"attribute": "Logic",
"specialization": "Gunnery",
"dice_pool": 12,
"type": "Action"
},
{
"name": "Perception",
"rating": 3,
"attribute": "Intuition",
"dice_pool": 9,
"type": "Action"
},
{
"name": "Firearms",
"rating": 3,
"attribute": "Agility",
"dice_pool": 5,
"type": "Action"
},
{
"name": "Electronics",
"rating": 2,
"attribute": "Logic",
"dice_pool": 6,
"type": "Action"
},
{
"name": "Con",
"rating": 2,
"attribute": "Charisma",
"dice_pool": 5,
"type": "Action"
},
{
"name": "Influence",
"rating": 2,
"attribute": "Charisma",
"dice_pool": 5,
"type": "Action"
},
{
"name": "Stealth",
"rating": 2,
"attribute": "Agility",
"dice_pool": 4,
"type": "Action"
},
{
"name": "Biotech",
"rating": 1,
"attribute": "Logic",
"dice_pool": 5,
"type": "Action"
},
{
"name": "Close Combat",
"rating": 1,
"attribute": "Agility",
"dice_pool": 3,
"type": "Action"
},
{
"name": "Seattle Metroplex Geography",
"rating": 2,
"attribute": "Logic",
"dice_pool": 6,
"type": "Knowledge"
},
{
"name": "Smuggling Routes",
"rating": 2,
"attribute": "Logic",
"dice_pool": 6,
"type": "Knowledge"
}
],
"qualities": [
{
"name": "Addiction (Cigarettes)",
"type": "Negative",
"notes": "Level 3 addiction; penalty if in withdrawal."
},
{
"name": "Gearhead",
"type": "Positive",
"notes": "May spend Edge on repair tests and vehicle handling."
},
{
"name": "In Debt",
"type": "Negative",
"notes": "Ciarniello Mafia: 200,000¥ debt, 8,000¥/month interest."
},
{
"name": "Juryrigger",
"type": "Positive",
"notes": "Gains bonus Edge point on Juryrigging tests."
},
{
"name": "SINner",
"type": "Negative",
"notes": "Standard national/system SIN; extra scrutiny and trace exposure."
}
],
"currency_and_ids": {
"nuyen": 265,
"primary_lifestyle": "Low (3 months prepaid)",
"licenses": "Concealed Carry (Assault Rifle), Drone Rigging, Cyberware",
"fake_ids": [
{
"name": "Kylie Mason",
"rating": 3,
"lifestyle": "Low",
"funds": "265¥",
"licenses": "Firearms, Cyberware, RCC"
}
]
},
"contacts": [
{
"name": "Giovanni Valenti",
"loyalty": 2,
"connection": 3,
"notes": "Drone and vehicle fixer, owner of Gio's Garage in Everett."
},
{
"name": "Lenny",
"loyalty": 3,
"connection": 2,
"notes": "Underground safehouse fixer."
},
{
"name": "Steve Draper",
"loyalty": 1,
"connection": 3,
"notes": "Knight Errant dispatcher on the take."
},
{
"name": "Tomaso di Stefano",
"loyalty": 2,
"connection": 2,
"notes": "Ciarniello Mafia loan shark ('Tommy D') and occasional Mr. Johnson."
}
],
"primary_armor": {
"name": "Armor jacket",
"rating": "+4",
"notes": "+4 Defense Rating bonus"
},
"primary_ranged_weapon": {
"name": "FN HAR Assault Rifle",
"dv": "5P",
"mode": "SA/BF/FA",
"attack_rating": { "close": 3, "near": 11, "far": 6, "extreme": 1 },
"ammo": "35(c)",
"notes": "w/ gas-vent system, laser sight"
},
"primary_melee_weapon": {
"name": "Unarmed Strike",
"dv": "2S",
"close": 2,
"notes": "Natural"
},
"ranged_weapons": [
{
"name": "FN HAR Assault Rifle",
"dv": "5P",
"mode": "SA/BF/FA",
"attack_rating": { "close": 3, "near": 11, "far": 6, "extreme": 1 },
"ammo": "35(c)",
"notes": "w/ gas-vent system, laser sight"
}
],
"melee_weapons": [
{
"name": "Unarmed Strike",
"dv": "2S",
"close": 2,
"notes": "Natural"
}
],
"armor": [
{
"name": "Armor jacket",
"rating": "+4",
"notes": "+4 DR bonus"
}
],
"matrix_stats": {
"device_name": "Maersk Spider RCC",
"device_rating": 4,
"attack": 0,
"sleaze": 0,
"data_processing": 4,
"firewall": 5,
"programs": ["Encryption", "Signal Scrubber", "Toolbox"],
"matrix_damage": 0
},
"augmentations": [
{
"name": "Control Rig",
"rating": 2,
"notes": "Grants +2 dice pool bonus and +1 Edge on vehicle skill tests when jumped in.",
"essence": 2.0
},
{
"name": "Cybereyes",
"rating": 3,
"notes": "w/ flare compensation, image link, low-light vision, smartlink, thermographic vision",
"essence": 0.4
},
{
"name": "Reflex Recorder",
"rating": 1,
"notes": "Piloting skill (+1 bonus rating)",
"essence": 0.1
}
],
"vehicles": [
{
"name": "GMC Commodore",
"handling": "4/3",
"accel": "14",
"speed": "120/160",
"armor": 8,
"body": 10,
"pilot": 2,
"sensor": 2,
"notes": "Sports sedan w/ 2 weapon mounts (Mossberg CMDT shotguns, 250 rds)"
},
{
"name": "GMC Bulldog",
"handling": "3/2",
"accel": "10",
"speed": "90/130",
"armor": 12,
"body": 16,
"pilot": 2,
"sensor": 2,
"notes": "Step-van rigger mobile workshop"
},
{
"name": "Lockheed Optic-X2",
"handling": "4",
"accel": "12",
"speed": "80",
"armor": 2,
"body": 3,
"pilot": 4,
"sensor": 4,
"notes": "Stealth recon drone w/ R6 Clearsight, R6 Stealth autosofts"
},
{
"name": "MCT-Nissan Roto-Drone (x3)",
"handling": "4",
"accel": "8",
"speed": "70",
"armor": 6,
"body": 6,
"pilot": 3,
"sensor": 3,
"notes": "Combat drone w/ weapon mount (Ingram Smartgun XI), R6 Maneuver, R6 Targeting autosofts"
},
{
"name": "MCT Hornet (x4)",
"handling": "3",
"accel": "5",
"speed": "30",
"armor": 0,
"body": 1,
"pilot": 2,
"sensor": 2,
"notes": "Micro indoor recon drone w/ R6 Stealth autosoft"
},
{
"name": "Horizon Flying Eye (x3)",
"handling": "3",
"accel": "6",
"speed": "40",
"armor": 1,
"body": 1,
"pilot": 2,
"sensor": 2,
"notes": "Micro surveillance drone w/ smoke grenade upgrade, R6 Maneuver autosoft"
}
],
"gear": [
{ "name": "Armor jacket", "rating": 4, "quantity": 1 },
{ "name": "Automotive mechanic shop", "quantity": 1 },
{
"name": "Maersk Spider RCC",
"rating": 4,
"quantity": 1,
"notes": "DR 4, D/F 4/5"
},
{
"name": "Renraku Sensei commlink",
"rating": 3,
"quantity": 1,
"notes": "DR 3, D/F 2/0"
},
{ "name": "Rating 3 Targeting Autosoft (HK 227)", "quantity": 1 },
{ "name": "Certified credstick", "quantity": 1, "notes": "265¥" },
{ "name": "FN HAR ammo box", "quantity": 175 }
],
"spells": [],
"adept_powers": []
}
}

View File

@@ -0,0 +1,461 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "4fb9f271-e5d4-4a41-b0be-4a18019b8833",
"ruleset_id": "sr6",
"entity_type": "dossier",
"display_name": "Frostburn (Ork Combat Mage Dossier)",
"description": "Full 4-page narrative dossier for Frostburn, Ork Combat Mage from the SR6 Beginner Box.",
"template_id": "dossier_sheet",
"properties": {
"alias": "Frostburn",
"real_name": "Kendra Maguire",
"player": "Catalyst Pregen",
"archetype": "Ork Combat Mage",
"quote": "\"That's why they say to geek the mage first.\" / \"Boring is always best.\" / \"(long-suffering sigh)\"",
"pitch": "Play this character if you like damaging things with fire and ice, and then fixing the people you like.",
"metatype": "Ork",
"ethnicity": "White",
"age": 25,
"sex": "Female",
"height": "2 m",
"weight": "115 kg",
"reputation": 2,
"heat": 0,
"karma": 0,
"total_karma": 0,
"misc": "Former NeoNET corporate security magical operative.",
"notes": "Ork Combat Mage specializing in fire, ice, protective illusions, and healing.",
"background": "As a child, Frostburn was a made-for-trid underdog success story. She and her family were forced out of the then–Ork Underground by dwarfs intent on 'reclaiming' property they hadn't cared about when it wasn't worth anything. They ended up in Redmond, scraping by on Frostburn's mom's job as a janitor at a NeoNET office building—until Frostburn Awakened. The megacorp fell all over itself to secure the woman's future, even giving her a full scholarship to the University of Washington. The day she showed up at the NeoNET offices, though, old stereotypes had started to creep in. Frostburn's tusks got her assigned to the Seattle division of NeoNET's corporate security force. Even if it wasn't her dream job, Frostburn would eventually decide that NeoSec wasn't such a bad place to work. A few years into the job, she was happy enough that when some 'loss-prevention consultant' gave the ork mage her commcode and said to call if she ever got tired of NeoSec, Frostburn took the pretty troll's contact info, then promptly forgot all about it. When NeoNET collapsed, that commcode became Frostburn's lifeline. The troll woman, known as Ms. Myth, didn't seem the slightest bit surprised when Frostburn called. Myth assured her there were plenty of jobs for a spell-slinger with a background in corporate security—if she was willing to work on the grey side of the law. Not wanting to see her family back in the Redmond Barrens (or worse), Frostburn took her first step into the shadows.",
"preferred_tactics": "Frostburn knows better than anyone else on the team that magic can turn the tide of a battle—and that chucking fireballs at every enemy she sees will have her passed out on the floor from the drain before she has a chance to really make a difference. Ironically, the 'fire and ice' combo of spells that earned the mage her street name are usually a last resort. Instead, she prefers illusory spells to make the team's enemies less effective or defensive spells to protect the others against hostile magic, and she can cast a Heal spell if one of her fellow runners takes a blow. If the team needs to capture a target alive, whether to question them about further resistance or because Mr. Johnson wants them retrieved, she'll use spells focused on stunning the target instead of killing them. In extended battles, Frostburn will summon spirits to fight on her behalf or resort to firearms to minimize drain, though she'll happily take potshots with her trademark elemental spells if she gets the chance. She also frequently uses astral projection to scout targets during the planning stage of a run.",
"roleplaying_tips": "Frostburn doesn't take anything for granted. Her Awakening might've made her special, but she tries not to let it go to her head. She's used to being the provider for her family, and she takes that role seriously, which sometimes means taking jobs that make her squeamish for the sake of paying the bills. She's also the closest thing the group has to a 'team mom,' constantly fussing over the others, which sometimes makes her seem like a bit of a party pooper. The rest of the team knows she has their back, though, even if they wish she'd get off it sometimes. Playing against ork stereotypes, Frostburn is also the most levelheaded member of the team (besides Rude, anyway). She likes runs going according to plan and would rather play it safe for a guaranteed payday than risk going double or nothing. She doesn't lose her cool when a run starts going to drek—hell, her job at NeoNET was to show up when things had already gone to drek—and she has enough leadership experience to rally her comrades when the going gets tough. If she ever blows a run, it won't be because she gave up.",
"vital_contacts": [
{
"name": "Brimstone",
"role": "Elf Combat Mage",
"description": "Former NeoNET coworker and current fellow runner."
},
{
"name": "Harrison Kellerman",
"role": "Talismonger",
"description": "Owner of the Io Pan! lore store in Downtown Seattle."
},
{
"name": "Jules Maguire",
"role": "Ork Rigger",
"description": "Frostburn's older brother and Cascade Ork smuggler."
}
],
"favorite_hangouts": [
{
"name": "Crusher 495",
"location": "Redmond",
"description": "Ork-run nightclub and community hub in Redmond."
},
{
"name": "Downfall",
"location": "Redmond",
"description": "Awakened-friendly bar in Redmond."
},
{
"name": "Magician's Feast",
"location": "Auburn",
"description": "Salish-American restaurant in Auburn offering illusion shows."
}
],
"favorite_downtime_activities": [
"Hiking and camping in the rural parts of the metroplex",
"Listening to goblin rock",
"Spending time with her family"
],
"sample_actions": [
{
"action": "Cast Spell (Sorcery)",
"action_type": "Major",
"dice_pool": 12,
"notes": "Sorcery 6 + Magic 6 (Adjust: Amp up +1 DV / +2 drain; Increase Area +2m / +1 drain)"
},
{
"action": "Counterspell (Boosted Defense)",
"action_type": "Major",
"dice_pool": 12,
"notes": "Sorcery 6 + Magic 6 (Sphere 2m radius, net hits add to defense pool of allies)"
},
{
"action": "Resist Drain",
"action_type": "Free",
"dice_pool": 12,
"notes": "Logic 5 + Willpower 7 (Drain value resisted, excess applied to Stun track)"
},
{
"action": "Ranged Shot (Walther Palm Pistol)",
"action_type": "Major",
"dice_pool": 7,
"notes": "Firearms 3 + Agility 4 (BF mode, DV 2P)"
},
{
"action": "Melee Strike (Extendible Baton)",
"action_type": "Major",
"dice_pool": 6,
"notes": "Close Combat 2 + Agility 4 (Club, DV 2S)"
},
{
"action": "Perception / Spot Trouble",
"action_type": "Minor",
"dice_pool": 4,
"notes": "Perception 3 + Intuition 1 (Low-Light Vision)"
},
{
"action": "Astral Perception / Assensing",
"action_type": "Minor",
"dice_pool": 4,
"notes": "Astral 3 + Intuition 1 (Inspect auras, signatures, and astral forms)"
},
{
"action": "Sneak / Infiltration",
"action_type": "Major",
"dice_pool": 5,
"notes": "Stealth 1 + Agility 4"
}
],
"edge_boosts": [
{
"cost": 1,
"name": "Reroll one die",
"description": "Reroll any one die from your roll that failed to score a hit (Post)."
},
{
"cost": 1,
"name": "Add 3 to Initiative",
"description": "Add +3 to your Initiative Score for the combat turn (Any)."
},
{
"cost": 2,
"name": "+1 to single die",
"description": "Add +1 to any single die result, turning a 4 into a 5 (hit) (Post)."
},
{
"cost": 2,
"name": "Give ally 1 Edge",
"description": "Pass 1 Edge point to an allied runner (Any)."
},
{
"cost": 3,
"name": "Heal Stun Damage",
"description": "Immediately clear 1 box from your stun damage track."
},
{
"cost": 4,
"name": "Add Edge to Pool",
"description": "Add full Edge attribute (+2) to dice pool and 6s explode (Pre)."
},
{
"cost": 5,
"name": "Glitch Foe",
"description": "2s count as glitches for target on their opposed test (Pre)."
}
],
"attributes": {
"body": 4,
"agility": 4,
"reaction": 2,
"strength": 3,
"willpower": 7,
"logic": 5,
"intuition": 1,
"charisma": 3,
"edge": 2,
"essence": 6.0,
"magic": 6,
"edge_points": 2
},
"combat_info": {
"initiative": "3 + 1D6",
"matrix_initiative": "3 + 1D6",
"astral_initiative": "7 + 2D6",
"defense_rating": 7,
"unarmed_ar": 4,
"composure": 10,
"judge_intentions": 4,
"memory": 12,
"lift_carry": 7,
"movement": "5/10/+1"
},
"condition_monitor": {
"physical_damage": 0,
"physical_boxes": 10,
"stun_damage": 0,
"stun_boxes": 12,
"overflow": 0
},
"skills": [
{
"name": "Sorcery",
"rating": 6,
"attribute": "Magic",
"dice_pool": 12,
"type": "Action"
},
{
"name": "Firearms",
"rating": 3,
"attribute": "Agility",
"dice_pool": 7,
"type": "Action"
},
{
"name": "Close Combat",
"rating": 2,
"attribute": "Agility",
"dice_pool": 6,
"type": "Action"
},
{
"name": "Astral",
"rating": 3,
"attribute": "Intuition",
"dice_pool": 4,
"type": "Action"
},
{
"name": "Perception",
"rating": 3,
"attribute": "Intuition",
"dice_pool": 4,
"type": "Action"
},
{
"name": "Athletics",
"rating": 1,
"attribute": "Agility",
"dice_pool": 5,
"type": "Action"
},
{
"name": "Stealth",
"rating": 1,
"attribute": "Agility",
"dice_pool": 5,
"type": "Action"
},
{
"name": "Magical Security",
"rating": 2,
"attribute": "Logic",
"dice_pool": 7,
"type": "Knowledge"
},
{
"name": "Security Procedures",
"rating": 2,
"attribute": "Logic",
"dice_pool": 7,
"type": "Knowledge"
},
{
"name": "Seattle Underground",
"rating": 2,
"attribute": "Logic",
"dice_pool": 7,
"type": "Knowledge"
}
],
"qualities": [
{
"name": "Built Tough",
"type": "Positive",
"notes": "+1 Physical damage track box."
},
{
"name": "Low-light Vision",
"type": "Positive",
"notes": "Natural ork low-light perception."
},
{
"name": "Dependents",
"type": "Negative",
"notes": "Supports her family in Redmond."
},
{
"name": "SINner",
"type": "Negative",
"notes": "Corporate SIN from former employment with NeoNET."
},
{
"name": "Socially Awkward",
"type": "Negative",
"notes": "Direct, blunt, and exasperated demeanor."
}
],
"currency_and_ids": {
"nuyen": 3500,
"primary_lifestyle": "Low (prepaid)",
"licenses": "Concealed Carry, Registered Awakened Practitioner",
"fake_ids": [
{
"name": "Evelyn Graves",
"rating": 3,
"lifestyle": "Low",
"funds": "3,500¥",
"licenses": "Firearms, Mage License"
}
]
},
"contacts": [
{
"name": "Brimstone",
"loyalty": 3,
"connection": 3,
"notes": "Elf combat mage, former NeoNET coworker and current fellow runner."
},
{
"name": "Harrison Kellerman",
"loyalty": 2,
"connection": 4,
"notes": "Talismonger and owner of the Io Pan! lore store in Downtown Seattle."
},
{
"name": "Jules Maguire",
"loyalty": 4,
"connection": 2,
"notes": "Ork rigger, Frostburn's older brother and Cascade Ork smuggler."
}
],
"primary_armor": {
"name": "Lined coat",
"rating": "+3",
"notes": "+3 Defense Rating bonus"
},
"primary_ranged_weapon": {
"name": "Walther Palm Pistol",
"dv": "2P",
"mode": "BF",
"attack_rating": { "close": 12, "near": 7, "far": "—", "extreme": "—" },
"ammo": "2(b)",
"notes": "Hold-out pistol"
},
"primary_melee_weapon": {
"name": "Extendible baton",
"dv": "2S",
"close": 5,
"notes": "Club"
},
"ranged_weapons": [
{
"name": "Walther Palm Pistol",
"dv": "2P",
"mode": "BF",
"attack_rating": { "close": 12, "near": 7, "far": "—", "extreme": "—" },
"ammo": "2(b)",
"notes": "Double-barreled hold-out"
}
],
"melee_weapons": [
{
"name": "Extendible baton",
"dv": "2S",
"close": 5,
"notes": "Concealable club"
}
],
"armor": [
{
"name": "Lined coat",
"rating": "+3",
"notes": "+3 DR bonus"
}
],
"matrix_stats": {
"device_name": "Renraku Sensei commlink",
"device_rating": 3,
"attack": 0,
"sleaze": 0,
"data_processing": 2,
"firewall": 0,
"programs": [],
"matrix_damage": 0
},
"augmentations": [],
"vehicles": [],
"gear": [
{ "name": "Lined coat", "rating": 3, "quantity": 1 },
{ "name": "Renraku Sensei commlink", "rating": 3, "quantity": 1 },
{ "name": "Certified credstick", "quantity": 1, "notes": "3,500¥" },
{ "name": "Walther Palm Pistol ammo box", "quantity": 20 }
],
"spells": [
{
"name": "Fireball",
"category": "S",
"type_target": "Physical",
"range": "LOS (Area)",
"duration": "Instant",
"drain": "5",
"notes": "Elemental fire damage area effect"
},
{
"name": "Flamestrike",
"category": "S",
"type_target": "Physical",
"range": "LOS",
"duration": "Instant",
"drain": "4",
"notes": "Single-target direct flame bolt"
},
{
"name": "Ice Spear",
"category": "S",
"type_target": "Physical",
"range": "LOS",
"duration": "Instant",
"drain": "4",
"notes": "Piercing elemental cold damage"
},
{
"name": "Heal",
"category": "S",
"type_target": "Essence",
"range": "Touch",
"duration": "Permanent",
"drain": "3",
"notes": "Repairs physical damage boxes"
},
{
"name": "Combat Sense",
"category": "S",
"type_target": "Mana",
"range": "Touch",
"duration": "Sustained",
"drain": "3",
"notes": "Adds defense dice and awareness"
},
{
"name": "Improved Invisibility",
"category": "S",
"type_target": "Physical",
"range": "Touch",
"duration": "Sustained",
"drain": "4",
"notes": "Cloaks target from visual & technological detection"
},
{
"name": "Levitate",
"category": "S",
"type_target": "Physical",
"range": "LOS",
"duration": "Sustained",
"drain": "4",
"notes": "Telekinetically lifts objects or persons"
},
{
"name": "Mana Barrier",
"category": "S",
"type_target": "Mana",
"range": "LOS (Area)",
"duration": "Sustained",
"drain": "4",
"notes": "Blocks astral forms and hostile spells"
}
],
"adept_powers": []
}
}

View File

@@ -0,0 +1,403 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "4c9e88d1-21e3-49fb-a720-6d38e24fa218",
"ruleset_id": "sr6",
"entity_type": "dossier",
"display_name": "Rude (Troll Street Samurai Dossier)",
"description": "Full 4-page narrative dossier for Rude, Troll Street Samurai from the SR6 Beginner Box.",
"template_id": "dossier_sheet",
"properties": {
"alias": "Rude",
"real_name": "Unknown (\"Rudiarius\")",
"player": "Catalyst Pregen",
"archetype": "Troll Street Samurai",
"quote": "\"You said non-lethal. He ain't dead. You gonna yap 'til he bleeds out?\" / \"I do the job, I get paid.\"",
"pitch": "Play this character if you like breaking things, shooting things, and forgetting to care what people think of you.",
"metatype": "Troll",
"ethnicity": "White",
"age": 29,
"sex": "Male",
"height": "2.4 m",
"weight": "310 kg",
"reputation": 3,
"heat": 1,
"karma": 0,
"total_karma": 0,
"misc": "Carries an ancient wooden practice sword from his gladiator past.",
"notes": "Street Samurai meat shield with amnesia and direct brutal tactics.",
"background": "Ask Rude where he's from, and he'll shrug and say he doesn't know—because he doesn't. There are a lot of things Rude doesn't know, like his real name or what his life was like growing up or how he first descended into the shadows. In fact, he doesn't remember much of anything before he woke up in a street clinic in Chicago about four years ago. He guesses he's from the CAS because he's got a drawl, and he calls himself Rude because one of the only things he does remember is a man calling him 'Rudiarius' and handing him a wooden sword. He found out later that a rudiarius was what people in ancient Rome called a gladiator who chose to keep fighting in the arena after they'd won their freedom. That wooden sword is the only thing Rude has from whatever his life used to be, just because he likes having it around. Of course, with no memory of how or why he'd been in Chicago, Rude didn't feel particularly compelled to stay there. Instead, he signed on with a smuggler crew as security and traveled the world. If there was one place Rude kept coming back to, though, it was Seattle; he probably had more layovers there than every other major city on the Pacific Rim combined. He ran into Ms. Myth at a bar in the Seattle Underground during one of those layovers, and when she offered him a spot on her new team, he decided to stick around for a while.",
"preferred_tactics": "Having a bomb go off inside his brain might've fragged Rude's memories, but it didn't dull his skills. In keeping with the whole 'gladiator' thing, his fighting style is direct and brutal. He's most comfortable with hacking his enemies to pieces with a sword, and since being a troll makes him the team's de facto meat shield, he might as well do it in a way the other side won't be able to ignore. When the situation calls for ranged combat, though, Rude's no slouch with a gun. His favorite sidearm is a beaten-up Ruger Super Warhawk sized to fit a troll, but he's equally comfortable with everything from submachine guns to assault cannons, and he burns through APDS and explosive ammo like it's going out of style. Rude isn't entirely 'pink mohawk'—he understands the value of stealth, even if his idea of stealth is 'shoot 'em before they see you' instead of 'sneak past so they don't.'",
"roleplaying_tips": "Rude lives up to his name, but not because he goes out of his way to be a jerk—he just doesn't really think about other people's feelings. A life on the streets has hardened Rude's will and stolen a lot of his empathy, and his amnesia means he can't remember what having empathy was like back when he cared. Because of that, he doesn't usually consider whether his words might offend someone before he speaks. The other side of Rude's lack of empathy is that his decisions are motivated solely by his own interests. He knows full well that in the long term, he'll benefit more from working with a competent, trustworthy team and keeping his rep clean than by betraying people over a few extra nuyen.",
"vital_contacts": [
{
"name": "Hez",
"role": "Neighborhood Watch Lieutenant",
"description": "Skraacha gang / neighborhood watch lieutenant in the Seattle Underground."
},
{
"name": "Jimmy Vitello",
"role": "Industrial Manager / Former Muscle",
"description": "Manager at Max's Ironworks and former Mafia muscle."
},
{
"name": "Momma Dot",
"role": "Arms Dealer",
"description": "Owner of BBC Weapon Works in Renton."
}
],
"favorite_hangouts": [
{
"name": "BBC Weapon Works",
"location": "Renton",
"description": "A troll-owned gun shop in Renton."
},
{
"name": "The Biz",
"location": "Seattle Underground",
"description": "The black-market hub of the Seattle Underground."
},
{
"name": "The Coliseum",
"location": "Snohomish",
"description": "A paracritter-fighting arena in Snohomish."
}
],
"favorite_downtime_activities": [
"Browsing gun-aficionado sites for new purchase ideas",
"Rocking out at orxploitation shows",
"Watching paracritter fights at the Coliseum"
],
"sample_actions": [
{
"action": "Sniper Shot (Ares Desert Strike)",
"action_type": "Major",
"dice_pool": 12,
"notes": "Firearms 6 + Agility 5 + smartlink 1 (SA mode, DV 5P)"
},
{
"action": "Heavy Pistol Double Tap (Predator VI)",
"action_type": "Major",
"dice_pool": 12,
"notes": "Firearms 6 + Agility 5 + smartlink 1 (SA mode, DV 4P)"
},
{
"action": "Melee Cleave (Sword)",
"action_type": "Major",
"dice_pool": 9,
"notes": "Close Combat 3 + Agility 5 + troll reach (DV 3P)"
},
{
"action": "Dodge Physical Attack",
"action_type": "Minor",
"dice_pool": 10,
"notes": "Reaction 6 + Intuition 4"
},
{
"action": "Damage Soak",
"action_type": "Free",
"dice_pool": 7,
"notes": "Body 7 (+2 Armor Jacket adds to Defense Rating)"
},
{
"action": "Pick Mechanical/Electronic Lock",
"action_type": "Major",
"dice_pool": 8,
"notes": "Engineering 2 + Logic 4 + Lockpicking spec 2"
},
{
"action": "Perception / Spot Ambush",
"action_type": "Minor",
"dice_pool": 7,
"notes": "Perception 3 + Intuition 4 (Thermographic Vision)"
}
],
"edge_boosts": [
{
"cost": 1,
"name": "Reroll one die",
"description": "Reroll any one die from your roll that failed to score a hit."
},
{
"cost": 2,
"name": "+1 to single die",
"description": "Add +1 to any single die result, turning a 4 into a 5 (hit)."
},
{
"cost": 3,
"name": "Heal Stun Damage",
"description": "Immediately clear 1 box from your stun damage track."
},
{
"cost": 4,
"name": "Add Edge to Pool",
"description": "Add full Edge attribute (+5) to dice pool and 6s explode."
}
],
"attributes": {
"body": 7,
"agility": 5,
"reaction": 6,
"strength": 6,
"willpower": 4,
"logic": 4,
"intuition": 4,
"charisma": 2,
"edge": 5,
"essence": 5.5,
"edge_points": 5
},
"combat_info": {
"initiative": "10 + 1D6",
"matrix_initiative": "10 + 1D6",
"astral_initiative": "—",
"defense_rating": 11,
"unarmed_ar": 7,
"composure": 6,
"judge_intentions": 6,
"memory": 8,
"lift_carry": 11,
"movement": "5/10/+1"
},
"condition_monitor": {
"physical_damage": 0,
"physical_boxes": 12,
"stun_damage": 0,
"stun_boxes": 10,
"overflow": 0
},
"skills": [
{
"name": "Firearms",
"rating": 6,
"attribute": "Agility",
"dice_pool": 12,
"type": "Action"
},
{
"name": "Close Combat",
"rating": 3,
"attribute": "Agility",
"dice_pool": 9,
"type": "Action"
},
{
"name": "Athletics",
"rating": 3,
"attribute": "Agility",
"dice_pool": 9,
"type": "Action"
},
{
"name": "Perception",
"rating": 3,
"attribute": "Intuition",
"dice_pool": 7,
"type": "Action"
},
{
"name": "Engineering",
"rating": 2,
"attribute": "Logic",
"specialization": "Lockpicking",
"dice_pool": 6,
"type": "Action"
},
{
"name": "Influence",
"rating": 2,
"attribute": "Charisma",
"dice_pool": 4,
"type": "Action"
},
{
"name": "Exotic Weapons",
"rating": 3,
"attribute": "Agility",
"specialization": "Grapple Gun",
"dice_pool": 9,
"type": "Action"
},
{
"name": "Exotic Weapons",
"rating": 4,
"attribute": "Agility",
"specialization": "Grapple Launcher",
"dice_pool": 9,
"type": "Action"
},
{
"name": "Firearms Manufacturers",
"rating": 2,
"attribute": "Logic",
"dice_pool": 6,
"type": "Knowledge"
},
{
"name": "Security Systems",
"rating": 2,
"attribute": "Logic",
"dice_pool": 6,
"type": "Knowledge"
},
{
"name": "Urban Brawl Teams",
"rating": 2,
"attribute": "Logic",
"dice_pool": 6,
"type": "Knowledge"
}
],
"qualities": [
{
"name": "Built Tough",
"type": "Positive",
"notes": "+1 Physical condition monitor box."
},
{
"name": "Dermal Deposits",
"type": "Positive",
"notes": "+1 natural Armor/Defense Rating."
},
{
"name": "High Pain Tolerance",
"type": "Positive",
"notes": "Reduces wound modifiers by 1."
},
{
"name": "Thermographic Vision",
"type": "Positive",
"notes": "Natural troll heat-signature vision."
},
{
"name": "Socially Awkward",
"type": "Negative",
"notes": "Blunt and lacking empathy due to amnesia."
}
],
"currency_and_ids": {
"nuyen": 3500,
"primary_lifestyle": "Low (1 month prepaid)",
"licenses": "Concealed Carry (Heavy Pistol, Sniper Rifle)",
"fake_ids": [
{
"name": "Marcus Vance",
"rating": 3,
"lifestyle": "Low",
"funds": "3,500¥",
"licenses": "Driver's, Concealed Carry"
}
]
},
"contacts": [
{
"name": "Billy Shen",
"loyalty": 2,
"connection": 4,
"notes": "Octagon Triad lieutenant, enforcer, and arms dealer."
},
{
"name": "Four-Finger Wong",
"loyalty": 3,
"connection": 3,
"notes": "Cyber-surgeon and underground street doc."
},
{
"name": "Mia",
"loyalty": 3,
"connection": 2,
"notes": "Part-time fixer and waitress at Peaceable Kingdom."
}
],
"primary_armor": {
"name": "Armor jacket",
"rating": "+2",
"notes": "+2 Defense Rating bonus"
},
"primary_ranged_weapon": {
"name": "Ares Desert Strike",
"dv": "5P",
"mode": "SA",
"attack_rating": { "close": 3, "near": 10, "far": 10, "extreme": 10 },
"ammo": "14(c)",
"notes": "Sniper rifle w/ imaging scope, shock pad"
},
"primary_melee_weapon": {
"name": "Sword",
"dv": "3P",
"close": 9,
"notes": "Blade"
},
"ranged_weapons": [
{
"name": "Ares Desert Strike (Sniper Rifle)",
"dv": "5P",
"mode": "SA",
"attack_rating": { "close": 3, "near": 10, "far": 10, "extreme": 10 },
"ammo": "14(c)",
"notes": "w/ imaging scope, shock pad"
},
{
"name": "Ares Predator VI (Heavy Pistol)",
"dv": "4P",
"mode": "SA",
"attack_rating": { "close": 9, "near": 9, "far": 7, "extreme": "—" },
"ammo": "15(c)",
"notes": "w/ smartlink"
}
],
"melee_weapons": [
{
"name": "Sword (Blade)",
"dv": "3P",
"close": 9,
"notes": "Monofilament edge"
}
],
"armor": [
{
"name": "Armor jacket",
"rating": "+2",
"notes": "Provides +2 DR"
}
],
"matrix_stats": {
"device_name": "Sony Emperor commlink",
"device_rating": 2,
"attack": 0,
"sleaze": 0,
"data_processing": 1,
"firewall": 1,
"programs": [],
"matrix_damage": 0
},
"augmentations": [
{
"name": "Cybereyes",
"rating": 2,
"notes": "w/ smartlink, low-light vision",
"essence": 0.2
},
{
"name": "Reaction enhancers",
"rating": 1,
"notes": "+1 REA, +1 Initiative",
"essence": 0.3
}
],
"vehicles": [],
"gear": [
{ "name": "Armor jacket", "rating": 2, "quantity": 1 },
{ "name": "Sony Emperor commlink", "rating": 2, "quantity": 1 },
{ "name": "Grapple gun", "quantity": 1, "notes": "w/ 100m stealth rope" },
{
"name": "Certified credstick",
"quantity": 1,
"notes": "Contains 3,500¥"
},
{ "name": "Spare ammo clips (Ares Predator)", "quantity": 3 },
{ "name": "Spare ammo clips (Ares Desert Strike)", "quantity": 2 }
],
"spells": [],
"adept_powers": []
}
}

View File

@@ -0,0 +1,401 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "43e2f491-b3b1-4c6e-827d-99572d4cfbe1",
"ruleset_id": "sr6",
"entity_type": "dossier",
"display_name": "Yu (Elf Covert Ops Specialist / Face Dossier)",
"description": "Full 4-page narrative dossier for Yu, Elf Covert Ops Specialist and Face from the SR6 Beginner Box.",
"template_id": "dossier_sheet",
"properties": {
"alias": "Yu",
"real_name": "Yu",
"player": "Catalyst Pregen",
"archetype": "Elf Covert Ops / Face",
"quote": "\"They can't hit what they can't see.\" / \"My team can handle the tough jobs, Mr. Johnson. The question is, are you willing to pay a premium price for premium work?\" / \"(roguish grin)\"",
"pitch": "Play this character if deception, sneakiness, charm, and guile are your weapons of choice!",
"metatype": "Elf",
"ethnicity": "Cantonese",
"age": 28,
"sex": "Male",
"height": "1.8 m",
"weight": "77 kg",
"reputation": 2,
"heat": 0,
"karma": 0,
"total_karma": 0,
"misc": "Red Dragon Triad affiliate; expert pickpocket, lockpick, and con artist.",
"notes": "Elf Covert Ops Specialist and Face adept at infiltration, disguise, charm, and fast-talking.",
"background": "Once upon a time, Yu was a corp kid from the Canton Confederation, living a life of relative abundance. He always assumed he'd end up a wageslave like his parents, until the Second Matrix Crash erased his family's identities and sent the corp they worked for into bankruptcy. Mere months later, Yu's parents were killed by Black Chrysanthemum Triad members demanding protection fees they couldn't pay, and ten-year-old Yu fled from Foshan to Hong Kong. He ended up as a member of a pickpocket gang, then joined the Red Dragon Triad as a thief and enforcer. Yu probably would've spent the rest of his life with the Red Dragons, but one of his Triad brothers got wrapped up in better-than-life chips and went on a frenzy, and Yu was forced to kill him in self-defense. The Triad bosses knew it wasn't Yu's fault, but they couldn't let the killing go unanswered, so they shipped him off to an allied syndicate in Seattle. There, Yu met Billy Shen, and Billy passed his name along to Ms. Myth when the troll fixer was putting her new crew together.",
"preferred_tactics": "Making trouble without being seen is Yu's bread and butter. The skills he acquired as a thief—moving silently, picking locks, placing and palming items without being seen—have served him well in the shadows. He prefers to be completely invisible when possible, but he's gotten pretty good at using disguises if the situation calls for it. Since meeting Ms. Myth, Yu has seen the value in being able to get what he wants through charm as well as deception. He's turning his natural elven charisma toward being the team's face and often takes the lead in negotiating with prospective employers who approach them for jobs. Ms. Myth sometimes ropes him into helping with her corporate infiltrations, now that she is looking after a new crew along with her old crew of Sledge, Coydog, Hardpoint, and Gentry. Yu's not afraid to use violence when he needs to, but he does try to keep the body count to a minimum—partly because he knows wageslaves are people too, and partly because he'd rather have those wageslaves' bosses go after them for letting their guard down than come after him to avenge their fallen comrade. He prefers not to 'fight' so much as 'ambush,' and if he finds himself in open combat, he'll do his best to stay behind cover and plink away with whatever firearm he has handy.",
"roleplaying_tips": "Yu has two distinct personalities: on the job, and off. Off the job, he's an overgrown teenager who hangs out with his friends, plays sports, sleeps around, experiments with drugs, and uses his charm to get out of trouble. He sees his fellow Red Dragon Triad members and their affiliates, like the Octagon Triad, as family and is loyal to them to a fault. He won't take a run that goes against the Red Dragons' interests, but he's upfront about it and tries to make sure his refusal won't screw the team over somehow. On the other hand, when he doesn't have to worry about the Red Dragons, Yu extends the same loyalty to his shadowrunner comrades as he does to his Triad family. He won't willingly turn on them for anything, and if one team member has a problem, he's always willing to help—for as long as they're willing to endure his barrages of terrible puns, anyway. On the job, Yu's a chameleon. He can be as charming or as cold as he needs to be, and he'll work with just about anyone—and do so with a smile—regardless of their background. The only people he refuses to deal with are BTL dealers and organleggers, because of how those two trades have wreaked havoc on his life. He takes a certain joy in tearing them down.",
"vital_contacts": [
{
"name": "Billy Shen",
"role": "Octagon Triad Lieutenant",
"description": "Octagon Triad lieutenant, enforcer, and arms dealer."
},
{
"name": "Four-Finger Wong",
"role": "Cyber-Surgeon",
"description": "Underground street doc and cyber-surgeon."
},
{
"name": "Mia",
"role": "Part-Time Fixer",
"description": "Waitress at Peaceable Kingdom and part-time fixer."
}
],
"favorite_hangouts": [
{
"name": "Peaceable Kingdom",
"location": "Tacoma",
"description": "A Chinese restaurant in Tacoma."
},
{
"name": "The Sports Bar",
"location": "Downtown Seattle",
"description": "A bustling sports bar in Downtown Seattle."
},
{
"name": "Vigilant Iron Schooling House",
"location": "Redmond",
"description": "A martial arts studio in Redmond."
}
],
"favorite_downtime_activities": [
"Hanging out with his fellow Triad members",
"Playing and watching basketball",
"Studying martial arts"
],
"sample_actions": [
{
"action": "Fast-Talk / Con Mark",
"action_type": "Major",
"dice_pool": 14,
"notes": "Con 6 + Charisma 8 (Opposed vs. target Willpower + Intuition)"
},
{
"action": "Negotiation / Influence Mr. Johnson",
"action_type": "Major",
"dice_pool": 14,
"notes": "Influence 6 + Charisma 8"
},
{
"action": "Sneak Past Guards / Infiltrate",
"action_type": "Major",
"dice_pool": 12,
"notes": "Stealth 6 + Agility 6 (Opposed vs. Perception + Intuition)"
},
{
"action": "Palming / Sleight of Hand",
"action_type": "Major",
"dice_pool": 12,
"notes": "Stealth 6 + Agility 6"
},
{
"action": "Ambush Baton Strike",
"action_type": "Major",
"dice_pool": 10,
"notes": "Close Combat 4 + Agility 6 (DV 3S, net hits add to damage)"
},
{
"action": "Silenced Pistol Shot (Ares Light Fire)",
"action_type": "Major",
"dice_pool": 8,
"notes": "Firearms 2 + Agility 6 (SA mode, DV 2P, smartlink contacts)"
},
{
"action": "Disguise Identity",
"action_type": "Major",
"dice_pool": 11,
"notes": "Stealth 6 + Intuition 5 (Threshold for observers to see through)"
},
{
"action": "Maglock Bypass (Passkey)",
"action_type": "Major",
"dice_pool": 4,
"notes": "Passkey Rating 4 vs. Maglock Rating"
}
],
"edge_boosts": [
{
"cost": 1,
"name": "Reroll one die",
"description": "Reroll any one die from your roll that failed to score a hit (Post)."
},
{
"cost": 1,
"name": "Add 3 to Initiative",
"description": "Add +3 to your Initiative Score for the combat turn (Any)."
},
{
"cost": 2,
"name": "+1 to single die",
"description": "Add +1 to any single die result, turning a 4 into a 5 (hit) (Post)."
},
{
"cost": 2,
"name": "Give ally 1 Edge",
"description": "Pass 1 Edge point to an allied runner (Any)."
},
{
"cost": 2,
"name": "Negate 1 Edge of foe",
"description": "Negate 1 point of Edge held by an opponent (Pre)."
},
{
"cost": 3,
"name": "Buy automatic hit",
"description": "Add 1 automatic hit to test result (Any)."
},
{
"cost": 3,
"name": "Heal Stun Damage",
"description": "Immediately clear 1 box from your stun damage track."
},
{
"cost": 4,
"name": "Add Edge to Pool",
"description": "Add full Edge attribute (+2) to dice pool and 6s explode (Pre)."
}
],
"attributes": {
"body": 3,
"agility": 6,
"reaction": 4,
"strength": 3,
"willpower": 5,
"logic": 5,
"intuition": 5,
"charisma": 8,
"edge": 2,
"essence": 6.0,
"magic": 0,
"edge_points": 2
},
"combat_info": {
"initiative": "9 + 1D6",
"matrix_initiative": "9 + 1D6",
"astral_initiative": "—",
"defense_rating": 6,
"unarmed_ar": 6,
"composure": 13,
"judge_intentions": 13,
"memory": 10,
"lift_carry": 6,
"movement": "5/10/+1"
},
"condition_monitor": {
"physical_damage": 0,
"physical_boxes": 10,
"stun_damage": 0,
"stun_boxes": 11,
"overflow": 0
},
"skills": [
{
"name": "Con",
"rating": 6,
"attribute": "Charisma",
"dice_pool": 14,
"type": "Action"
},
{
"name": "Influence",
"rating": 6,
"attribute": "Charisma",
"dice_pool": 14,
"type": "Action"
},
{
"name": "Stealth",
"rating": 6,
"attribute": "Agility",
"dice_pool": 12,
"type": "Action"
},
{
"name": "Close Combat",
"rating": 4,
"attribute": "Agility",
"dice_pool": 10,
"type": "Action"
},
{
"name": "Firearms",
"rating": 2,
"attribute": "Agility",
"dice_pool": 8,
"type": "Action"
},
{
"name": "Perception",
"rating": 2,
"attribute": "Intuition",
"dice_pool": 7,
"type": "Action"
},
{
"name": "Piloting",
"rating": 2,
"attribute": "Reaction",
"dice_pool": 6,
"type": "Action"
},
{
"name": "Biotech",
"rating": 1,
"attribute": "Logic",
"dice_pool": 6,
"type": "Action"
},
{
"name": "Outdoors",
"rating": 1,
"attribute": "Intuition",
"dice_pool": 6,
"type": "Action"
},
{
"name": "Architecture",
"rating": 2,
"attribute": "Logic",
"dice_pool": 7,
"type": "Knowledge"
},
{
"name": "Close-up Magic",
"rating": 2,
"attribute": "Logic",
"dice_pool": 7,
"type": "Knowledge"
},
{
"name": "Wildlife",
"rating": 2,
"attribute": "Logic",
"dice_pool": 7,
"type": "Knowledge"
}
],
"qualities": [
{
"name": "Catlike",
"type": "Positive",
"notes": "+1 dice pool bonus on stealth and acrobatics tests."
},
{
"name": "Double-Jointed",
"type": "Positive",
"notes": "+1 dice pool bonus on escape and contortion tests."
},
{
"name": "Low-Light Vision",
"type": "Positive",
"notes": "Natural elf low-light perception."
}
],
"currency_and_ids": {
"nuyen": 3500,
"primary_lifestyle": "Low (1 month prepaid)",
"licenses": "Concealed Carry (Light Pistol)",
"fake_ids": [
{
"name": "Raymond Chang",
"rating": 4,
"lifestyle": "Low",
"funds": "3,500¥",
"licenses": "Firearms, Security Tech"
}
]
},
"contacts": [
{
"name": "Billy Shen",
"loyalty": 2,
"connection": 4,
"notes": "Octagon Triad lieutenant, enforcer, and arms dealer."
},
{
"name": "Four-Finger Wong",
"loyalty": 3,
"connection": 3,
"notes": "Cyber-surgeon and underground street doc."
},
{
"name": "Mia",
"loyalty": 3,
"connection": 2,
"notes": "Part-time fixer and waitress at Peaceable Kingdom."
}
],
"primary_armor": {
"name": "Lined coat",
"rating": "+3",
"notes": "+3 Defense Rating bonus"
},
"primary_ranged_weapon": {
"name": "Ares Light Fire 75",
"dv": "2P",
"mode": "SA",
"attack_rating": { "close": 10, "near": 7, "far": 6, "extreme": "—" },
"ammo": "16(c)",
"notes": "w/ silencer, smartgun"
},
"primary_melee_weapon": {
"name": "Extendible baton",
"dv": "3S",
"close": 6,
"notes": "Club"
},
"ranged_weapons": [
{
"name": "Ares Light Fire 75 (Light Pistol)",
"dv": "2P",
"mode": "SA",
"attack_rating": { "close": 10, "near": 7, "far": 6, "extreme": "—" },
"ammo": "16(c)",
"notes": "w/ silencer, smartgun"
}
],
"melee_weapons": [
{
"name": "Extendible baton",
"dv": "3S",
"close": 6,
"notes": "Club"
}
],
"armor": [
{
"name": "Lined coat",
"rating": "+3",
"notes": "+3 DR bonus"
}
],
"matrix_stats": {
"device_name": "Erika Elite commlink",
"device_rating": 4,
"attack": 0,
"sleaze": 0,
"data_processing": 2,
"firewall": 1,
"programs": [],
"matrix_damage": 0
},
"augmentations": [],
"vehicles": [],
"gear": [
{ "name": "Lined coat", "rating": 3, "quantity": 1 },
{ "name": "Erika Elite commlink", "rating": 4, "quantity": 1 },
{ "name": "Disguise kit", "quantity": 1 },
{ "name": "First Aid Kit", "rating": 3, "quantity": 1 },
{ "name": "Passkey", "rating": 4, "quantity": 1 },
{ "name": "Contacts", "quantity": 1, "notes": "w/ smartlink" },
{ "name": "Certified credstick", "quantity": 1, "notes": "3,500¥" },
{ "name": "Spare ammo clips (Ares Light Fire 75)", "quantity": 3 }
],
"spells": [],
"adept_powers": []
}
}

View File

@@ -0,0 +1,394 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "98dc54b1-8b02-44df-91ea-72e8fa440a33",
"ruleset_id": "sr6",
"entity_type": "dossier",
"display_name": "Zipfile (Dwarf Decker Dossier)",
"description": "Full 4-page narrative dossier for Zipfile, Dwarf Decker from the SR6 Beginner Box.",
"template_id": "dossier_sheet",
"properties": {
"alias": "Zipfile",
"real_name": "Zipfile",
"player": "Catalyst Pregen",
"archetype": "Dwarf Decker",
"quote": "\"How can you support a system that pays us to destroy itself?\" / \"Eish! That IC is wiz!\" / \"(triumphant vuvuzela noise)\"",
"pitch": "Play this character if you like illicit hacking and weaponizing people's devices against them.",
"metatype": "Dwarf",
"ethnicity": "Bantu",
"age": 23,
"sex": "Female",
"height": "1.1 m",
"weight": "36 kg",
"reputation": 2,
"heat": 0,
"karma": 0,
"total_karma": 0,
"misc": "Azanian neo-anarchist activist and urban brawl fan with a custom Renraku Kitsune deck.",
"notes": "Dwarf Decker and virtual saboteur specializing in host penetration, device spoofing, and matrix surveillance.",
"background": "Zipfile grew up in the urban brawl hotspot of Emfuleni, part of Azania's Pretoria-Witwatersrand-Vaal Metroplex. As a young girl, she loved the excitement, the chaos, the adrenaline-junkie rush that came with watching the local brawlers every weekend. It almost made her forget how she used to live in one of the buildings inside that urban arena, until Ares had evicted her family from their home and turned Emfuleni into an international spectacle. As a teenager, Zipfile lost both of her parents when DeBeers-Omnitech literally worked them to death in one of their mines. The shock and bitterness over their deaths led Zipfile and her younger brother to join a neo-anarchist cell in PWV. The neo-anarchists saw potential in her and sent her off to one of their training camps, where she learned how to use her cyberdeck to disrupt the system. Zipfile ultimately left Azania, though, when a conflict between neo-anarchist groups turned violent and most of her former runner team got killed. She met Ms. Myth through a fellow decker who was an urban brawl fan, and she uses her nuyen from those jobs to support both the neo-anarchist cause and the brother she left in Azania.",
"preferred_tactics": "Zipfile has never been one for the subtle approach. She delights in acting as a virtual saboteur, throwing wrenches in the corps' Matrix machinery until it collapses under its own weight. As far as she's concerned, disrupting systems is a metaphor for disrupting the system, and she considers tagging corporate hosts with virtual graffiti a vital component of her neo-anarchist outreach. She doesn't just want to beat the 'tools of the establishment' at their own game: she wants to make sure they know who beat them. Zipfile isn't completely lacking in discretion though. She knows her team will only succeed if they support each other, so she will (grudgingly) pass on chances to make the corps look bad if doing so would jeopardize the run or her teammates. Due to her love for urban brawl, Zipfile has gone out of her way to make sure she's a passable runner-and-gunner in addition to her Matrix expertise. She's pestered Rude into helping her improve her marksmanship, and she regularly takes her Ruger Super Warhawk on runs, although she has yet to fire it in anger (outside of a Miracle Shooter game, anyway).",
"roleplaying_tips": "What Zipfile lacks in physical stature, she makes up for in personality and presence. She's outgoing, bombastic, and more cheerful than anyone not perpetually stoned has any right to be. She's also a walking encyclopedia of random facts, which is how she got her runner handle; a fellow decker christened her Zipfile because she's 'a lot of data in a compressed package.' Zipfile is also the most principled member of the team. She doesn't hesitate to preach about the virtues of neo-anarchism and doesn't always take kindly to people questioning her beliefs. She's equally apt to argue with her fellow neo-anarchists though, since she's willing to take nuyen from the system to fund her efforts to bring it down. Zipfile's zeal for her cause is rooted in one of the rarest qualities in the shadows: caring. She cares about her teammates, her fellow neo-anarchists, her brother in Azania, and the people of the world in general. She's been on the receiving end of the worst that corps and governments can inflict on people, and she truly believes that her ideals are the best way to solve that problem. Finally, although she's not big on nations—governments are part of the system, too—Zipfile takes pride in her heritage. When people ask her about her accent or verbal tics like the omnipresent eish, she'll gladly tell them that she's Xhosa, and she can often be spotted wearing merch from the PWV Imikhonto urban brawl team (and feeling guilty for giving in to her consumer urges).",
"vital_contacts": [
{
"name": "Arclight",
"role": "Combat Mage / Coordinator",
"description": "Combat mage and coordinator for the Black Star neo-anarchist shadowrunner collective."
},
{
"name": "Casey Connors",
"role": "Tech Bar Owner",
"description": "Owner of the Cathode Glow e-sports bar and tech museum in Tacoma."
},
{
"name": "Jules Maguire",
"role": "Cascade Ork Smuggler",
"description": "Cascade Ork smuggler and fixer connection."
}
],
"favorite_hangouts": [
{
"name": "Dawn of Atlantis",
"location": "Matrix Node",
"description": "A Matrix-based fantasy RPG set in 'historical' Atlantis."
},
{
"name": "The Cathode Glow",
"location": "Tacoma",
"description": "An e-sports bar and tech museum in Tacoma."
},
{
"name": "The Sports Bar",
"location": "Downtown Seattle",
"description": "A lively sports bar in Downtown Seattle."
}
],
"favorite_downtime_activities": [
"Attending neo-anarchist events",
"Playing Matrix games, especially Dawn of Atlantis and Miracle Shooter",
"Watching urban brawl"
],
"sample_actions": [
{
"action": "Hack Host / Gain Access",
"action_type": "Major",
"dice_pool": 12,
"notes": "Cracking 6 + Logic 6 (Illegal, opposed vs. Firewall + Willpower)"
},
{
"action": "Crack File Protection",
"action_type": "Major",
"dice_pool": 14,
"notes": "Cracking 6 + Logic 6 + Decryption 2 (Removes encryption/protection)"
},
{
"action": "Matrix Perception / Scan Node",
"action_type": "Minor",
"dice_pool": 12,
"notes": "Electronics 6 + Intuition 6 (Inspect icons, hosts, and hidden personas)"
},
{
"action": "Spoof Command (Elevators / Systems)",
"action_type": "Major",
"dice_pool": 12,
"notes": "Cracking 6 + Logic 6 (Send false instructions to connected devices)"
},
{
"action": "Snoop Communications Feed",
"action_type": "Major",
"dice_pool": 12,
"notes": "Cracking 6 + Logic 6 (Overclock program adds +1 Edge)"
},
{
"action": "Data Spike Attack",
"action_type": "Major",
"dice_pool": 12,
"notes": "Cracking 6 + Logic 6 (Attack rating 6, inflicts Matrix damage)"
},
{
"action": "Heavy Pistol Shot (Ruger Warhawk)",
"action_type": "Major",
"dice_pool": 5,
"notes": "Firearms 2 + Agility 3 (SS mode, DV 4P)"
},
{
"action": "Spot Ambush / Physical Perception",
"action_type": "Minor",
"dice_pool": 9,
"notes": "Perception 3 + Intuition 6 (Thermographic Vision)"
}
],
"edge_boosts": [
{
"cost": 1,
"name": "Reroll one die",
"description": "Reroll any one die from your roll that failed to score a hit (Post)."
},
{
"cost": 1,
"name": "Add 3 to Initiative",
"description": "Add +3 to your Initiative Score for the combat turn (Any)."
},
{
"cost": 2,
"name": "+1 to single die",
"description": "Add +1 to any single die result, turning a 4 into a 5 (hit) (Post)."
},
{
"cost": 2,
"name": "Give ally 1 Edge",
"description": "Pass 1 Edge point to an allied runner (Any)."
},
{
"cost": 2,
"name": "Negate 1 Edge of foe",
"description": "Negate 1 point of Edge held by an opponent (Pre)."
},
{
"cost": 3,
"name": "Buy automatic hit",
"description": "Add 1 automatic hit to test result (Any)."
},
{
"cost": 3,
"name": "Heal Stun Damage",
"description": "Immediately clear 1 box from your stun damage track."
},
{
"cost": 4,
"name": "Add Edge to Pool",
"description": "Add full Edge attribute (+4) to dice pool and 6s explode (Pre)."
}
],
"attributes": {
"body": 5,
"agility": 3,
"reaction": 4,
"strength": 4,
"willpower": 6,
"logic": 6,
"intuition": 6,
"charisma": 3,
"edge": 4,
"essence": 3.7,
"magic": 0,
"edge_points": 4
},
"combat_info": {
"initiative": "10 + 1D6",
"matrix_initiative": "12 + 1D6",
"astral_initiative": "—",
"defense_rating": 8,
"unarmed_ar": 4,
"composure": 9,
"judge_intentions": 9,
"memory": 12,
"lift_carry": 9,
"movement": "5/10/+1"
},
"condition_monitor": {
"physical_damage": 0,
"physical_boxes": 11,
"stun_damage": 0,
"stun_boxes": 11,
"overflow": 0
},
"skills": [
{
"name": "Cracking",
"rating": 6,
"attribute": "Logic",
"dice_pool": 12,
"type": "Action"
},
{
"name": "Electronics",
"rating": 6,
"attribute": "Logic",
"dice_pool": 12,
"type": "Action"
},
{
"name": "Engineering",
"rating": 2,
"attribute": "Logic",
"dice_pool": 8,
"type": "Action"
},
{
"name": "Perception",
"rating": 3,
"attribute": "Intuition",
"dice_pool": 9,
"type": "Action"
},
{
"name": "Firearms",
"rating": 2,
"attribute": "Agility",
"dice_pool": 5,
"type": "Action"
},
{
"name": "Close Combat",
"rating": 2,
"attribute": "Agility",
"dice_pool": 5,
"type": "Action"
},
{
"name": "Cyberdeck Models",
"rating": 2,
"attribute": "Logic",
"dice_pool": 8,
"type": "Knowledge"
},
{
"name": "German Expressionist Cinema",
"rating": 2,
"attribute": "Logic",
"dice_pool": 8,
"type": "Knowledge"
},
{
"name": "Matrix Security",
"rating": 2,
"attribute": "Logic",
"dice_pool": 8,
"type": "Knowledge"
}
],
"qualities": [
{
"name": "Gearhead",
"type": "Positive",
"notes": "May spend Edge on Repair tests and during downtime repairs."
},
{
"name": "Guts",
"type": "Positive",
"notes": "+2 dice pool bonus on tests resisting fear and intimidation."
},
{
"name": "Thermographic Vision",
"type": "Positive",
"notes": "Natural dwarf heat-signature vision."
}
],
"currency_and_ids": {
"nuyen": 3500,
"primary_lifestyle": "Low (1 month prepaid)",
"licenses": "Concealed Carry (Heavy Pistol), Cyberdeck Operator License",
"fake_ids": [
{
"name": "Nandi Mandela",
"rating": 3,
"lifestyle": "Low",
"funds": "3,500¥",
"licenses": "Heavy Pistol, Hardware Tech"
}
]
},
"contacts": [
{
"name": "Arclight",
"loyalty": 3,
"connection": 4,
"notes": "Combat mage and coordinator for the Black Star neo-anarchist shadowrunner collective."
},
{
"name": "Casey Connors",
"loyalty": 2,
"connection": 3,
"notes": "Owner of the Cathode Glow e-sports bar and tech museum in Tacoma."
},
{
"name": "Jules Maguire",
"loyalty": 2,
"connection": 2,
"notes": "Cascade Ork smuggler."
}
],
"primary_armor": {
"name": "Armor vest",
"rating": "+3",
"notes": "+3 Defense Rating bonus"
},
"primary_ranged_weapon": {
"name": "Ruger Super Warhawk",
"dv": "4P",
"mode": "SS",
"attack_rating": { "close": 8, "near": 11, "far": 8, "extreme": "—" },
"ammo": "6(cy)",
"notes": "Heavy cylinder revolver"
},
"primary_melee_weapon": {
"name": "Unarmed Strike",
"dv": "2S",
"close": 4,
"notes": "Natural"
},
"ranged_weapons": [
{
"name": "Ruger Super Warhawk (Heavy Pistol)",
"dv": "4P",
"mode": "SS",
"attack_rating": { "close": 8, "near": 11, "far": 8, "extreme": "—" },
"ammo": "6(cy)",
"notes": "Heavy revolver"
}
],
"melee_weapons": [
{
"name": "Unarmed Strike",
"dv": "2S",
"close": 4,
"notes": "Natural"
}
],
"armor": [
{
"name": "Armor vest",
"rating": "+3",
"notes": "+3 DR bonus"
}
],
"matrix_stats": {
"device_name": "Renraku Kitsune cyberdeck",
"device_rating": 4,
"attack": 6,
"sleaze": 7,
"data_processing": 4,
"firewall": 4,
"programs": ["Decryption", "Exploit", "Overclock"],
"matrix_damage": 0
},
"augmentations": [
{
"name": "Cyberjack MPI",
"rating": 4,
"notes": "7/6, +2 Matrix initiative",
"essence": 2.3
}
],
"vehicles": [],
"gear": [
{ "name": "Armor vest", "rating": 3, "quantity": 1 },
{
"name": "Renraku Kitsune cyberdeck",
"rating": 4,
"quantity": 1,
"notes": "DR 4, 7/6, 8 slots"
},
{
"name": "Hermes Ikon commlink",
"rating": 5,
"quantity": 1,
"notes": "DR 5, 3/0"
},
{ "name": "Certified credstick", "quantity": 1, "notes": "3,500¥" },
{ "name": "Heavy pistol ammo box (Ruger)", "quantity": 30 }
],
"spells": [],
"adept_powers": []
}
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "e8027c8e-c25c-49a4-b8f8-f680b49ac492",
"ruleset_id": "sr6",
"entity_type": "gear",
"display_name": "Decryption",
"description": "Files all over the Matrix are encrypted; this program helps break that protection.",
"properties": {
"filename": "decryption",
"name": "Decryption",
"category": "Program",
"rules": "+2 dice on Crack File Action.",
"notes": "Files all over the Matrix are encrypted; this program helps break that protection."
}
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "a631687e-cabd-46b7-bb4e-0567ce368310",
"ruleset_id": "sr6",
"entity_type": "gear",
"display_name": "Erika Elite",
"description": "The elegant design and smooth functionality make this commlink live up to its name.",
"properties": {
"filename": "erika_elite",
"name": "Erika Elite",
"category": "Commlink",
"device_rating": 4,
"attributes_df": "2/1",
"active_program_slots": 2,
"notes": "The elegant design and smooth functionality make this commlink live up to its name."
}
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "4ef5c6a6-3d5a-419a-a8f3-12e7b1452fa6",
"ruleset_id": "sr6",
"entity_type": "gear",
"display_name": "Exploit",
"description": "Crowbar to help deckers get past entryways that might otherwise stay locked.",
"properties": {
"filename": "exploit",
"name": "Exploit",
"category": "Program",
"rules": "Reduce Defense Rating of hacking target by 2.",
"notes": "Crowbar to help deckers get past entryways that might otherwise stay locked."
}
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "46eb16b3-8048-418b-8260-fab94623cf78",
"ruleset_id": "sr6",
"entity_type": "gear",
"display_name": "Hermes Ikon",
"description": "A commlink for people who have money to spend on keeping their personal data safe.",
"properties": {
"filename": "hermes_ikon",
"name": "Hermes Ikon",
"category": "Commlink",
"device_rating": 5,
"attributes_df": "3/0",
"active_program_slots": 2,
"notes": "A commlink for people who have money to spend on keeping their personal data safe."
}
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "fd0a9d2b-8440-4c2e-b1f4-7c0bf563f877",
"ruleset_id": "sr6",
"entity_type": "gear",
"display_name": "Overclock",
"description": "Amp up a machine past manufacturer limits to get a dangerous job done.",
"properties": {
"filename": "overclock",
"name": "Overclock",
"category": "Program",
"rules": "Add two dice to a Matrix action. Adds to Overwatch Score as do defense hits.",
"notes": "Amp up a machine past manufacturer limits to get a dangerous job done."
}
}

View File

@@ -0,0 +1,18 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "6174631f-599b-4678-8779-4da6fe1bb454",
"ruleset_id": "sr6",
"entity_type": "gear",
"display_name": "Renraku Kitsune",
"description": "Get where you’re not supposed to be...and do it with style!",
"properties": {
"filename": "renraku_kitsune",
"name": "Renraku Kitsune",
"category": "Cyberdeck",
"device_rating": 4,
"attack": 7,
"sleaze": 6,
"program_slots": 8,
"notes": "Get where you’re not supposed to be...and do it with style!"
}
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "8e6c3af8-1816-407f-a39e-62af989576cc",
"ruleset_id": "sr6",
"entity_type": "gear",
"display_name": "Renraku Sensei",
"description": "Middle managers and teenagers with money like the Sensei for giving a sense of status.",
"properties": {
"filename": "renraku_sensei",
"name": "Renraku Sensei",
"category": "Commlink",
"device_rating": 3,
"attributes_df": "2/0",
"active_program_slots": 1,
"notes": "Middle managers and teenagers with money like the Sensei for giving a sense of status."
}
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "edc2ae00-5a28-4fb7-a312-bb08e19c2ace",
"ruleset_id": "sr6",
"entity_type": "gear",
"display_name": "Sony Emperor",
"description": "Popular because it’s cheap and not the worst, this commlink works with few frills.",
"properties": {
"filename": "sony_emperor",
"name": "Sony Emperor",
"category": "Commlink",
"device_rating": 2,
"attributes_df": "1/1",
"active_program_slots": 1,
"notes": "Popular because it’s cheap and not the worst, this commlink works with few frills."
}
}

View File

@@ -0,0 +1,16 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "4cf564fe-5b8b-4c26-a063-ce692d263b02",
"ruleset_id": "sr6",
"entity_type": "gear",
"display_name": "White Noise Generator",
"description": "Creates a field of random noise that inhibits audio surveillance.",
"properties": {
"filename": "white_noise_generator",
"name": "White Noise Generator",
"category": "Security",
"device_rating": 3,
"rules": "All Perception tests to overhear a conversation within 3m receive a -DR modifier.",
"notes": "Creates a field of random noise that inhibits audio surveillance."
}
}

View File

@@ -0,0 +1,104 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "0c3cd5a4-294c-4fda-867f-d5c3b01db5e9",
"ruleset_id": "sr6",
"entity_type": "npc",
"display_name": "Ancients",
"description": "Go-Gang / Elf Gangers from the SR6 Beginner Box.",
"template_id": "character_picker",
"properties": {
"alias": "Ancients",
"archetype": "Go-Gang / Elf Gangers",
"metatype": "Elf",
"notes": "Vicious elves who use the speed of their racing bikes to strike with viper-like speed and lethality. They all rock the neon green encircled A on their jackets.",
"quote": "“Our blood gives us pride. Yours will bring you shame.”",
"hook": "Fiercely territorial, the Ancients cause trouble for any intruders and trespassers.",
"attributes": {
"body": 2,
"agility": 4,
"reaction": 3,
"strength": 2,
"willpower": 2,
"logic": 2,
"intuition": 2,
"charisma": 3,
"edge": 0,
"essence": 6
},
"combat_info": {
"initiative": "5 + 1D6",
"defense_rating": 3,
"condition_monitor": 9
},
"skills": [
{
"name": "Piloting",
"rating": 7,
"attribute": "Reaction",
"dice_pool": 10
},
{
"name": "Close Combat",
"rating": 5,
"attribute": "Agility",
"dice_pool": 9
},
{
"name": "Firearms",
"rating": 5,
"attribute": "Agility",
"dice_pool": 9
},
{
"name": "Influence",
"rating": 5,
"attribute": "Charisma",
"dice_pool": 8
},
{
"name": "Perception",
"rating": 4,
"attribute": "Intuition",
"dice_pool": 6
}
],
"gear": [
{
"name": "Gang leather",
"rating": "+1 DR"
},
{
"name": "Sony Emperor commlink",
"rating": 2
},
{
"name": "Yamaha Rapier motorcycle"
},
{
"name": "Katana"
},
{
"name": "Uzi V submachine gun"
}
],
"primary_ranged_weapon": {
"name": "Uzi V",
"dv": "3P",
"mode": "SA/BF",
"attack_rating": {
"close": 8,
"near": 8,
"far": 7
}
},
"primary_melee_weapon": {
"name": "Katana",
"dv": "4P",
"close": 10
},
"primary_armor": {
"name": "Gang leather",
"rating": "+1"
}
}
}

View File

@@ -0,0 +1,96 @@
{
"$schema": "../../../schema/entity.json",
"uuid": "72a0806c-4ba5-4ea3-87d9-04e6dd5f3648",
"ruleset_id": "sr6",
"entity_type": "npc",
"display_name": "Big Ben",
"description": "Ragers Leader / Male Ork Ganger from the SR6 Beginner Box.",
"template_id": "character_picker",
"properties": {
"alias": "Big Ben",
"real_name": "Ben Jones",
"archetype": "Ragers Leader / Male Ork Ganger",
"metatype": "Ork",
"notes": "Ben Jones grew up tough. He was Big Ben by the time he was ten. He came to join the Ragers because half his family was already in, and he made his way up the ranks fast.",
"quote": "“If you’re not pissed off at the world, then you’re not RRAARRGGHH!”",
"hook": "Big Ben balances chaos with prudence, never taking a fight he’ll definitely lose.",
"attributes": {
"body": 7,
"agility": 4,
"reaction": 4,
"strength": 6,
"willpower": 3,
"logic": 3,
"intuition": 3,
"charisma": 4,
"edge": 2,
"essence": 6
},
"combat_info": {
"initiative": "7 + 1D6",
"defense_rating": 7,
"condition_monitor": 13
},
"skills": [
{
"name": "Close Combat",
"rating": 10,
"attribute": "Agility",
"dice_pool": 14
},
{
"name": "Firearms",
"rating": 8,
"attribute": "Agility",
"dice_pool": 12
},
{
"name": "Influence",
"rating": 8,
"attribute": "Charisma",
"dice_pool": 12
},
{
"name": "Perception",
"rating": 7,
"attribute": "Intuition",
"dice_pool": 10
}
],
"gear": [
{
"name": "Gang Leather",
"rating": "+1 DR"
},
{
"name": "Erika Elite commlink",
"rating": 4
},
{
"name": "Bike Chain"
},
{
"name": "Defiance T-250 shotgun"
}
],
"primary_ranged_weapon": {
"name": "Defiance T-250",
"dv": "4P",
"mode": "SA",
"attack_rating": {
"close": 7,
"near": 10,
"far": 6
}
},
"primary_melee_weapon": {
"name": "Bike Chain",
"dv": "2S",
"close": 4
},
"primary_armor": {
"name": "Gang Leather",
"rating": "+1"
}
}
}

Some files were not shown because too many files have changed in this diff Show More