diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/models/Monster.java b/Android/app/src/main/java/com/majinnaibu/monstercards/models/Monster.java index bb31122..403fe85 100644 --- a/Android/app/src/main/java/com/majinnaibu/monstercards/models/Monster.java +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/models/Monster.java @@ -3,11 +3,19 @@ package com.majinnaibu.monstercards.models; import com.majinnaibu.monstercards.helpers.StringHelper; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Set; public class Monster { + public Monster() { + mSavingThrows = new HashSet<>(); + mSkills = new HashSet<>(); + } + private String mName; public String getName() { return mName; @@ -284,4 +292,331 @@ public class Monster { private static final int SPLINT_ARMOR_CLASS = BASE_ARMOR_CLASS + 7; private static final int PLATE_ARMOR_CLASS = BASE_ARMOR_CLASS + 8; + private int mHitDice; + public int getHitDice() { + return mHitDice; + } + public void setHitDice(int value) { + mHitDice = value; + } + + private boolean mCustomHP; + public boolean getCustomHP() { + return mCustomHP; + } + public void setCustomHP(boolean value) { + mCustomHP = value; + } + + private String mHPText; + public String getHPText() { + return mHPText; + } + public void setHPText(String value) { + mHPText = value; + } + + public String getHitPoints() { + if (getCustomHP()) { + return getHPText(); + } else { + int hitDice = getHitDice(); + int dieSize = getHitDieForSize(getSize()); + int conMod = getConstitutionModifier(); + int hpTotal = (int) Math.max(1, Math.ceil(hitDice * ((dieSize + 1) / 2.0 + conMod))); + return String.format(Locale.US, "%d (%dd%d %+d)", hpTotal, hitDice, dieSize, conMod * hitDice); + } + } + + private static int getHitDieForSize(String size) { + if ("tiny".equals(size)) { + return 4; + } else if ("small".equals(size)) { + return 6; + } else if ("medium".equals(size)) { + return 8; + } else if ("large".equals(size)) { + return 10; + } else if ("huge".equals(size)) { + return 12; + } else if ("gargantuan".equals(size)) { + return 20; + } else { + return 8; + } + } + + private String mSpeed; + public String getSpeed() { + return mSpeed; + } + public void setSpeed(String value) { + mSpeed = value; + } + + private String mBurrowSpeed; + public String getBurrowSpeed() { + return mBurrowSpeed; + } + public void setBurrowSpeed(String value) { + mBurrowSpeed = value; + } + + private String mClimbSpeed; + public String getClimbSpeed() { + return mClimbSpeed; + } + public void setClimbSpeed(String value) { + mClimbSpeed = value; + } + + private String mFlySpeed; + public String getFlySpeed() { + return mFlySpeed; + } + public void setFlySpeed(String value) { + mFlySpeed = value; + } + + private boolean mHover; + public boolean getHover() { + return mHover; + } + public void setHover(boolean value) { + mHover = value; + } + + private String mSwimSpeed; + public String getSwimSpeed() { + return mSwimSpeed; + } + public void setSwimSpeed(String value) { + mSwimSpeed = value; + } + + private boolean mCustomSpeed; + public boolean getCustomSpeed() { + return mCustomSpeed; + } + public void setCustomSpeed(boolean value) { + mCustomSpeed = value; + } + + private String mSpeedDescription; + public String getSpeedDescription() { + return mSpeedDescription; + } + public void setSpeedDescription(String value) { + mSpeedDescription = value; + } + + public String getSpeedText() { + if (getCustomSpeed()) { + return getSpeedDescription(); + } else { + ArrayList speedParts = new ArrayList<>(); + speedParts.add(String.format("%s ft.", getSpeed())); + String burrowSpeed = getBurrowSpeed(); + if (!StringHelper.isNullOrEmpty(burrowSpeed) && !"0".equals(burrowSpeed)) { + speedParts.add(String.format("burrow %s ft.", burrowSpeed)); + } + + String climbSpeed = getClimbSpeed(); + if (!StringHelper.isNullOrEmpty(climbSpeed) && !"0".equals(climbSpeed)) { + speedParts.add(String.format("climb %s ft.", climbSpeed)); + } + + String flySpeed = getFlySpeed(); + if (!StringHelper.isNullOrEmpty(flySpeed) && !"0".equals(flySpeed)) { + speedParts.add(String.format("fly %s ft.%s", flySpeed, getHover() ? " (hover)" : "")); + } + + String swimSpeed = getSwimSpeed(); + if (!StringHelper.isNullOrEmpty(swimSpeed) && !"0".equals(swimSpeed)) { + speedParts.add(String.format("swim %s ft.", swimSpeed)); + } + + return StringHelper.join(", ", speedParts); + } + } + + public String getStrengthDescription() { + return String.format(Locale.US, "%d (%+d)", getStrengthScore(), getStrengthModifier()); + } + + public String getDexterityDescription() { + return String.format(Locale.US, "%d (%+d)", getDexterityScore(), getDexterityModifier()); + } + + public String getConstitutionDescription() { + return String.format(Locale.US, "%d (%+d)", getConstitutionScore(), getConstitutionModifier()); + } + + public String getIntelligenceDescription() { + return String.format(Locale.US, "%d (%+d)", getIntelligenceScore(), getIntelligenceModifier()); + } + + public String getWisdomDescription() { + return String.format(Locale.US, "%d (%+d)", getWisdomScore(), getWisdomModifier()); + } + + public String getCharismaDescription() { + return String.format(Locale.US, "%d (%+d)", getCharismaScore(), getCharismaModifier()); + } + + private HashSet mSavingThrows; + public Set getSavingThrows() { + return mSavingThrows; + } + public void addSavingThrow(SavingThrow savingThrow) { + mSavingThrows.add(savingThrow); + } + public void removeSavingThrow(SavingThrow savingThrow) { + mSavingThrows.remove(savingThrow); + } + public void clearSavingThrows() { + mSavingThrows.clear(); + } + + public String getSavingThrowsDescription() { + SavingThrow[] elements = new SavingThrow[mSavingThrows.size()]; + elements = mSavingThrows.toArray(elements); + Arrays.sort(elements); + StringBuilder sb = new StringBuilder(); + boolean isFirst = true; + for (SavingThrow st : elements) { + if (!isFirst) { + sb.append(", "); + } + String name = st.getName(); + + sb.append(String.format(Locale.US, "%s%s %+d", name.substring(0,1).toUpperCase(Locale.US), name.substring(1), getAbilityModifier(name) + getProficiencyBonus())); + isFirst = false; + } + return sb.toString(); + } + + public int getProficiencyBonus() { + String challengeRating = getChallengeRating(); + if ("*".equals(challengeRating)) { + return getCustomProficiencyBonus(); + } else if ( + "0".equals(challengeRating) || + "1/8".equals(challengeRating) || + "1/4".equals(challengeRating) || + "1/2".equals(challengeRating) || + "1".equals(challengeRating) || + "2".equals(challengeRating) || + "3".equals(challengeRating) || + "4".equals(challengeRating) + ) { + return 2; + } else if ( + "5".equals(challengeRating) || + "6".equals(challengeRating) || + "7".equals(challengeRating) || + "8".equals(challengeRating) + ) { + return 3; + } else if ( + "9".equals(challengeRating) || + "10".equals(challengeRating) || + "11".equals(challengeRating) || + "12".equals(challengeRating) + ) { + return 4; + } else if ( + "13".equals(challengeRating) || + "14".equals(challengeRating) || + "15".equals(challengeRating) || + "16".equals(challengeRating) + ) { + return 5; + } else if ( + "17".equals(challengeRating) || + "18".equals(challengeRating) || + "19".equals(challengeRating) || + "20".equals(challengeRating) + ) { + return 6; + } else if ( + "21".equals(challengeRating) || + "22".equals(challengeRating) || + "23".equals(challengeRating) || + "24".equals(challengeRating) + ) { + return 7; + } else if ( + "25".equals(challengeRating) || + "26".equals(challengeRating) || + "27".equals(challengeRating) || + "28".equals(challengeRating) + ) { + return 8; + } else if ( + "29".equals(challengeRating) || + "30".equals(challengeRating) + ) { + return 9; + } else { + return 0; + } + } + + private String mChallengeRating; + public String getChallengeRating() { + return mChallengeRating; + } + public void setChallengeRating(String challengeRating) { + mChallengeRating = challengeRating; + // TODO: update proficiency bonus based on CR + } + + private String mCustomChallengeRating; + public String getCustomChallengeRating() { + return mCustomChallengeRating; + } + public void setCustomChallengeRating(String challengeRating) { + mCustomChallengeRating = challengeRating; + } + + private int mCustomProficiencyBonus; + public int getCustomProficiencyBonus() { + return mCustomProficiencyBonus; + } + public void setCustomProficiencyBonus(int proficiencyBonus) { + mCustomProficiencyBonus = proficiencyBonus; + } + + private HashSet mSkills; + public Set getSkills() { + return mSkills; + } + public void addSkill(Skill skill) { + mSkills.add(skill); + } + public void removeSkill(Skill skill) { + mSkills.remove(skill); + } + public void clearSkill(Skill skill) { + mSkills.clear(); + } + + public String getSkillsDescription() { + Skill[] elements = new Skill[mSkills.size()]; + elements = mSkills.toArray(elements); + Arrays.sort(elements); + StringBuilder sb = new StringBuilder(); + boolean isFirst = true; + for (Skill skill : elements) { + if (!isFirst) { + sb.append(", "); + } + String name = skill.getName(); + sb.append(skill.getText(this)); + isFirst = false; + } + return sb.toString(); + } + } diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/models/Skill.java b/Android/app/src/main/java/com/majinnaibu/monstercards/models/Skill.java index 9730cc5..345d20c 100644 --- a/Android/app/src/main/java/com/majinnaibu/monstercards/models/Skill.java +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/models/Skill.java @@ -1,95 +1,68 @@ package com.majinnaibu.monstercards.models; -import android.annotation.SuppressLint; - -import androidx.annotation.Nullable; - -import com.majinnaibu.monstercards.data.enums.AbilityScore; -import com.majinnaibu.monstercards.data.enums.AdvantageType; -import com.majinnaibu.monstercards.data.enums.ProficiencyType; - import java.util.Comparator; -import java.util.Objects; +import java.util.Locale; -@SuppressLint("DefaultLocale") public class Skill implements Comparator, Comparable { - public String name; - public AbilityScore abilityScore; - public AdvantageType advantageType; - public ProficiencyType proficiencyType; + private String mName; + private String mAbilityScoreName; + private String mNote; - public Skill(String name, AbilityScore abilityScore) { - this(name, abilityScore, AdvantageType.NONE, ProficiencyType.PROFICIENT); + public Skill(String name, String abilityScoreName) { + mName = name; + mAbilityScoreName = abilityScoreName; + mNote = ""; } - public Skill(String name, AbilityScore abilityScore, AdvantageType advantageType) { - this(name, abilityScore, advantageType, ProficiencyType.PROFICIENT); + public Skill(String name, String abilityScoreName, String note) { + mName = name; + mAbilityScoreName = abilityScoreName; + mNote = note; } - public Skill(String name, AbilityScore abilityScore, AdvantageType advantageType, ProficiencyType proficiencyType) { - this.name = name; - this.abilityScore = abilityScore; - this.advantageType = advantageType; - this.proficiencyType = proficiencyType; + public String getName() { + return mName; + } + public void setName(String name) { + mName = name; + } + + public String getAbilityScoreName() { + return mAbilityScoreName; + } + public void setAbilityScoreName(String abilityScoreName) { + mAbilityScoreName = abilityScoreName; + } + + public String getNote() { + return mNote; } public int getSkillBonus(Monster monster) { - int modifier = monster.getAbilityModifier(abilityScore); - switch (proficiencyType) { - case PROFICIENT: - return modifier + monster.getProficiencyBonus(); - case EXPERTISE: - return modifier + monster.getProficiencyBonus() * 2; - case NONE: - default: - return modifier; + int bonus = monster.getAbilityModifier(mAbilityScoreName); + if (" (ex)".equals(getNote())) { + bonus += 2 * monster.getProficiencyBonus(); + } else { + bonus += monster.getProficiencyBonus(); } + return bonus; } public String getText(Monster monster) { int bonus = getSkillBonus(monster); - return String.format( - "%s%s %+d%s", - name.charAt(0), - name.substring(1), - bonus, - advantageType == AdvantageType.ADVANTAGE ? " A" : advantageType == AdvantageType.DISADVANTAGE ? " D" : "" - ); + return String.format(Locale.US, "%s%s %d", mName.substring(0,1), mName.substring(1), bonus); } @Override public int compareTo(Skill o) { - return this.name.compareToIgnoreCase(o.name); + return this.getName().compareToIgnoreCase(o.getName()); } @Override public int compare(Skill o1, Skill o2) { - return o1.name.compareToIgnoreCase(o2.name); + return o1.getName().compareToIgnoreCase(o2.getName()); } - @Override - public boolean equals(@Nullable Object obj) { - if (obj == null) { - return false; - } - if (!(obj instanceof Skill)) { - return false; - } - Skill otherSkill = (Skill) obj; - if (!Objects.equals(this.name, otherSkill.name)) { - return false; - } - if (this.abilityScore != otherSkill.abilityScore) { - return false; - } - if (this.advantageType != otherSkill.advantageType) { - return false; - } - if (this.proficiencyType != otherSkill.proficiencyType) { - return false; - } - return true; - } } diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/monster/MonsterFragment.java b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/monster/MonsterFragment.java index 217c614..e25773a 100644 --- a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/monster/MonsterFragment.java +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/monster/MonsterFragment.java @@ -14,7 +14,10 @@ import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; import com.majinnaibu.monstercards.R; +import com.majinnaibu.monstercards.helpers.StringHelper; import com.majinnaibu.monstercards.models.Monster; +import com.majinnaibu.monstercards.models.SavingThrow; +import com.majinnaibu.monstercards.models.Skill; @SuppressWarnings("FieldCanBeLocal") public class MonsterFragment extends Fragment { @@ -38,6 +41,40 @@ public class MonsterFragment extends Fragment { monster.setShieldBonus(0); monster.setNaturalArmorBonus(7); monster.setOtherArmorDescription("14"); + // Hit Points + monster.setHitDice(1); + monster.setCustomHP(false); + monster.setHPText("11 (2d8 + 2)"); + monster.setSpeed("10"); + monster.setBurrowSpeed("0"); + monster.setClimbSpeed("0"); + monster.setFlySpeed("30"); + monster.setHover(false); + monster.setSwimSpeed("0"); + monster.setCustomSpeed(false); + monster.setSpeedDescription("30 ft., swim 30 ft."); + // Ability Scores + monster.setStrengthScore(Integer.parseInt("2")); + monster.setDexterityScore(Integer.parseInt("20")); + monster.setConstitutionScore(Integer.parseInt("8")); + monster.setIntelligenceScore(Integer.parseInt("10")); + monster.setWisdomScore(Integer.parseInt("14")); + monster.setCharismaScore(Integer.parseInt("15")); + // Saving Throws + monster.addSavingThrow(new SavingThrow("str", 0)); + monster.addSavingThrow(new SavingThrow("dex", 1)); + monster.addSavingThrow(new SavingThrow("con", 2)); + monster.addSavingThrow(new SavingThrow("int", 3)); + monster.addSavingThrow(new SavingThrow("wis", 4)); + monster.addSavingThrow(new SavingThrow("cha", 5)); + //Skills + monster.addSkill(new Skill("perception", "wis")); + monster.addSkill(new Skill("stealth", "dexterity")); + + // Challenge Rating + monster.setChallengeRating("*"); + monster.setCustomChallengeRating("Infinite (0XP)"); + monster.setCustomProficiencyBonus(4); // END remove block monsterViewModel = new ViewModelProvider(this).get(MonsterViewModel.class); View root = inflater.inflate(R.layout.fragment_monster, container, false); @@ -67,6 +104,96 @@ public class MonsterFragment extends Fragment { } }); + final TextView monsterHitPoints = root.findViewById(R.id.hit_points); + monsterViewModel.getHitPoints().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(String hitPoints) { + monsterHitPoints.setText(Html.fromHtml("Hit Points " + hitPoints)); + } + }); + + final TextView monsterSpeed = root.findViewById(R.id.speed); + monsterViewModel.getSpeed().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(String speed) { + monsterSpeed.setText(Html.fromHtml("Speed " + speed)); + } + }); + + final TextView monsterStrength = root.findViewById(R.id.strength); + monsterViewModel.getStrength().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(String strength) { + monsterStrength.setText(strength); + } + }); + + final TextView monsterDexterity = root.findViewById(R.id.dexterity); + monsterViewModel.getDexterity().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(String dexterity) { + monsterDexterity.setText(dexterity); + } + }); + + final TextView monsterConstitution = root.findViewById(R.id.constitution); + monsterViewModel.getConstitution().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(String constitution) { + monsterConstitution.setText(constitution); + } + }); + + final TextView monsterIntelligence = root.findViewById(R.id.intelligence); + monsterViewModel.getIntelligence().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(String intelligence) { + monsterIntelligence.setText(intelligence); + } + }); + + final TextView monsterWisdom = root.findViewById(R.id.wisdom); + monsterViewModel.getWisdom().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(String wisdom) { + monsterWisdom.setText(wisdom); + } + }); + + final TextView monsterCharisma = root.findViewById(R.id.charisma); + monsterViewModel.getCharisma().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(String charisma) { + monsterCharisma.setText(charisma); + } + }); + + final TextView monsterSavingThrows = root.findViewById(R.id.saving_throws); + monsterViewModel.getSavingThrows().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(String savingThrows) { + if (StringHelper.isNullOrEmpty(savingThrows)) { + monsterSavingThrows.setVisibility(View.GONE); + } else { + monsterSavingThrows.setVisibility(View.VISIBLE); + } + monsterSavingThrows.setText(Html.fromHtml("Saving Throws " + savingThrows)); + } + }); + + final TextView monsterSkills = root.findViewById(R.id.skills); + monsterViewModel.getSkills().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(String skills) { + if (StringHelper.isNullOrEmpty(skills)) { + monsterSkills.setVisibility(View.GONE); + } else { + monsterSkills.setVisibility(View.VISIBLE); + } + monsterSkills.setText(Html.fromHtml("Skills " + skills)); + } + }); + return root; } } diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/monster/MonsterViewModel.java b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/monster/MonsterViewModel.java index 26f03d3..31bd2a5 100644 --- a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/monster/MonsterViewModel.java +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/monster/MonsterViewModel.java @@ -16,7 +16,26 @@ public class MonsterViewModel extends ViewModel { mMeta.setValue(""); mArmorClass = new MutableLiveData<>(); mArmorClass.setValue(""); - + mHitPoints = new MutableLiveData<>(); + mHitPoints.setValue(""); + mSpeed = new MutableLiveData<>(); + mSpeed.setValue(""); + mStrength = new MutableLiveData<>(); + mStrength.setValue(""); + mDexterity = new MutableLiveData<>(); + mDexterity.setValue(""); + mConstitution = new MutableLiveData<>(); + mConstitution.setValue(""); + mIntelligence = new MutableLiveData<>(); + mIntelligence.setValue(""); + mWisdom = new MutableLiveData<>(); + mWisdom.setValue(""); + mCharisma = new MutableLiveData<>(); + mCharisma.setValue(""); + mSavingThrows = new MutableLiveData<>(); + mSavingThrows.setValue(""); + mSkills = new MutableLiveData<>(); + mSkills.setValue(""); } private MutableLiveData mName; @@ -31,6 +50,46 @@ public class MonsterViewModel extends ViewModel { public LiveData getArmorClass() { return mArmorClass; } + private MutableLiveData mHitPoints; + public LiveData getHitPoints() { + return mHitPoints; + } + private MutableLiveData mSpeed; + public LiveData getSpeed() { + return mSpeed; + } + private MutableLiveData mStrength; + public LiveData getStrength() { + return mStrength; + } + private MutableLiveData mDexterity; + public LiveData getDexterity() { + return mDexterity; + } + private MutableLiveData mConstitution; + public LiveData getConstitution() { + return mConstitution; + } + private MutableLiveData mIntelligence; + public LiveData getIntelligence() { + return mIntelligence; + } + private MutableLiveData mWisdom; + public LiveData getWisdom() { + return mWisdom; + } + private MutableLiveData mCharisma; + public LiveData getCharisma() { + return mCharisma; + } + private MutableLiveData mSavingThrows; + public LiveData getSavingThrows() { + return mSavingThrows; + } + private MutableLiveData mSkills; + public LiveData getSkills() { + return mSkills; + } private Monster mMonster; public void setMonster(Monster monster) { @@ -38,5 +97,15 @@ public class MonsterViewModel extends ViewModel { mName.setValue(mMonster.getName()); mMeta.setValue(mMonster.getMeta()); mArmorClass.setValue(mMonster.getArmorClass()); + mHitPoints.setValue(mMonster.getHitPoints()); + mSpeed.setValue(mMonster.getSpeedText()); + mStrength.setValue(monster.getStrengthDescription()); + mDexterity.setValue(monster.getDexterityDescription()); + mConstitution.setValue(monster.getConstitutionDescription()); + mIntelligence.setValue(monster.getIntelligenceDescription()); + mWisdom.setValue(monster.getWisdomDescription()); + mCharisma.setValue(monster.getCharismaDescription()); + mSavingThrows.setValue(monster.getSavingThrowsDescription()); + mSkills.setValue(monster.getSkillsDescription()); } } \ No newline at end of file diff --git a/Android/app/src/main/res/layout/fragment_monster.xml b/Android/app/src/main/res/layout/fragment_monster.xml index 5f140dd..7ddd2b7 100644 --- a/Android/app/src/main/res/layout/fragment_monster.xml +++ b/Android/app/src/main/res/layout/fragment_monster.xml @@ -69,5 +69,267 @@ app:layout_constraintTop_toBottomOf="@+id/divider1" tools:text="Armor Class 15" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file