Merge pull request #1 from headhunter45/add-file-import
Add Opening monster files from tetra cube's generator
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
E20209FC25D8E19100EFE733 /* MonsterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20209FA25D8E19100EFE733 /* MonsterViewModel.swift */; };
|
E20209FC25D8E19100EFE733 /* MonsterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20209FA25D8E19100EFE733 /* MonsterViewModel.swift */; };
|
||||||
E210B83A25B42D980083EAC5 /* MCProficiencyPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E210B83925B42D980083EAC5 /* MCProficiencyPicker.swift */; };
|
E210B83A25B42D980083EAC5 /* MCProficiencyPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E210B83925B42D980083EAC5 /* MCProficiencyPicker.swift */; };
|
||||||
E210B83F25B42DAB0083EAC5 /* MCAdvantagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E210B83E25B42DAB0083EAC5 /* MCAdvantagePicker.swift */; };
|
E210B83F25B42DAB0083EAC5 /* MCAdvantagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E210B83E25B42DAB0083EAC5 /* MCAdvantagePicker.swift */; };
|
||||||
|
E21661D12616E9A800117782 /* ImportMonster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21661D02616E9A800117782 /* ImportMonster.swift */; };
|
||||||
E216B791260C1FE800FB205F /* LanguageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216B790260C1FE800FB205F /* LanguageViewModel.swift */; };
|
E216B791260C1FE800FB205F /* LanguageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216B790260C1FE800FB205F /* LanguageViewModel.swift */; };
|
||||||
E216B799260C2DF200FB205F /* EditLanguages.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216B798260C2DF200FB205F /* EditLanguages.swift */; };
|
E216B799260C2DF200FB205F /* EditLanguages.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216B798260C2DF200FB205F /* EditLanguages.swift */; };
|
||||||
E216B79E260C396F00FB205F /* EditLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216B79D260C396F00FB205F /* EditLanguage.swift */; };
|
E216B79E260C396F00FB205F /* EditLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216B79D260C396F00FB205F /* EditLanguage.swift */; };
|
||||||
@@ -22,6 +23,13 @@
|
|||||||
E216B7BC260C691400FB205F /* EditChallengeRating.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216B7BB260C691400FB205F /* EditChallengeRating.swift */; };
|
E216B7BC260C691400FB205F /* EditChallengeRating.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216B7BB260C691400FB205F /* EditChallengeRating.swift */; };
|
||||||
E216B7C1260C6B6000FB205F /* MCChallengeRatingPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216B7C0260C6B6000FB205F /* MCChallengeRatingPicker.swift */; };
|
E216B7C1260C6B6000FB205F /* MCChallengeRatingPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E216B7C0260C6B6000FB205F /* MCChallengeRatingPicker.swift */; };
|
||||||
E2182E6425B22F8A00DFAEF8 /* Monster+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2182E6225B22F8A00DFAEF8 /* Monster+CoreDataClass.swift */; };
|
E2182E6425B22F8A00DFAEF8 /* Monster+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2182E6225B22F8A00DFAEF8 /* Monster+CoreDataClass.swift */; };
|
||||||
|
E219247B261989B400C84E12 /* MonsterDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = E219247A261989B400C84E12 /* MonsterDTO.swift */; };
|
||||||
|
E2192480261989F700C84E12 /* SavingThrowDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = E219247F261989F700C84E12 /* SavingThrowDTO.swift */; };
|
||||||
|
E219248526198A1200C84E12 /* SkillDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = E219248426198A1200C84E12 /* SkillDTO.swift */; };
|
||||||
|
E219248A26198A5400C84E12 /* TraitDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = E219248926198A5400C84E12 /* TraitDTO.swift */; };
|
||||||
|
E219248F26198A6A00C84E12 /* DamageTypeDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = E219248E26198A6A00C84E12 /* DamageTypeDTO.swift */; };
|
||||||
|
E219249426198A8200C84E12 /* LanguageDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = E219249326198A8200C84E12 /* LanguageDTO.swift */; };
|
||||||
|
E219249926198E0D00C84E12 /* MonsterImportHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E219249826198E0D00C84E12 /* MonsterImportHelper.swift */; };
|
||||||
E24ACE502607326E009BF703 /* EditBasicInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24ACE4F2607326E009BF703 /* EditBasicInfo.swift */; };
|
E24ACE502607326E009BF703 /* EditBasicInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24ACE4F2607326E009BF703 /* EditBasicInfo.swift */; };
|
||||||
E24ACE562607EE94009BF703 /* EditArmor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24ACE552607EE94009BF703 /* EditArmor.swift */; };
|
E24ACE562607EE94009BF703 /* EditArmor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24ACE552607EE94009BF703 /* EditArmor.swift */; };
|
||||||
E24ACE5B2607F0F2009BF703 /* EditSpeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24ACE5A2607F0F2009BF703 /* EditSpeed.swift */; };
|
E24ACE5B2607F0F2009BF703 /* EditSpeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24ACE5A2607F0F2009BF703 /* EditSpeed.swift */; };
|
||||||
@@ -87,6 +95,7 @@
|
|||||||
E20209FA25D8E19100EFE733 /* MonsterViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MonsterViewModel.swift; sourceTree = "<group>"; };
|
E20209FA25D8E19100EFE733 /* MonsterViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MonsterViewModel.swift; sourceTree = "<group>"; };
|
||||||
E210B83925B42D980083EAC5 /* MCProficiencyPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCProficiencyPicker.swift; sourceTree = "<group>"; };
|
E210B83925B42D980083EAC5 /* MCProficiencyPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCProficiencyPicker.swift; sourceTree = "<group>"; };
|
||||||
E210B83E25B42DAB0083EAC5 /* MCAdvantagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCAdvantagePicker.swift; sourceTree = "<group>"; };
|
E210B83E25B42DAB0083EAC5 /* MCAdvantagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCAdvantagePicker.swift; sourceTree = "<group>"; };
|
||||||
|
E21661D02616E9A800117782 /* ImportMonster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportMonster.swift; sourceTree = "<group>"; };
|
||||||
E216B790260C1FE800FB205F /* LanguageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageViewModel.swift; sourceTree = "<group>"; };
|
E216B790260C1FE800FB205F /* LanguageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageViewModel.swift; sourceTree = "<group>"; };
|
||||||
E216B798260C2DF200FB205F /* EditLanguages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditLanguages.swift; sourceTree = "<group>"; };
|
E216B798260C2DF200FB205F /* EditLanguages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditLanguages.swift; sourceTree = "<group>"; };
|
||||||
E216B79D260C396F00FB205F /* EditLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditLanguage.swift; sourceTree = "<group>"; };
|
E216B79D260C396F00FB205F /* EditLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditLanguage.swift; sourceTree = "<group>"; };
|
||||||
@@ -94,6 +103,13 @@
|
|||||||
E216B7BB260C691400FB205F /* EditChallengeRating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditChallengeRating.swift; sourceTree = "<group>"; };
|
E216B7BB260C691400FB205F /* EditChallengeRating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditChallengeRating.swift; sourceTree = "<group>"; };
|
||||||
E216B7C0260C6B6000FB205F /* MCChallengeRatingPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCChallengeRatingPicker.swift; sourceTree = "<group>"; };
|
E216B7C0260C6B6000FB205F /* MCChallengeRatingPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCChallengeRatingPicker.swift; sourceTree = "<group>"; };
|
||||||
E2182E6225B22F8A00DFAEF8 /* Monster+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Monster+CoreDataClass.swift"; sourceTree = "<group>"; };
|
E2182E6225B22F8A00DFAEF8 /* Monster+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Monster+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||||
|
E219247A261989B400C84E12 /* MonsterDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonsterDTO.swift; sourceTree = "<group>"; };
|
||||||
|
E219247F261989F700C84E12 /* SavingThrowDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavingThrowDTO.swift; sourceTree = "<group>"; };
|
||||||
|
E219248426198A1200C84E12 /* SkillDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkillDTO.swift; sourceTree = "<group>"; };
|
||||||
|
E219248926198A5400C84E12 /* TraitDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraitDTO.swift; sourceTree = "<group>"; };
|
||||||
|
E219248E26198A6A00C84E12 /* DamageTypeDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamageTypeDTO.swift; sourceTree = "<group>"; };
|
||||||
|
E219249326198A8200C84E12 /* LanguageDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageDTO.swift; sourceTree = "<group>"; };
|
||||||
|
E219249826198E0D00C84E12 /* MonsterImportHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonsterImportHelper.swift; sourceTree = "<group>"; };
|
||||||
E24ACE4F2607326E009BF703 /* EditBasicInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditBasicInfo.swift; sourceTree = "<group>"; };
|
E24ACE4F2607326E009BF703 /* EditBasicInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditBasicInfo.swift; sourceTree = "<group>"; };
|
||||||
E24ACE552607EE94009BF703 /* EditArmor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditArmor.swift; sourceTree = "<group>"; };
|
E24ACE552607EE94009BF703 /* EditArmor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditArmor.swift; sourceTree = "<group>"; };
|
||||||
E24ACE5A2607F0F2009BF703 /* EditSpeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditSpeed.swift; sourceTree = "<group>"; };
|
E24ACE5A2607F0F2009BF703 /* EditSpeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditSpeed.swift; sourceTree = "<group>"; };
|
||||||
@@ -259,6 +275,7 @@
|
|||||||
E2CB0DE526088CE400142591 /* EditStrings.swift */,
|
E2CB0DE526088CE400142591 /* EditStrings.swift */,
|
||||||
E254F912260D1F6D009295A5 /* EditTrait.swift */,
|
E254F912260D1F6D009295A5 /* EditTrait.swift */,
|
||||||
E254F90D260D19A0009295A5 /* EditTraits.swift */,
|
E254F90D260D19A0009295A5 /* EditTraits.swift */,
|
||||||
|
E21661D02616E9A800117782 /* ImportMonster.swift */,
|
||||||
E2570FFE25B1AE180055B23B /* Library.swift */,
|
E2570FFE25B1AE180055B23B /* Library.swift */,
|
||||||
E2CB0DB726081A2F00142591 /* MCAbilityScorePicker.swift */,
|
E2CB0DB726081A2F00142591 /* MCAbilityScorePicker.swift */,
|
||||||
E210B83E25B42DAB0083EAC5 /* MCAdvantagePicker.swift */,
|
E210B83E25B42DAB0083EAC5 /* MCAdvantagePicker.swift */,
|
||||||
@@ -279,13 +296,19 @@
|
|||||||
children = (
|
children = (
|
||||||
E254F905260D0818009295A5 /* AbilityViewModel.swift */,
|
E254F905260D0818009295A5 /* AbilityViewModel.swift */,
|
||||||
E216B7B6260C5A9800FB205F /* ChallengeRatingViewModel.swift */,
|
E216B7B6260C5A9800FB205F /* ChallengeRatingViewModel.swift */,
|
||||||
|
E219248E26198A6A00C84E12 /* DamageTypeDTO.swift */,
|
||||||
|
E20209E625D8DEB600EFE733 /* Enums */,
|
||||||
|
E219249326198A8200C84E12 /* LanguageDTO.swift */,
|
||||||
E216B790260C1FE800FB205F /* LanguageViewModel.swift */,
|
E216B790260C1FE800FB205F /* LanguageViewModel.swift */,
|
||||||
E2182E6225B22F8A00DFAEF8 /* Monster+CoreDataClass.swift */,
|
E2182E6225B22F8A00DFAEF8 /* Monster+CoreDataClass.swift */,
|
||||||
|
E219247A261989B400C84E12 /* MonsterDTO.swift */,
|
||||||
E20209FA25D8E19100EFE733 /* MonsterViewModel.swift */,
|
E20209FA25D8E19100EFE733 /* MonsterViewModel.swift */,
|
||||||
|
E219247F261989F700C84E12 /* SavingThrowDTO.swift */,
|
||||||
E20209D225D8DD9600EFE733 /* Skill+CoreDataClass.swift */,
|
E20209D225D8DD9600EFE733 /* Skill+CoreDataClass.swift */,
|
||||||
|
E219248426198A1200C84E12 /* SkillDTO.swift */,
|
||||||
E20209F925D8E19100EFE733 /* SkillViewModel.swift */,
|
E20209F925D8E19100EFE733 /* SkillViewModel.swift */,
|
||||||
E2CB0DE0260887ED00142591 /* StringViewModel.swift */,
|
E2CB0DE0260887ED00142591 /* StringViewModel.swift */,
|
||||||
E20209E625D8DEB600EFE733 /* Enums */,
|
E219248926198A5400C84E12 /* TraitDTO.swift */,
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -294,6 +317,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E2D473FC25B532C900CB36D7 /* Color+Hex.swift */,
|
E2D473FC25B532C900CB36D7 /* Color+Hex.swift */,
|
||||||
|
E219249826198E0D00C84E12 /* MonsterImportHelper.swift */,
|
||||||
E2CB0DD62608720000142591 /* StringHelper.swift */,
|
E2CB0DD62608720000142591 /* StringHelper.swift */,
|
||||||
);
|
);
|
||||||
path = Helpers;
|
path = Helpers;
|
||||||
@@ -435,11 +459,13 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
E20209FB25D8E19100EFE733 /* SkillViewModel.swift in Sources */,
|
E20209FB25D8E19100EFE733 /* SkillViewModel.swift in Sources */,
|
||||||
|
E21661D12616E9A800117782 /* ImportMonster.swift in Sources */,
|
||||||
E24ACE602607F45E009BF703 /* EditAbilityScores.swift in Sources */,
|
E24ACE602607F45E009BF703 /* EditAbilityScores.swift in Sources */,
|
||||||
E2570FC225B1AC550055B23B /* Persistence.swift in Sources */,
|
E2570FC225B1AC550055B23B /* Persistence.swift in Sources */,
|
||||||
E216B799260C2DF200FB205F /* EditLanguages.swift in Sources */,
|
E216B799260C2DF200FB205F /* EditLanguages.swift in Sources */,
|
||||||
E2570FBB25B1AC520055B23B /* ContentView.swift in Sources */,
|
E2570FBB25B1AC520055B23B /* ContentView.swift in Sources */,
|
||||||
E24ACE502607326E009BF703 /* EditBasicInfo.swift in Sources */,
|
E24ACE502607326E009BF703 /* EditBasicInfo.swift in Sources */,
|
||||||
|
E219249426198A8200C84E12 /* LanguageDTO.swift in Sources */,
|
||||||
E254F90E260D19A0009295A5 /* EditTraits.swift in Sources */,
|
E254F90E260D19A0009295A5 /* EditTraits.swift in Sources */,
|
||||||
E2570FC525B1AC550055B23B /* MonsterCards.xcdatamodeld in Sources */,
|
E2570FC525B1AC550055B23B /* MonsterCards.xcdatamodeld in Sources */,
|
||||||
E2182E6425B22F8A00DFAEF8 /* Monster+CoreDataClass.swift in Sources */,
|
E2182E6425B22F8A00DFAEF8 /* Monster+CoreDataClass.swift in Sources */,
|
||||||
@@ -447,11 +473,14 @@
|
|||||||
E210B83A25B42D980083EAC5 /* MCProficiencyPicker.swift in Sources */,
|
E210B83A25B42D980083EAC5 /* MCProficiencyPicker.swift in Sources */,
|
||||||
E2570FF025B1ADC10055B23B /* Search.swift in Sources */,
|
E2570FF025B1ADC10055B23B /* Search.swift in Sources */,
|
||||||
E257100925B1B2480055B23B /* MonsterDetail.swift in Sources */,
|
E257100925B1B2480055B23B /* MonsterDetail.swift in Sources */,
|
||||||
|
E219248526198A1200C84E12 /* SkillDTO.swift in Sources */,
|
||||||
E2D473FD25B532C900CB36D7 /* Color+Hex.swift in Sources */,
|
E2D473FD25B532C900CB36D7 /* Color+Hex.swift in Sources */,
|
||||||
E2B5285925B3028700AAA69E /* EditMonster.swift in Sources */,
|
E2B5285925B3028700AAA69E /* EditMonster.swift in Sources */,
|
||||||
|
E219247B261989B400C84E12 /* MonsterDTO.swift in Sources */,
|
||||||
E2CB0DD72608720000142591 /* StringHelper.swift in Sources */,
|
E2CB0DD72608720000142591 /* StringHelper.swift in Sources */,
|
||||||
E2570FF525B1ADEB0055B23B /* Dashboard.swift in Sources */,
|
E2570FF525B1ADEB0055B23B /* Dashboard.swift in Sources */,
|
||||||
E2CB0DB826081A2F00142591 /* MCAbilityScorePicker.swift in Sources */,
|
E2CB0DB826081A2F00142591 /* MCAbilityScorePicker.swift in Sources */,
|
||||||
|
E219249926198E0D00C84E12 /* MonsterImportHelper.swift in Sources */,
|
||||||
E2CB0DC026086E3C00142591 /* ChallengeRating.swift in Sources */,
|
E2CB0DC026086E3C00142591 /* ChallengeRating.swift in Sources */,
|
||||||
E257100425B1AF4A0055B23B /* SearchBar.swift in Sources */,
|
E257100425B1AF4A0055B23B /* SearchBar.swift in Sources */,
|
||||||
E20209F525D8E04300EFE733 /* AdvantageType.swift in Sources */,
|
E20209F525D8E04300EFE733 /* AdvantageType.swift in Sources */,
|
||||||
@@ -465,6 +494,7 @@
|
|||||||
E2CB0DCA26086E8300142591 /* ArmorType.swift in Sources */,
|
E2CB0DCA26086E8300142591 /* ArmorType.swift in Sources */,
|
||||||
E24ACE562607EE94009BF703 /* EditArmor.swift in Sources */,
|
E24ACE562607EE94009BF703 /* EditArmor.swift in Sources */,
|
||||||
E2CB0DE1260887ED00142591 /* StringViewModel.swift in Sources */,
|
E2CB0DE1260887ED00142591 /* StringViewModel.swift in Sources */,
|
||||||
|
E219248F26198A6A00C84E12 /* DamageTypeDTO.swift in Sources */,
|
||||||
E20209F425D8E04300EFE733 /* ProficiencyType.swift in Sources */,
|
E20209F425D8E04300EFE733 /* ProficiencyType.swift in Sources */,
|
||||||
E2CB0DC526086E5F00142591 /* SizeType.swift in Sources */,
|
E2CB0DC526086E5F00142591 /* SizeType.swift in Sources */,
|
||||||
E254F906260D0818009295A5 /* AbilityViewModel.swift in Sources */,
|
E254F906260D0818009295A5 /* AbilityViewModel.swift in Sources */,
|
||||||
@@ -472,12 +502,14 @@
|
|||||||
E24ACE5B2607F0F2009BF703 /* EditSpeed.swift in Sources */,
|
E24ACE5B2607F0F2009BF703 /* EditSpeed.swift in Sources */,
|
||||||
E2570FB925B1AC520055B23B /* MonsterCardsApp.swift in Sources */,
|
E2570FB925B1AC520055B23B /* MonsterCardsApp.swift in Sources */,
|
||||||
E254F913260D1F6D009295A5 /* EditTrait.swift in Sources */,
|
E254F913260D1F6D009295A5 /* EditTrait.swift in Sources */,
|
||||||
|
E2192480261989F700C84E12 /* SavingThrowDTO.swift in Sources */,
|
||||||
E216B7B7260C5A9800FB205F /* ChallengeRatingViewModel.swift in Sources */,
|
E216B7B7260C5A9800FB205F /* ChallengeRatingViewModel.swift in Sources */,
|
||||||
E20209D325D8DD9600EFE733 /* Skill+CoreDataClass.swift in Sources */,
|
E20209D325D8DD9600EFE733 /* Skill+CoreDataClass.swift in Sources */,
|
||||||
E24ACE652607F55D009BF703 /* EditSavingThrows.swift in Sources */,
|
E24ACE652607F55D009BF703 /* EditSavingThrows.swift in Sources */,
|
||||||
E2BD702C25B3A8D70058ED69 /* MCTextField.swift in Sources */,
|
E2BD702C25B3A8D70058ED69 /* MCTextField.swift in Sources */,
|
||||||
E216B7BC260C691400FB205F /* EditChallengeRating.swift in Sources */,
|
E216B7BC260C691400FB205F /* EditChallengeRating.swift in Sources */,
|
||||||
E20209E825D8DEC100EFE733 /* AbilityScore.swift in Sources */,
|
E20209E825D8DEC100EFE733 /* AbilityScore.swift in Sources */,
|
||||||
|
E219248A26198A5400C84E12 /* TraitDTO.swift in Sources */,
|
||||||
E210B83F25B42DAB0083EAC5 /* MCAdvantagePicker.swift in Sources */,
|
E210B83F25B42DAB0083EAC5 /* MCAdvantagePicker.swift in Sources */,
|
||||||
E216B7C1260C6B6000FB205F /* MCChallengeRatingPicker.swift in Sources */,
|
E216B7C1260C6B6000FB205F /* MCChallengeRatingPicker.swift in Sources */,
|
||||||
E26CDA2B25CFB38E00E3F50D /* MCArmorTypePicker.swift in Sources */,
|
E26CDA2B25CFB38E00E3F50D /* MCArmorTypePicker.swift in Sources */,
|
||||||
|
|||||||
111
iOS/MonsterCards/Helpers/MonsterImportHelper.swift
Normal file
111
iOS/MonsterCards/Helpers/MonsterImportHelper.swift
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
//
|
||||||
|
// MonsterImportHelper.swift
|
||||||
|
// MonsterCards
|
||||||
|
//
|
||||||
|
// Created by Tom Hicks on 4/3/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension MonsterViewModel {
|
||||||
|
func maybeAddSense(_ name: String, _ distance: Int) {
|
||||||
|
if (distance > 0) {
|
||||||
|
senses.append(StringViewModel("\(name): \(distance) ft."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MonsterImportHelper {
|
||||||
|
static func import5ESBMonster(_ monsterDTO: MonsterDTO) -> MonsterViewModel {
|
||||||
|
let monster = MonsterViewModel()
|
||||||
|
|
||||||
|
monster.name = monsterDTO.name
|
||||||
|
monster.size = monsterDTO.size
|
||||||
|
monster.type = monsterDTO.type
|
||||||
|
monster.subType = monsterDTO.tag
|
||||||
|
monster.alignment = monsterDTO.alignment
|
||||||
|
monster.hitDice = Int64(monsterDTO.hitDice)
|
||||||
|
monster.armorType = ArmorType(rawValue: monsterDTO.armorName) ?? .none
|
||||||
|
monster.hasShield = monsterDTO.shieldBonus != 0
|
||||||
|
monster.naturalArmorBonus = Int64(monsterDTO.natArmorBonus)
|
||||||
|
monster.customArmor = monsterDTO.otherArmorDesc
|
||||||
|
monster.walkSpeed = Int64(monsterDTO.speed)
|
||||||
|
monster.burrowSpeed = Int64(monsterDTO.burrowSpeed)
|
||||||
|
monster.climbSpeed = Int64(monsterDTO.climbSpeed)
|
||||||
|
monster.flySpeed = Int64(monsterDTO.flySpeed)
|
||||||
|
monster.swimSpeed = Int64(monsterDTO.swimSpeed)
|
||||||
|
monster.canHover = monsterDTO.hover
|
||||||
|
monster.hasCustomHP = monsterDTO.customHP
|
||||||
|
monster.customHP = monsterDTO.hpText
|
||||||
|
monster.hasCustomSpeed = monsterDTO.customSpeed
|
||||||
|
monster.customSpeed = monsterDTO.speedDesc
|
||||||
|
monster.strengthScore = Int64(monsterDTO.strPoints)
|
||||||
|
monster.dexterityScore = Int64(monsterDTO.dexPoints)
|
||||||
|
monster.constitutionScore = Int64(monsterDTO.conPoints)
|
||||||
|
monster.intelligenceScore = Int64(monsterDTO.intPoints)
|
||||||
|
monster.wisdomScore = Int64(monsterDTO.wisPoints)
|
||||||
|
monster.charismaScore = Int64(monsterDTO.chaPoints)
|
||||||
|
monster.isBlind = monsterDTO.blind
|
||||||
|
monster.maybeAddSense("blindsight", monsterDTO.blindsight)
|
||||||
|
monster.maybeAddSense("darkvision", monsterDTO.darkvision)
|
||||||
|
monster.maybeAddSense("tremorsense", monsterDTO.tremorsense)
|
||||||
|
monster.maybeAddSense("turesight", monsterDTO.truesight)
|
||||||
|
monster.telepathy = Int64(monsterDTO.telepathy)
|
||||||
|
monster.challengeRating = ChallengeRating(rawValue: monsterDTO.cr) ?? ChallengeRating.one
|
||||||
|
monster.customChallengeRating = monsterDTO.customCr
|
||||||
|
monster.customProficiencyBonus = Int64(monsterDTO.customProf)
|
||||||
|
// TODO: Think about adding legendary properties isLegendary, legendariesDescription, isLair, lairDescription, lairDescriptionEnd, isRegional, regionalDescription, regionalDescriptionEnd
|
||||||
|
monster.abilities = monsterDTO.abilities.map({AbilityViewModel($0.name, $0.desc)})
|
||||||
|
monster.actions = monsterDTO.actions.map({AbilityViewModel($0.name, $0.desc)})
|
||||||
|
monster.reactions = monsterDTO.reactions.map({AbilityViewModel($0.name, $0.desc)})
|
||||||
|
monster.legendaryActions = monsterDTO.legendaries.map({AbilityViewModel($0.name, $0.desc)})
|
||||||
|
monster.lairActions = monsterDTO.lairs.map({AbilityViewModel($0.name, $0.desc)})
|
||||||
|
monster.regionalActions = monsterDTO.regionals.map({AbilityViewModel($0.name, $0.desc)})
|
||||||
|
monsterDTO.sthrows.forEach({
|
||||||
|
switch $0.name {
|
||||||
|
case "str":
|
||||||
|
monster.strengthSavingThrowProficiency = .proficient
|
||||||
|
case "dex":
|
||||||
|
monster.dexteritySavingThrowProficiency = .proficient
|
||||||
|
case "con":
|
||||||
|
monster.constitutionSavingThrowProficiency = .proficient
|
||||||
|
case "int":
|
||||||
|
monster.intelligenceSavingThrowProficiency = .proficient
|
||||||
|
case "wis":
|
||||||
|
monster.wisdomSavingThrowProficiency = .proficient
|
||||||
|
case "cha":
|
||||||
|
monster.charismaSavingThrowProficiency = .proficient
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
monster.skills = monsterDTO.skills.map({
|
||||||
|
// TODO: consider using a lookup table to make fixing missing stats easier
|
||||||
|
SkillViewModel(
|
||||||
|
$0.name,
|
||||||
|
AbilityScore(rawValue: $0.stat) ?? .dexterity,
|
||||||
|
$0.note == " (ex)" ? .expertise : .proficient)
|
||||||
|
})
|
||||||
|
monster.damageImmunities = monsterDTO.damageTypes
|
||||||
|
.filter({$0.type == "i" || $0.type == " (Immune)"})
|
||||||
|
.map({StringViewModel($0.name)})
|
||||||
|
monster.damageResistances = monsterDTO.damageTypes
|
||||||
|
.filter({$0.type == "r" || $0.type == " (Resistant)"})
|
||||||
|
.map({StringViewModel($0.name)})
|
||||||
|
monster.damageVulnerabilities = monsterDTO.damageTypes
|
||||||
|
.filter({$0.type == "v" || $0.type == " (Vulnerable)"})
|
||||||
|
.map({StringViewModel($0.name)})
|
||||||
|
monster.conditionImmunities = monsterDTO.conditions.map({StringViewModel($0.name)})
|
||||||
|
monster.languages = monsterDTO.languages
|
||||||
|
.map({
|
||||||
|
LanguageViewModel($0.name, $0.speaks)
|
||||||
|
})
|
||||||
|
monster.understandsBut = monsterDTO.understandsBut
|
||||||
|
// TODO: add shortName or nickname
|
||||||
|
// monster.shortName = monsterDTO.shortName
|
||||||
|
|
||||||
|
// TODO: look into what goes in specialdamage and damage
|
||||||
|
|
||||||
|
return monster
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,5 +50,76 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>UISupportsDocumentBrowser</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||||
|
<false/>
|
||||||
|
<key>CFBundleDocumentTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>Monster Data</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Owner</string>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>com.majinnaibu.Monster</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>UTExportedTypeDeclarations</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.json</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>Monster data file</string>
|
||||||
|
<key>UTTypeIconFiles</key>
|
||||||
|
<array/>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>com.majinnaibu.Monster</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>monster</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>text/vnd.monstercards.monster</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>UTImportedTypeDeclarations</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.json</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>Monster data file</string>
|
||||||
|
<key>UTTypeIconFiles</key>
|
||||||
|
<array/>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>com.majinnaibu.Monster</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>monster</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>text/vnd.monstercards.monster</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public class AbilityViewModel: NSObject, ObservableObject, Identifiable, NSSecur
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func renderedText(_ monster: Monster) -> String {
|
func renderedText(_ monster: MonsterViewModel) -> String {
|
||||||
let strSave = monster.strengthModifier + monster.proficiencyBonus + 8
|
let strSave = monster.strengthModifier + monster.proficiencyBonus + 8
|
||||||
let dexSave = monster.dexterityModifier + monster.proficiencyBonus + 8
|
let dexSave = monster.dexterityModifier + monster.proficiencyBonus + 8
|
||||||
let conSave = monster.constitutionModifier + monster.proficiencyBonus + 8
|
let conSave = monster.constitutionModifier + monster.proficiencyBonus + 8
|
||||||
|
|||||||
42
iOS/MonsterCards/Models/DamageTypeDTO.swift
Normal file
42
iOS/MonsterCards/Models/DamageTypeDTO.swift
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// DamageTypeDTO.swift
|
||||||
|
// MonsterCards
|
||||||
|
//
|
||||||
|
// Created by Tom Hicks on 3/28/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct DamageTypeDTO {
|
||||||
|
var name: String
|
||||||
|
var note: String
|
||||||
|
var type: String
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum DamageTypeDTOCodingKeys: String, CodingKey {
|
||||||
|
case name = "name"
|
||||||
|
case note = "note"
|
||||||
|
case type = "type"
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DamageTypeDTO: Decodable {
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
|
||||||
|
let container = try decoder.container(keyedBy: DamageTypeDTOCodingKeys.self)
|
||||||
|
self.name = (try? container.decode(String.self, forKey: .name)) ?? ""
|
||||||
|
self.note = (try? container.decode(String.self, forKey: .note)) ?? ""
|
||||||
|
self.type = (try? container.decode(String.self, forKey: .type)) ?? ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DamageTypeDTO: Encodable {
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
|
||||||
|
var container = encoder.container(keyedBy: DamageTypeDTOCodingKeys.self)
|
||||||
|
try container.encode(self.name, forKey: .name)
|
||||||
|
try container.encode(self.note, forKey: .note)
|
||||||
|
try container.encode(self.type, forKey: .type)
|
||||||
|
}
|
||||||
|
}
|
||||||
38
iOS/MonsterCards/Models/LanguageDTO.swift
Normal file
38
iOS/MonsterCards/Models/LanguageDTO.swift
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// LanguageDTO.swift
|
||||||
|
// MonsterCards
|
||||||
|
//
|
||||||
|
// Created by Tom Hicks on 3/28/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct LanguageDTO {
|
||||||
|
var name: String
|
||||||
|
var speaks: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum LanguageDTOCodingKeys: String, CodingKey {
|
||||||
|
case name = "name"
|
||||||
|
case speaks = "speaks"
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LanguageDTO: Decodable {
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
|
||||||
|
let container = try decoder.container(keyedBy: LanguageDTOCodingKeys.self)
|
||||||
|
self.name = (try? container.decode(String.self, forKey: .name)) ?? ""
|
||||||
|
self.speaks = (try? container.decode(Bool.self, forKey: .speaks)) ?? false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LanguageDTO: Encodable {
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
|
||||||
|
var container = encoder.container(keyedBy: LanguageDTOCodingKeys.self)
|
||||||
|
try container.encode(self.name, forKey: .name)
|
||||||
|
try container.encode(self.speaks, forKey: .speaks)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -100,8 +100,8 @@ public class Monster: NSManagedObject {
|
|||||||
} else {
|
} else {
|
||||||
var parts: [String] = []
|
var parts: [String] = []
|
||||||
|
|
||||||
if (baseSpeed > 0) {
|
if (walkSpeed > 0) {
|
||||||
parts.append("\(baseSpeed) ft.")
|
parts.append("\(walkSpeed) ft.")
|
||||||
}
|
}
|
||||||
if (burrowSpeed > 0) {
|
if (burrowSpeed > 0) {
|
||||||
parts.append("burrow \(burrowSpeed) ft.")
|
parts.append("burrow \(burrowSpeed) ft.")
|
||||||
|
|||||||
278
iOS/MonsterCards/Models/MonsterDTO.swift
Normal file
278
iOS/MonsterCards/Models/MonsterDTO.swift
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
//
|
||||||
|
// MonsterDTO.swift
|
||||||
|
// MonsterCards
|
||||||
|
//
|
||||||
|
// Created by Tom Hicks on 3/28/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct MonsterDTO {
|
||||||
|
var name: String
|
||||||
|
var type: String
|
||||||
|
var alignment: String
|
||||||
|
var size: String
|
||||||
|
var hitDice: Int
|
||||||
|
var armorName: String
|
||||||
|
var otherArmorDesc: String
|
||||||
|
var shieldBonus: Int
|
||||||
|
var natArmorBonus: Int
|
||||||
|
var speed: Int
|
||||||
|
var burrowSpeed: Int
|
||||||
|
var climbSpeed: Int
|
||||||
|
var flySpeed: Int
|
||||||
|
var hover: Bool
|
||||||
|
var swimSpeed: Int
|
||||||
|
var speedDesc: String
|
||||||
|
var customSpeed: Bool
|
||||||
|
var strPoints: Int
|
||||||
|
var dexPoints: Int
|
||||||
|
var conPoints: Int
|
||||||
|
var intPoints: Int
|
||||||
|
var wisPoints: Int
|
||||||
|
var chaPoints: Int
|
||||||
|
var cr: String
|
||||||
|
var customCr: String
|
||||||
|
var customProf: Int
|
||||||
|
var hpText: String
|
||||||
|
var sthrows: [SavingThrowDTO]
|
||||||
|
var skills: [SkillDTO]
|
||||||
|
var actions: [TraitDTO]
|
||||||
|
var legendaryDescription: String
|
||||||
|
var legendaries: [TraitDTO]
|
||||||
|
var reactions: [TraitDTO]// TODO: verify this
|
||||||
|
var abilities: [TraitDTO]
|
||||||
|
var damageTypes: [DamageTypeDTO]
|
||||||
|
var conditions: [DamageTypeDTO] // TODO: figure this out
|
||||||
|
var languages: [LanguageDTO]
|
||||||
|
var telepathy: Int
|
||||||
|
var understandsBut: String
|
||||||
|
var blindsight: Int
|
||||||
|
var blind: Bool
|
||||||
|
var darkvision: Int
|
||||||
|
var tremorsense: Int
|
||||||
|
var truesight: Int
|
||||||
|
var tag: String
|
||||||
|
var customHP: Bool
|
||||||
|
var isLegendary: Bool
|
||||||
|
var isLair: Bool
|
||||||
|
var lairDescription: String
|
||||||
|
var lairDescriptionEnd: String
|
||||||
|
var isRegional: Bool
|
||||||
|
var regionalDescription: String
|
||||||
|
var regionalDescriptionEnd: String
|
||||||
|
// var properties: [???] // TODO: figure this out
|
||||||
|
var lairs: [TraitDTO]
|
||||||
|
var regionals: [TraitDTO]
|
||||||
|
var specialDamage: [DamageTypeDTO]
|
||||||
|
var shortName: String
|
||||||
|
var doubleColumns: Bool
|
||||||
|
var separationPoint: Int
|
||||||
|
var damage: [DamageTypeDTO] // TODO: figure this out
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MonsterDTOCodingKeys: String, CodingKey {
|
||||||
|
case name = "name"
|
||||||
|
case type = "type"
|
||||||
|
case alignment = "alignment"
|
||||||
|
case size = "size"
|
||||||
|
case hitDice = "hitDice"
|
||||||
|
case armorName = "armorName"
|
||||||
|
case otherArmorDesc = "otherArmorDesc"
|
||||||
|
case shieldBonus = "shieldBonus"
|
||||||
|
case natArmorBonus = "natArmorBonus"
|
||||||
|
case speed = "speed"
|
||||||
|
case burrowSpeed = "burrowSpeed"
|
||||||
|
case climbSpeed = "climbSpeed"
|
||||||
|
case flySpeed = "flySpeed"
|
||||||
|
case hover = "hover"
|
||||||
|
case swimSpeed = "swimSpeed"
|
||||||
|
case speedDesc = "speedDesc"
|
||||||
|
case customSpeed = "customSpeed"
|
||||||
|
case strPoints = "strPoints"
|
||||||
|
case dexPoints = "dexPoints"
|
||||||
|
case conPoints = "conPoints"
|
||||||
|
case intPoints = "intPoints"
|
||||||
|
case wisPoints = "wisPoints"
|
||||||
|
case chaPoints = "chaPoints"
|
||||||
|
case cr = "cr"
|
||||||
|
case customCr = "customCr"
|
||||||
|
case customProf = "customProf"
|
||||||
|
case hpText = "hpText"
|
||||||
|
case sthrows = "sthrows"
|
||||||
|
case skills = "skills"
|
||||||
|
case actions = "actions"
|
||||||
|
case legendaryDescription = "legendaryDescription"
|
||||||
|
case legendaries = "legendaries"
|
||||||
|
case reactions = "reactions"
|
||||||
|
case abilities = "abilities"
|
||||||
|
case damageTypes = "damageTypes"
|
||||||
|
case conditions = "conditions"
|
||||||
|
case languages = "languages"
|
||||||
|
case telepathy = "telepathy"
|
||||||
|
case understandsBut = "understandsBut"
|
||||||
|
case blindsight = "blindsight"
|
||||||
|
case blind = "blind"
|
||||||
|
case darkvision = "darkvision"
|
||||||
|
case tremorsense = "tremorsense"
|
||||||
|
case truesight = "truesight"
|
||||||
|
case tag = "tag"
|
||||||
|
case customHP = "customHP"
|
||||||
|
case isLegendary = "isLegendary"
|
||||||
|
case isLair = "isLair"
|
||||||
|
case lairDescription = "lairDescription"
|
||||||
|
case lairDescriptionEnd = "lairDescriptionEnd"
|
||||||
|
case isRegional = "isRegional"
|
||||||
|
case regionalDescription = "regionalDescription"
|
||||||
|
case regionalDescriptionEnd = "regionalDescriptionEnd"
|
||||||
|
case properties = "properties"
|
||||||
|
case lairs = "lairs"
|
||||||
|
case regionals = "regionals"
|
||||||
|
case specialDamage = "specialDamage"
|
||||||
|
case shortName = "shortName"
|
||||||
|
case doubleColumns = "doubleColumns"
|
||||||
|
case separationPoint = "separationPoint"
|
||||||
|
case damage = "damage"
|
||||||
|
}
|
||||||
|
|
||||||
|
extension MonsterDTO: Decodable {
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: MonsterDTOCodingKeys.self)
|
||||||
|
self.name = (try? container.decode(String.self, forKey: .name)) ?? "Imported Monster"
|
||||||
|
self.type = (try? container.decode(String.self, forKey: .type)) ?? ""
|
||||||
|
self.alignment = (try? container.decode(String.self, forKey: .alignment)) ?? ""
|
||||||
|
self.size = (try? container.decode(String.self, forKey: .size)) ?? ""
|
||||||
|
self.hitDice = (try? container.decode(Int.self, forKey: .hitDice)) ?? 0
|
||||||
|
self.armorName = (try? container.decode(String.self, forKey: .armorName)) ?? ""
|
||||||
|
self.otherArmorDesc = (try? container.decode(String.self, forKey: .otherArmorDesc)) ?? ""
|
||||||
|
self.shieldBonus = (try? container.decode(Int.self, forKey: .shieldBonus)) ?? 0
|
||||||
|
self.natArmorBonus = (try? container.decode(Int.self, forKey: .natArmorBonus)) ?? 0
|
||||||
|
self.speed = (try? container.decode(Int.self, forKey: .speed)) ?? 0
|
||||||
|
self.burrowSpeed = (try? container.decode(Int.self, forKey: .burrowSpeed)) ?? 0
|
||||||
|
self.climbSpeed = (try? container.decode(Int.self, forKey: .climbSpeed)) ?? 0
|
||||||
|
self.flySpeed = (try? container.decode(Int.self, forKey: .flySpeed)) ?? 0
|
||||||
|
self.hover = (try? container.decode(Bool.self, forKey: .hover)) ?? false
|
||||||
|
self.swimSpeed = (try? container.decode(Int.self, forKey: .swimSpeed)) ?? 0
|
||||||
|
self.speedDesc = (try? container.decode(String.self, forKey: .speedDesc)) ?? ""
|
||||||
|
self.customSpeed = (try? container.decode(Bool.self, forKey: .customSpeed)) ?? false
|
||||||
|
self.strPoints = (try? container.decode(Int.self, forKey: .strPoints)) ?? 0
|
||||||
|
self.dexPoints = (try? container.decode(Int.self, forKey: .dexPoints)) ?? 0
|
||||||
|
self.conPoints = (try? container.decode(Int.self, forKey: .conPoints)) ?? 0
|
||||||
|
self.intPoints = (try? container.decode(Int.self, forKey: .intPoints)) ?? 0
|
||||||
|
self.wisPoints = (try? container.decode(Int.self, forKey: .wisPoints)) ?? 0
|
||||||
|
self.chaPoints = (try? container.decode(Int.self, forKey: .chaPoints)) ?? 0
|
||||||
|
self.cr = (try? container.decode(String.self, forKey: .cr)) ?? ""
|
||||||
|
self.customCr = (try? container.decode(String.self, forKey: .customCr)) ?? ""
|
||||||
|
self.customProf = (try? container.decode(Int.self, forKey: .customProf)) ?? 0
|
||||||
|
self.hpText = (try? container.decode(String.self, forKey: .hpText)) ?? ""
|
||||||
|
self.legendaryDescription = (try? container.decode(String.self, forKey: .legendaryDescription)) ?? ""
|
||||||
|
self.understandsBut = (try? container.decode(String.self, forKey: .understandsBut)) ?? ""
|
||||||
|
self.tag = (try? container.decode(String.self, forKey: .tag)) ?? ""
|
||||||
|
self.lairDescription = (try? container.decode(String.self, forKey: .lairDescription)) ?? ""
|
||||||
|
self.lairDescriptionEnd = (try? container.decode(String.self, forKey: .lairDescriptionEnd)) ?? ""
|
||||||
|
self.regionalDescription = (try? container.decode(String.self, forKey: .regionalDescription)) ?? ""
|
||||||
|
self.regionalDescriptionEnd = (try? container.decode(String.self, forKey: .regionalDescriptionEnd)) ?? ""
|
||||||
|
self.shortName = (try? container.decode(String.self, forKey: .shortName)) ?? ""
|
||||||
|
|
||||||
|
self.telepathy = (try? container.decode(Int.self, forKey: .telepathy)) ?? 0
|
||||||
|
self.blindsight = (try? container.decode(Int.self, forKey: .blindsight)) ?? 0
|
||||||
|
self.darkvision = (try? container.decode(Int.self, forKey: .darkvision)) ?? 0
|
||||||
|
self.tremorsense = (try? container.decode(Int.self, forKey: .tremorsense)) ?? 0
|
||||||
|
self.truesight = (try? container.decode(Int.self, forKey: .truesight)) ?? 0
|
||||||
|
self.separationPoint = (try? container.decode(Int.self, forKey: .separationPoint)) ?? 0
|
||||||
|
|
||||||
|
self.blind = (try? container.decode(Bool.self, forKey: .blind)) ?? false
|
||||||
|
self.customHP = (try? container.decode(Bool.self, forKey: .customHP)) ?? false
|
||||||
|
self.isLegendary = (try? container.decode(Bool.self, forKey: .isLegendary)) ?? false
|
||||||
|
self.isLair = (try? container.decode(Bool.self, forKey: .isLair)) ?? false
|
||||||
|
self.isRegional = (try? container.decode(Bool.self, forKey: .isRegional)) ?? false
|
||||||
|
self.doubleColumns = (try? container.decode(Bool.self, forKey: .doubleColumns)) ?? false
|
||||||
|
|
||||||
|
// properties is always an empty array
|
||||||
|
|
||||||
|
// self.properties = (try? container.decode([String].self, forKey: .properties)) ?? []
|
||||||
|
self.lairs = (try? container.decode([TraitDTO].self, forKey: .lairs)) ?? []
|
||||||
|
self.regionals = (try? container.decode([TraitDTO].self, forKey: .regionals)) ?? []
|
||||||
|
self.specialDamage = (try? container.decode([DamageTypeDTO].self, forKey: .specialDamage)) ?? []
|
||||||
|
self.damage = (try? container.decode([DamageTypeDTO].self, forKey: .damage)) ?? []
|
||||||
|
self.conditions = (try? container.decode([DamageTypeDTO].self, forKey: .conditions)) ?? []
|
||||||
|
|
||||||
|
|
||||||
|
self.sthrows = (try? container.decode([SavingThrowDTO].self, forKey: .sthrows)) ?? []
|
||||||
|
self.skills = (try? container.decode([SkillDTO].self, forKey: .skills)) ?? []
|
||||||
|
self.actions = (try? container.decode([TraitDTO].self, forKey: .actions)) ?? []
|
||||||
|
self.legendaries = (try? container.decode([TraitDTO].self, forKey: .legendaries)) ?? []
|
||||||
|
self.reactions = (try? container.decode([TraitDTO].self, forKey: .reactions)) ?? []
|
||||||
|
self.abilities = (try? container.decode([TraitDTO].self, forKey: .abilities)) ?? []
|
||||||
|
self.damageTypes = (try? container.decode([DamageTypeDTO].self, forKey: .damageTypes)) ?? []
|
||||||
|
self.languages = (try? container.decode([LanguageDTO].self, forKey: .languages)) ?? []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension MonsterDTO: Encodable {
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: MonsterDTOCodingKeys.self)
|
||||||
|
try container.encode(self.name, forKey: .name)
|
||||||
|
try container.encode(self.type, forKey: .type)
|
||||||
|
try container.encode(self.alignment, forKey: .alignment)
|
||||||
|
try container.encode(self.size, forKey: .size)
|
||||||
|
try container.encode(self.hitDice, forKey: .hitDice)
|
||||||
|
try container.encode(self.armorName, forKey: .armorName)
|
||||||
|
try container.encode(self.otherArmorDesc, forKey: .otherArmorDesc)
|
||||||
|
try container.encode(self.shieldBonus, forKey: .shieldBonus)
|
||||||
|
try container.encode(self.natArmorBonus, forKey: .natArmorBonus)
|
||||||
|
try container.encode(self.speed, forKey: .speed)
|
||||||
|
try container.encode(self.burrowSpeed, forKey: .burrowSpeed)
|
||||||
|
try container.encode(self.climbSpeed, forKey: .climbSpeed)
|
||||||
|
try container.encode(self.flySpeed, forKey: .flySpeed)
|
||||||
|
try container.encode(self.hover, forKey: .hover)
|
||||||
|
try container.encode(self.swimSpeed, forKey: .swimSpeed)
|
||||||
|
try container.encode(self.speedDesc, forKey: .speedDesc)
|
||||||
|
try container.encode(self.customSpeed, forKey: .customSpeed)
|
||||||
|
try container.encode(self.strPoints, forKey: .strPoints)
|
||||||
|
try container.encode(self.dexPoints, forKey: .dexPoints)
|
||||||
|
try container.encode(self.conPoints, forKey: .conPoints)
|
||||||
|
try container.encode(self.intPoints, forKey: .intPoints)
|
||||||
|
try container.encode(self.wisPoints, forKey: .wisPoints)
|
||||||
|
try container.encode(self.chaPoints, forKey: .chaPoints)
|
||||||
|
try container.encode(self.cr, forKey: .cr)
|
||||||
|
try container.encode(self.customCr, forKey: .customCr)
|
||||||
|
try container.encode(self.customProf, forKey: .customProf)
|
||||||
|
try container.encode(self.hpText, forKey: .hpText)
|
||||||
|
try container.encode(self.sthrows, forKey: .sthrows)
|
||||||
|
try container.encode(self.skills, forKey: .skills)
|
||||||
|
try container.encode(self.actions, forKey: .actions)
|
||||||
|
try container.encode(self.legendaryDescription, forKey: .legendaryDescription)
|
||||||
|
try container.encode(self.legendaries, forKey: .legendaries)
|
||||||
|
try container.encode(self.reactions, forKey: .reactions)
|
||||||
|
try container.encode(self.abilities, forKey: .abilities)
|
||||||
|
try container.encode(self.damageTypes, forKey: .damageTypes)
|
||||||
|
try container.encode(self.conditions, forKey: .conditions)
|
||||||
|
try container.encode(self.languages, forKey: .languages)
|
||||||
|
try container.encode(self.telepathy, forKey: .telepathy)
|
||||||
|
try container.encode(self.understandsBut, forKey: .understandsBut)
|
||||||
|
try container.encode(self.blindsight, forKey: .blindsight)
|
||||||
|
try container.encode(self.blind, forKey: .blind)
|
||||||
|
try container.encode(self.darkvision, forKey: .darkvision)
|
||||||
|
try container.encode(self.tremorsense, forKey: .tremorsense)
|
||||||
|
try container.encode(self.truesight, forKey: .truesight)
|
||||||
|
try container.encode(self.tag, forKey: .tag)
|
||||||
|
try container.encode(self.customHP, forKey: .customHP)
|
||||||
|
try container.encode(self.isLegendary, forKey: .isLegendary)
|
||||||
|
try container.encode(self.isLair, forKey: .isLair)
|
||||||
|
try container.encode(self.lairDescription, forKey: .lairDescription)
|
||||||
|
try container.encode(self.lairDescriptionEnd, forKey: .lairDescriptionEnd)
|
||||||
|
try container.encode(self.isRegional, forKey: .isRegional)
|
||||||
|
try container.encode(self.regionalDescription, forKey: .regionalDescription)
|
||||||
|
try container.encode(self.regionalDescriptionEnd, forKey: .regionalDescriptionEnd)
|
||||||
|
// try container.encode(self.properties, forKey: .properties)
|
||||||
|
try container.encode(self.lairs, forKey: .lairs)
|
||||||
|
try container.encode(self.regionals, forKey: .regionals)
|
||||||
|
try container.encode(self.specialDamage, forKey: .specialDamage)
|
||||||
|
try container.encode(self.shortName, forKey: .shortName)
|
||||||
|
try container.encode(self.doubleColumns, forKey: .doubleColumns)
|
||||||
|
try container.encode(self.separationPoint, forKey: .separationPoint)
|
||||||
|
try container.encode(self.damage, forKey: .damage)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@ import CoreData
|
|||||||
|
|
||||||
class MonsterViewModel: ObservableObject {
|
class MonsterViewModel: ObservableObject {
|
||||||
|
|
||||||
|
// TODO: Determine whether to prefer Int or Int64 for these fields and switch as many as possible to the winner.
|
||||||
|
|
||||||
@Published var name: String
|
@Published var name: String
|
||||||
@Published var size: String
|
@Published var size: String
|
||||||
@Published var type: String
|
@Published var type: String
|
||||||
@@ -19,10 +21,12 @@ class MonsterViewModel: ObservableObject {
|
|||||||
@Published var hasCustomHP: Bool
|
@Published var hasCustomHP: Bool
|
||||||
@Published var customHP: String
|
@Published var customHP: String
|
||||||
@Published var armorType: ArmorType
|
@Published var armorType: ArmorType
|
||||||
@Published var hasShield: Bool
|
@Published var hasShield: Bool {
|
||||||
|
didSet { shieldBonus = hasShield ? 2 : 0 }
|
||||||
|
}
|
||||||
@Published var naturalArmorBonus: Int64
|
@Published var naturalArmorBonus: Int64
|
||||||
@Published var customArmor: String
|
@Published var customArmor: String
|
||||||
@Published var baseSpeed: Int64
|
@Published var walkSpeed: Int64
|
||||||
@Published var burrowSpeed: Int64
|
@Published var burrowSpeed: Int64
|
||||||
@Published var climbSpeed: Int64
|
@Published var climbSpeed: Int64
|
||||||
@Published var flySpeed: Int64
|
@Published var flySpeed: Int64
|
||||||
@@ -63,6 +67,28 @@ class MonsterViewModel: ObservableObject {
|
|||||||
@Published var abilities: [AbilityViewModel]
|
@Published var abilities: [AbilityViewModel]
|
||||||
@Published var actions: [AbilityViewModel]
|
@Published var actions: [AbilityViewModel]
|
||||||
@Published var legendaryActions: [AbilityViewModel]
|
@Published var legendaryActions: [AbilityViewModel]
|
||||||
|
@Published var lairActions: [AbilityViewModel]
|
||||||
|
@Published var regionalActions: [AbilityViewModel]
|
||||||
|
@Published var reactions: [AbilityViewModel]
|
||||||
|
@Published var isBlind: Bool
|
||||||
|
|
||||||
|
private var shieldBonus: Int
|
||||||
|
private var otherArmorDescription: String
|
||||||
|
|
||||||
|
let kBaseArmorClassUnarmored = 10;
|
||||||
|
let kBaseArmorClassMageArmor = 13;
|
||||||
|
let kBaseArmorClassPadded = 11;
|
||||||
|
let kBaseArmorClassLeather = 11;
|
||||||
|
let kBaseArmorClassStudded = 12;
|
||||||
|
let kBaseArmorClassHide = 12;
|
||||||
|
let kBaseArmorClassChainShirt = 13;
|
||||||
|
let kBaseArmorClassScaleMail = 14;
|
||||||
|
let kBaseArmorClassBreastplate = 14;
|
||||||
|
let kBaseArmorClassHalfPlate = 15;
|
||||||
|
let kBaseArmorClassRingMail = 14;
|
||||||
|
let kBaseArmorClassChainMail = 16;
|
||||||
|
let kBaseArmorClassSplintMail = 17;
|
||||||
|
let kBaseArmorClassPlate = 18;
|
||||||
|
|
||||||
init(_ rawMonster: Monster? = nil) {
|
init(_ rawMonster: Monster? = nil) {
|
||||||
self.name = ""
|
self.name = ""
|
||||||
@@ -77,7 +103,7 @@ class MonsterViewModel: ObservableObject {
|
|||||||
self.hasShield = false
|
self.hasShield = false
|
||||||
self.naturalArmorBonus = 0
|
self.naturalArmorBonus = 0
|
||||||
self.customArmor = ""
|
self.customArmor = ""
|
||||||
self.baseSpeed = 0
|
self.walkSpeed = 0
|
||||||
self.burrowSpeed = 0
|
self.burrowSpeed = 0
|
||||||
self.climbSpeed = 0
|
self.climbSpeed = 0
|
||||||
self.flySpeed = 0
|
self.flySpeed = 0
|
||||||
@@ -118,7 +144,16 @@ class MonsterViewModel: ObservableObject {
|
|||||||
self.abilities = []
|
self.abilities = []
|
||||||
self.actions = []
|
self.actions = []
|
||||||
self.legendaryActions = []
|
self.legendaryActions = []
|
||||||
|
self.lairActions = []
|
||||||
|
self.regionalActions = []
|
||||||
|
self.reactions = []
|
||||||
|
self.isBlind = false
|
||||||
|
|
||||||
|
// Private properties
|
||||||
|
self.shieldBonus = 0
|
||||||
|
self.otherArmorDescription = ""
|
||||||
|
|
||||||
|
// Call the copy constructor
|
||||||
if (rawMonster != nil) {
|
if (rawMonster != nil) {
|
||||||
self.copyFromMonster(monster: rawMonster!)
|
self.copyFromMonster(monster: rawMonster!)
|
||||||
}
|
}
|
||||||
@@ -137,7 +172,7 @@ class MonsterViewModel: ObservableObject {
|
|||||||
self.hasShield = monster.hasShield
|
self.hasShield = monster.hasShield
|
||||||
self.naturalArmorBonus = monster.naturalArmorBonus
|
self.naturalArmorBonus = monster.naturalArmorBonus
|
||||||
self.customArmor = monster.customArmor ?? ""
|
self.customArmor = monster.customArmor ?? ""
|
||||||
self.baseSpeed = monster.baseSpeed
|
self.walkSpeed = monster.walkSpeed
|
||||||
self.burrowSpeed = monster.burrowSpeed
|
self.burrowSpeed = monster.burrowSpeed
|
||||||
self.climbSpeed = monster.climbSpeed
|
self.climbSpeed = monster.climbSpeed
|
||||||
self.flySpeed = monster.flySpeed
|
self.flySpeed = monster.flySpeed
|
||||||
@@ -168,6 +203,7 @@ class MonsterViewModel: ObservableObject {
|
|||||||
self.challengeRating = monster.challengeRatingEnum
|
self.challengeRating = monster.challengeRatingEnum
|
||||||
self.customChallengeRating = monster.customChallengeRating ?? ""
|
self.customChallengeRating = monster.customChallengeRating ?? ""
|
||||||
self.customProficiencyBonus = monster.customProficiencyBonus
|
self.customProficiencyBonus = monster.customProficiencyBonus
|
||||||
|
self.isBlind = monster.isBlind
|
||||||
|
|
||||||
self.skills = (monster.skills?.allObjects.map {SkillViewModel(($0 as! Skill))})!.sorted()
|
self.skills = (monster.skills?.allObjects.map {SkillViewModel(($0 as! Skill))})!.sorted()
|
||||||
|
|
||||||
@@ -204,6 +240,20 @@ class MonsterViewModel: ObservableObject {
|
|||||||
|
|
||||||
self.legendaryActions = (monster.legendaryActions ?? [])
|
self.legendaryActions = (monster.legendaryActions ?? [])
|
||||||
.map {AbilityViewModel($0.name, $0.abilityDescription)}
|
.map {AbilityViewModel($0.name, $0.abilityDescription)}
|
||||||
|
|
||||||
|
self.lairActions = (monster.lairActions ?? [])
|
||||||
|
.map {AbilityViewModel($0.name, $0.abilityDescription)}
|
||||||
|
|
||||||
|
self.regionalActions = (monster.regionalActions ?? [])
|
||||||
|
.map {AbilityViewModel($0.name, $0.abilityDescription)}
|
||||||
|
|
||||||
|
self.reactions = (monster.reactions ?? [])
|
||||||
|
.map {AbilityViewModel($0.name, $0.abilityDescription)}
|
||||||
|
|
||||||
|
// Private fields
|
||||||
|
|
||||||
|
self.shieldBonus = Int(monster.shieldBonus)
|
||||||
|
self.otherArmorDescription = monster.otherArmorDescription ?? ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyToMonster(monster: Monster) {
|
func copyToMonster(monster: Monster) {
|
||||||
@@ -219,7 +269,7 @@ class MonsterViewModel: ObservableObject {
|
|||||||
monster.hasShield = hasShield
|
monster.hasShield = hasShield
|
||||||
monster.naturalArmorBonus = naturalArmorBonus
|
monster.naturalArmorBonus = naturalArmorBonus
|
||||||
monster.customArmor = customArmor
|
monster.customArmor = customArmor
|
||||||
monster.baseSpeed = baseSpeed
|
monster.walkSpeed = walkSpeed
|
||||||
monster.burrowSpeed = burrowSpeed
|
monster.burrowSpeed = burrowSpeed
|
||||||
monster.climbSpeed = climbSpeed
|
monster.climbSpeed = climbSpeed
|
||||||
monster.flySpeed = flySpeed
|
monster.flySpeed = flySpeed
|
||||||
@@ -250,6 +300,7 @@ class MonsterViewModel: ObservableObject {
|
|||||||
monster.challengeRatingEnum = challengeRating
|
monster.challengeRatingEnum = challengeRating
|
||||||
monster.customChallengeRating = customChallengeRating
|
monster.customChallengeRating = customChallengeRating
|
||||||
monster.customProficiencyBonus = customProficiencyBonus
|
monster.customProficiencyBonus = customProficiencyBonus
|
||||||
|
monster.isBlind = isBlind
|
||||||
|
|
||||||
// Remove missing skills from raw monster
|
// Remove missing skills from raw monster
|
||||||
monster.skills?.forEach {s in
|
monster.skills?.forEach {s in
|
||||||
@@ -285,5 +336,547 @@ class MonsterViewModel: ObservableObject {
|
|||||||
monster.actions = actions.map {AbilityViewModel($0.name, $0.abilityDescription)}
|
monster.actions = actions.map {AbilityViewModel($0.name, $0.abilityDescription)}
|
||||||
|
|
||||||
monster.legendaryActions = legendaryActions.map {AbilityViewModel($0.name, $0.abilityDescription)}
|
monster.legendaryActions = legendaryActions.map {AbilityViewModel($0.name, $0.abilityDescription)}
|
||||||
|
|
||||||
|
monster.lairActions = lairActions.map {AbilityViewModel($0.name, $0.abilityDescription)}
|
||||||
|
|
||||||
|
monster.regionalActions = regionalActions.map {AbilityViewModel($0.name, $0.abilityDescription)}
|
||||||
|
|
||||||
|
monster.reactions = reactions.map {AbilityViewModel($0.name, $0.abilityDescription)}
|
||||||
|
|
||||||
|
monster.shieldBonus = Int64(shieldBonus)
|
||||||
|
monster.otherArmorDescription = otherArmorDescription
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Basic Info
|
||||||
|
|
||||||
|
var meta: String {
|
||||||
|
get {
|
||||||
|
// size type (subtype) alignment
|
||||||
|
var parts: [String] = []
|
||||||
|
|
||||||
|
if (!(self.size.isEmpty)) {
|
||||||
|
parts.append(self.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(self.type.isEmpty)) {
|
||||||
|
parts.append(self.type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(self.subType.isEmpty)) {
|
||||||
|
parts.append(String.init(format: "(%@)", arguments: [self.subType]))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(self.alignment.isEmpty)) {
|
||||||
|
parts.append(self.alignment)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.joined(separator: " ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sizeEnum: SizeType {
|
||||||
|
get {
|
||||||
|
return SizeType.init(rawValue: size) ?? .medium
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
size = newValue.rawValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var hitPoints: String {
|
||||||
|
get {
|
||||||
|
if (hasCustomHP) {
|
||||||
|
return customHP;
|
||||||
|
} else {
|
||||||
|
let dieSize = Double(Monster.hitDieForSize(sizeEnum))
|
||||||
|
let conMod = Double(constitutionModifier)
|
||||||
|
// let level1HP = Double(dieSize + conMod)
|
||||||
|
let level1HP = Double(dieSize/2.0 + conMod)
|
||||||
|
let extraLevels = Double(hitDice - 1)
|
||||||
|
let levelNHP = (dieSize + 1.0) / 2.0 + conMod
|
||||||
|
let extraLevelsHP = extraLevels * levelNHP
|
||||||
|
let hpTotal = Int(ceil(level1HP + extraLevelsHP))
|
||||||
|
let conBonus = Int(conMod) * Int(hitDice)
|
||||||
|
return String(format: "%d (%dd%d%+d)", hpTotal, hitDice, Int(dieSize), conBonus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var speed: String {
|
||||||
|
get {
|
||||||
|
if (hasCustomSpeed) {
|
||||||
|
return customSpeed
|
||||||
|
} else {
|
||||||
|
var parts: [String] = []
|
||||||
|
|
||||||
|
if (walkSpeed > 0) {
|
||||||
|
parts.append("\(walkSpeed) ft.")
|
||||||
|
}
|
||||||
|
if (burrowSpeed > 0) {
|
||||||
|
parts.append("burrow \(burrowSpeed) ft.")
|
||||||
|
}
|
||||||
|
if (climbSpeed > 0) {
|
||||||
|
parts.append("climb \(climbSpeed) ft.")
|
||||||
|
}
|
||||||
|
if (flySpeed > 0) {
|
||||||
|
parts.append("fly \(flySpeed) ft.\(canHover ? " (hover)": "")")
|
||||||
|
}
|
||||||
|
if (swimSpeed > 0) {
|
||||||
|
parts.append("swim \(swimSpeed) ft.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.joined(separator: ", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Ability Scores
|
||||||
|
class func abilityModifierForScore(_ score: Int) -> Int {
|
||||||
|
return Int(floor(Double((score - 10)) / 2.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func abilityModifierForAbilityScore(_ abilityScore: AbilityScore) -> Int {
|
||||||
|
switch abilityScore {
|
||||||
|
case .strength:
|
||||||
|
return strengthModifier;
|
||||||
|
case .dexterity:
|
||||||
|
return dexterityModifier
|
||||||
|
case .constitution:
|
||||||
|
return constitutionModifier
|
||||||
|
case .intelligence:
|
||||||
|
return intelligenceModifier
|
||||||
|
case .wisdom:
|
||||||
|
return wisdomModifier
|
||||||
|
case .charisma:
|
||||||
|
return charismaModifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var strengthModifier: Int {
|
||||||
|
get {
|
||||||
|
return MonsterViewModel.abilityModifierForScore(Int(strengthScore))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dexterityModifier: Int {
|
||||||
|
get {
|
||||||
|
return MonsterViewModel.abilityModifierForScore(Int(dexterityScore))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var constitutionModifier: Int {
|
||||||
|
get {
|
||||||
|
return MonsterViewModel.abilityModifierForScore(Int(constitutionScore))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var intelligenceModifier: Int {
|
||||||
|
get {
|
||||||
|
return MonsterViewModel.abilityModifierForScore(Int(intelligenceScore))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var wisdomModifier: Int {
|
||||||
|
get {
|
||||||
|
return MonsterViewModel.abilityModifierForScore(Int(wisdomScore))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var charismaModifier: Int {
|
||||||
|
get {
|
||||||
|
return MonsterViewModel.abilityModifierForScore(Int(charismaScore))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Armor
|
||||||
|
|
||||||
|
var armorClassDescription: String {
|
||||||
|
get {
|
||||||
|
var armorClassTotal = 0
|
||||||
|
if (armorType == ArmorType.none) {
|
||||||
|
// 10 + dexMod + 2 for shieldBonus "15" or "17 (shield)"
|
||||||
|
armorClassTotal = kBaseArmorClassUnarmored + dexterityModifier + Int(shieldBonus)
|
||||||
|
return "\(armorClassTotal)\(hasShield ? " (shield)" : "")"
|
||||||
|
} else if (armorType == .naturalArmor) {
|
||||||
|
// 10 + dexMod + naturalArmorBonus + 2 for shieldBonus "16 (natural armor)" or "18 (natural armor, shield)"
|
||||||
|
armorClassTotal = kBaseArmorClassUnarmored + dexterityModifier + Int(naturalArmorBonus) + Int(shieldBonus)
|
||||||
|
return "\(armorClassTotal) (natural armor\(hasShield ? " (shield)" : ""))"
|
||||||
|
} else if (armorType == .mageArmor) {
|
||||||
|
// 10 + dexMod + 2 for shield + 3 for mage armor "15 (18 with mage armor)" or 17 (shield, 20 with mage armor)
|
||||||
|
armorClassTotal = kBaseArmorClassUnarmored + dexterityModifier + Int(shieldBonus)
|
||||||
|
let acWithMageArmor = kBaseArmorClassMageArmor + dexterityModifier + Int(shieldBonus)
|
||||||
|
return String(format: "%d (%@%d with mage armor)", armorClassTotal, (hasShield ? "shield, " : ""), acWithMageArmor)
|
||||||
|
} else if (armorType == .padded) {
|
||||||
|
// 11 + dexMod + 2 for shield "18 (padded armor, shield)"
|
||||||
|
armorClassTotal = kBaseArmorClassPadded + dexterityModifier + Int(shieldBonus)
|
||||||
|
return String(format: "%d (padded%@)", armorClassTotal, (hasShield ? "shield, " : ""))
|
||||||
|
} else if (armorType == .leather) {
|
||||||
|
// 11 + dexMod + 2 for shield "18 (leather, shield)"
|
||||||
|
armorClassTotal = kBaseArmorClassLeather + dexterityModifier + Int(shieldBonus)
|
||||||
|
return String(format:"%d (leather%@)", armorClassTotal, (hasShield ? "shield, " : ""))
|
||||||
|
} else if (armorType == .studdedLeather) {
|
||||||
|
// 12 + dexMod +2 for shield "17 (studded leather)"
|
||||||
|
armorClassTotal = kBaseArmorClassStudded + dexterityModifier + Int(shieldBonus)
|
||||||
|
return String(format: "%d (studded leather%@)", armorClassTotal, (hasShield ? "shield, " : ""))
|
||||||
|
} else if (armorType == .hide) {
|
||||||
|
// 12 + Min(2, dexMod) + 2 for shield "12 (hide armor)"
|
||||||
|
armorClassTotal = kBaseArmorClassHide + min(2, dexterityModifier) + Int(shieldBonus)
|
||||||
|
return String(format: "%d (hide%@)", armorClassTotal, (hasShield ? ", shield" : ""))
|
||||||
|
} else if (armorType == .chainShirt) {
|
||||||
|
// 13 + Min(2, dexMod) + 2 for shield "12 (chain shirt)"
|
||||||
|
armorClassTotal = kBaseArmorClassChainShirt + min(2, dexterityModifier) + Int(shieldBonus)
|
||||||
|
return String(format: "%d (chain shirt%@)", armorClassTotal, (hasShield ? ", shield" : ""))
|
||||||
|
} else if (armorType == .scaleMail) {
|
||||||
|
// 14 + Min(2, dexMod) + 2 for shield "14 (scale mail)"
|
||||||
|
armorClassTotal = kBaseArmorClassScaleMail + min(2, dexterityModifier) + Int(shieldBonus)
|
||||||
|
return String(format: "%d (scale mail%@)", armorClassTotal, (hasShield ? ", shield" : ""))
|
||||||
|
} else if (armorType == .breastplate) {
|
||||||
|
// 14 + Min(2, dexMod) + 2 for shield "16 (breastplate)"
|
||||||
|
armorClassTotal = kBaseArmorClassBreastplate + min(2, dexterityModifier) + Int(shieldBonus)
|
||||||
|
return String(format: "%d (breastplate%@)", armorClassTotal, (hasShield ? ", shield" : ""))
|
||||||
|
} else if (armorType == .halfPlate) {
|
||||||
|
// 15 + Min(2, dexMod) + 2 for shield "17 (half plate)"
|
||||||
|
armorClassTotal = kBaseArmorClassHalfPlate + min(2, dexterityModifier) + Int(shieldBonus)
|
||||||
|
return String(format: "%d (half plate%@)", armorClassTotal, (hasShield ? ", shield" : ""))
|
||||||
|
} else if (armorType == .ringMail) {
|
||||||
|
// 14 + 2 for shield "14 (ring mail)
|
||||||
|
armorClassTotal = kBaseArmorClassRingMail + Int(shieldBonus)
|
||||||
|
return String(format: "%d (ring mail%@)", armorClassTotal, (hasShield ? ", shield" : ""))
|
||||||
|
} else if (armorType == .chainMail) {
|
||||||
|
// 16 + 2 for shield "16 (chain mail)"
|
||||||
|
armorClassTotal = kBaseArmorClassChainMail + Int(shieldBonus)
|
||||||
|
return String(format: "%d (chain mail%@)", armorClassTotal, (hasShield ? ", shield" : ""))
|
||||||
|
} else if (armorType == .splintMail) {
|
||||||
|
// 17 + 2 for shield "17 (splint)"
|
||||||
|
armorClassTotal = kBaseArmorClassSplintMail + Int(shieldBonus)
|
||||||
|
return String(format: "%d (splint%@)", armorClassTotal, (hasShield ? ", shield" : ""))
|
||||||
|
} else if (armorType == .plateMail) {
|
||||||
|
// 18 + 2 for shield "18 (plate)"
|
||||||
|
armorClassTotal = kBaseArmorClassPlate + Int(shieldBonus)
|
||||||
|
return String(format: "%d (plate%@)", armorClassTotal, (hasShield ? ", shield" : ""))
|
||||||
|
} else if (armorType == .other) {
|
||||||
|
// pure string value shield check does nothing just copies the string from otherArmorDesc
|
||||||
|
return otherArmorDescription;
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Challenge Rating / Proficiency Bonus
|
||||||
|
|
||||||
|
|
||||||
|
var proficiencyBonus: Int {
|
||||||
|
switch challengeRating {
|
||||||
|
case .custom:
|
||||||
|
return Int(customProficiencyBonus)
|
||||||
|
case .zero:
|
||||||
|
fallthrough
|
||||||
|
case .oneEighth:
|
||||||
|
fallthrough
|
||||||
|
case .oneQuarter:
|
||||||
|
fallthrough
|
||||||
|
case .oneHalf:
|
||||||
|
fallthrough
|
||||||
|
case .one:
|
||||||
|
fallthrough
|
||||||
|
case .two:
|
||||||
|
fallthrough
|
||||||
|
case .three:
|
||||||
|
fallthrough
|
||||||
|
case .four:
|
||||||
|
return 2
|
||||||
|
case .five:
|
||||||
|
fallthrough
|
||||||
|
case .six:
|
||||||
|
fallthrough
|
||||||
|
case .seven:
|
||||||
|
fallthrough
|
||||||
|
case .eight:
|
||||||
|
return 3
|
||||||
|
case .nine:
|
||||||
|
fallthrough
|
||||||
|
case .ten:
|
||||||
|
fallthrough
|
||||||
|
case .eleven:
|
||||||
|
fallthrough
|
||||||
|
case .twelve:
|
||||||
|
return 4
|
||||||
|
case .thirteen:
|
||||||
|
fallthrough
|
||||||
|
case .fourteen:
|
||||||
|
fallthrough
|
||||||
|
case .fifteen:
|
||||||
|
fallthrough
|
||||||
|
case .sixteen:
|
||||||
|
return 5
|
||||||
|
case .seventeen:
|
||||||
|
fallthrough
|
||||||
|
case .eighteen:
|
||||||
|
fallthrough
|
||||||
|
case .nineteen:
|
||||||
|
fallthrough
|
||||||
|
case .twenty:
|
||||||
|
return 6
|
||||||
|
case .twentyOne:
|
||||||
|
fallthrough
|
||||||
|
case .twentyTwo:
|
||||||
|
fallthrough
|
||||||
|
case .twentyThree:
|
||||||
|
fallthrough
|
||||||
|
case .twentyFour:
|
||||||
|
return 7
|
||||||
|
case .twentyFive:
|
||||||
|
fallthrough
|
||||||
|
case .twentySix:
|
||||||
|
fallthrough
|
||||||
|
case .twentySeven:
|
||||||
|
fallthrough
|
||||||
|
case .twentyEight:
|
||||||
|
return 8
|
||||||
|
case .twentyNine:
|
||||||
|
fallthrough
|
||||||
|
case .thirty:
|
||||||
|
return 9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func proficiencyBonusForType(_ profType: ProficiencyType) -> Int {
|
||||||
|
switch profType {
|
||||||
|
case .none:
|
||||||
|
return 0
|
||||||
|
case .proficient:
|
||||||
|
return proficiencyBonus
|
||||||
|
case .expertise:
|
||||||
|
return proficiencyBonus * 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Saving Throws
|
||||||
|
|
||||||
|
var savingThrowsDescription: String {
|
||||||
|
get {
|
||||||
|
// TODO: port from objective-c
|
||||||
|
var parts: [String] = []
|
||||||
|
var name: String
|
||||||
|
var advantage: String
|
||||||
|
var bonus: Int
|
||||||
|
|
||||||
|
if (strengthSavingThrowAdvantage != .none || strengthSavingThrowProficiency != .none) {
|
||||||
|
name = "Strength"
|
||||||
|
bonus = strengthModifier + proficiencyBonusForType(strengthSavingThrowProficiency)
|
||||||
|
advantage = Monster.advantageLabelStringForType(strengthSavingThrowAdvantage)
|
||||||
|
if (!advantage.isEmpty) {
|
||||||
|
advantage = " " + advantage
|
||||||
|
}
|
||||||
|
parts.append(String(format: "%@ %+d%@", name, bonus, advantage))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dexteritySavingThrowAdvantage != .none || dexteritySavingThrowProficiency != .none) {
|
||||||
|
name = "Dexterity"
|
||||||
|
bonus = dexterityModifier + proficiencyBonusForType(dexteritySavingThrowProficiency)
|
||||||
|
advantage = Monster.advantageLabelStringForType(dexteritySavingThrowAdvantage)
|
||||||
|
if (!advantage.isEmpty) {
|
||||||
|
advantage = " " + advantage
|
||||||
|
}
|
||||||
|
parts.append(String(format: "%@ %+d%@", name, bonus, advantage))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (constitutionSavingThrowAdvantage != .none || constitutionSavingThrowProficiency != .none) {
|
||||||
|
name = "Constitution"
|
||||||
|
bonus = constitutionModifier + proficiencyBonusForType(constitutionSavingThrowProficiency)
|
||||||
|
advantage = Monster.advantageLabelStringForType(constitutionSavingThrowAdvantage)
|
||||||
|
if (!advantage.isEmpty) {
|
||||||
|
advantage = " " + advantage
|
||||||
|
}
|
||||||
|
parts.append(String(format: "%@ %+d%@", name, bonus, advantage))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intelligenceSavingThrowAdvantage != .none || intelligenceSavingThrowProficiency != .none) {
|
||||||
|
name = "Intelligence"
|
||||||
|
bonus = intelligenceModifier + proficiencyBonusForType(intelligenceSavingThrowProficiency)
|
||||||
|
advantage = Monster.advantageLabelStringForType(intelligenceSavingThrowAdvantage)
|
||||||
|
if (!advantage.isEmpty) {
|
||||||
|
advantage = " " + advantage
|
||||||
|
}
|
||||||
|
parts.append(String(format: "%@ %+d%@", name, bonus, advantage))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wisdomSavingThrowAdvantage != .none || wisdomSavingThrowProficiency != .none) {
|
||||||
|
name = "Wisdom"
|
||||||
|
bonus = wisdomModifier + proficiencyBonusForType(wisdomSavingThrowProficiency)
|
||||||
|
advantage = Monster.advantageLabelStringForType(wisdomSavingThrowAdvantage)
|
||||||
|
if (!advantage.isEmpty) {
|
||||||
|
advantage = " " + advantage
|
||||||
|
}
|
||||||
|
parts.append(String(format: "%@ %+d%@", name, bonus, advantage))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charismaSavingThrowAdvantage != .none || charismaSavingThrowProficiency != .none) {
|
||||||
|
name = "Charisma"
|
||||||
|
bonus = charismaModifier + proficiencyBonusForType(charismaSavingThrowProficiency)
|
||||||
|
advantage = Monster.advantageLabelStringForType(charismaSavingThrowAdvantage)
|
||||||
|
if (!advantage.isEmpty) {
|
||||||
|
advantage = " " + advantage
|
||||||
|
}
|
||||||
|
parts.append(String(format: "%@ %+d%@", name, bonus, advantage))
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.joined(separator: ", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Misc Helpers
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Skills
|
||||||
|
|
||||||
|
var skillsDescription: String {
|
||||||
|
get {
|
||||||
|
let sortedSkills = self.skills.sorted {$0.name < $1.name}
|
||||||
|
return sortedSkills.reduce("") {
|
||||||
|
if $0 == "" {
|
||||||
|
return $1.skillDescription(forMonster: self)
|
||||||
|
} else {
|
||||||
|
return $0 + ", " + $1.skillDescription(forMonster: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Immunities, Resistances, and Vulnerabilities
|
||||||
|
|
||||||
|
var damageVulnerabilitiesDescription: String {
|
||||||
|
get {
|
||||||
|
// TODO: sort "bludgeoning, piercing, and slashing from nonmagical attacks" to the end and use ; as a separator before it.
|
||||||
|
let sortedVulnerabilities = self.damageVulnerabilities.sorted().map({$0.name})
|
||||||
|
return StringHelper.oxfordJoin(sortedVulnerabilities)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var damageResistancesDescription: String {
|
||||||
|
get {
|
||||||
|
// TODO: sort "bludgeoning, piercing, and slashing from nonmagical attacks" to the end and use ; as a separator before it.
|
||||||
|
let sortedResistances = self.damageResistances.sorted().map({$0.name})
|
||||||
|
return StringHelper.oxfordJoin(sortedResistances)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var damageImmunitiesDescription: String {
|
||||||
|
get {
|
||||||
|
// TODO: sort "bludgeoning, piercing, and slashing from nonmagical attacks" to the end and use ; as a separator before it.
|
||||||
|
let sortedImmunities = self.damageImmunities.sorted().map({$0.name})
|
||||||
|
return StringHelper.oxfordJoin(sortedImmunities)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var conditionImmunitiesDescription: String {
|
||||||
|
get {
|
||||||
|
let sortedImmunities = self.conditionImmunities.sorted().map({$0.name})
|
||||||
|
return StringHelper.oxfordJoin(sortedImmunities)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: OTHER
|
||||||
|
|
||||||
|
var passivePerception: Int {
|
||||||
|
get {
|
||||||
|
let perceptionSkill = skills.first(where: {
|
||||||
|
StringHelper.safeEqualsIgnoreCase($0.name, "Perception")
|
||||||
|
})
|
||||||
|
if (perceptionSkill == nil) {
|
||||||
|
return 10 + wisdomModifier
|
||||||
|
} else if (perceptionSkill!.proficiency == ProficiencyType.expertise) {
|
||||||
|
return 10 + wisdomModifier + proficiencyBonus + proficiencyBonus
|
||||||
|
} else if (perceptionSkill!.proficiency == ProficiencyType.proficient) {
|
||||||
|
return 10 + wisdomModifier + proficiencyBonus
|
||||||
|
} else {
|
||||||
|
return 10 + wisdomModifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sensesDescription: String {
|
||||||
|
get {
|
||||||
|
var modifiedSenses = self.senses.sorted().map({$0.name})
|
||||||
|
let hasPassivePerceptionSense = modifiedSenses.contains(where: {
|
||||||
|
$0.starts(with: "passive Perception")
|
||||||
|
})
|
||||||
|
if (!hasPassivePerceptionSense) {
|
||||||
|
let calculatedPassivePerception = String(format: "passive Perception %d", passivePerception)
|
||||||
|
modifiedSenses.append(calculatedPassivePerception)
|
||||||
|
}
|
||||||
|
|
||||||
|
return modifiedSenses.sorted().joined(separator: ", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var languagesDescription: String {
|
||||||
|
get {
|
||||||
|
let spokenLanguages =
|
||||||
|
languages
|
||||||
|
.filter({ $0.speaks })
|
||||||
|
.map({$0.name})
|
||||||
|
.sorted()
|
||||||
|
let understoodLanguages =
|
||||||
|
languages
|
||||||
|
.filter({ !$0.speaks })
|
||||||
|
.map({$0.name})
|
||||||
|
.sorted()
|
||||||
|
|
||||||
|
let understandsButText = understandsBut.isEmpty
|
||||||
|
? ""
|
||||||
|
: String(format: " but %@", understandsBut)
|
||||||
|
|
||||||
|
let telepathyText = telepathy > 0
|
||||||
|
? String(format: ", telepathy %d ft.", telepathy)
|
||||||
|
: ""
|
||||||
|
|
||||||
|
if (spokenLanguages.count > 0) {
|
||||||
|
if (understoodLanguages.count > 0) {
|
||||||
|
return String(
|
||||||
|
format:"%@ and understands %@%@%@",
|
||||||
|
StringHelper.oxfordJoin(spokenLanguages),
|
||||||
|
StringHelper.oxfordJoin(understoodLanguages),
|
||||||
|
understandsButText,
|
||||||
|
telepathyText)
|
||||||
|
} else {
|
||||||
|
return String(
|
||||||
|
format: "%@%@%@",
|
||||||
|
StringHelper.oxfordJoin(spokenLanguages),
|
||||||
|
understandsButText,
|
||||||
|
telepathyText)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (understoodLanguages.count > 0) {
|
||||||
|
return String(
|
||||||
|
format: "understands %@%@%@",
|
||||||
|
StringHelper.oxfordJoin(understoodLanguages),
|
||||||
|
understandsButText,
|
||||||
|
telepathyText)
|
||||||
|
} else if (telepathy > 0){
|
||||||
|
return String(format: "telepathy %d ft.", telepathy)
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var challengeRatingDescription: String {
|
||||||
|
get {
|
||||||
|
if (challengeRating != .custom) {
|
||||||
|
return challengeRating.displayName
|
||||||
|
} else {
|
||||||
|
return customChallengeRating
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: End
|
||||||
}
|
}
|
||||||
|
|||||||
38
iOS/MonsterCards/Models/SavingThrowDTO.swift
Normal file
38
iOS/MonsterCards/Models/SavingThrowDTO.swift
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// SavingThrowDTO.swift
|
||||||
|
// MonsterCards
|
||||||
|
//
|
||||||
|
// Created by Tom Hicks on 3/28/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct SavingThrowDTO {
|
||||||
|
var name: String
|
||||||
|
var order: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum SavingThrowDTOCodingKeys: String, CodingKey {
|
||||||
|
case name = "name"
|
||||||
|
case order = "order"
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SavingThrowDTO: Decodable {
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
|
||||||
|
let container = try decoder.container(keyedBy: SavingThrowDTOCodingKeys.self)
|
||||||
|
self.name = (try? container.decode(String.self, forKey: .name)) ?? ""
|
||||||
|
self.order = (try? container.decode(Int.self, forKey: .order)) ?? 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SavingThrowDTO: Encodable {
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
|
||||||
|
var container = encoder.container(keyedBy: SavingThrowDTOCodingKeys.self)
|
||||||
|
try container.encode(self.name, forKey: .name)
|
||||||
|
try container.encode(self.order, forKey: .order)
|
||||||
|
}
|
||||||
|
}
|
||||||
42
iOS/MonsterCards/Models/SkillDTO.swift
Normal file
42
iOS/MonsterCards/Models/SkillDTO.swift
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// SkillDTO.swift
|
||||||
|
// MonsterCards
|
||||||
|
//
|
||||||
|
// Created by Tom Hicks on 3/28/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct SkillDTO {
|
||||||
|
var name: String
|
||||||
|
var stat: String
|
||||||
|
var note: String
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum SkillDTOCodingKeys: String, CodingKey {
|
||||||
|
case name = "name"
|
||||||
|
case stat = "stat"
|
||||||
|
case note = "note"
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SkillDTO: Decodable {
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
|
||||||
|
let container = try decoder.container(keyedBy: SkillDTOCodingKeys.self)
|
||||||
|
self.name = (try? container.decode(String.self, forKey: .name)) ?? ""
|
||||||
|
self.note = (try? container.decode(String.self, forKey: .note)) ?? ""
|
||||||
|
self.stat = (try? container.decode(String.self, forKey: .stat)) ?? ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SkillDTO: Encodable {
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
|
||||||
|
var container = encoder.container(keyedBy: SkillDTOCodingKeys.self)
|
||||||
|
try container.encode(self.name, forKey: .name)
|
||||||
|
try container.encode(self.note, forKey: .note)
|
||||||
|
try container.encode(self.stat, forKey: .stat)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,6 +65,13 @@ class SkillViewModel: ObservableObject, Comparable, Hashable, Identifiable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init(_ name: String, _ abilityScore: AbilityScore, _ proficiency: ProficiencyType = .proficient, _ advantage: AdvantageType = .none) {
|
||||||
|
_name = name
|
||||||
|
_abilityScore = abilityScore
|
||||||
|
_proficiency = proficiency
|
||||||
|
_advantage = advantage
|
||||||
|
}
|
||||||
|
|
||||||
private var _name: String = ""
|
private var _name: String = ""
|
||||||
var name: String {
|
var name: String {
|
||||||
get {
|
get {
|
||||||
@@ -125,4 +132,25 @@ class SkillViewModel: ObservableObject, Comparable, Hashable, Identifiable {
|
|||||||
newSkill.wrappedAdvantage = advantage
|
newSkill.wrappedAdvantage = advantage
|
||||||
return newSkill
|
return newSkill
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func modifier(forMonster: MonsterViewModel) -> Int {
|
||||||
|
let proficiencyBonus = Double(forMonster.proficiencyBonus)
|
||||||
|
let abilityScoreModifier = Double(forMonster.abilityModifierForAbilityScore(abilityScore))
|
||||||
|
switch proficiency {
|
||||||
|
case .none:
|
||||||
|
return Int(abilityScoreModifier)
|
||||||
|
case .proficient:
|
||||||
|
return Int(abilityScoreModifier + proficiencyBonus)
|
||||||
|
case .expertise:
|
||||||
|
return Int(abilityScoreModifier + 2 * proficiencyBonus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func skillDescription(forMonster: MonsterViewModel) -> String {
|
||||||
|
var advantageLabel = Monster.advantageLabelStringForType(advantage)
|
||||||
|
if (advantageLabel != "") {
|
||||||
|
advantageLabel = " " + advantageLabel
|
||||||
|
}
|
||||||
|
return String(format: "%@ %+d%@", name, modifier(forMonster: forMonster), advantageLabel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
42
iOS/MonsterCards/Models/TraitDTO.swift
Normal file
42
iOS/MonsterCards/Models/TraitDTO.swift
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// TraitDTO.swift
|
||||||
|
// MonsterCards
|
||||||
|
//
|
||||||
|
// Created by Tom Hicks on 3/28/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct TraitDTO {
|
||||||
|
var name: String
|
||||||
|
var note: String
|
||||||
|
var desc: String
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum TraitDTOCodingKeys: String, CodingKey {
|
||||||
|
case name = "name"
|
||||||
|
case note = "note"
|
||||||
|
case desc = "desc"
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TraitDTO: Decodable {
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
|
||||||
|
let container = try decoder.container(keyedBy: TraitDTOCodingKeys.self)
|
||||||
|
self.name = (try? container.decode(String.self, forKey: .name)) ?? ""
|
||||||
|
self.note = (try? container.decode(String.self, forKey: .note)) ?? ""
|
||||||
|
self.desc = (try? container.decode(String.self, forKey: .desc)) ?? ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TraitDTO: Encodable {
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
|
||||||
|
var container = encoder.container(keyedBy: TraitDTOCodingKeys.self)
|
||||||
|
try container.encode(self.name, forKey: .name)
|
||||||
|
try container.encode(self.note, forKey: .note)
|
||||||
|
try container.encode(self.desc, forKey: .desc)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@
|
|||||||
<attribute name="actions" optional="YES" attributeType="Transformable" valueTransformerName="AbilityViewModelValueTransformer" customClassName="[AbilityViewModel]"/>
|
<attribute name="actions" optional="YES" attributeType="Transformable" valueTransformerName="AbilityViewModelValueTransformer" customClassName="[AbilityViewModel]"/>
|
||||||
<attribute name="alignment" attributeType="String" defaultValueString=""/>
|
<attribute name="alignment" attributeType="String" defaultValueString=""/>
|
||||||
<attribute name="armorType" attributeType="String" defaultValueString=""/>
|
<attribute name="armorType" attributeType="String" defaultValueString=""/>
|
||||||
<attribute name="baseSpeed" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
|
||||||
<attribute name="blindsightDistance" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
<attribute name="blindsightDistance" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
<attribute name="burrowSpeed" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
<attribute name="burrowSpeed" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
<attribute name="canHover" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
<attribute name="canHover" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||||
@@ -39,11 +38,14 @@
|
|||||||
<attribute name="intelligenceSavingThrowProficiency" attributeType="String" defaultValueString="none"/>
|
<attribute name="intelligenceSavingThrowProficiency" attributeType="String" defaultValueString="none"/>
|
||||||
<attribute name="intelligenceScore" attributeType="Integer 64" defaultValueString="10" usesScalarValueType="YES"/>
|
<attribute name="intelligenceScore" attributeType="Integer 64" defaultValueString="10" usesScalarValueType="YES"/>
|
||||||
<attribute name="isBlind" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
<attribute name="isBlind" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="lairActions" optional="YES" attributeType="Transformable" valueTransformerName="AbilityViewModelValueTransformer" customClassName="[AbilityViewModel]"/>
|
||||||
<attribute name="languages" optional="YES" attributeType="Transformable" valueTransformerName="LanguageViewModelValueTransformer" customClassName="[LanguageViewModel]"/>
|
<attribute name="languages" optional="YES" attributeType="Transformable" valueTransformerName="LanguageViewModelValueTransformer" customClassName="[LanguageViewModel]"/>
|
||||||
<attribute name="legendaryActions" optional="YES" attributeType="Transformable" valueTransformerName="AbilityViewModelValueTransformer" customClassName="[AbilityViewModel]"/>
|
<attribute name="legendaryActions" optional="YES" attributeType="Transformable" valueTransformerName="AbilityViewModelValueTransformer" customClassName="[AbilityViewModel]"/>
|
||||||
<attribute name="name" attributeType="String" defaultValueString="Unnamed Monster"/>
|
<attribute name="name" attributeType="String" defaultValueString="Unnamed Monster"/>
|
||||||
<attribute name="naturalArmorBonus" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
<attribute name="naturalArmorBonus" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
<attribute name="otherArmorDescription" attributeType="String" defaultValueString=""/>
|
<attribute name="otherArmorDescription" attributeType="String" defaultValueString=""/>
|
||||||
|
<attribute name="reactions" optional="YES" attributeType="Transformable" valueTransformerName="AbilityViewModelValueTransformer" customClassName="[AbilityViewModel]"/>
|
||||||
|
<attribute name="regionalActions" optional="YES" attributeType="Transformable" valueTransformerName="AbilityViewModelValueTransformer" customClassName="[AbilityViewModel]"/>
|
||||||
<attribute name="senses" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromDataTransformerName" customClassName="[String]"/>
|
<attribute name="senses" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromDataTransformerName" customClassName="[String]"/>
|
||||||
<attribute name="shieldBonus" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
<attribute name="shieldBonus" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
<attribute name="size" attributeType="String" defaultValueString=""/>
|
<attribute name="size" attributeType="String" defaultValueString=""/>
|
||||||
@@ -57,6 +59,7 @@
|
|||||||
<attribute name="truesightDistance" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
<attribute name="truesightDistance" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
<attribute name="type" attributeType="String" defaultValueString=""/>
|
<attribute name="type" attributeType="String" defaultValueString=""/>
|
||||||
<attribute name="understandsBut" attributeType="String" defaultValueString=""/>
|
<attribute name="understandsBut" attributeType="String" defaultValueString=""/>
|
||||||
|
<attribute name="walkSpeed" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
<attribute name="wisdomSavingThrowAdvantage" attributeType="String" defaultValueString="none"/>
|
<attribute name="wisdomSavingThrowAdvantage" attributeType="String" defaultValueString="none"/>
|
||||||
<attribute name="wisdomSavingThrowProficiency" attributeType="String" defaultValueString="none"/>
|
<attribute name="wisdomSavingThrowProficiency" attributeType="String" defaultValueString="none"/>
|
||||||
<attribute name="wisdomScore" attributeType="Integer 64" defaultValueString="10" usesScalarValueType="YES"/>
|
<attribute name="wisdomScore" attributeType="Integer 64" defaultValueString="10" usesScalarValueType="YES"/>
|
||||||
@@ -70,7 +73,7 @@
|
|||||||
<relationship name="monster" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Monster" inverseName="skills" inverseEntity="Monster"/>
|
<relationship name="monster" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Monster" inverseName="skills" inverseEntity="Monster"/>
|
||||||
</entity>
|
</entity>
|
||||||
<elements>
|
<elements>
|
||||||
<element name="Monster" positionX="-63" positionY="-18" width="128" height="929"/>
|
<element name="Monster" positionX="-63" positionY="-18" width="128" height="974"/>
|
||||||
<element name="Skill" positionX="-63" positionY="135" width="128" height="14"/>
|
<element name="Skill" positionX="-63" positionY="135" width="128" height="14"/>
|
||||||
</elements>
|
</elements>
|
||||||
</model>
|
</model>
|
||||||
@@ -8,9 +8,15 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import CoreData
|
import CoreData
|
||||||
|
|
||||||
|
struct ImportInfo {
|
||||||
|
var monster: MonsterViewModel = MonsterViewModel()
|
||||||
|
}
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@Environment(\.managedObjectContext) private var viewContext
|
@Environment(\.managedObjectContext) private var viewContext
|
||||||
|
@State private var importInfo = ImportInfo()
|
||||||
|
@State private var isShowingImportDialog = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
TabView {
|
TabView {
|
||||||
Search()
|
Search()
|
||||||
@@ -35,70 +41,33 @@ struct ContentView: View {
|
|||||||
Text("Library")
|
Text("Library")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onOpenURL(perform: beginImportingMonster)
|
||||||
|
.sheet(isPresented: self.$isShowingImportDialog) {
|
||||||
|
ImportMonster(monster: $importInfo.monster)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func beginImportingMonster(url: URL) {
|
||||||
|
|
||||||
|
// TOOD: only do this if the file name ends in .json or .monster
|
||||||
|
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
do {
|
||||||
|
let data = try Data(contentsOf: url)
|
||||||
|
let monsterDTO = try decoder.decode(MonsterDTO.self, from: data)
|
||||||
|
print(String(format: "Loaded monster: %@", monsterDTO.name))
|
||||||
|
// TODO: check for some minimal set of properties to ensure this is the expected json schema
|
||||||
|
self.importInfo.monster = MonsterImportHelper.import5ESBMonster(monsterDTO)
|
||||||
|
// TODO: throw or set an err here and don't set isShowingImportDialog to true if the file didn't match any of our supported monster schemas.
|
||||||
|
self.isShowingImportDialog = true
|
||||||
|
} catch let error as NSError {
|
||||||
|
// TODO: handle this better
|
||||||
|
print(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @FetchRequest(
|
|
||||||
// sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
|
|
||||||
// animation: .default)
|
|
||||||
// private var items: FetchedResults<Item>
|
|
||||||
//
|
|
||||||
// var body: some View {
|
|
||||||
// List {
|
|
||||||
// ForEach(items) { item in
|
|
||||||
// Text("Item at \(item.timestamp!, formatter: itemFormatter)")
|
|
||||||
// }
|
|
||||||
// .onDelete(perform: deleteItems)
|
|
||||||
// }
|
|
||||||
// .toolbar {
|
|
||||||
// #if os(iOS)
|
|
||||||
// EditButton()
|
|
||||||
// #endif
|
|
||||||
//
|
|
||||||
// Button(action: addItem) {
|
|
||||||
// Label("Add Item", systemImage: "plus")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private func addItem() {
|
|
||||||
// withAnimation {
|
|
||||||
// let newItem = Item(context: viewContext)
|
|
||||||
// newItem.timestamp = Date()
|
|
||||||
//
|
|
||||||
// do {
|
|
||||||
// try viewContext.save()
|
|
||||||
// } catch {
|
|
||||||
// // Replace this implementation with code to handle the error appropriately.
|
|
||||||
// // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
|
|
||||||
// let nsError = error as NSError
|
|
||||||
// fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private func deleteItems(offsets: IndexSet) {
|
|
||||||
// withAnimation {
|
|
||||||
// offsets.map { items[$0] }.forEach(viewContext.delete)
|
|
||||||
//
|
|
||||||
// do {
|
|
||||||
// try viewContext.save()
|
|
||||||
// } catch {
|
|
||||||
// // Replace this implementation with code to handle the error appropriately.
|
|
||||||
// // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
|
|
||||||
// let nsError = error as NSError
|
|
||||||
// fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//private let itemFormatter: DateFormatter = {
|
|
||||||
// let formatter = DateFormatter()
|
|
||||||
// formatter.dateStyle = .short
|
|
||||||
// formatter.timeStyle = .medium
|
|
||||||
// return formatter
|
|
||||||
//}()
|
|
||||||
|
|
||||||
struct ContentView_Previews: PreviewProvider {
|
struct ContentView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
|
ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ import CoreData
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct EditMonster: View {
|
struct EditMonster: View {
|
||||||
// TODO: add challengeRating/challengeRatingEnum and customChallengeRating maybe in basicInfo
|
// TODO: Add challengeRating/challengeRatingEnum and customChallengeRating maybe in basicInfo
|
||||||
|
// TODO: Add a way to edit the monster being blind. Probably a header section to the senses section.
|
||||||
|
// TODO: Add a way to edit lair actions
|
||||||
|
// TODO: Add a way to edit regional actions
|
||||||
|
// TODO: Add a way to edit reactions
|
||||||
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
|
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
|
||||||
@Environment(\.managedObjectContext) private var viewContext
|
@Environment(\.managedObjectContext) private var viewContext
|
||||||
|
|
||||||
@@ -141,7 +145,7 @@ struct EditMonster_Previews: PreviewProvider {
|
|||||||
monster.hitDice = 6
|
monster.hitDice = 6
|
||||||
monster.hasCustomHP = true
|
monster.hasCustomHP = true
|
||||||
monster.customHP = "12 (1d10)+2"
|
monster.customHP = "12 (1d10)+2"
|
||||||
monster.baseSpeed = 5
|
monster.walkSpeed = 5
|
||||||
monster.burrowSpeed = 10
|
monster.burrowSpeed = 10
|
||||||
monster.climbSpeed = 15
|
monster.climbSpeed = 15
|
||||||
monster.flySpeed = 20
|
monster.flySpeed = 20
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ struct EditSpeed: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
List {
|
||||||
// Number bound to monster.baseSpeed
|
// Number bound to monster.walkSpeed
|
||||||
MCStepperField(
|
MCStepperField(
|
||||||
label: "Base",
|
label: "Base",
|
||||||
step: 5,
|
step: 5,
|
||||||
suffix: " ft.",
|
suffix: " ft.",
|
||||||
value: $monsterViewModel.baseSpeed)
|
value: $monsterViewModel.walkSpeed)
|
||||||
|
|
||||||
// Number bound to monster.burrowSpeed
|
// Number bound to monster.burrowSpeed
|
||||||
MCStepperField(
|
MCStepperField(
|
||||||
|
|||||||
24
iOS/MonsterCards/Views/ImportMonster.swift
Normal file
24
iOS/MonsterCards/Views/ImportMonster.swift
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// ImportMonster.swift
|
||||||
|
// MonsterCards
|
||||||
|
//
|
||||||
|
// Created by Tom Hicks on 4/1/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ImportMonster: View {
|
||||||
|
@Binding var monster: MonsterViewModel
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
MonsterDetailView(viewModel: monster)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ImportMonster_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
ImportMonster(
|
||||||
|
monster: .constant(MonsterViewModel())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ struct Library: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView{
|
NavigationView{
|
||||||
List(allMonsters) { monster in
|
List(allMonsters) { monster in
|
||||||
NavigationLink(destination: MonsterDetail(monster: monster)) {
|
NavigationLink(destination: MonsterDetailWrapper(monster: monster)) {
|
||||||
Text(monster.name ?? "")
|
Text(monster.name ?? "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ struct SmallAbilityScore: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct BasicInfoView: View {
|
struct BasicInfoView: View {
|
||||||
@ObservedObject var monster: Monster
|
@ObservedObject var monster: MonsterViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
let monsterMeta = monster.meta
|
let monsterMeta = monster.meta
|
||||||
@@ -80,6 +80,11 @@ struct BasicInfoView: View {
|
|||||||
let monsterHitPoints = monster.hitPoints
|
let monsterHitPoints = monster.hitPoints
|
||||||
let monsterSpeed = monster.speed
|
let monsterSpeed = monster.speed
|
||||||
|
|
||||||
|
if (!monster.name.isEmpty) {
|
||||||
|
Text(monster.name)
|
||||||
|
.font(.largeTitle)
|
||||||
|
}
|
||||||
|
|
||||||
// meta: "(large humanoid (elf) lawful evil"
|
// meta: "(large humanoid (elf) lawful evil"
|
||||||
if (!monsterMeta.isEmpty) {
|
if (!monsterMeta.isEmpty) {
|
||||||
Text(monsterMeta)
|
Text(monsterMeta)
|
||||||
@@ -113,7 +118,7 @@ struct BasicInfoView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct AbilityScoresView: View {
|
struct AbilityScoresView: View {
|
||||||
@ObservedObject var monster: Monster
|
@ObservedObject var monster: MonsterViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
SectionDivider()
|
SectionDivider()
|
||||||
@@ -131,7 +136,7 @@ struct AbilityScoresView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct ResistancesAndImmunitiesView: View {
|
struct ResistancesAndImmunitiesView: View {
|
||||||
@ObservedObject var monster: Monster
|
@ObservedObject var monster: MonsterViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
let monsterDamageVulnerabilitiesDescription = monster.damageVulnerabilitiesDescription
|
let monsterDamageVulnerabilitiesDescription = monster.damageVulnerabilitiesDescription
|
||||||
@@ -178,7 +183,7 @@ struct ResistancesAndImmunitiesView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct SavingThrowsAndSkillsView: View {
|
struct SavingThrowsAndSkillsView: View {
|
||||||
@ObservedObject var monster: Monster
|
@ObservedObject var monster: MonsterViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
let savingThrowsDescription = monster.savingThrowsDescription
|
let savingThrowsDescription = monster.savingThrowsDescription
|
||||||
@@ -200,37 +205,28 @@ struct SavingThrowsAndSkillsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MonsterDetail: View {
|
struct MonsterDetailView: View {
|
||||||
let kTextColor: Color = Color(hex: 0x982818)
|
let kTextColor = Color(hex: 0x982818)
|
||||||
|
|
||||||
@ObservedObject var monster: Monster
|
var viewModel: MonsterViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
let monsterLanguagesDescription = viewModel.languagesDescription
|
||||||
|
let monsterChallengeRatingDescription = viewModel.challengeRatingDescription
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
// TODO: Consider adding an inmage here at the top
|
// TODO: Consider adding an inmage here at the top
|
||||||
VStack (alignment: .leading) {
|
VStack (alignment: .leading) {
|
||||||
let monsterLanguagesDescription = monster.languagesDescription
|
|
||||||
let monsterChallengeRatingDescription = monster.challengeRatingDescription
|
|
||||||
let monsterAbilities: [AbilityViewModel] = monster.abilities ?? []
|
|
||||||
let monsterActions: [AbilityViewModel] = monster.actions ?? []
|
|
||||||
let monsterLegendaryActions: [AbilityViewModel] = monster.legendaryActions ?? []
|
|
||||||
|
|
||||||
BasicInfoView(monster: monster)
|
|
||||||
|
|
||||||
// TODO: Find a way to hide unnecessarry dividiers.
|
// TODO: Find a way to hide unnecessarry dividiers.
|
||||||
// if sections 0, 1, 2, and 3 are present there should be a divider between each of them
|
// if sections 0, 1, 2, and 3 are present there should be a divider between each of them
|
||||||
// if section 1 is not present there should be one and only one divider between sections 0 and 2 as well as the one between 2 and 3
|
// if section 1 is not present there should be one and only one divider between sections 0 and 2 as well as the one between 2 and 3
|
||||||
// if sections 1 and 2 are not present there should be a single divider between sections 0 and 3
|
// if sections 1 and 2 are not present there should be a single divider between sections 0 and 3
|
||||||
|
|
||||||
|
|
||||||
AbilityScoresView(monster: monster)
|
|
||||||
|
|
||||||
|
BasicInfoView(monster: viewModel)
|
||||||
|
AbilityScoresView(monster: viewModel)
|
||||||
SectionDivider()
|
SectionDivider()
|
||||||
|
SavingThrowsAndSkillsView(monster: viewModel)
|
||||||
SavingThrowsAndSkillsView(monster: monster)
|
ResistancesAndImmunitiesView(monster: viewModel)
|
||||||
|
|
||||||
ResistancesAndImmunitiesView(monster: monster)
|
|
||||||
|
|
||||||
Group {
|
Group {
|
||||||
// Languages
|
// Languages
|
||||||
if (!monsterLanguagesDescription.isEmpty) {
|
if (!monsterLanguagesDescription.isEmpty) {
|
||||||
@@ -248,27 +244,27 @@ struct MonsterDetail: View {
|
|||||||
|
|
||||||
// Proficiency Bonus
|
// Proficiency Bonus
|
||||||
LabeledField("Proficiency Bonus") {
|
LabeledField("Proficiency Bonus") {
|
||||||
Text(String(monster.proficiencyBonus))
|
Text(String(viewModel.proficiencyBonus))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Abilities
|
// Abilities
|
||||||
if (monsterAbilities.count > 0) {
|
if (viewModel.abilities.count > 0) {
|
||||||
ForEach(monsterAbilities) { ability in
|
ForEach(viewModel.abilities) { ability in
|
||||||
VStack {
|
VStack {
|
||||||
Markdown(Document(ability.renderedText(monster)))
|
Markdown(Document(ability.renderedText(viewModel)))
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
if (monsterActions.count > 0) {
|
if (viewModel.actions.count > 0) {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text("Actions")
|
Text("Actions")
|
||||||
.font(.system(size: 24, weight: .bold))
|
.font(.system(size: 24, weight: .bold))
|
||||||
ForEach(monsterActions) { action in
|
ForEach(viewModel.actions) { action in
|
||||||
VStack {
|
VStack {
|
||||||
Markdown(Document(action.renderedText(monster)))
|
Markdown(Document(action.renderedText(viewModel)))
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -276,13 +272,13 @@ struct MonsterDetail: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Legendary Actions
|
// Legendary Actions
|
||||||
if (monsterLegendaryActions.count > 0) {
|
if (viewModel.legendaryActions.count > 0) {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text("Legendary Actions")
|
Text("Legendary Actions")
|
||||||
.font(.system(size: 20, weight: .bold))
|
.font(.system(size: 20, weight: .bold))
|
||||||
ForEach(monsterLegendaryActions) { action in
|
ForEach(viewModel.legendaryActions) { action in
|
||||||
VStack {
|
VStack {
|
||||||
Markdown(Document(action.renderedText(monster)))
|
Markdown(Document(action.renderedText(viewModel)))
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -293,13 +289,32 @@ struct MonsterDetail: View {
|
|||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.foregroundColor(kTextColor)
|
.foregroundColor(kTextColor)
|
||||||
}
|
}
|
||||||
.toolbar(content: {
|
}
|
||||||
ToolbarItem(placement: .primaryAction) {
|
}
|
||||||
NavigationLink("Edit", destination: EditMonster(monster: monster))
|
|
||||||
}
|
struct MonsterDetailWrapper: View {
|
||||||
})
|
// TODO: Add display for when the monster is blind
|
||||||
.navigationTitle(monster.name ?? "")
|
// TODO: Add display for lair actions
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
// TODO: Add display for regional actions
|
||||||
|
// TODO: Add display for reactions
|
||||||
|
let kTextColor: Color = Color(hex: 0x982818)
|
||||||
|
|
||||||
|
@ObservedObject var monster: Monster
|
||||||
|
@StateObject private var viewModel = MonsterViewModel()
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
|
||||||
|
MonsterDetailView(viewModel: viewModel)
|
||||||
|
.onAppear(perform: {
|
||||||
|
viewModel.copyFromMonster(monster: monster)
|
||||||
|
})
|
||||||
|
.toolbar(content: {
|
||||||
|
ToolbarItem(placement: .primaryAction) {
|
||||||
|
NavigationLink("Edit", destination: EditMonster(monster: monster))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.navigationTitle(monster.name ?? "")
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func editMonster() {
|
private func editMonster() {
|
||||||
@@ -307,7 +322,7 @@ struct MonsterDetail: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MonsterDetail_Previews: PreviewProvider {
|
struct MonsterDetailWrapper_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let context = PersistenceController.preview.container.viewContext
|
let context = PersistenceController.preview.container.viewContext
|
||||||
let monster = Monster.init(context: context)
|
let monster = Monster.init(context: context)
|
||||||
@@ -319,7 +334,7 @@ struct MonsterDetail_Previews: PreviewProvider {
|
|||||||
monster.hitDice = 6
|
monster.hitDice = 6
|
||||||
monster.hasCustomHP = true
|
monster.hasCustomHP = true
|
||||||
monster.customHP = "12 (1d10)+2"
|
monster.customHP = "12 (1d10)+2"
|
||||||
monster.baseSpeed = 5
|
monster.walkSpeed = 5
|
||||||
monster.burrowSpeed = 10
|
monster.burrowSpeed = 10
|
||||||
monster.climbSpeed = 15
|
monster.climbSpeed = 15
|
||||||
monster.flySpeed = 20
|
monster.flySpeed = 20
|
||||||
@@ -352,10 +367,10 @@ struct MonsterDetail_Previews: PreviewProvider {
|
|||||||
]
|
]
|
||||||
|
|
||||||
return Group {
|
return Group {
|
||||||
MonsterDetail(monster: monster)
|
MonsterDetailWrapper(monster: monster)
|
||||||
.environment(\.managedObjectContext, context)
|
.environment(\.managedObjectContext, context)
|
||||||
.previewDevice("iPod touch (7th generation)")
|
.previewDevice("iPod touch (7th generation)")
|
||||||
MonsterDetail(monster: monster)
|
MonsterDetailWrapper(monster: monster)
|
||||||
.environment(\.managedObjectContext, context)
|
.environment(\.managedObjectContext, context)
|
||||||
.previewDevice("iPad Pro (11-inch) (2nd generation)")
|
.previewDevice("iPad Pro (11-inch) (2nd generation)")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ struct Search: View {
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
})) { monster in
|
})) { monster in
|
||||||
NavigationLink(destination: MonsterDetail(monster: monster)) {
|
NavigationLink(destination: MonsterDetailWrapper(monster: monster)) {
|
||||||
Text(monster.name ?? "")
|
Text(monster.name ?? "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user