package main import ( "encoding/json" "errors" "flag" "fmt" "io/ioutil" "os" "strings" "github.com/fatih/color" "gitlab.com/EternalWanderer/dice-roller/Dice" ) const exampleFile string = "/etc/sheet-parser/example.json" var ( path, pathFlag, skillString, saveString, statString string modifier, diceThrows, surfaces int X, Y int char Character skillMap = make(map[string]int) statMap = make(map[string]int) advantage, disadvantage, stat_list, skill_list, verbose, trivia, feats bool ) func parseFlags() { flag.StringVar(&pathFlag, "file", pathFlag, "Used to point to character sheet") flag.StringVar(&pathFlag, "f", pathFlag, "Used to point to character sheet") flag.StringVar(&skillString, "skill", "", "Skill to parse") flag.StringVar(&statString, "stat", "", "Stat check") flag.StringVar(&saveString, "save", "", "Saving throw to... throw") flag.BoolVar(&advantage, "advantage", advantage, "Roll with advantage") flag.BoolVar(&disadvantage, "disadvantage", disadvantage, "Roll with disadvantage") flag.BoolVar(&stat_list, "stat-list", false, "Print list of stats, can also be used with -save flag") flag.BoolVar(&skill_list, "skill-list", false, "Print list of skills to be used in combination with the -skill flag") flag.BoolVar(&verbose, "verbose", false, "Print stat numbers, to be used in combination with -stat-list or -skill-list") flag.BoolVar(&verbose, "v", false, "Print stat numbers, to be used in combination with -stat-list or -skill-list") flag.BoolVar(&trivia, "t", false, "Print character name, level and proficiency") flag.BoolVar(&trivia, "trivia", false, "Print character name, level and proficiency") flag.BoolVar(&feats, "feats", false, "Print character feats") flag.Parse() } func handleError(err error) { if err != nil { fmt.Println(err.Error()) os.Exit(1) } } func readJson() { envPath, isSet := os.LookupEnv("CHARSHEET") switch { case len(pathFlag) > 0: path = pathFlag case isSet: path = envPath default: path = exampleFile } fmt.Println("Opening file:", path) var file, err = os.Open(path) handleError(err) defer file.Close() byteValue, _ := ioutil.ReadAll(file) err = json.Unmarshal(byteValue, &char) handleError(err) } func initMaps() { for i := 0; i < len(char.Skills); i++ { skillMap[char.Skills[i].SkillName] = i } for i := 0; i < len(char.Stats); i++ { statMap[char.Stats[i].StatName] = i } } func main() { parseFlags() readJson() initMaps() switch { case trivia: fmt.Printf("Name: %s\tLevel: %d\tProficiency: %d\n", char.Misc.Name, char.Misc.Level, GetProficiency()) fmt.Printf("Race: %s\tClass: %s\tBackground: %s\n", char.Misc.Race, char.Misc.Class, char.Misc.Background) fmt.Printf("\n") fmt.Printf("Personality trait: %s\n", char.Misc.PersonalityTrait) fmt.Printf("Ideals: %s\n", char.Misc.Ideals) fmt.Printf("Bonds: %s\n", char.Misc.Bonds) fmt.Printf("Flaws: %s\n", char.Misc.Flaws) fmt.Printf("Quirk: %s\n", char.Misc.Quirk) fmt.Printf("\n") fmt.Printf("Passive perception: %d\n", passiveSkillCheck(GetSkill("perception"))) fmt.Printf("Passive investigation: %d\n", passiveSkillCheck(GetSkill("investigation"))) case feats: listFeats() case stat_list && skill_list: printStatList(verbose) printSkillList(verbose) case stat_list: printStatList(verbose) case skill_list: printSkillList(verbose) case advantage && disadvantage: fmt.Println("You can't roll with both advantage and disadvantage") os.Exit(1) case len(saveString) > 0: result, plainResult, err := savingThrow(GetStat(saveString)) badCheck(saveString, err) if advantage { color.Yellow("Rolling %s saving throw with advantage...", saveString) fmt.Printf("x: %d\ty: %d\n", X, Y) fmt.Printf("Modifier: %d\n", result-plainResult) color.Green("%d\n", result) } else if disadvantage { color.Yellow("Rolling %s saving throw with disadvantage...", saveString) fmt.Printf("x: %d\ty: %d\n", X, Y) fmt.Printf("Modifier: %d\n", result-plainResult) color.Red("%d\n", result) } else { color.Yellow("Rolling %s saving throw...", saveString) fmt.Printf("Without modifier: %d\tModifier: %d\n", plainResult, result-plainResult) color.Green("%d\n", result) } case len(skillString) > 0 && char.Misc.IsKurthog && strings.Contains(skillString, "performance"): result, plainResult, err := skillCheck(GetSkill(skillString)) badCheck(skillString, err) if advantage { color.Yellow("Rolling %s check with advantage...", skillString) fmt.Printf("x: %d\ty: %d\n", X, Y) fmt.Printf("Modifier: %d\n", result-plainResult) fmt.Printf("Kurthogifier: %d\n", -1) color.Green("%d\n", result*-1) } else if disadvantage { color.Yellow("Rolling %s check with disadvantage...", skillString) fmt.Printf("x: %d\ty: %d\n", X, Y) fmt.Printf("Modifier: %d\n", result-plainResult) fmt.Printf("Kurthogifier: %d\n", -1) color.Red("%d\n", result*-1) } else { color.Yellow("Rolling %s check...", skillString) fmt.Printf("Without modifier: %d\tModifier: %d\n", plainResult, result-plainResult) fmt.Printf("Kurthogifier: %d\n", -1) color.Green("%d\n", result*-1) } case len(skillString) > 0: result, plainResult, err := skillCheck(GetSkill(skillString)) badCheck(skillString, err) if advantage { color.Yellow("Rolling %s check with advantage...", skillString) fmt.Printf("x: %d\ty: %d\n", X, Y) fmt.Printf("Modifier: %d\n", result-plainResult) color.Green("%d\n", result) } else if disadvantage { color.Yellow("Rolling %s check with disadvantage...", skillString) fmt.Printf("x: %d\ty: %d\n", X, Y) fmt.Printf("Modifier: %d\n", result-plainResult) color.Red("%d\n", result) } else { color.Yellow("Rolling %s check...", skillString) fmt.Printf("Without modifier: %d\tModifier: %d\n", plainResult, result-plainResult) color.Green("%d\n", result) } case len(statString) > 0: result, plainResult, err := statCheck(GetStat(statString)) badCheck(statString, err) if advantage { color.Yellow("Rolling %s check with advantage...", statString) fmt.Printf("x: %d\ty: %d\n", X, Y) fmt.Printf("Modifier: %d\n", result-plainResult) color.Green("%d\n", result) } else if disadvantage { color.Yellow("Rolling %s check with disadvantage...", statString) fmt.Printf("x: %d\ty: %d\n", X, Y) fmt.Printf("Modifier: %d\n", result-plainResult) color.Red("%d\n", result) } else { color.Yellow("Rolling %s check...", statString) fmt.Printf("Without modifier: %d\tModifier: %d\n", plainResult, result-plainResult) color.Green("%d\n", result) } default: flag.Usage() } } func GetProficiency() int { // https://worldbuildersjunction.com/what-is-proficiency-bonus-in-dd-5e-how-it-works-calculated/ return (char.Misc.Level-1)/4 + 2 } func GetSkill(skillName string) Skill { return char.Skills[skillMap[skillName]] } func GetStat(statName string) Stat { return char.Stats[statMap[statName]] } func GetModifier(stat Stat) int { // https://worldbuildersjunction.com/dungeon-and-dragons-ability-scores-explained-for-beginners/ return (stat.Score - 10) / 2 } func rollDice() int { var die int switch { case advantage: die, X, Y = Dice.Advantage() case disadvantage: die, X, Y = Dice.Disadvantage() default: die = Dice.SimpleCast() } switch die { case 20: color.Magenta("Natural 20!\n") case 1: color.Magenta("Natural 1!\n") } return die } func badCheck(errorMessage string, err error) { if err == nil { return } fmt.Println(err) fmt.Println(errorMessage) os.Exit(1) } func statErrorCheck() error { for i := 0; i < len(char.Stats); i++ { if statString == char.Stats[i].StatName || saveString == char.Stats[i].StatName { return nil } } return errors.New("Unknown stat:") } func skillErrorCheck() error { for i := 0; i < len(char.Skills); i++ { if skillString == char.Skills[i].SkillName { return nil } } return errors.New("Unknown skill:") } func statCheck(stat Stat) (int, int, error) { die := rollDice() return die + GetModifier(stat), die, statErrorCheck() } func savingThrow(stat Stat) (int, int, error) { die := rollDice() plainDie := die if stat.SaveProficient { die += GetProficiency() } return die + GetModifier(stat), plainDie, statErrorCheck() } func passiveSkillCheck(skill Skill) int { die := 10 die += GetModifier(GetStat(skill.BaseStat)) switch { case skill.Expertise: die += GetProficiency() * 2 case skill.Proficient: die += GetProficiency() case !skill.Proficient && char.Misc.JackOfAllTrades: die += (GetProficiency() / 2) } switch { case advantage: die += 5 case disadvantage: die -= 5 } return die } func skillCheck(skill Skill) (int, int, error) { die := rollDice() if char.Misc.ReliableTalent && skill.Proficient && die < 10 { die = 10 } plainDie := die die += GetModifier(GetStat(skill.BaseStat)) switch { case skill.Expertise: die += GetProficiency() * 2 case skill.Proficient: die += GetProficiency() case !skill.Proficient && char.Misc.JackOfAllTrades: die += (GetProficiency() / 2) } return die, plainDie, skillErrorCheck() } func printStatList(verbose bool) { color.Magenta("Listing stats...") if verbose { var proficiency string for i := 0; i < len(char.Stats); i++ { name := char.Stats[i].StatName isProficient := char.Stats[i].Proficient if isProficient { proficiency = "Proficient" } else { proficiency = "Not proficient" } score := char.Stats[i].Score fmt.Printf("Stat: %13s\t%s\tStat score: %2d\tStat modifier: %2d\n", name, proficiency, score, GetModifier(char.Stats[i])) } } else { for i := 0; i < len(char.Stats); i++ { fmt.Println(char.Stats[i].StatName) } } } func printSkillList(verbose bool) { color.Magenta("Listing skills...") var proficiency string var expertise string if verbose { for i := 0; i < len(char.Skills); i++ { name := char.Skills[i].SkillName localModifier := GetModifier(GetStat(char.Skills[i].BaseStat)) if char.Skills[i].Proficient { proficiency = "Proficient" localModifier += GetProficiency() } else { proficiency = "Not proficient" } if char.Skills[i].Expertise { expertise = "Has expertise" localModifier += GetProficiency() } else { expertise = "Doesn't have expertise" } fmt.Printf("Skill:%16s\tSkill modifier:%3d\t%s\t%s\n", name, localModifier, proficiency, expertise) } } else { for i := 0; i < len(char.Skills); i++ { fmt.Println(char.Skills[i].SkillName) } } } func listFeats() { color.Magenta("Listing feats...") for i := 0; i < len(char.Feats); i++ { fmt.Printf("%25s\t%s\n", char.Feats[i].FeatName, char.Feats[i].FeatDescription) } }