private async Task _removePrey(Species predatorSpecies, Species preySpecies) { if (predatorSpecies is null || preySpecies is null) { return; } // Remove the relationship. // Ensure that the user has necessary privileges to use this command. if (!await BotUtils.ReplyHasPrivilegeOrOwnershipAsync(Context, PrivilegeLevel.ServerModerator, predatorSpecies)) { return; } PreyInfo[] existing_prey = await SpeciesUtils.GetPreyAsync(predatorSpecies); if (existing_prey.Any(x => x.Prey.Id == preySpecies.Id)) { using (SQLiteCommand cmd = new SQLiteCommand("DELETE FROM Predates WHERE species_id = $species_id AND eats_id = $eats_id")) { cmd.Parameters.AddWithValue("$species_id", predatorSpecies.Id); cmd.Parameters.AddWithValue("$eats_id", preySpecies.Id); await Database.ExecuteNonQuery(cmd); } await BotUtils.ReplyAsync_Success(Context, string.Format("**{0}** no longer preys upon **{1}**.", predatorSpecies.ShortName, preySpecies.ShortName)); } else { await BotUtils.ReplyAsync_Warning(Context, string.Format("**{0}** does not prey upon **{1}**.", predatorSpecies.ShortName, preySpecies.ShortName)); } }
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(); }