diff --git a/extensions/dq/calculators.py b/extensions/dq/calculators.py index 5c59bab..b72c3a1 100644 --- a/extensions/dq/calculators.py +++ b/extensions/dq/calculators.py @@ -3,7 +3,7 @@ from discord.ext import commands from ._base import DQBase -from utils.calculators import calculate_upgrade_cost, calculate_potential, calculate_damage +from utils.dq.calculators import calculate_upgrade_cost, calculate_potential, calculate_damage, calculate_runs from utils import Embed from typing import Optional from decimal import Decimal @@ -36,6 +36,7 @@ async def calc_potential(self, ctx, current_power: int, current_upgrades: int, t humanized_potential = f"({n(potential)})" embed = Embed(title="Potential Calculator") + embed.set_author(name="Dungeon Quest Helper", url="https://www.roblox.com/games/2414851778") embed.add_field(name="💪 Max Power", value=f"{potential:,} {humanized_potential if potential > 999 else ''}", inline=False) embed.add_field(name="💰 Upgrade Cost", value=f"{upgrade_cost:,} {humanized_cost if upgrade_cost > 999 else ''}", inline=False) @@ -82,9 +83,53 @@ async def calc_damage(self, ctx, ability: app_commands.Choice[str], helmet_power ei_high = damage["With Enhanced Inner"]["High Damage"] embed = Embed(title="Damage Range Calculator") + embed.set_author(name="Dungeon Quest Helper", url="https://www.roblox.com/games/2414851778") embed.add_field(name="❌ No Inner", value=f"**Low Damage:** {fn(ni_low)}\n**Average Damage:** {fn(ni_avg)}\n**High Damage:** {fn(ni_high)}", inline=False) embed.add_field(name="✨ With Inner", value=f"**Low Damage:** {fn(wi_low)}\n**Average Damage:** {fn(wi_avg)}\n**High Damage:** {fn(wi_high)}", inline=False) embed.add_field(name="🌟 With Enhanced Inner", value=f"**Low Damage:** {fn(ei_low)}\n**Average Damage:** {fn(ei_avg)}\n**High Damage:** {fn(ei_high)}", inline=False) - return await ctx.send(embed=embed) \ No newline at end of file + return await ctx.send(embed=embed) + + @commands.hybrid_command(description="Calculate the number of runs needed to reach the goal level given the selected dungeon.") + @app_commands.describe(current_level="The current level.") + @app_commands.describe(goal_level="The goal level.") + @app_commands.describe(dungeon_name="The name of the dungeon.") + @app_commands.describe(difficulty="The difficulty of the dungeon.") + @app_commands.describe(event_active="Whether the x2 EXP event is active.") + @app_commands.describe(booster_active="Whether the EXP potion is active.") + @app_commands.describe(vip="Whether you have VIP.") + @app_commands.choice(dungeon_name=[ + app_commands.Choice(name="Abyssal Void", value="Abyssal Void"), + app_commands.Choice(name="Yokai Peak", value="Yokai Peak"), + app_commands.Choice(name="Gilded Skies", value="Gilded Skies"), + app_commands.Choice(name="Northern Lands", value="Northern Lands"), + app_commands.Choice(name="Enchanted Forest", value="Enchanted Forest"), + app_commands.Choice(name="Aquatic Temple", value="Aquatic Temple"), + app_commands.Choice(name="Volcanic Chambers", value="Volcanic Chambers"), + app_commands.Choice(name="Orbital Outpost", value="Orbital Outpost"), + app_commands.Choice(name="Steampunk Sewers", value="Steampunk Sewers"), + app_commands.Choice(name="Ghastly Harbor", value="Ghastly Harbor"), + app_commands.Choice(name="The Canals", value="The Canals"), + app_commands.Choice(name="Samurai Palace", value="Samurai Palace"), + app_commands.Choice(name="The Underworld", value="The Underworld"), + app_commands.Choice(name="King's Castle", value="King's Castle"), + app_commands.Choice(name="Pirate Island", value="Pirate Island"), + app_commands.Choice(name="Winter Outpost (Current)", value="Winter Outpost (Current)"), + app_commands.Choice(name="Winter Outpost (Legacy)", value="Winter Outpost (Legacy)"), + app_commands.Choice(name="Desert Temple (Current)", value="Desert Temple (Current)"), + app_commands.Choice(name="Desert Temple (Legacy)", value="Desert Temple (Legacy)"), + ]) + async def calc_runs(self, ctx, current_level: int, goal_level: int, dungeon_name: Optional[str] = None, event_active: Optional[bool] = False, booster_active: Optional[bool] = False, vip: Optional[bool] = False): + result = calculate_runs(current_level, goal_level, dungeon_name, event_active, booster_active, vip) + + embed = Embed(title="Dungeon Runs Calculator", + description=f"To go from **Level {result["current_level"]}** to **Level {result["goal_level"]}** in **{result["dungeon_name"]}** you will need **{result["xp_needed"]} XP**") + embed.set_author(name="Dungeon Quest Helper", url="https://www.roblox.com/games/2414851778") + + runs = result["runs"] + + for run in runs: + embed.add_field(name=f"{run.key} Runs", value=f"**{run.value}** runs", inline=False) + + await ctx.send(embed=embed) \ No newline at end of file diff --git a/utils/calculators.py b/utils/calculators.py deleted file mode 100644 index 017245e..0000000 --- a/utils/calculators.py +++ /dev/null @@ -1,100 +0,0 @@ -def calculate_potential(power, current, total): - potential = power - upgrades = current - while potential < 200 and upgrades < total: - if potential < 20: - potential += 1 - else: - potential += potential // 20 - upgrades += 1 - potential += (total - upgrades) * 10 - return potential - -def calculate_upgrade_cost(current, total): - cost = 0 - if current < 24: - if current == 0 and total > 0: - cost = 100 - c = 100 - for i in range(1, min(24, total)): - c = c * 1.06 + 50 - if i >= current: - cost += int(c) - - s = 24 if current < 24 else 466 if current > 466 else current - e = 24 if total < 24 else 466 if total > 466 else total - cost += (e - s) * (110 * (e + s) - 2445) - - s = 466 if current < 466 else current - e = 466 if total < 466 else total - cost += (e - s) * 100000 - return cost - - -def calculate_damage(selected_ability, armor, helmet, weapon, ring1, ring2, skill): - abilities = [ - {"name": "Spinning Blade Smash / Void Dragon", "multiplier": 148}, - {"name": "Kunai Knives (3 ticks)", "multiplier": 150 / 3}, - {"name": "Rift Beam (37 ticks)", "multiplier": 203 / 37}, - {"name": "Triple Quake (3 ticks)", "multiplier": 144 / 3}, - {"name": "Chain Storm (6 ticks)", "multiplier": 147 / 6}, - {"name": "Blade Barrage / God Spear / Amethyst Beams / Jade Rain", "multiplier": 133}, - {"name": "Jade Roller", "multiplier": 126}, - {"name": "Solar Beam (2 ticks)", "multiplier": 126 / 2} - ] - - ability_multiplier = 1 - - for ability in abilities: - if ability["name"] == selected_ability.value: - ability_multiplier = ability["multiplier"] - - dmg = int(weapon * (0.6597 + 0.013202 * skill) * (armor + helmet + ring1 + ring2) * 0.0028 * ability_multiplier) - low = dmg * 0.95 - high = dmg * 1.05 - - # Inner is 80%, so 1.8 - # E(nhanced) Inner is 90%, so 1.9 - - low_inner = dmg * 1.8 * 0.95 - base_inner = dmg * 1.8 - high_inner = dmg * 1.8 * 1.05 - - low_e_inner = dmg * 1.9 * 0.95 - base_e_inner = dmg * 1.9 - high_e_inner = dmg * 1.9 * 1.05 - - low_damage = low - base_damage = dmg - high_damage = high - - low_inner_damage = low_inner - base_inner_damage = base_inner - high_inner_damage = high_inner - - low_e_inner_damage = low_e_inner - base_e_inner_damage = base_e_inner - high_e_inner_damage = high_e_inner - - return { - "No Inner": { - "Low Damage": low_damage, - "Average": base_damage, - "High Damage": high_damage - }, - "With Inner": { - "Low Damage": low_inner_damage, - "Average": base_inner_damage, - "High Damage": high_inner_damage - }, - "With Enhanced Inner": { - "Low Damage": low_e_inner_damage, - "Average": base_e_inner_damage, - "High Damage": high_e_inner_damage - }, - f"Other Information": { - "ability_multiplier": ability_multiplier, - "dmg": dmg, - "selected_ability": selected_ability - } - } diff --git a/utils/dq/calculators.py b/utils/dq/calculators.py new file mode 100644 index 0000000..8d03539 --- /dev/null +++ b/utils/dq/calculators.py @@ -0,0 +1,193 @@ +# --------------------------------------------- # +# Please view 'notice.md' for more information. # +# --------------------------------------------- # + +import json +from typing import Optional +from .data import abilities, dungeons + +def calculate_potential(power, current, total): + """ + Calculate the potential of an item. + + Parameters: + power (int): The current power of the item. + current (int): The current amount of upgrades on the item. + total (int): The total amount of upgrades available for the item. + + Returns: + int: The potential of the item. + """ + potential = power + upgrades = current + while potential < 200 and upgrades < total: + if potential < 20: + potential += 1 + else: + potential += potential // 20 + upgrades += 1 + potential += (total - upgrades) * 10 + return potential + + +def calculate_upgrade_cost(current, total): + """ + Calculate the upgrade cost of an item. + Code is very weird, do not question it. + + Parameters: + current (int): The current amount of upgrades on the item. + total (int): The total amount of upgrades available for the item. + + Returns: + int: The upgrade cost of the item. + """ + cost = 0 + if current < 24: + if current == 0 and total > 0: + cost = 100 + c = 100 + for i in range(1, min(24, total)): + c = c * 1.06 + 50 + if i >= current: + cost += int(c) + + s = 24 if current < 24 else 466 if current > 466 else current + e = 24 if total < 24 else 466 if total > 466 else total + cost += (e - s) * (110 * (e + s) - 2445) + + s = 466 if current < 466 else current + e = 466 if total < 466 else total + cost += (e - s) * 100000 + return cost + + +def calculate_damage(selected_ability, armor, helmet, weapon, ring1, ring2, skill): + """ + Calculate the damage range you can do with the selected ability and the given stats. + + Parameters: + selected_ability (app_commands.Choice[str]): The selected ability. + armor (int): The power of the armor. + helmet (int): The power of the helmet. + weapon (int): The power of the weapon. + ring1 (int): The power of the first ring. + ring2 (int): The power of the second ring. + skill (int): The skill points you have. + + Returns: + dict: A dictionary containing the damage range for each type of damage. (With/Without Enhanced/Non-Enhanced Inner Rage/Focus) + """ + + ability_multiplier = 1 + + for ability in abilities: + if ability["name"] == selected_ability.value: + ability_multiplier = ability["multiplier"] + + dmg = int(weapon * (0.6597 + 0.013202 * skill) * (armor + helmet + ring1 + ring2) * 0.0028 * ability_multiplier) + low = dmg * 0.95 + high = dmg * 1.05 + + # Inner is 80%, so 1.8 + # E(nhanced) Inner is 90%, so 1.9 + + low_inner = dmg * 1.8 * 0.95 + base_inner = dmg * 1.8 + high_inner = dmg * 1.8 * 1.05 + + low_e_inner = dmg * 1.9 * 0.95 + base_e_inner = dmg * 1.9 + high_e_inner = dmg * 1.9 * 1.05 + + low_damage = low + base_damage = dmg + high_damage = high + + low_inner_damage = low_inner + base_inner_damage = base_inner + high_inner_damage = high_inner + + low_e_inner_damage = low_e_inner + base_e_inner_damage = base_e_inner + high_e_inner_damage = high_e_inner + + return { + "No Inner": { + "Low Damage": low_damage, + "Average": base_damage, + "High Damage": high_damage + }, + "With Inner": { + "Low Damage": low_inner_damage, + "Average": base_inner_damage, + "High Damage": high_inner_damage + }, + "With Enhanced Inner": { + "Low Damage": low_e_inner_damage, + "Average": base_e_inner_damage, + "High Damage": high_e_inner_damage + }, + f"Other Information": { + "ability_multiplier": ability_multiplier, + "dmg": dmg, + "selected_ability": selected_ability + } + } + + +def calculate_xp(current_level, goal_level): + """ + Calculate the XP required to go from current_level to goal_level. + + Parameters: + current_level (int): The current level. + goal_level (int): The goal level. + + Returns: + int: The XP required to go from current_level to goal_level. + """ + xp = 0 + for x in range(current_level, goal_level): + xp += round(84 * (1.13 ** (x - 1))) + return xp + + +def calculate_runs(current_level: int, goal_level: int, dungeon_name: str, event_active, booster_active, vip): + """ + Calculcate the number of runs needed to reach the goal level given the selected dungeon. + + Parameters: + current_level (int): The current level. + goal_level (int): The goal level. + dungeon_name (str): The name of the dungeon. + event_active (bool): Whether the event is active. + booster_active (bool): Whether the booster is active. + vip (bool): Whether the VIP is active. + + Returns: + dict: A dictionary containing the number of runs needed, the amount of VIP runs needed, and the dungeon name and difficulty. + """ + xp_needed = calculate_xp(current_level, goal_level) + + modifier = 1 + if event_active: + modifier += 1 + if booster_active: + modifier += 1 + + runs_required = {} + + for difficulty, dungeon_exp in dungeons[dungeon_name].items(): + amount_of_runs = int((xp_needed / (modifier * dungeon_exp)) + 0.5) + runs_required[difficulty.lower()] = amount_of_runs + + output = { + "current_level": current_level, + "goal_level": goal_level, + "dungeon_name": dungeon_name, + "xp_needed": xp_needed, + "runs": runs_required + } + + return output diff --git a/utils/dq/data.py b/utils/dq/data.py new file mode 100644 index 0000000..e8e0ea4 --- /dev/null +++ b/utils/dq/data.py @@ -0,0 +1,104 @@ +# --------------------------------------------- # +# Please view 'notice.md' for more information. # +# --------------------------------------------- # + +abilities = [ + {"name": "Spinning Blade Smash / Void Dragon", "multiplier": 148}, + {"name": "Kunai Knives (3 ticks)", "multiplier": 150 / 3}, + {"name": "Rift Beam (37 ticks)", "multiplier": 203 / 37}, + {"name": "Triple Quake (3 ticks)", "multiplier": 144 / 3}, + {"name": "Chain Storm (6 ticks)", "multiplier": 147 / 6}, + {"name": "Blade Barrage / God Spear / Amethyst Beams / Jade Rain", "multiplier": 133}, + {"name": "Jade Roller", "multiplier": 126}, + {"name": "Solar Beam (2 ticks)", "multiplier": 126 / 2} +] + + +dungeons = { + "Abyssal Void": { + "Insane [210]": 1.07e12, + "Nightmare [215]": 1.47e12, + "Nightmare (with The Voidborn) [215]": 1.91e12 + }, + "Yokai Peak": { + "Insane [200]": 192650000000, + "Nightmare [205]": 350950000000 + }, + "Gilded Skies": { + "Insane [190]": 63500000000, + "Nightmare [195]": 115500000000 + }, + "Northern Lands": { + "Insane [180]": 21820000000, + "Nightmare [185]": 36600000000, + "Nightmare (with Odin-R) [185]": 58600000000 + }, + "Enchanted Forest": { + "Insane [170]": 6900000000, + "Nightmare [175]": 11280000000 + }, + "Aquatic Temple": { + "Insane [160]": 2034000000, + "Nightmare [165]": 3564000000 + }, + "Volcanic Chambers": { + "Insane [150]": 755000000, + "Nightmare [155]": 1225000000 + }, + "Orbital Outpost": { + "Insane [135]": 329000000, + "Nightmare [140]": 506500000 + }, + "Steampunk Sewers": { + "Insane [120]": 35700000, + "Nightmare [125]": 59600000 + }, + "Ghastly Harbor": { + "Insane [110]": 12840000, + "Nightmare [115]": 24160000 + }, + "The Canals": { + "Insane [100]": 4594000, + "Nightmare [105]": 8005000 + }, + "Samurai Palace": { + "Insane [90]": 1934000, + "Nightmare [95]": 3500000 + }, + "The Underworld": { + "Insane [80]": 546000, + "Nightmare [85]": 924000 + }, + "King's Castle": { + "Insane [70]": 135900, + "Nightmare [75]": 271800 + }, + "Pirate Island": { + "Insane [60]": 51150, + "Nightmare [65]": 82200 + }, + "Winter Outpost (Current)": { + "Easy [30]": 18800, + "Medium [40]": 46800, + "Hard [50]": 69000 + }, + "Winter Outpost (Legacy)": { + "Easy [30]": 8340, + "Medium [40]": 11300, + "Hard [45]": 16140, + "Insane [50]": 27840, + "Nightmare [55]": 46180 + }, + "Desert Temple (Current)": { + "Easy [1]": 490, + "Medium [5]": 1296, + "Hard [15]": 4789 + }, + "Desert Temple (Legacy)": { + "Easy [1]": 253, + "Medium [6]": 396, + "Hard [12]": 785, + "Insane [20]": 1307, + "Nightmare [27]": 2669 + }, +} \ No newline at end of file diff --git a/utils/dq/notice.md b/utils/dq/notice.md new file mode 100644 index 0000000..39b3b84 --- /dev/null +++ b/utils/dq/notice.md @@ -0,0 +1,14 @@ +## Huge thanks to the Dungeon Quest Wiki. +All of the information you see in `calculators.py` or `data.py` has been extracted from the source code of the Dungeon Quest Wiki. +All of the custom values are extracted from the source code of the Dungeon Quest Wiki. This is why most of them don't make sense to the naked eye. + +This would not be possible without the Dungeon Quest Wiki. +I want to thank the Dungeon Quest Wiki for providing such a great resource for the community. +I have simply extracted the JavaScript code, and turned it into Python code. + +If you have any questions or concerns, please feel free to contact me on Discord. + +## Notice +This is not an official project of the Dungeon Quest Wiki. +This is a personal project that I have created for my own use. +I am not affiliated with the Dungeon Quest Wiki. \ No newline at end of file