From 28f0787020812262cec14ee7a7cf5baf3995f0df Mon Sep 17 00:00:00 2001 From: Tom Hicks Date: Sat, 29 May 2021 19:46:28 -0700 Subject: [PATCH] Adds Edit Armor screen to edit a monster's armor stats. --- .../monstercards/helpers/ArrayHelper.java | 4 +- .../ui/editmonster/EditArmorFragment.java | 27 +-- .../ui/editmonster/EditBasicInfoFragment.java | 45 +---- .../ui/editmonster/EditMonsterFragment.java | 8 + .../ui/editmonster/EditMonsterViewModel.java | 154 +++++++++++++++--- .../main/res/layout/fragment_edit_armor.xml | 45 +++-- .../main/res/navigation/mobile_navigation.xml | 8 + Android/app/src/main/res/values/strings.xml | 68 ++++---- 8 files changed, 236 insertions(+), 123 deletions(-) diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/helpers/ArrayHelper.java b/Android/app/src/main/java/com/majinnaibu/monstercards/helpers/ArrayHelper.java index 2a5d044..961db8d 100644 --- a/Android/app/src/main/java/com/majinnaibu/monstercards/helpers/ArrayHelper.java +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/helpers/ArrayHelper.java @@ -1,11 +1,9 @@ package com.majinnaibu.monstercards.helpers; -import androidx.annotation.NonNull; - import java.util.Objects; public final class ArrayHelper { - public static int indexOf(@NonNull Object[] array, Object target) { + public static int indexOf(Object[] array, Object target) { for (int index = 0; index < array.length; index++) { if (Objects.equals(array[index], target)) { return index; diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditArmorFragment.java b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditArmorFragment.java index a50534e..a913d27 100644 --- a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditArmorFragment.java +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditArmorFragment.java @@ -13,6 +13,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.SwitchCompat; +import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.navigation.NavBackStackEntry; import androidx.navigation.NavController; @@ -21,21 +22,23 @@ import androidx.navigation.Navigation; import com.majinnaibu.monstercards.R; import com.majinnaibu.monstercards.data.enums.ArmorType; import com.majinnaibu.monstercards.helpers.ArrayHelper; -import com.majinnaibu.monstercards.ui.components.Stepper; -import com.majinnaibu.monstercards.ui.shared.MCFragment; import com.majinnaibu.monstercards.utils.TextChangedListener; -public class EditArmorFragment extends MCFragment { +@SuppressWarnings("FieldCanBeLocal") +public class EditArmorFragment extends Fragment { private EditMonsterViewModel mViewModel; private ViewHolder mHolder; @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment); NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation); mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class); + + // Inflate the layout for this fragment View root = inflater.inflate(R.layout.fragment_edit_armor, container, false); + mHolder = new ViewHolder(root); mHolder.armorType.setAdapter(new ArrayAdapter(requireContext(), R.layout.dropdown_list_item, ArmorType.values()) { @@ -71,14 +74,14 @@ public class EditArmorFragment extends MCFragment { }); mHolder.armorType.setSelection(ArrayHelper.indexOf(ArmorType.values(), mViewModel.getArmorType().getValue())); - mHolder.naturalArmorBonus.setValue(mViewModel.getNaturalArmorBonusUnboxed()); - mHolder.naturalArmorBonus.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setNaturalArmorBonus(newValue)); + mHolder.naturalArmorBonus.setText(mViewModel.getNaturalArmorBonusValueAsString()); + mHolder.naturalArmorBonus.addTextChangedListener((new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setNaturalArmorBonus(s.toString())))); mHolder.hasShield.setChecked(mViewModel.getHasShieldValueAsBoolean()); mHolder.hasShield.setOnCheckedChangeListener((buttonView, isChecked) -> mViewModel.setHasShield(isChecked)); - mHolder.shieldBonus.setValue(mViewModel.getShieldBonusUnboxed()); - mHolder.shieldBonus.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setShieldBonus(newValue)); + mHolder.shieldBonus.setText(mViewModel.getShieldBonusValueAsString()); + mHolder.shieldBonus.addTextChangedListener((new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setShieldBonus(s.toString())))); mHolder.customArmor.setText(mViewModel.getCustomArmor().getValue()); mHolder.customArmor.addTextChangedListener((new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setCustomArmor(s.toString())))); @@ -88,12 +91,12 @@ public class EditArmorFragment extends MCFragment { private static class ViewHolder { private final Spinner armorType; - private final Stepper naturalArmorBonus; + private final EditText naturalArmorBonus; private final SwitchCompat hasShield; - private final Stepper shieldBonus; + private final EditText shieldBonus; private final EditText customArmor; - ViewHolder(@NonNull View root) { + ViewHolder(View root) { armorType = root.findViewById(R.id.armorType); naturalArmorBonus = root.findViewById(R.id.naturalArmorBonus); hasShield = root.findViewById(R.id.hasShield); @@ -101,4 +104,4 @@ public class EditArmorFragment extends MCFragment { customArmor = root.findViewById(R.id.customArmor); } } -} +} \ No newline at end of file diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditBasicInfoFragment.java b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditBasicInfoFragment.java index 3b9b045..9a99c88 100644 --- a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditBasicInfoFragment.java +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditBasicInfoFragment.java @@ -6,7 +6,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.EditText; -import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.navigation.NavBackStackEntry; import androidx.navigation.NavController; @@ -15,12 +14,8 @@ import androidx.navigation.Navigation; import com.google.android.material.switchmaterial.SwitchMaterial; import com.majinnaibu.monstercards.R; import com.majinnaibu.monstercards.ui.MCFragment; -import com.majinnaibu.monstercards.utils.Logger; import com.majinnaibu.monstercards.utils.TextChangedListener; -/** - * A simple {@link Fragment} subclass. - */ @SuppressWarnings("FieldCanBeLocal") public class EditBasicInfoFragment extends MCFragment { private EditMonsterViewModel mViewModel; @@ -38,52 +33,28 @@ public class EditBasicInfoFragment extends MCFragment { mHolder = new ViewHolder(root); mHolder.name.setText(mViewModel.getName().getValue()); - mHolder.name.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> { - mViewModel.setName(s.toString()); - Logger.logDebug(String.format("Monster Name changed to %s", mViewModel.getName().getValue())); - })); + mHolder.name.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setName(s.toString()))); mHolder.size.setText(mViewModel.getSize().getValue()); - mHolder.size.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> { - mViewModel.setSize(s.toString()); - Logger.logDebug(String.format("Monster Size changed to %s", mViewModel.getSize().getValue())); - })); + mHolder.size.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setSize(s.toString()))); mHolder.type.setText(mViewModel.getType().getValue()); - mHolder.type.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> { - mViewModel.setType(s.toString()); - Logger.logDebug(String.format("Monster Type changed to %s", mViewModel.getType().getValue())); - })); + mHolder.type.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setType(s.toString()))); mHolder.subtype.setText(mViewModel.getSubtype().getValue()); - mHolder.subtype.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> { - mViewModel.setSubtype(s.toString()); - Logger.logDebug(String.format("Monster Subtype changed to %s", mViewModel.getSubtype().getValue())); - })); + mHolder.subtype.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setSubtype(s.toString()))); mHolder.alignment.setText(mViewModel.getAlignment().getValue()); - mHolder.alignment.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> { - mViewModel.setAlignment(s.toString()); - Logger.logDebug(String.format("Monster Alignment changed to %s", mViewModel.getAlignment().getValue())); - })); + mHolder.alignment.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setAlignment(s.toString()))); mHolder.customHitPoints.setText(mViewModel.getCustomHitPoints().getValue()); - mHolder.customHitPoints.addTextChangedListener((new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> { - mViewModel.setCustomHitPoints(s.toString()); - Logger.logDebug(String.format("Monster Custom Hit Points changed to %s", mViewModel.getCustomHitPoints().getValue())); - }))); + mHolder.customHitPoints.addTextChangedListener((new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setCustomHitPoints(s.toString())))); mHolder.hitDice.setText(mViewModel.getHitDiceValueAsString()); - mHolder.hitDice.addTextChangedListener((new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> { - mViewModel.setHitDice(s.toString()); - Logger.logDebug(String.format("Monster Hit Dice changed to %s", mViewModel.getHitDiceValueAsString())); - }))); + mHolder.hitDice.addTextChangedListener((new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setHitDice(s.toString())))); mHolder.hasCustomHitPoints.setChecked(mViewModel.getHasCustomHitPointsValueAsBoolean()); - mHolder.hasCustomHitPoints.setOnCheckedChangeListener((button, isChecked) -> { - mViewModel.setHasCustomHitPoints(isChecked); - Logger.logDebug(String.format("Monster Has Custom Hit Points changed to %s", isChecked ? "TRUE" : "FALSE")); - }); + mHolder.hasCustomHitPoints.setOnCheckedChangeListener((button, isChecked) -> mViewModel.setHasCustomHitPoints(isChecked)); return root; } diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditMonsterFragment.java b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditMonsterFragment.java index a011985..d0d34b9 100644 --- a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditMonsterFragment.java +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditMonsterFragment.java @@ -86,6 +86,12 @@ public class EditMonsterFragment extends MCFragment { assert view != null; Navigation.findNavController(view).navigate(action); }); + mHolder.armorButton.setOnClickListener(v -> { + NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditArmorFragment(); + View view = getView(); + assert view != null; + Navigation.findNavController(view).navigate(action); + }); requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) { @Override @@ -137,9 +143,11 @@ public class EditMonsterFragment extends MCFragment { private static class ViewHolder { TextView basicInfoButton; + TextView armorButton; ViewHolder(View root) { basicInfoButton = root.findViewById(R.id.basicInfo); + armorButton = root.findViewById(R.id.armor); } } } \ No newline at end of file diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditMonsterViewModel.java b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditMonsterViewModel.java index afb9bdf..e0d1fdb 100644 --- a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditMonsterViewModel.java +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditMonsterViewModel.java @@ -5,26 +5,34 @@ import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; +import com.majinnaibu.monstercards.data.enums.ArmorType; import com.majinnaibu.monstercards.helpers.StringHelper; import com.majinnaibu.monstercards.models.Monster; +import com.majinnaibu.monstercards.utils.Logger; +import java.util.Objects; import java.util.UUID; @SuppressWarnings({"ConstantConditions", "unused"}) public class EditMonsterViewModel extends ViewModel { - private final MutableLiveData mName; private final MutableLiveData mMonsterId; - private final MutableLiveData mErrorMessage; private final MutableLiveData mHasError; private final MutableLiveData mHasLoaded; + private final MutableLiveData mHasChanges; + private final MutableLiveData mHasCustomHitPoints; + private final MutableLiveData mHasShield; + private final MutableLiveData mArmorType; + private final MutableLiveData mHitDice; + private final MutableLiveData mNaturalArmorBonus; + private final MutableLiveData mShieldBonus; + private final MutableLiveData mName; + private final MutableLiveData mErrorMessage; private final MutableLiveData mSize; private final MutableLiveData mType; private final MutableLiveData mSubtype; private final MutableLiveData mAlignment; private final MutableLiveData mCustomHitPoints; - private final MutableLiveData mHasChanges; - private final MutableLiveData mHitDice; - private final MutableLiveData mHasCustomHitPoints; + private final MutableLiveData mCustomArmor; public EditMonsterViewModel() { @@ -39,7 +47,12 @@ public class EditMonsterViewModel extends ViewModel { mAlignment = new MutableLiveData<>(""); mCustomHitPoints = new MutableLiveData<>(""); mHitDice = new MutableLiveData<>(0); + mNaturalArmorBonus = new MutableLiveData<>(0); mHasCustomHitPoints = new MutableLiveData<>(false); + mArmorType = new MutableLiveData<>(ArmorType.NONE); + mHasShield = new MutableLiveData<>(false); + mShieldBonus = new MutableLiveData<>(0); + mCustomArmor = new MutableLiveData<>(""); // TODO: consider initializing this to true so all new monsters need saving mHasChanges = new MutableLiveData<>(false); } @@ -54,7 +67,12 @@ public class EditMonsterViewModel extends ViewModel { mAlignment.setValue(monster.alignment); mCustomHitPoints.setValue(monster.customHPDescription); mHitDice.setValue(monster.hitDice); + mNaturalArmorBonus.setValue(monster.naturalArmorBonus); mHasCustomHitPoints.setValue(monster.hasCustomHP); + mArmorType.setValue(monster.armorType); + mHasShield.setValue(monster.shieldBonus != 0); + mShieldBonus.setValue(monster.shieldBonus); + mCustomArmor.setValue(monster.otherArmorDescription); mHasChanges.setValue(false); } @@ -63,8 +81,10 @@ public class EditMonsterViewModel extends ViewModel { } public void setName(@NonNull String name) { - mName.setValue(name); - mHasChanges.setValue(true); + if (!Objects.equals(mName.getValue(), name)) { + mName.setValue(name); + mHasChanges.setValue(true); + } } public LiveData getMonsterId() { @@ -108,8 +128,10 @@ public class EditMonsterViewModel extends ViewModel { } public void setSize(@NonNull String size) { - mSize.setValue(size); - mHasChanges.setValue(true); + if (!Objects.equals(mSize.getValue(), size)) { + mSize.setValue(size); + mHasChanges.setValue(true); + } } public LiveData getType() { @@ -117,17 +139,21 @@ public class EditMonsterViewModel extends ViewModel { } public void setType(@NonNull String type) { - mType.setValue(type); - mHasChanges.setValue(true); + if (!Objects.equals(mType.getValue(), type)) { + mType.setValue(type); + mHasChanges.setValue(true); + } } public LiveData getSubtype() { return mSubtype; } - public void setSubtype(@NonNull String subType) { - mSubtype.setValue(subType); - mHasChanges.setValue(true); + public void setSubtype(@NonNull String subtype) { + if (!Objects.equals(mSubtype.getValue(), subtype)) { + mSubtype.setValue(subtype); + mHasChanges.setValue(true); + } } public LiveData getAlignment() { @@ -135,8 +161,10 @@ public class EditMonsterViewModel extends ViewModel { } public void setAlignment(@NonNull String alignment) { - mAlignment.setValue(alignment); - mHasChanges.setValue(true); + if (!Objects.equals(mAlignment.getValue(), alignment)) { + mAlignment.setValue(alignment); + mHasChanges.setValue(true); + } } public LiveData getCustomHitPoints() { @@ -144,8 +172,10 @@ public class EditMonsterViewModel extends ViewModel { } public void setCustomHitPoints(String customHitPoints) { - mCustomHitPoints.setValue(customHitPoints); - mHasChanges.setValue(true); + if (!Objects.equals(mCustomHitPoints.getValue(), customHitPoints)) { + mCustomHitPoints.setValue(customHitPoints); + mHasChanges.setValue(true); + } } public LiveData getHasChanges() { @@ -165,8 +195,10 @@ public class EditMonsterViewModel extends ViewModel { } public void setHitDice(int hitDice) { - mHitDice.setValue(hitDice); - mHasChanges.setValue(true); + if (!Objects.equals(mHitDice.getValue(), hitDice)) { + mHitDice.setValue(hitDice); + mHasChanges.setValue(true); + } } public void setHitDice(String hitDice) { @@ -178,19 +210,93 @@ public class EditMonsterViewModel extends ViewModel { return mHitDice.getValue().toString(); } + public LiveData getNaturalArmorBonus() { + return mNaturalArmorBonus; + } + + public void setNaturalArmorBonus(int naturalArmorBonus) { + if (!Objects.equals(mNaturalArmorBonus.getValue(), naturalArmorBonus)) { + mNaturalArmorBonus.setValue(naturalArmorBonus); + mHasChanges.setValue(true); + } + } + + public void setNaturalArmorBonus(String naturalArmorBonus) { + Integer parsedValue = StringHelper.parseInt(naturalArmorBonus); + this.setNaturalArmorBonus(parsedValue != null ? parsedValue : 0); + } + + public String getNaturalArmorBonusValueAsString() { + return mNaturalArmorBonus.getValue().toString(); + } + public LiveData getHasCustomHitPoints() { return mHasCustomHitPoints; } public void setHasCustomHitPoints(boolean hasCustomHitPoints) { - mHasCustomHitPoints.setValue(hasCustomHitPoints); - mHasChanges.setValue(true); + if (!Objects.equals(mHasCustomHitPoints.getValue(), hasCustomHitPoints)) { + mHasCustomHitPoints.setValue(hasCustomHitPoints); + mHasChanges.setValue(true); + } } public boolean getHasCustomHitPointsValueAsBoolean() { return mHasCustomHitPoints.getValue(); } + public LiveData getArmorType() { + return mArmorType; + } + + public void setArmorType(ArmorType armorType) { + Logger.logDebug(String.format("Setting ArmorType to %s", armorType.displayName)); + if (!Objects.equals(mArmorType.getValue(), armorType)) { + mArmorType.setValue(armorType); + mHasChanges.setValue(true); + } + } + + public LiveData getHasShield() { + return mHasShield; + } + + public void setHasShield(boolean hasShield) { + mHasShield.setValue(hasShield); + mHasChanges.setValue(true); + } + + public boolean getHasShieldValueAsBoolean() { + return mHasShield.getValue(); + } + + public LiveData getShieldBonus() { + return mShieldBonus; + } + + public void setShieldBonus(int shieldBonus) { + mShieldBonus.setValue(shieldBonus); + mHasChanges.setValue(true); + } + + public void setShieldBonus(String shieldBonus) { + Integer parsedValue = StringHelper.parseInt(shieldBonus); + this.setShieldBonus(parsedValue != null ? parsedValue : 0); + } + + public LiveData getCustomArmor() { + return mCustomArmor; + } + + public void setCustomArmor(String customArmor) { + mCustomArmor.setValue(customArmor); + mHasChanges.setValue(true); + } + + public String getShieldBonusValueAsString() { + return mShieldBonus.getValue().toString(); + } + public Monster buildMonster() { Monster monster = new Monster(); @@ -203,6 +309,10 @@ public class EditMonsterViewModel extends ViewModel { monster.customHPDescription = mCustomHitPoints.getValue(); monster.hitDice = mHitDice.getValue(); monster.hasCustomHP = mHasCustomHitPoints.getValue(); + monster.armorType = mArmorType.getValue(); + monster.naturalArmorBonus = mNaturalArmorBonus.getValue(); + monster.shieldBonus = mShieldBonus.getValue(); + monster.otherArmorDescription = mCustomArmor.getValue(); return monster; } diff --git a/Android/app/src/main/res/layout/fragment_edit_armor.xml b/Android/app/src/main/res/layout/fragment_edit_armor.xml index 1f212d1..36c4206 100644 --- a/Android/app/src/main/res/layout/fragment_edit_armor.xml +++ b/Android/app/src/main/res/layout/fragment_edit_armor.xml @@ -1,6 +1,5 @@ - + android:layout_margin="@dimen/text_margin"> + + + + android:textSize="20sp" /> - + android:layout_margin="@dimen/text_margin"> + + + - + \ No newline at end of file diff --git a/Android/app/src/main/res/navigation/mobile_navigation.xml b/Android/app/src/main/res/navigation/mobile_navigation.xml index e8eee2d..f9db55e 100644 --- a/Android/app/src/main/res/navigation/mobile_navigation.xml +++ b/Android/app/src/main/res/navigation/mobile_navigation.xml @@ -75,12 +75,20 @@ + + \ No newline at end of file diff --git a/Android/app/src/main/res/values/strings.xml b/Android/app/src/main/res/values/strings.xml index fcb9d4d..76c790e 100644 --- a/Android/app/src/main/res/values/strings.xml +++ b/Android/app/src/main/res/values/strings.xml @@ -1,49 +1,53 @@ + Add monster + Edit Actions MonsterCards CHA CON + Unnamed Monster DEX INT - Query - section divider - STR - Collections - Dashboard - Library - Search - WIS - Add monster - Edit - Basic Info - Armor - Speed Ability Scores - Saving Throws - Skills + Abilities + Actions + Alignment + Armor + Basic Info + Challenge Rating Condition Immunities + Custom Armor + Custom HP Damage Immunities Damage Resistances Damage Vulnerabilities - Senses - Languages - Challenge Rating - Abilities - Actions - Reactions - Legendary Actions + Has Custom HP + Has a Shield + Hit Dice Lair Actions + Languages + Legendary Actions + Natural Armor Bonus + Name + Reactions Regional Actions - Edit %1$s - Unnamed Monster + Saving Throws + Query + Senses + Shield Bonus + Size + Skills + Speed + Subtype + Type + section divider Failed to create monster %1$s created - Name - Size - Type - Subtype - Alignment - Custom HP - Has Custom HP - Hit Dice + STR + Collections + Dashboard + Edit %1$s + Library + Search + WIS \ No newline at end of file