private static async Task _calculateDescriptionBasedBaseStats(Gotchi gotchi, GotchiStats stats) { Species species = await SpeciesUtils.GetSpeciesAsync(gotchi.SpeciesId); int weight = 20; if (Regex.IsMatch(species.Description, "photosynthesi(s|izes)", RegexOptions.IgnoreCase)) { stats.MaxHp += weight; } if (Regex.IsMatch(species.Description, "spikes?|claws?|teeth|jaws|fangs", RegexOptions.IgnoreCase)) { stats.Atk += weight; } if (Regex.IsMatch(species.Description, "shell|carapace|exoskeleton", RegexOptions.IgnoreCase)) { stats.Def += weight; } foreach (Match m in Regex.Matches(species.Description, "flies|fly|quick|fast|agile|nimble", RegexOptions.IgnoreCase)) { stats.Spd += weight; } foreach (Match m in Regex.Matches(species.Description, "slow|heavy", RegexOptions.IgnoreCase)) { stats.Spd -= weight; } }
public async Task <GotchiStats> GetStatsAsync(Gotchi gotchi) { GotchiStats result = await GetBaseStatsAsync(gotchi); result.Experience = gotchi.Experience; // Calculate final stats based on level and base stats. // #todo Implement IVs/EVs int level = result.Level; result.MaxHp = _calculateHp(result.MaxHp, 0, 0, level); result.Atk = _calculateStat(result.Atk, 0, 0, level); result.Def = _calculateStat(result.Def, 0, 0, level); result.Spd = _calculateStat(result.Spd, 0, 0, level); result.Hp = result.MaxHp; return(await Task.FromResult(result)); }
public async Task <GotchiStats> GetBaseStatsAsync(Gotchi gotchi) { GotchiStats result = new GotchiStats(); int denominator = 0; GotchiType[] gotchiTypes = await Context.TypeRegistry.GetTypesAsync(gotchi); if (gotchiTypes.Count() > 0) { // Include the average of all types of this species. result.MaxHp = gotchiTypes.Sum(x => x.BaseHp); result.Atk = gotchiTypes.Sum(x => x.BaseAtk); result.Def = gotchiTypes.Sum(x => x.BaseDef); result.Spd = gotchiTypes.Sum(x => x.BaseSpd); denominator += gotchiTypes.Count(); } long[] ancestor_ids = await SpeciesUtils.GetAncestorIdsAsync(gotchi.SpeciesId); // Factor in the base stats of this species' ancestor (which will, in turn, factor in all other ancestors). if (ancestor_ids.Count() > 0) { GotchiStats ancestor_stats = await GetBaseStatsAsync(new Gotchi { SpeciesId = ancestor_ids.Last() }); result.MaxHp += ancestor_stats.MaxHp; result.Atk += ancestor_stats.Atk; result.Def += ancestor_stats.Def; result.Spd += ancestor_stats.Spd; denominator += 1; } // Add 20 points if this species has an ancestor (this effect will be compounded by the previous loop). if (ancestor_ids.Count() > 0) { result.MaxHp += 20; result.Atk += 20; result.Def += 20; result.Spd += 20; } // Get the average of each base stat. denominator = Math.Max(denominator, 1); result.MaxHp /= denominator; result.Atk /= denominator; result.Def /= denominator; result.Spd /= denominator; // Add 0.5 points for every week the gotchi has been alive. int age_bonus = (int)(0.5 * (gotchi.Age / 7)); result.MaxHp += age_bonus; result.Atk += age_bonus; result.Def += age_bonus; result.Spd += age_bonus; // Add or remove stats based on the species' description. await _calculateDescriptionBasedBaseStats(gotchi, result); return(result); }
public static int ExperienceToNextLevel(GotchiStats currentStats) { return(ExperienceToLevel(currentStats.ExperienceGroup, currentStats.Experience, currentStats.Level + 1)); }
private async Task _useMoveOnAsync(ICommandContext context, PlayerState user, PlayerState target) { // Execute the selected move. StringBuilder battle_text = new StringBuilder(); battle_text.AppendLine(battleText); if (!string.IsNullOrEmpty(user.SelectedMove.LuaScriptFilePath)) { // Create, initialize, and execute the script associated with this move. GotchiMoveLuaScript moveScript = new GotchiMoveLuaScript(user.SelectedMove.LuaScriptFilePath); GotchiMoveCallbackArgs args = await _createCallbackArgsAsync(user, target); // Call the move's "OnInit" callback (only applicable for some moves). await moveScript.OnInitAsync(args); // It's possible for a move to be used more than once in a turn (e.g., multi-hit moves). // Each time will trigger the callback to be called and display a new message. for (int i = 0; i < Math.Max(1, args.Times); ++i) { // Check if this was a critical hit, or if the move missed. bool is_hit = user.SelectedMove.IgnoreAccuracy || (BotUtils.RandomInteger(0, 20 + 1) < 20 * user.SelectedMove.Accuracy * Math.Max(0.1, user.Gotchi.Stats.Acc - target.Gotchi.Stats.Eva)); args.IsCritical = !user.SelectedMove.IgnoreCritical && (BotUtils.RandomInteger(0, (int)(10 / user.SelectedMove.CriticalRate)) == 0 || (await SpeciesUtils.GetPreyAsync(user.Gotchi.Gotchi.SpeciesId)).Any(x => x.Prey.Id == target.Gotchi.Gotchi.Id)); if (is_hit) { // Clone each user's stats before triggering the callback, so we can compare them before and after. GotchiStats user_before = user.Gotchi.Stats.Clone(); GotchiStats target_before = target.Gotchi.Stats.Clone(); // Invoke the callback. try { if (!await moveScript.OnMoveAsync(args)) { args.DealDamage(user.SelectedMove.Power); } } catch (Exception) { args.Text = "but something went wrong"; } // Apply the target's status if a new status was acquired. if (target.Gotchi.StatusChanged && target.Gotchi.HasStatus) { await new GotchiStatusLuaScript(target.Gotchi.Status.LuaScriptFilePath).OnAcquireAsync(args); target.Gotchi.StatusChanged = false; } // Prevent the target from fainting if they're able to endure the hit. if (target.Gotchi.HasStatus && target.Gotchi.Status.Endure) { target.Gotchi.Stats.Hp = Math.Max(1, target.Gotchi.Stats.Hp); } // If the user is heal-blocked, prevent recovery by resetting the HP back to what it was before recovery. if (user.Gotchi.HasStatus && !user.Gotchi.Status.AllowRecovery && user.Gotchi.Stats.Hp > user_before.Hp) { user.Gotchi.Stats.Hp = user_before.Hp; } // Show the battle text. // If the move doesn't specify a text, choose one automatically (where possible). string text = args.Text; if (string.IsNullOrEmpty(text)) { if (target.Gotchi.Stats.Hp < target_before.Hp) { text = "dealing {target:damage} damage"; //user.SelectedMove.info.Type = GotchiMoveType.Offensive; } else if (target.Gotchi.Stats.Atk < target_before.Atk) { text = "lowering its opponent's ATK by {target:atk%}"; //user.SelectedMove.info.Type = GotchiMoveType.Buff; } else if (target.Gotchi.Stats.Def < target_before.Def) { text = "lowering its opponent's DEF by {target:def%}"; //user.SelectedMove.info.Type = GotchiMoveType.Buff; } else if (target.Gotchi.Stats.Spd < target_before.Spd) { text = "lowering its opponent's SPD by {target:spd%}"; //user.SelectedMove.info.Type = GotchiMoveType.Buff; } else if (target.Gotchi.Stats.Acc < target_before.Acc) { text = "lowering its opponent's accuracy by {target:acc%}"; //user.SelectedMove.info.Type = GotchiMoveType.Buff; } else if (target.Gotchi.Stats.Eva < target_before.Eva) { text = "lowering its opponent's evasion by {target:eva%}"; //user.SelectedMove.info.Type = GotchiMoveType.Buff; } else if (user.Gotchi.Stats.Hp > user_before.Hp) { text = "recovering {user:recovered} HP"; //user.SelectedMove.info.Type = GotchiMoveType.Recovery; } else if (user.Gotchi.Stats.Atk > user_before.Atk) { text = "boosting its ATK by {user:atk%}"; //user.SelectedMove.info.Type = GotchiMoveType.Buff; } else if (user.Gotchi.Stats.Def > user_before.Def) { text = "boosting its DEF by {user:def%}"; //user.SelectedMove.info.Type = GotchiMoveType.Buff; } else if (user.Gotchi.Stats.Spd > user_before.Spd) { text = "boosting its SPD by {user:spd%}"; //user.SelectedMove.info.Type = GotchiMoveType.Buff; } else if (user.Gotchi.Stats.Acc > user_before.Acc) { text = "boosting its accuracy by {user:acc%}"; //user.SelectedMove.info.Type = GotchiMoveType.Buff; } else if (user.Gotchi.Stats.Eva > user_before.Eva) { text = "boosting its evasion by {user:eva%}"; //user.SelectedMove.info.Type = GotchiMoveType.Buff; } else { text = "but nothing happened?"; } } // Various replacements are allowed, which the user can specify in the move's battle text. text = Regex.Replace(text, @"\{([^\}]+)\}", m => { switch (m.Groups[1].Value.ToLower()) { case "damage": case "target:damage": return(string.Format("{0:0.#}", target_before.Hp - target.Gotchi.Stats.Hp)); case "target:atk%": return(string.Format("{0:0.#}%", Math.Abs(target_before.Atk - target.Gotchi.Stats.Atk) / (double)target_before.Atk * 100.0)); case "target:def%": return(string.Format("{0:0.#}%", Math.Abs(target_before.Def - target.Gotchi.Stats.Def) / (double)target_before.Def * 100.0)); case "target:spd%": return(string.Format("{0:0.#}%", Math.Abs(target_before.Spd - target.Gotchi.Stats.Spd) / (double)target_before.Spd * 100.0)); case "target:acc%": return(string.Format("{0:0.#}%", Math.Abs(target_before.Acc - target.Gotchi.Stats.Acc) / target_before.Acc * 100.0)); case "target:eva%": return(string.Format("{0:0.#}%", Math.Abs(target_before.Eva - target.Gotchi.Stats.Eva) / target_before.Eva * 100.0)); case "user:atk%": return(string.Format("{0:0.#}%", Math.Abs(user_before.Atk - user.Gotchi.Stats.Atk) / (double)user_before.Atk * 100.0)); case "user:def%": return(string.Format("{0:0.#}%", Math.Abs(user_before.Def - user.Gotchi.Stats.Def) / (double)user_before.Def * 100.0)); case "user:spd%": return(string.Format("{0:0.#}%", Math.Abs(user_before.Spd - user.Gotchi.Stats.Spd) / (double)user_before.Spd * 100.0)); case "user:acc%": return(string.Format("{0:0.#}%", Math.Abs(user_before.Acc - user.Gotchi.Stats.Acc) / user_before.Acc * 100.0)); case "user:eva%": return(string.Format("{0:0.#}%", (user_before.Eva == 0.0 ? user.Gotchi.Stats.Eva : (Math.Abs(user_before.Eva - user.Gotchi.Stats.Eva) / user_before.Eva)) * 100.0)); case "user:recovered": return(string.Format("{0:0.#}", user.Gotchi.Stats.Hp - user_before.Hp)); default: return("???"); } }); battle_text.Append(string.Format("{0} **{1}** used **{2}**, {3}!", "💥", //user.SelectedMove.info.Icon(), user.Gotchi.Gotchi.Name, user.SelectedMove.Name, text)); if (args.IsCritical && target.Gotchi.Stats.Hp < target_before.Hp) { battle_text.Append(" Critical hit!"); } if (args.MatchupMultiplier > 1.0) { battle_text.Append(" It's super effective!"); } else if (args.MatchupMultiplier < 1.0) { battle_text.Append(" It's not very effective..."); } battle_text.AppendLine(); } else { // If the move missed, so display a failure message. battle_text.AppendLine(string.Format("{0} **{1}** used **{2}**, but it missed!", "💥", //user.SelectedMove.info.Icon(), user.Gotchi.Gotchi.Name, user.SelectedMove.Name)); } } if (args.Times > 1) { battle_text.Append(string.Format(" Hit {0} times!", args.Times)); } } else { // If there is no Lua script associated with the given move, display a failure message. battle_text.Append(string.Format("{0} **{1}** used **{2}**, but it forgot how!", "💥", //user.SelectedMove.info.Icon(), user.Gotchi.Gotchi.Name, user.SelectedMove.Name)); } battleText = battle_text.ToString(); }