diff --git a/szf-documentation.md b/szf-documentation.md new file mode 100644 index 0000000..91dd52a --- /dev/null +++ b/szf-documentation.md @@ -0,0 +1,235 @@ +# Session Zero Format (.szf) Documentation + +## Overview + +The Session Zero Format (.szf) is a human-readable, structured data format designed specifically for the Session Zero tabletop RPG management application. It provides a standardized way to represent game data, templates, and character sheets for offline storage, interchange, and sharing. + +## Format Principles + +- **Human-Readable**: Content is stored in plain text with a clear, consistent structure +- **Self-Documenting**: Field types and metadata are explicitly defined +- **Extensible**: Format supports future expansion without breaking compatibility +- **Portable**: Files can be easily shared and imported across devices +- **Compact**: Efficient representation without unnecessary verbosity + +## File Structure + +All .szf files follow a common structure: + +1. **Header**: Identifies the file type and schema version +2. **Metadata**: Contains descriptive information about the content +3. **Content Sections**: Contains the actual data organized in logical sections + +### Header Format + +Files begin with type declaration and schema version: + +``` +!type: [file_type] +!schema: [schema_version] +``` + +Where `[file_type]` can be: +- `dataset` - Collection of related game elements +- `character_template` - Character sheet definition +- `character` - Actual character data +- `session` - Game session data + +And `[schema_version]` follows semantic versioning (e.g., `1.0.0`). + +### Section Format + +Data is organized into named sections with optional nesting: + +``` +[Section: Name] +FieldName (field_type) = value + +[Section.SubsectionName] +FieldName (field_type) = value +``` + +For entries in datasets, the format is: + +``` +[Entry: EntryName] +FieldName (field_type) = value + +[Entry.EntryName.SubsectionName] +FieldName (field_type) = value +``` + +## Field Types + +The .szf format supports the following field types: + +| Type | Description | Example | +|------|-------------|--------| +| `text` | Single-line string | `Name (text) = Longsword` | +| `text-field` | Multi-line text | `Description (text-field) = A long sword...` | +| `number` | Integer or floating-point | `Weight (number) = 3` | +| `bool` | Boolean true/false | `IsMartial (bool) = true` | +| `group` | Container for related fields | `Currency (group) = true` | +| `calculated` | Formula-based computed value | `StrengthMod (calculated) = (Strength - 10) / 2` | +| `system` | Special application-handled field | `DatasetType (system) = items` | +| `dataset-reference` | Reference to specific dataset | `Race (dataset-reference) = PlayableRaces` | +| `dataset-type` | Reference to any dataset of type | `PrimaryWeapon (dataset-type) = items` | +| `dataset-reference-multiple` | Multiple references to specific dataset | `Feats (dataset-reference-multiple) = CoreFeats` | +| `dataset-type-multiple` | Multiple references to dataset type | `Level1Spells (dataset-type-multiple) = spells` | + +## Dataset Format + +Datasets contain collections of related game elements like items, spells, or character classes. + +### Example + +```szf +!type: dataset +!schema: 1.0.0 + +[Metadata] +Name (text) = Core Fantasy Items +Type (text) = items +Version (text) = 1.0.0 +Description (text-field) = Basic fantasy items for any campaign + +[Entry: Longsword] +Name (text) = Longsword +Description (text-field) = A standard sword with a long blade +Category (text) = Weapon + +[Entry.Longsword.Stats] +Damage (text) = 1d8 +Weight (number) = 3 +IsMartial (bool) = true +``` + +## Character Template Format + +Templates define the structure of character sheets, including fields, calculations, and dataset references. + +### Example + +```szf +!type: character_template +!schema: 1.0.0 + +[Metadata] +Name (text) = Fantasy Character Sheet +Version (text) = 2.0.0 +GUID (text) = f9e8d7c6-b5a4-3210-9876-543210fedcba +Description (text-field) = Advanced character sheet for fantasy RPGs +Author (text) = Template Master +GameSystem (text) = Fantasy RPG Universal + +[Required Datasets] +ClassesDataset (text) = Core Classes|c3d4e5f6-g7h8-9012-cdef-123456789012|1.5.0 +RacesDataset (text) = Playable Races|d4e5f6g7-h8i9-0123-def0-234567890123|1.3.0 + +[Section: Ability Scores] +Strength (number) = 10 +Dexterity (number) = 10 +Constitution (number) = 10 + +[Section.Ability Scores.Modifiers] +StrengthMod (calculated) = (Strength - 10) / 2 +DexterityMod (calculated) = (Dexterity - 10) / 2 +``` + +## Special Features + +### Dataset Dependencies + +Character templates can specify required datasets using the `Name|GUID|Version` triple format: + +``` +[Required Datasets] +ClassesDataset (text) = Core Classes|c3d4e5f6-g7h8-9012-cdef-123456789012|1.5.0 +``` + +This ensures that character sheets created from the template have access to the correct datasets. + +### Calculated Fields + +Formulas can reference other fields to create dynamic values: + +``` +StrengthMod (calculated) = (Strength - 10) / 2 +SpellSaveDC (calculated) = 8 + ProficiencyBonus + IntelligenceMod +``` + +Supported operations include basic arithmetic, comparisons, and functions like SUM, MIN, MAX. + +### Nested Sections + +Sections can be nested to create a logical hierarchy: + +``` +[Section: Equipment] +DatasetType (system) = items + +[Section.Equipment.Weapons] +PrimaryWeapon (dataset-type) = items +``` + +### System Properties + +Special fields that control application behavior: + +``` +DatasetType (system) = items +SelectionMode (system) = multiple +``` + +## Best Practices + +1. **Use Descriptive Names**: Choose clear, meaningful section and field names +2. **Include Metadata**: Always provide version, description, and author information +3. **Organize Logically**: Group related fields into appropriate sections +4. **Set Defaults**: Provide sensible default values where appropriate +5. **Document Dependencies**: List all required datasets with their correct identifiers +6. **Validate Before Sharing**: Ensure all references are valid and values are appropriate +7. **Follow Naming Conventions**: Use consistent capitalization and naming patterns + +## Import/Export Guidelines + +### Exporting + +When exporting .szf files: + +1. Include all required metadata (especially GUID and version) +2. List all dependencies explicitly +3. Keep file sizes reasonable (prefer multiple focused files over single massive ones) +4. Set appropriate default values + +### Importing + +When importing .szf files: + +1. Validate the file structure before processing +2. Check for missing dependencies +3. Handle version conflicts appropriately +4. Validate all field values against their declared types + +## Character Sheet Data + +When a character is created from a template, the resulting .szf file contains: + +1. Reference to the source template (GUID and version) +2. All field values populated by the user +3. References to selected dataset entries +4. Character-specific notes and customizations + +## Versioning and Compatibility + +- **GUID**: Unique identifier that remains constant across versions +- **Version**: Semantic version number (MAJOR.MINOR.PATCH) +- **Compatibility**: Minor versions should maintain backward compatibility +- **Breaking Changes**: Major version increases indicate potentially incompatible changes + +## Common Use Cases + +1. **Sharing Game Systems**: Distribute templates and datasets for specific RPG systems +2. **Character Backups**: Export characters for safekeeping or transfer between devices +3. **Community Content**: Share custom content with other Session Zero users +4. **Game Preparation**: Pre-build NPCs, items, and other game elements \ No newline at end of file diff --git a/technical-specifications.md b/technical-specifications.md index f537704..5cff350 100644 --- a/technical-specifications.md +++ b/technical-specifications.md @@ -35,6 +35,12 @@ The Library serves as the central content repository, organized into modular dat - **Number**: Integer or floating-point values - **Boolean**: True/false checkboxes - **Group**: Nested collections of related fields +- **Calculated**: Dynamically computed values based on mathematical formulas referencing other fields +- **System**: Special fields handled by the application framework for configuration purposes +- **Dataset-Reference**: Reference to a specific dataset identified by its unique identifier +- **Dataset-Type**: Generic reference to any dataset of a specified type (e.g., items, spells) +- **Dataset-Reference-Multiple**: Multiple selection reference to a specific dataset +- **Dataset-Type-Multiple**: Multiple selection reference to any dataset of a specified type #### Data Management - **Local Storage**: All datasets stored locally in LocalStorage for fast access @@ -66,6 +72,16 @@ Templates define the structure and layout of character sheets for specific game - **Validation**: Enforce required fields and data type constraints - **Reusability**: One template can generate unlimited characters +#### Dataset Integration +- **Required Datasets**: Templates specify dependencies via Name|GUID|Version format +- **Reference Types**: + - **Dataset-Reference**: Direct reference to specific dataset entries by key + - **Dataset-Type**: Generic reference to any dataset of a specified type + - **Multiple Selection**: Support for selecting multiple items from datasets +- **Section Binding**: Entire sections can be bound to dataset types +- **Selection Modes**: Single selection, multiple selection with optional count limits +- **System Properties**: Special template properties handled by the application framework + #### Data Management - **Local Storage**: All templates stored locally in LocalStorage for fast access - **Cloud Sync**: Optional synchronization for paid accounts @@ -73,6 +89,13 @@ Templates define the structure and layout of character sheets for specific game - **Version Control**: Each template has unique GUID and semantic versioning - **Online Library**: Community templates hub (download free, upload requires paid account) +#### Dataset Dependencies +- **Reference Format**: `Name|GUID|Version` triple identifies exact dataset requirements +- **Dependency Resolution**: Templates fetch dependencies from local storage by GUID and version +- **Fallback Behavior**: Missing dependencies prompt download from community library +- **Graceful Degradation**: Templates function with empty datasets if dependencies unavailable +- **Type Discovery**: Dataset types determined from metadata, not hardcoded in templates + ### 3. Character Manager (Character Sheet Interface) Character sheets are runtime instances of templates, populated with player data and linked to relevant datasets. @@ -153,6 +176,13 @@ Sessions provide the active gaming environment where GMs and players interact wi - **Type Coercion**: Automatic conversion between compatible types - **Hierarchical Validation**: Groups must contain at least one child entry +#### Calculated Fields +- **Formula Parsing**: Support for mathematical expressions with field references +- **Runtime Evaluation**: Formulas re-evaluated whenever dependency values change +- **Function Support**: Built-in math functions (SUM, MIN, MAX, etc.) +- **Cross-Field References**: Reference other fields by name in calculations +- **Nested References**: Access properties of referenced dataset entries (e.g., Equipment.Weight) + ### File Format Specification (.szf) Session Zero Format provides human-readable, structured data representation for all content types. @@ -186,19 +216,48 @@ IsMartial (bool) = true [Metadata] Name (text) = Fantasy Character Sheet -Version (text) = 1.0.0 -Description (text-field) = Character sheet for fantasy RPGs +Version (text) = 2.0.0 +GUID (text) = f9e8d7c6-b5a4-3210-9876-543210fedcba +Description (text-field) = Advanced character sheet for fantasy RPGs +Author (text) = Template Master +GameSystem (text) = Fantasy RPG Universal -[Section: Core] +[Required Datasets] +ClassesDataset (text) = Core Classes|c3d4e5f6-g7h8-9012-cdef-123456789012|1.5.0 +RacesDataset (text) = Playable Races|d4e5f6g7-h8i9-0123-def0-234567890123|1.3.0 + +[Section: Core Identity] +Name (text) = +Race (dataset-reference) = RacesDataset +Class (dataset-reference) = ClassesDataset +Level (number) = 1 + +[Section: Ability Scores] Strength (number) = 10 Dexterity (number) = 10 Constitution (number) = 10 -[Section: Inventory] -# Links to 'items' dataset type for dropdown population +[Section.Ability Scores.Modifiers] +StrengthMod (calculated) = (Strength - 10) / 2 +DexterityMod (calculated) = (Dexterity - 10) / 2 -[Section: Journal] -Notes (text-field) = "" +[Section: Inventory] +DatasetType (system) = items +SelectionMode (system) = multiple +MaxItems (number) = 50 +CarryingCapacity (calculated) = Strength * 15 + +[Section: Equipment] +PrimaryWeapon (dataset-type) = items +SecondaryWeapon (dataset-type) = items + +[Section: Spellcasting] +DatasetType (system) = spells +SpellSaveDC (calculated) = 8 + ProficiencyBonus + IntelligenceMod + +[Section.Spellcasting.Known Spells] +Cantrips (dataset-type-multiple) = spells +Level1Spells (dataset-type-multiple) = spells ``` ## Business Model diff --git a/test_char_template.szf b/test_char_template.szf new file mode 100644 index 0000000..8ada35d --- /dev/null +++ b/test_char_template.szf @@ -0,0 +1,217 @@ +!type: character_template +!schema: 1.0.0 + +[Metadata] +Name (text) = Advanced Fantasy Character Sheet +Version (text) = 2.1.0 +GUID (text) = f9e8d7c6-b5a4-3210-9876-543210fedcba +Description (text-field) = Comprehensive character template for fantasy RPGs with full dataset integration, calculated fields, and advanced features +Author (text) = Template Master +GameSystem (text) = Fantasy RPG Universal + +[Required Datasets] +PlayerCharacterClasses (text) = Player Character Classes|c3d4e5f6-g7h8-9012-cdef-123456789012|1.5.3 +PlayableRaces (text) = Playable Races|d4e5f6g7-h8i9-0123-def0-234567890123|1.3.0 +CoreSkillsList (text) = Core Skills List|e5f6g7h8-i9j0-1234-ef01-345678901234|1.0.2 + +[Section: Core Identity] +CharacterName (text) = +PlayerName (text) = +Race (dataset-reference) = PlayableRaces +Class (dataset-reference) = PlayerCharacterClasses +Level (number) = 1 +Experience (number) = 0 +Alignment (text) = Neutral +Background (text) = +Age (number) = 25 +Height (text) = +Weight (text) = +Gender (text) = + +[Section: Ability Scores] +Strength (number) = 10 +Dexterity (number) = 10 +Constitution (number) = 10 +Intelligence (number) = 10 +Wisdom (number) = 10 +Charisma (number) = 10 + +[Section.Ability Scores.Modifiers] +StrengthMod (calculated) = (Strength - 10) / 2 +DexterityMod (calculated) = (Dexterity - 10) / 2 +ConstitutionMod (calculated) = (Constitution - 10) / 2 +IntelligenceMod (calculated) = (Intelligence - 10) / 2 +WisdomMod (calculated) = (Wisdom - 10) / 2 +CharismaMod (calculated) = (Charisma - 10) / 2 + +[Section: Combat Statistics] +HitPoints (number) = 10 +MaxHitPoints (calculated) = BaseHP + (ConstitutionMod * Level) +ArmorClass (calculated) = 10 + DexterityMod + ArmorBonus + ShieldBonus +Initiative (calculated) = DexterityMod + MiscInitiative +Speed (number) = 30 +BaseAttackBonus (number) = 0 + +[Section.Combat Statistics.Saving Throws] +Fortitude (calculated) = BaseFortitude + ConstitutionMod +Reflex (calculated) = BaseReflex + DexterityMod +Will (calculated) = BaseWill + WisdomMod +BaseFortitude (number) = 0 +BaseReflex (number) = 0 +BaseWill (number) = 0 + +[Section: Skills] +DatasetReference (system) = CoreSkillsList +Acrobatics (number) = 0 +Athletics (number) = 0 +Deception (number) = 0 +History (number) = 0 +Insight (number) = 0 +Intimidation (number) = 0 +Investigation (number) = 0 +Medicine (number) = 0 +Nature (number) = 0 +Perception (number) = 0 +Performance (number) = 0 +Persuasion (number) = 0 +Religion (number) = 0 +SleightOfHand (number) = 0 +Stealth (number) = 0 +Survival (number) = 0 + +[Section.Skills.Skill Modifiers] +AcrobaticsTotal (calculated) = Acrobatics + DexterityMod +AthleticsTotal (calculated) = Athletics + StrengthMod +DeceptionTotal (calculated) = Deception + CharismaMod +HistoryTotal (calculated) = History + IntelligenceMod +InsightTotal (calculated) = Insight + WisdomMod +IntimidationTotal (calculated) = Intimidation + CharismaMod +InvestigationTotal (calculated) = Investigation + IntelligenceMod +MedicineTotal (calculated) = Medicine + WisdomMod +NatureTotal (calculated) = Nature + IntelligenceMod +PerceptionTotal (calculated) = Perception + WisdomMod +PerformanceTotal (calculated) = Performance + CharismaMod +PersuasionTotal (calculated) = Persuasion + CharismaMod +ReligionTotal (calculated) = Religion + IntelligenceMod +SleightOfHandTotal (calculated) = SleightOfHand + DexterityMod +StealthTotal (calculated) = Stealth + DexterityMod +SurvivalTotal (calculated) = Survival + WisdomMod + +[Section: Equipment] +DatasetType (system) = items +SelectionMode (system) = multiple +Currency (group) = true + +[Section.Equipment.Weapons] +PrimaryWeapon (dataset-type) = items +SecondaryWeapon (dataset-type) = items +RangedWeapon (dataset-type) = items +Ammunition (dataset-type) = items +AmmoCount (number) = 0 + +[Section.Equipment.Armor] +Armor (dataset-type) = items +Shield (dataset-type) = items +ArmorBonus (calculated) = Armor.ArmorClass +ShieldBonus (calculated) = Shield.ArmorClass + +[Section.Equipment.Currency] +PlatinumPieces (number) = 0 +GoldPieces (number) = 0 +SilverPieces (number) = 0 +CopperPieces (number) = 0 +TotalGoldValue (calculated) = (PlatinumPieces * 10) + GoldPieces + (SilverPieces / 10) + (CopperPieces / 100) + +[Section: Inventory] +DatasetType (system) = items +SelectionMode (system) = multiple +MaxItems (number) = 50 +CarryingCapacity (calculated) = Strength * 15 +CurrentWeight (calculated) = SUM(SelectedItems.Weight) +EncumbranceLevel (calculated) = CurrentWeight / CarryingCapacity + +[Section: Spellcasting] +DatasetType (system) = spells +SelectionMode (system) = multiple +SpellcastingAbility (text) = Intelligence +SpellSaveDC (calculated) = 8 + ProficiencyBonus + SpellcastingAbilityMod +SpellAttackBonus (calculated) = ProficiencyBonus + SpellcastingAbilityMod +SpellsKnown (number) = 0 +SpellSlots (group) = true + +[Section.Spellcasting.Spell Slots] +Level1Slots (number) = 0 +Level2Slots (number) = 0 +Level3Slots (number) = 0 +Level4Slots (number) = 0 +Level5Slots (number) = 0 +Level6Slots (number) = 0 +Level7Slots (number) = 0 +Level8Slots (number) = 0 +Level9Slots (number) = 0 + +[Section.Spellcasting.Known Spells] +Cantrips (dataset-type-multiple) = spells +Level1Spells (dataset-type-multiple) = spells +Level2Spells (dataset-type-multiple) = spells +Level3Spells (dataset-type-multiple) = spells +Level4Spells (dataset-type-multiple) = spells +Level5Spells (dataset-type-multiple) = spells + +[Section: Companions] +DatasetType (system) = npcs +SelectionMode (system) = multiple +MaxCompanions (number) = 3 + +[Section.Companions.Active Companions] +Animal Companion (dataset-type) = npcs +Familiar (dataset-type) = npcs +Hireling (dataset-type) = npcs + +[Section: Features and Traits] +RacialTraits (group) = true +ClassFeatures (group) = true +Feats (group) = true + +[Section.Features and Traits.Racial Traits] +DarkvisionRange (number) = 0 +Size (text) = Medium +Languages (text-field) = Common +SpecialAbilities (text-field) = + +[Section.Features and Traits.Class Features] +HitDie (text) = d8 +ProficiencyBonus (number) = 2 +ArmorProficiencies (text-field) = +WeaponProficiencies (text-field) = +ToolProficiencies (text-field) = +SavingThrowProficiencies (text) = + +[Section: Personal Journal] +CharacterDescription (text-field) = +Personality (text-field) = +Ideals (text-field) = +Bonds (text-field) = +Flaws (text-field) = +Backstory (text-field) = +Goals (text-field) = +Notes (text-field) = + +[Section: Combat Tracker] +CurrentHitPoints (number) = 10 +TemporaryHitPoints (number) = 0 +HitDiceRemaining (text) = 1d8 +DeathSaves (group) = true +Conditions (text-field) = +ActionEconomy (group) = true + +[Section.Combat Tracker.Death Saves] +Successes (number) = 0 +Failures (number) = 0 +IsStabilized (bool) = false + +[Section.Combat Tracker.Action Economy] +ActionUsed (bool) = false +BonusActionUsed (bool) = false +ReactionUsed (bool) = false +MovementUsed (number) = 0 \ No newline at end of file diff --git a/test_dataset.szf b/test_dataset.szf new file mode 100644 index 0000000..4d05f69 --- /dev/null +++ b/test_dataset.szf @@ -0,0 +1,106 @@ +!type: dataset +!schema: 1.0.0 + +[Metadata] +Name (text) = Complete Fantasy Arsenal +Type (text) = items +Version (text) = 1.2.0 +GUID (text) = a1b2c3d4-e5f6-7890-abcd-ef1234567890 +Description (text-field) = Comprehensive collection of fantasy weapons, armor, and magical items with full stat blocks and lore +Author (text) = Fantasy Content Creator +Tags (text) = fantasy, weapons, armor, magic, core + +[Entry: Flamebrand Longsword] +Name (text) = Flamebrand Longsword +Description (text-field) = An ancient blade wreathed in perpetual flames, forged in the heart of a dying star. The crossguard bears draconic runes that pulse with inner fire. +Category (text) = Weapon +ItemType (text) = Sword +Rarity (text) = Legendary +Value (number) = 5000 +Weight (number) = 3.5 +IsStackable (bool) = false +RequiresAttunement (bool) = true + +[Entry.Flamebrand Longsword.Combat Stats] +Damage (text) = 1d8 + 2 +DamageType (text) = Slashing + Fire +AttackBonus (number) = 2 +CriticalRange (text) = 19-20 +CriticalMultiplier (text) = x2 +Reach (number) = 5 +WeaponType (text) = Martial + +[Entry.Flamebrand Longsword.Properties] +IsMagical (bool) = true +IsCursed (bool) = false +IsArtifact (bool) = false +School (text) = Evocation +CasterLevel (number) = 12 + +[Entry.Flamebrand Longsword.Special Abilities] +FlameStrike (text-field) = Once per day, wielder can activate to deal additional 2d6 fire damage on next successful hit +FireImmunity (bool) = true +LightSource (text) = Sheds bright light in 20-foot radius, dim light for additional 20 feet +IgniteFlammables (bool) = true + +[Entry.Flamebrand Longsword.Lore] +Origin (text-field) = Created by the legendary smith Valdris Fireforge during the War of Burning Skies +History (text-field) = Wielded by three kings before disappearing into the Tomb of Echoes. Recently recovered by the Sunblade Company. +Legends (text-field) = Said to grow stronger when facing creatures of darkness and cold +Identification (text) = DC 20 Arcana check reveals basic properties, DC 25 reveals special abilities + +[Entry: Dragonscale Plate] +Name (text) = Dragonscale Plate Armor +Description (text-field) = Masterwork plate armor crafted from the scales of an ancient red dragon, providing exceptional protection and resistance to elemental damage. +Category (text) = Armor +ItemType (text) = Heavy Armor +Rarity (text) = Very Rare +Value (number) = 8000 +Weight (number) = 55 +IsStackable (bool) = false +RequiresAttunement (bool) = false + +[Entry.Dragonscale Plate.Armor Stats] +ArmorClass (number) = 18 +ArmorType (text) = Plate +MaxDexBonus (number) = 0 +ArmorCheckPenalty (number) = -6 +ArcaneSpellFailure (number) = 35 +SpeedReduction (number) = 10 + +[Entry.Dragonscale Plate.Resistances] +FireResistance (bool) = true +ColdResistance (bool) = false +AcidResistance (bool) = true +ElectricResistance (bool) = false +SonicResistance (bool) = false + +[Entry.Dragonscale Plate.Special Properties] +DragonAura (text-field) = Intimidation checks gain +2 circumstance bonus +ScaleRegeneration (text-field) = Armor self-repairs minor damage over time +BreathWeaponProtection (number) = 50 + +[Entry: Healing Potion Lesser] +Name (text) = Potion of Lesser Healing +Description (text-field) = A crimson liquid that glows faintly with restorative magic, contained in a crystal vial with a cork stopper. +Category (text) = Consumable +ItemType (text) = Potion +Rarity (text) = Common +Value (number) = 50 +Weight (number) = 0.5 +IsStackable (bool) = true +MaxStack (number) = 10 + +[Entry.Healing Potion Lesser.Effects] +HealingAmount (text) = 2d4 + 2 +Duration (text) = Instantaneous +ActionType (text) = Standard Action +UsageLimit (number) = 1 +DestroyOnUse (bool) = true + +[Entry.Healing Potion Lesser.Crafting] +CraftDC (number) = 15 +CraftSkill (text) = Alchemy +MaterialCost (number) = 25 +CraftTime (text) = 4 hours +RequiredLevel (number) = 3 \ No newline at end of file