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));
 }
예제 #5
0
        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();
        }