private async Task <string> ParseValues(string Raw, Character c, JObject values = null) { if (values == null) { values = await SheetService.GetValues(c); if (values == null) { foreach (Match m in AttributeRegex.Matches(Raw)) { Raw = Raw.Replace(m.Value, ""); } return(Raw); } } foreach (Match m in AttributeRegex.Matches(Raw)) { if (Enum.TryParse(m.Groups[2].Value.ToLower(), out Score sc)) { Raw = Raw.Replace(m.Value, ((int)values[m.Groups[2].Value.ToLower()]["value"]).GetModifier().ToString()); } if (values.TryGetValue(m.Groups[2].Value, out JToken jToken)) { Raw = Raw.Replace(m.Value, (string)values[m.Groups[2].Value.ToLower()]["bonus"] ?? "0"); } else { Raw = Raw.Replace(m.Value, ""); } } return(Raw); }
public async Task ChangeHP(int value, [Remainder] string args = null) { Character c; args = args.ToLower(); if (args.Contains("-c")) { c = GetCompanion(); args = args.Replace("-c", "").Trim(); if (c == null) { await ReplyAsync("You have no active companion."); return; } } else { c = GetCharacter(); if (c == null) { await ReplyAsync("You have no active character."); return; } } int CurrentHp = 0; int MaxHp = 0; int TempHP = 0; int Damage = 0; JArray Json1 = await SheetService.Get(c, "damage+hptemp/"); Damage = (string)Json1["damage"] == "null" ? 0 : (int)Json1["damage"]; TempHP = (string)Json1["hptemp"] == "null" ? 0 : (int)Json1["hptemp"]; JObject values = await SheetService.GetValues(c); MaxHp = (int)values["hp"]["value"]; CurrentHp = MaxHp - Damage; }
public async Task Initiative([Remainder] string skill = null) { var b = GetBattle(Context.Channel.Id); if (!b.Active) { await ReplyAsync("There is no encounter happening on this channel. Start one with `!Encounter Start`"); return; } Character c = GetCharacter(); skill = skill?.ToLower() ?? ""; if (c == null) { await ReplyAsync("You have no active character!"); return; } string[] Bonuses = new string[0]; if (BonusRegex.IsMatch(skill)) { Bonuses = BonusRegex.Matches(skill).Select(x => x.Value).ToArray(); foreach (var bo in Bonuses) { skill = skill.Replace(bo, ""); } skill = skill.Trim(); } var embed = new EmbedBuilder() .WithTitle(c.Name + " Rolled initiative!") .WithThumbnailUrl(c.ImageUrl); var values = await SheetService.GetValues(c); if (skill.NullorEmpty()) { var bonus = ((int)values["perception"]["bonus"] - (int)values["perception"]["penalty"]); try { var results = Roller.Roll("1d20 + " + bonus + (Bonuses.Length > 0 ? string.Join(" ", Bonuses) : "")); embed.WithDescription("Perception: " + results.ParseResult() + " = `" + results.Value + "`"); if (b.Participants.Any(x => x.Name.ToLower() == c.Name.ToLower())) { var i = b.Participants.FindIndex(x => x.Name.ToLower() == c.Name.ToLower()); b.Participants[i] = new Participant( ) { Initiative = (int)results.Value, Name = c.Name, Player = c.Owner }; b.Participants = b.Participants.OrderBy(x => x.Initiative).Reverse().ToList(); UpdateBattle(b); } else { b.Participants.Add(new Participant() { Initiative = (int)results.Value, Name = c.Name, Player = c.Owner }); b.Participants = b.Participants.OrderBy(x => x.Initiative).Reverse().ToList(); UpdateBattle(b); } } catch { await ReplyAsync("Something went wrong when rolling the dice, make sure you only added a bonus/penalty using +X or -X where x is an integer (No fractions or decimals)."); return; } } else { var sheet = await SheetService.GetFullSheet(c); var snk = from sk in sheet["skills"].Children() where ((string)sk["name"]).ToLower().StartsWith(skill.ToLower()) || (sk["lore"] != null && ((string)sk["lore"]).ToLower().StartsWith(skill.ToLower())) orderby sk["name"] select sk; if (snk.Count() == 0) { await ReplyAsync("You have no skill whose name starts with that."); return; } var s = snk.FirstOrDefault(); string name = (string)s["lore"] ?? (string)s["name"]; var bonus = (int)values[name.ToLower()]["bonus"] - (int)values[name.ToLower()]["penalty"]; try { var results = Roller.Roll("1d20 + " + bonus + (Bonuses.Length > 0 ? string.Join(" ", Bonuses) : "")); embed.WithDescription(name.Uppercase() + ": " + results.ParseResult() + " = `" + results.Value + "`"); if (b.Participants.Any(x => x.Name.ToLower() == c.Name.ToLower())) { var i = b.Participants.FindIndex(x => x.Name.ToLower() == c.Name.ToLower()); b.Participants[i] = new Participant() { Initiative = (int)results.Value, Name = c.Name, Player = c.Owner }; b.Participants = b.Participants.OrderBy(x => x.Initiative).Reverse().ToList(); UpdateBattle(b); } else { b.Participants.Add(new Participant() { Initiative = (int)results.Value, Name = c.Name, Player = c.Owner }); b.Participants = b.Participants.OrderBy(x => x.Initiative).Reverse().ToList(); UpdateBattle(b); } } catch { await ReplyAsync("Something went wrong when rolling the dice, make sure you only added a bonus/penalty using +X or -X where x is an integer (No fractions or decimals)."); return; } } Random randonGen = new Random(); Color randomColor = new Color(randonGen.Next(255), randonGen.Next(255), randonGen.Next(255)); embed.WithColor(randomColor); if (c.Color != null) { embed.WithColor(new Color(c.Color[0], c.Color[1], c.Color[2])); } await ReplyAsync(" ", embed.Build()); }
public async Task SkillCheck([Remainder] string Skill) { Character c; Skill = Skill.ToLower(); bool familiar = false; if (Skill.Contains("-c")) { c = GetCompanion(); if (c == null) { await ReplyAsync("You have no active companion."); return; } Skill = Skill.Replace("-c", "").Trim(); } else { c = GetCharacter(); if (c == null) { await ReplyAsync("You have no active character."); return; } } if (Skill.Contains("-f")) { familiar = true; Skill = Skill.Replace("-f", "".Trim()); } string[] Bonuses = new string[0]; if (BonusRegex.IsMatch(Skill)) { Bonuses = BonusRegex.Matches(Skill).Select(x => x.Value).ToArray(); foreach (var b in Bonuses) { Skill = Skill.Replace(b, ""); } Skill = Skill.Trim(); } var sheet = await SheetService.GetFullSheet(c); var values = await SheetService.GetValues(c); ; if (values == null || sheet == null) { var embed = new EmbedBuilder() .WithTitle("Click here") .WithUrl("https://character.pf2.tools/?" + c.RemoteId) .WithDescription("Seems like we cannot fetch " + c.Name + "'s values. This is due to the fact values are only updated when you open the sheet in pf2.tools. To fix this, click the link above to generate those values."); await ReplyAsync("", embed.Build()); return; } if (Skill == "perception" || Skill == "per" || Skill == "perc") { var embed = new EmbedBuilder(); JToken bonus; string message = ""; if (familiar) { if (c.Familiar.NullorEmpty()) { await ReplyAsync("You have no named familiars."); return; } bonus = (int)values["famperception " + c.Familiar]["bonus"] - (int)values["famperception " + c.Familiar]["penalty"];; message = c.Name + "'s familiar makes a Perception check!"; embed.WithThumbnailUrl(c.FamImg); } else { bonus = values["perception"]["bonus"] ?? 0; message = c.Name + " makes a Perception check!"; embed.WithThumbnailUrl(c.ImageUrl); } var result = Roller.Roll("d20 + " + bonus + (Bonuses.Length > 0?string.Join(" ", Bonuses):"")); embed.WithTitle(message) .WithDescription(result.ParseResult() + "\nTotal = `" + result.Value + "`") .WithFooter((c.ValuesLastUpdated.Outdated() ? "⚠️ Couldn't retrieve updated values. Roll might not be accurate" : DateTime.Now.ToString()));; Random randonGen = new Random(); Color randomColor = new Color(randonGen.Next(255), randonGen.Next(255), randonGen.Next(255)); embed.WithColor(randomColor); if (c.Color != null) { embed.WithColor(new Color(c.Color[0], c.Color[1], c.Color[2])); } await ReplyAsync("", embed.Build()); return; } else { var embed = new EmbedBuilder(); JToken bonus; string message = ""; if (familiar) { if (c.Familiar.NullorEmpty()) { await ReplyAsync("You have no named familiars."); return; } switch (Skill.ToLower()) { case "acrobatics": bonus = (int)values["famacrobatics " + c.Familiar]["bonus"] - (int)values["famacrobatics " + c.Familiar]["penalty"]; message = c.Name + "'s familiar makes an Acrobatics check!"; break; case "stealth": bonus = (int)values["famstealth " + c.Familiar]["bonus"] - (int)values["famstealth " + c.Familiar]["penalty"]; message = c.Name + "'s familiar makes a Stealth check!"; break; default: bonus = (int)values["famother " + c.Familiar]["bonus"] - (int)values["famother " + c.Familiar]["penalty"]; message = c.Name + "'s familiar makes a skill check!"; break; } embed.WithThumbnailUrl(c.FamImg); } else { var skill = from sk in sheet["skills"].Children() where ((string)sk["name"]).ToLower().StartsWith(Skill.ToLower()) || (sk["lore"] != null && ((string)sk["lore"]).ToLower().StartsWith(Skill.ToLower())) orderby sk["name"] select sk; if (skill.Count() == 0) { await ReplyAsync("You have no skill whose name starts with that."); return; } var s = skill.FirstOrDefault(); string name = (string)s["lore"] ?? (string)s["name"]; message = c.Name + " makes " + (name.StartsWithVowel() ? "an " : "a ") + name.Uppercase() + " check!"; bonus = values[name.ToLower()]["bonus"] ?? 0; embed.WithThumbnailUrl(c.ImageUrl); } var result = Roller.Roll("d20 + " + bonus + (Bonuses.Length > 0 ? string.Join(" ", Bonuses) : "")); embed.WithTitle(message) .WithDescription(result.ParseResult() + "\nTotal = `" + result.Value + "`") .WithFooter((c.ValuesLastUpdated.Outdated()? "⚠️ Couldn't retrieve updated values. Roll might not be accurate" : DateTime.Now.ToString())); Random randonGen = new Random(); Color randomColor = new Color(randonGen.Next(255), randonGen.Next(255), randonGen.Next(255)); embed.WithColor(randomColor); if (c.Color != null) { embed.WithColor(new Color(c.Color[0], c.Color[1], c.Color[2])); } await ReplyAsync(" ", embed.Build()); } }
public async Task Attack([Remainder] string args = "") { Character c; args = args.ToLower(); if (args.Contains("-c")) { c = GetCompanion(); args = args.Replace("-c", ""); if (c == null) { await ReplyAsync("You have no active companion."); return; } } else { c = GetCharacter(); if (c == null) { await ReplyAsync("You have no active character."); return; } } if (args.NullorEmpty()) { var strikes = await SheetService.Get(c, "strikes"); var embed = new EmbedBuilder() .WithTitle(c.Name + "'s strikes") .WithThumbnailUrl(c.ImageUrl); if (strikes.Count() == 0) { await ReplyAsync(c.Name + " has no strikes."); return; } var sb = new StringBuilder(); foreach (var s in strikes) { string act = Icons.Actions["1"]; if (!((string)s["actions"]).NullorEmpty()) { if (Icons.Actions.TryGetValue((string)s["actions"], out string ic)) { act = ic; } else { act = "[" + s["actions"] + "]"; } } sb.AppendLine(Icons.Strike[(string)s["attack"]] + " " + (((string)s["name"]).NullorEmpty() ? "Unnamed Strike" : (string)s["name"]) + " " + act); } embed.WithDescription(sb.ToString()); if (c.Color != null) { embed.WithColor(new Color(c.Color[0], c.Color[1], c.Color[2])); } else { Random randonGen = new Random(); Color randomColor = new Color(randonGen.Next(255), randonGen.Next(255), randonGen.Next(255)); embed.WithColor(randomColor); } await ReplyAsync("", embed.Build()); return; } else { string[] Bonuses = new string[0]; if (BonusRegex.IsMatch(args)) { Bonuses = BonusRegex.Matches(args).Select(x => x.Value).ToArray(); foreach (var b in Bonuses) { args = args.Replace(b, ""); } args = args.Trim(); } var Jstrikes = await SheetService.Get(c, "strikes"); var values = await SheetService.GetValues(c); if (values == null || Jstrikes == null) { var err = new EmbedBuilder() .WithTitle("Click here") .WithUrl("https://character.pf2.tools/?" + c.RemoteId) .WithDescription("Seems like we cannot fetch " + c.Name + "'s values. This is due to the fact values are only updated when you open the sheet in pf2.tools. To fix this, click the link above to generate those values."); await ReplyAsync("", err.Build()); return; } var strikes = from sk in Jstrikes where ((string)sk["name"]).ToLower().StartsWith(args) orderby sk["name"] select sk; if (strikes.Count() == 0) { await ReplyAsync("You have no strikes whose name starts with '" + args + "'."); return; } var s = strikes.FirstOrDefault(); var embed = new EmbedBuilder() .WithTitle(c.Name + " strikes with a " + (((string)s["name"]).NullorEmpty() ? "Unnamed Strike" : (string)s["name"]) + "!") .WithThumbnailUrl(c.ImageUrl); if (c.Color != null) { embed.WithColor(new Color(c.Color[0], c.Color[1], c.Color[2])); } else { Random randonGen = new Random(); Color randomColor = new Color(randonGen.Next(255), randonGen.Next(255), randonGen.Next(255)); embed.WithColor(randomColor); } await ReplyAsync("Rolling..."); string hit = ""; string dmg = ""; string penalties = ""; string damagebonus = ""; string dmgtype = "Untyped"; if ((string)s["attack"] == "spell") { dmgtype = "Spell"; var classes = await SheetService.Get(c, "classes"); JToken cl = null; if (((string)s["class"]).NullorEmpty()) { cl = classes.FirstOrDefault(); } else { cl = classes.First(x => (string)x["id"] == (string)s["class"]); } if (classes == null || classes.Count == 0) { await ReplyAsync("You don't seem to have a class. Without one you can't make spell attacks."); return; } if (!values.ContainsKey(((string)cl["name"]).ToLower())) { await ReplyAsync("Sorry! It seems we cannot retrieve the data for this strike! This issue is outside of the bot's control and will likely be solved later."); return; } hit = (string)values[((string)cl["name"]).ToLower()]["bonus"]; int dc = int.Parse(hit ?? "0") + 10; dmg = (string)s["overridedamage"]; if (!dmg.NullorEmpty() && AttributeRegex.IsMatch(dmg)) { dmg = await ParseValues(dmg, c, values); } string summary = ""; var result = Roller.Roll("d20 + " + hit + (Bonuses.Length > 0?string.Join(" ", Bonuses):"")); summary += "Spell Attack roll: " + result.ParseResult() + " = `" + result.Value + "`" + "\nDC: `" + dc + "`"; if (!((string)s["spell"]).NullorEmpty()) { var spells = await SheetService.Get(c, "spells"); var sp = spells.First(x => (string)x["id"] == (string)s["spell"]); if (!((string)sp["body"]).NullorEmpty()) { embed.AddField("Spell description", sp["body"]); } if (!((string)sp["savingthrow"]).NullorEmpty()) { summary += "\nSaving Throw: " + sp["savingthrow"]; } embed.WithTitle(c.Name + " casts " + (sp["name"] ?? "Unnamed Spell") + "!"); } if (!dmg.NullorEmpty()) { try { RollResult result2 = Roller.Roll(dmg + damagebonus); summary += "\n" + dmgtype.Uppercase() + " damage: " + result2.ParseResult() + " = `" + result2.Value + "` "; if (!((string)s["extradamage"]).NullorEmpty()) { RollResult result3 = Roller.Roll((string)s["extradamage"]); summary += "\n" + ((string)s["overridedamage"]).Uppercase() + " damage: " + result3.ParseResult() + " = `" + result3.Value + "`"; } } catch { await ReplyAsync("It seems like this strike doesn't have a valid dice roll on its damage or additional damage fields. If this is a spell make sure you have a valid dice expression on the damage fields."); return; } } embed.WithDescription(summary) .WithFooter((c.ValuesLastUpdated.Outdated() ? "⚠️ Couldn't retrieve updated values. Roll might not be accurate" : DateTime.Now.ToString())); await ReplyAsync(" ", embed.Build()); } else { if (!((string)s["weapon"]).NullorEmpty()) { var items = await SheetService.Get(c, "items"); var i = items.First(x => (string)x["id"] == (string)s["weapon"]); dmgtype = (string)i["damagetype"] ?? "Untyped"; } else { dmgtype = "Bludgeoning"; } if ((string)s["attack"] == "melee" || ((string)s["attack"]).NullorEmpty()) { hit = (string)values["melee " + (string)s["name"]]["bonus"]; penalties = (string)values["melee " + (string)s["name"]]["penalty"]; } else { hit = (string)values["ranged " + (string)s["name"]]["bonus"]; penalties = (string)values["ranged " + (string)s["name"]]["penalty"]; } damagebonus = (string)values["damage " + (string)s["name"]]["value"]; dmg = (string)values["damagedice " + (string)s["name"]]["value"] + GetDie((int)values["damagedie " + (string)s["name"]]["value"]); string summary = ""; var result = Roller.Roll("d20 + " + hit + (penalties != "0"? "-" + penalties:"") + (Bonuses.Length > 0 ? string.Join(" ", Bonuses) : "")); summary += "Attack roll: " + result.ParseResult() + " = `" + result.Value + "`"; if (!dmg.NullorEmpty()) { try { RollResult result2 = Roller.Roll(dmg + "+" + damagebonus); summary += "\n" + dmgtype.Uppercase() + " damage: " + result2.ParseResult() + " = `" + result2.Value + "` "; if (!((string)s["extradamage"]).NullorEmpty()) { RollResult result3 = Roller.Roll((string)s["extradamage"]); summary += "\n" + ((string)s["overridedamage"]).Uppercase() + " damage: " + result3.ParseResult() + " = `" + result3.Value + "`"; } } catch { await ReplyAsync("It seems like this strike doesn't have a valid dice roll on its damage or additional damage fields. If this is a spell make sure you have a valid dice expression on the damage fields."); return; } } embed.WithDescription(summary) .WithFooter((c.ValuesLastUpdated.Outdated() ? "⚠️ Couldn't retrieve updated values. Roll might not be accurate" : DateTime.Now.ToString())); await ReplyAsync(" ", embed.Build()); } } }
public async Task ability(Saves saves, params string[] args) { Character c; if (args.Length >= 1 && args.Contains("-c")) { c = GetCompanion(); if (c == null) { await ReplyAsync("You have no active companion."); return; } } else { c = GetCharacter(); if (c == null) { await ReplyAsync("You have no active character."); return; } } for (int i = 0; i < args.Length; i++) { if (!int.TryParse(args[i], out int a) && args[i].ToLower() != "-c" && args[i].ToLower() != "-f") { args[i] = " "; } } string arguments = string.Join(" ", args).Replace("-c", ""); var sheet = await SheetService.GetFullSheet(c); var values = await SheetService.GetValues(c); if (values == null || sheet == null) { var err = new EmbedBuilder() .WithTitle("Click here") .WithUrl("https://character.pf2.tools/?" + c.RemoteId) .WithDescription("Seems like we cannot fetch " + c.Name + "'s values. This is due to the fact values are only updated when you open the sheet in pf2.tools. To fix this, click the link above to generate those values."); await ReplyAsync("", err.Build()); return; } JToken bonus = null; string message = ""; switch ((int)saves) { case 1: bonus = ((int)values["strength"]["values"]).GetModifier(); message = c.Name + " makes a strength check!"; break; case 2: bonus = ((int)values["dexterity"]["values"]).GetModifier(); message = c.Name + " makes a dexterity check!"; break; case 3: bonus = ((int)values["constitution"]["values"]).GetModifier(); message = c.Name + " makes a constitution check!"; break; case 4: bonus = ((int)values["intelligence"]["values"]).GetModifier(); message = c.Name + " makes a intelligence check!"; break; case 5: bonus = ((int)values["wisdom"]["values"]).GetModifier(); message = c.Name + " makes a wisdom check!"; break; case 6: bonus = ((int)values["charisma"]["values"]).GetModifier(); message = c.Name + " makes a charisma check!"; break; } var result = Roller.Roll("d20 + " + bonus + arguments); var embed = new EmbedBuilder() .WithTitle(message) .WithThumbnailUrl(c.ImageUrl) .WithDescription(result.ParseResult() + "\nTotal = `" + result.Value + "`") .WithFooter((c.ValuesLastUpdated.Outdated() ? "⚠️ Couldn't retrieve updated values. Roll might not be accurate" : DateTime.Now.ToString())); Random randonGen = new Random(); Color randomColor = new Color(randonGen.Next(255), randonGen.Next(255), randonGen.Next(255)); embed.WithColor(randomColor); if (c.Color != null) { embed.WithColor(new Color(c.Color[0], c.Color[1], c.Color[2])); } await ReplyAsync(" ", embed.Build()); }
public async Task Save(Saves Throw, params string[] args) { Character c; if (args.Length >= 1 && args.Contains("-c")) { c = GetCompanion(); if (c == null) { await ReplyAsync("You have no active companion."); return; } } else { c = GetCharacter(); if (c == null) { await ReplyAsync("You have no active character."); return; } } for (int i = 0; i < args.Length; i++) { if (!int.TryParse(args[i], out int a) && args[i].ToLower() != "-c" && args[i].ToLower() != "-f") { args[i] = " "; } } string arguments = string.Join(" ", args).Replace("-c", ""); var sheet = await SheetService.GetFullSheet(c); var values = await SheetService.GetValues(c); if (values == null || sheet == null) { var err = new EmbedBuilder() .WithTitle("Click here") .WithUrl("https://character.pf2.tools/?" + c.RemoteId) .WithDescription("Seems like we cannot fetch " + c.Name + "'s values. This is due to the fact values are only updated when you open the sheet in pf2.tools. To fix this, click the link above to generate those values."); await ReplyAsync("", err.Build()); return; } var embed = new EmbedBuilder(); int bonus = 0; string message = ""; if (args.Contains("-f")) { if (c.Familiar.NullorEmpty()) { await ReplyAsync("You have no named familiars."); return; } switch ((int)Throw) { case 1: bonus = (int)values["famfort " + c.Familiar]["bonus"] + (int)values["famfort " + c.Familiar]["penalty"]; message = c.Name + "'s familiar makes a fortitude check!"; break; case 2: bonus = (int)values["famref " + c.Familiar]["bonus"] + (int)values["famref " + c.Familiar]["penalty"]; message = c.Name + "'s familiar makes a reflex check!"; break; case 3: bonus = (int)values["famwill " + c.Familiar]["bonus"] + (int)values["famwill " + c.Familiar]["penalty"]; message = c.Name + "'s familiar makes a will check!"; break; } arguments = arguments.Replace("-f", ""); embed.WithThumbnailUrl(c.FamImg); } else { switch ((int)Throw) { case 1: bonus = (int)values["fortitude"]["bonus"] + (int)values["fortitude"]["penalty"]; message = c.Name + " makes a fortitude check!"; break; case 2: bonus = (int)values["reflex"]["bonus"] + (int)values["reflex"]["penalty"]; message = c.Name + " makes a reflex check!"; break; case 3: bonus = (int)values["will"]["bonus"] + (int)values["will"]["penalty"]; message = c.Name + " makes a will check!"; break; } embed.WithThumbnailUrl(c.ImageUrl); } var result = Roller.Roll("d20 + " + bonus + arguments); embed.WithTitle(message) .WithDescription(result.ParseResult() + "\nTotal = `" + result.Value + "`") .WithFooter((c.ValuesLastUpdated.Outdated() ? "⚠️ Couldn't retrieve updated values. Roll might not be accurate" : DateTime.Now.ToString())); Random randonGen = new Random(); Color randomColor = new Color(randonGen.Next(255), randonGen.Next(255), randonGen.Next(255)); embed.WithColor(randomColor); if (c.Color != null) { embed.WithColor(new Color(c.Color[0], c.Color[1], c.Color[2])); } await ReplyAsync(" ", embed.Build()); }
public async Task Attack([Remainder] string args = "") { Character c; args = args.ToLower(); if (args.Contains("-c")) { c = GetCompanion(); args = args.Replace("-c", "").Trim(); if (c == null) { await ReplyAsync("You have no active companion."); return; } } else { c = GetCharacter(); if (c == null) { await ReplyAsync("You have no active character."); return; } } if (args.NullorEmpty()) { var strikes = await SheetService.Get(c, "strikes"); var embed = new EmbedBuilder() .WithTitle(c.Name + "'s strikes") .WithThumbnailUrl(c.ImageUrl); if (strikes.Count() == 0) { await ReplyAsync(c.Name + " has no strikes."); return; } var sb = new StringBuilder(); foreach (var s in strikes) { string act = Icons.Actions["1"]; if (!((string)s["actions"]).NullorEmpty()) { if (Icons.Actions.TryGetValue((string)s["actions"], out string ic)) { act = ic; } else { act = "[" + s["actions"] + "]"; } } sb.AppendLine(Icons.Strike[(string)s["attack"]] + " " + (((string)s["name"]).NullorEmpty() ? "Unnamed Strike" : (string)s["name"]) + " " + act); } embed.WithDescription(sb.ToString()); if (c.Color != null) { embed.WithColor(new Color(c.Color[0], c.Color[1], c.Color[2])); } else { Random randonGen = new Random(); Color randomColor = new Color(randonGen.Next(255), randonGen.Next(255), randonGen.Next(255)); embed.WithColor(randomColor); } await ReplyAsync("", embed.Build()); return; } else { string[] Bonuses = new string[0]; if (BonusRegex.IsMatch(args)) { Bonuses = BonusRegex.Matches(args).Select(x => x.Value).ToArray(); foreach (var b in Bonuses) { args = args.Replace(b, ""); } args = args.Trim(); } List <string> Toggles = new List <string>(); if (ToggleableRegex.IsMatch(args)) { MatchCollection RawToggles = ToggleableRegex.Matches(args); foreach (Match m in RawToggles) { args = args.Replace(m.Value, ""); Toggles.Add(m.Groups[2].Value.Trim()); } args = args.Trim(); } var Jstrikes = await SheetService.Get(c, "strikes"); var values = await SheetService.GetValues(c); if (values == null || Jstrikes == null) { var err = new EmbedBuilder() .WithTitle("Click here") .WithUrl("https://character.pf2.tools/?" + c.RemoteId) .WithDescription("Seems like we cannot fetch " + c.Name + "'s values. This is due to the fact values are only updated when you open the sheet in pf2.tools. To fix this, click the link above to generate those values."); await ReplyAsync("", err.Build()); return; } var strikes = from sk in Jstrikes where ((string)sk["name"]).ToLower().StartsWith(args) orderby sk["name"] select sk; if (strikes.Count() == 0) { await ReplyAsync("You have no strikes whose name starts with '" + args + "'."); return; } string strikename = ""; var s = strikes.FirstOrDefault(); var embed = new EmbedBuilder() .WithTitle(c.Name + " strikes with a " + (((string)s["name"]).NullorEmpty() ? "Unnamed Strike" : (string)s["name"]) + "!") .WithThumbnailUrl(c.ImageUrl); if (c.Color != null) { embed.WithColor(new Color(c.Color[0], c.Color[1], c.Color[2])); } else { Random randonGen = new Random(); Color randomColor = new Color(randonGen.Next(255), randonGen.Next(255), randonGen.Next(255)); embed.WithColor(randomColor); } await ReplyAsync("Rolling..."); string hit = ""; string dmg = ""; string penalties = ""; string damagebonus = ""; string dmgtype = "Untyped"; if ((string)s["attack"] == "spell") { JToken cl = null; var classes = await SheetService.Get(c, "classes"); var traditions = await SheetService.Get(c, "traditions"); classes.Merge(traditions); if (classes == null || classes.Count == 0) { await ReplyAsync("You don't seem to have a class. Without one you can't make spell attacks."); return; } if (((string)s["class"]).NullorEmpty()) { cl = classes.FirstOrDefault(); } else { cl = classes.First(x => (string)x["id"] == (string)s["class"]); } if (!values.ContainsKey(((string)cl["name"]).ToLower())) { await ReplyAsync("Sorry! It seems we cannot retrieve the data for this strike! This issue is outside of the bot's control and will likely be solved later."); return; } hit = (string)values[((string)cl["name"]).ToLower()]["bonus"]; int dc = int.Parse(hit ?? "0") + 10; string summary = ""; if (!((string)s["spell"]).NullorEmpty()) { var spells = await SheetService.Get(c, "spells"); var sp = spells.First(x => (string)x["id"] == (string)s["spell"]); if (!((string)sp["body"]).NullorEmpty()) { var sb = new StringBuilder(); sb.AppendLine((string)sp["body"]); if (sb.Length <= 1024) { embed.AddField("Spell Description", sb.ToString()); } else { var segments = sb.ToString().Split(1000).ToArray(); for (int i = 0; i < segments.Length; i++) { embed.AddField("Spell Description (" + (i + 1) + "/" + (segments.Length) + ")", segments[i]); } } } if (!((string)sp["attackroll"]).NullorEmpty() && (bool)sp["attackroll"]) { var result = Roller.Roll("d20 + " + hit + (Bonuses.Length > 0 ? string.Join(" ", Bonuses) : "")); summary += "**Spell Attack roll**: " + result.ParseResult() + " = `" + result.Value + "`"; } if (!((string)sp["savingthrow"]).NullorEmpty()) { summary += "\n**Saving Throw**: " + sp["savingthrow"] + " (DC: `" + dc + "`)"; } embed.WithTitle(c.Name + " casts " + (sp["name"] ?? "Unnamed Spell") + "!"); } dmg = (string)s["extradamage"]; if (!dmg.NullorEmpty() && AttributeRegex.IsMatch(dmg)) { dmg = await ParseValues(dmg, c, values); } if (DmgRegex.IsMatch(dmg)) { foreach (var m in DmgRegex.Matches(dmg).ToList()) { try { RollResult dmgroll = Roller.Roll(m.Groups[2].Value.ToLower()); summary += "\n**" + m.Groups[1].Value + " damage**: " + dmgroll.ParseResult() + " = `" + dmgroll.Value + "` "; } catch { continue; } } } JToken details = s["details"]; if (details != null && details.HasValues) { foreach (var value in details.Where(x => (bool)x["active"] == true || Toggles.Contains(((string)x["name"]).ToLower()))) { try { RollResult Eresult = Roller.Roll(((string)value["comment"]).ToLower()); summary += "\n**" + (((string)value["name"]).NullorEmpty() ? "Extra Damage (" + (((string)value["type"]).NullorEmpty() ? "Untyped" : (string)value["type"]) + ") " : (string)value["name"] + " (" + (((string)value["type"]).NullorEmpty() ? "Untyped" : (string)value["type"]) + ") ") + "**: " + Eresult.ParseResult() + " = `" + Eresult.Value + "`"; } catch { continue; } } } embed.WithDescription(summary) .WithFooter((c.ValuesLastUpdated.Outdated() ? "⚠️ Couldn't retrieve updated values. Roll might not be accurate" : DateTime.Now.ToString())); await ReplyAsync(" ", embed.Build()); } else if ((string)s["attack"] == "custom") { string summary = ""; if (!((string)s["weaponcustom"]).NullorEmpty()) { embed.WithTitle(c.Name + " strikes with a " + s["weaponcustom"]); } if (!((string)s["damagetypecustom"]).NullorEmpty()) { dmgtype = (string)s["damagetypecustom"]; } if (!((string)s["traitscustom"]).NullorEmpty()) { string[] traits = ((string)s["traitscustom"]).Split(','); foreach (var x in traits) { summary += "[" + x.ToUpper() + "] "; } summary += "\n"; } int range = 0; if (!((string)s["range"]).NullorEmpty()) { range += int.Parse((string)s["range"]); } if (!((string)s["modrange"]).NullorEmpty()) { range += int.Parse((string)s["modrange"]); } summary += "**Range** " + range + "ft.\n"; if ((string)s["attackcustom"] == "ranged" || ((string)s["attack"]).NullorEmpty()) { hit = (string)values["ranged " + (string)s["name"]]["bonus"]; penalties = (string)values["ranged " + (string)s["name"]]["penalty"]; var result = Roller.Roll("d20 + " + hit + (penalties != "0" ? "-" + penalties : "") + (Bonuses.Length > 0 ? string.Join(" ", Bonuses) : "")); summary += "**Attack roll**: " + result.ParseResult() + " = `" + result.Value + "`"; } else if ((string)s["attackcustom"] == "melee" || ((string)s["attack"]).NullorEmpty()) { hit = (string)values["melee " + (string)s["name"]]["bonus"]; penalties = (string)values["melee " + (string)s["name"]]["penalty"]; var result = Roller.Roll("d20 + " + hit + (penalties != "0" ? "-" + penalties : "") + (Bonuses.Length > 0 ? string.Join(" ", Bonuses) : "")); summary += "**Attack roll**: " + result.ParseResult() + " = `" + result.Value + "`"; } damagebonus = (string)values["damage " + (string)s["name"]]["value"]; dmg = (string)values["damagedice " + (string)s["name"]]["value"] + GetDie((int)values["damagedie " + (string)s["name"]]["value"]); if (!dmg.NullorEmpty()) { try { RollResult result2 = Roller.Roll(dmg + "+" + damagebonus); summary += "\n**" + dmgtype.Uppercase() + " damage**: " + result2.ParseResult() + " = `" + result2.Value + "` "; if (!((string)s["extradamage"]).NullorEmpty()) { string extradmg = (string)s["extradamage"]; if (!extradmg.NullorEmpty() && AttributeRegex.IsMatch(extradmg)) { extradmg = await ParseValues(extradmg, c, values); } if (DmgRegex.IsMatch(extradmg)) { foreach (var m in DmgRegex.Matches(extradmg).ToList()) { try { RollResult dmgroll = Roller.Roll(m.Groups[2].Value); summary += "\n**" + m.Groups[1].Value + " damage**: " + dmgroll.ParseResult() + " = `" + dmgroll.Value + "` "; } catch { continue; } } } } } catch { await ReplyAsync("It seems like this strike doesn't have a valid dice roll on its damage or additional damage fields. If this is a spell make sure you have a valid dice expression on the damage fields."); return; } } JToken details = s["details"]; if (details != null && details.HasValues) { foreach (var value in details.Where(x => (bool)x["active"] == true || Toggles.Contains(((string)x["name"]).ToLower()))) { try { RollResult Eresult = Roller.Roll((string)value["comment"]); summary += "\n**" + (((string)value["name"]).NullorEmpty() ? "Extra Damage (" + (((string)value["type"]).NullorEmpty() ? "Untyped" : (string)value["type"]) + ") " : (string)value["name"] + " (" + (((string)value["type"]).NullorEmpty() ? "Untyped" : (string)value["type"]) + ") ") + "**: " + Eresult.ParseResult() + " = `" + Eresult.Value + "`"; } catch { continue; } } } embed.WithDescription(summary) .WithFooter((c.ValuesLastUpdated.Outdated() ? "⚠️ Couldn't retrieve updated values. Roll might not be accurate" : DateTime.Now.ToString())); await ReplyAsync(" ", embed.Build()); } else { string summary = ""; if (!((string)s["weapon"]).NullorEmpty()) { var items = await SheetService.Get(c, "items"); var i = items.First(x => (string)x["id"] == (string)s["weapon"]); dmgtype = (string)i["damagetype"] ?? "Untyped"; if (!((string)i["traits"]).NullorEmpty()) { string[] traits = ((string)i["traits"]).Split(','); foreach (var x in traits) { summary += "[" + x.ToUpper() + "] "; } summary += "\n"; } int range = 0; if (!((string)i["range"]).NullorEmpty() && int.TryParse((string)i["range"], out int r)) { range += r; } if (!((string)s["modrange"]).NullorEmpty() && int.TryParse((string)s["modrange"], out int r2)) { range += r2; } summary += "**Range**: " + range + "ft.\n"; if ((string)i["attack"] == "melee") { hit = (string)values["melee " + (string)s["name"]]["bonus"]; penalties = (string)values["melee " + (string)s["name"]]["penalty"]; } else if ((string)i["attack"] == "ranged") { hit = (string)values["ranged " + (string)s["name"]]["bonus"]; penalties = (string)values["ranged " + (string)s["name"]]["penalty"]; } if (!((string)i["name"]).NullorEmpty()) { embed.WithTitle(c.Name + " strikes with a " + (string)i["name"] + "!"); } } if ((string)s["attack"] == "melee") { hit = (string)values["melee " + (string)s["name"]]["bonus"]; penalties = (string)values["melee " + (string)s["name"]]["penalty"]; } else if ((string)s["attack"] == "ranged") { hit = (string)values["ranged " + (string)s["name"]]["bonus"]; penalties = (string)values["ranged " + (string)s["name"]]["penalty"]; } damagebonus = (string)values["damage " + (string)s["name"]]["value"]; dmg = (string)values["damagedice " + (string)s["name"]]["value"] + GetDie((int)values["damagedie " + (string)s["name"]]["value"]); var result = Roller.Roll("d20 + " + hit + (penalties != "0"? "-" + penalties:"") + (Bonuses.Length > 0 ? string.Join(" ", Bonuses) : "")); summary += "**Attack roll**: " + result.ParseResult() + " = `" + result.Value + "`"; if (!dmg.NullorEmpty()) { try { RollResult result2 = Roller.Roll(dmg + "+" + damagebonus); summary += "\n**" + dmgtype.Uppercase() + " damage**: " + result2.ParseResult() + " = `" + result2.Value + "` "; if (!((string)s["extradamage"]).NullorEmpty()) { string extradmg = (string)s["extradamage"]; if (!extradmg.NullorEmpty() && AttributeRegex.IsMatch(extradmg)) { extradmg = await ParseValues(extradmg, c, values); } if (DmgRegex.IsMatch(extradmg)) { foreach (var m in DmgRegex.Matches(extradmg).ToList()) { try { RollResult dmgroll = Roller.Roll(m.Groups[2].Value); summary += "\n**" + m.Groups[1].Value + " damage**: " + dmgroll.ParseResult() + " = `" + dmgroll.Value + "` "; } catch { continue; } } } } } catch { await ReplyAsync("It seems like this strike doesn't have a valid dice roll on its damage or additional damage fields. If this is a spell make sure you have a valid dice expression on the damage fields."); return; } } JToken details = s["details"]; if (details != null && details.HasValues) { foreach (var value in details.Where(x => (bool)x["active"] == true || Toggles.Contains(((string)x["name"]).ToLower()))) { try { RollResult Eresult = Roller.Roll((string)value["comment"]); summary += "\n**" + (((string)value["name"]).NullorEmpty() ? "Extra Damage (" + (((string)value["type"]).NullorEmpty() ? "Untyped" : (string)value["type"]) + ") " : (string)value["name"] + " (" + (((string)value["type"]).NullorEmpty() ? "Untyped" : (string)value["type"]) + ") ") + "**: " + Eresult.ParseResult() + " = `" + Eresult.Value + "`"; } catch { continue; } } } embed.WithDescription(summary) .WithFooter((c.ValuesLastUpdated.Outdated() ? "⚠️ Couldn't retrieve updated values. Roll might not be accurate" : DateTime.Now.ToString())); await ReplyAsync(" ", embed.Build()); } } }