diff --git a/src/assets/img/general/profile/nametag/seperator-nametag.png b/src/assets/img/general/profile/nametag/separator-nametag.png similarity index 100% rename from src/assets/img/general/profile/nametag/seperator-nametag.png rename to src/assets/img/general/profile/nametag/separator-nametag.png diff --git a/src/assets/img/general/profile/palette.png b/src/assets/img/general/profile/palette.png new file mode 100644 index 0000000..0de0930 Binary files /dev/null and b/src/assets/img/general/profile/palette.png differ diff --git a/src/bot.py b/src/bot.py index a9c8ee6..80fcfe5 100644 --- a/src/bot.py +++ b/src/bot.py @@ -10,140 +10,19 @@ from lightbulb.ext import tasks -VERSION = '1.3.3' +from utils.general.config import VERSION, get_setting_json, update_settings, get_setting, write_setting ## Functions ## def install(package): subprocess.check_call([sys.executable, '-m', 'pip', 'install', package]) -def get_setting_json(): - bot = { - 'version': VERSION, # DO NOT CHANGE - 'token': '', # BOT TOKEN (REQUIRED) - 'owner_id': [], # BOT OWNER IDS (REQUIRED) - 'test_guild_id': [], # APPLICATION COMMAND ENABLED GUILDS (OPTIONAL) - 'wordnik_api_key': '', # WORDNIK API KEY (OPTIONAL) - } - general = { - 'database_data_dir': 'database/database.sqlite', - 'command_cooldown': 5, - 'embed_color': '#249EDB', - 'embed_important_color': 'b03f58', - 'embed_success_color': '#32CD32', - 'embed_error_color': '#FF0000', - 'auto_translate_conf': 0.80, - 'auto_translate_min_relative_distance': 0.90, - } - economy = { - 'starting_balance': 300, - 'starting_tux_pass': 0, - 'daily_max_streak': 30, - } - profile = { - 'coin': { - 'gray-banner': 200, - 'float-nametag': 200, - 'separator-nametag': 200, - 'tuxedo-nametag': 200, - 'apple-base': 500, - 'burgundy-base': 500, - 'blueberry-base': 500, - 'grape-base': 500, - 'snow-base': 1000, - 'snow-nametag': 1000, - 'plastic-banner': 1000, - 'plastic-base': 1000, - 'plastic-nametag': 1000, - 'blue-banner': 2000, - 'orange-banner': 2000, - 'grassiant-banner': 5000, - 'sky-banner': 5000, - 'purp-banner': 5000, - 'purp-base': 5000, - 'purp-nametag': 5000, - 'charged_rose-banner': 5000, - 'rushsite_s3-banner': 7000, - 'france-banner': 10000, - 'usa-banner': 10000, - }, - 'tpass': { - 'nippuwu-banner': 1, - } - } - pokemon = { - 'pokemon_pack_amount': 7, - 'pokemon_max_capacity': 2000, - } - - json = { - 'bot': bot, - 'general': general, - 'economy': economy, - 'profile': profile, - 'pokemon': pokemon, - } - - return json - -def update_settings(): - settings = get_setting_json() - with open('settings.json', 'r') as openfile: - data = json.load(openfile) - - # Add or update settings - for section in settings: - if section not in data: - data[section] = settings[section] - else: - for option in settings[section]: - if option not in data[section]: - data[section][option] = settings[section][option] - - # Remove settings not present in get_setting_json() - sections_to_remove = [section for section in data if section not in settings] - for section in sections_to_remove: - del data[section] - - for section in data: - options_to_remove = [option for option in data[section] if option not in settings[section]] - for option in options_to_remove: - del data[section][option] - - with open('settings.json', 'w') as openfile: - json.dump(data, openfile, indent=4) - -def get_setting(section: str, option: str = None): - with open('settings.json', 'r') as openfile: - data = json.load(openfile) - if option: - if section in data and option in data[section]: - return data[section][option] - else: - return None - elif section in data: - return data[section] - else: - return None - -def write_setting(section: str, option: str, value): - with open('settings.json', 'r') as openfile: - data = json.load(openfile) - - if section not in data: - data[section] = {} - - data[section][option] = value - - with open('settings.json', 'w') as openfile: - json.dump(data, openfile, indent=4) - def register_user(user: hikari.User): db = sqlite3.connect(get_setting('general', 'database_data_dir')) cursor = db.cursor() - sql = ('INSERT INTO economy(user_id, balance, total, loss, tpass, streak, date, level, experience) VALUES (?,?,?,?,?,?,?,?,?)') - val = (user.id, get_setting('economy', 'starting_balance'), get_setting('economy', 'starting_balance'), 0, get_setting('economy', 'starting_tux_pass'), 0, None, 0, 0) + sql = ('INSERT INTO economy(user_id, balance, total, loss, tpass, streak, date) VALUES (?,?,?,?,?,?,?)') + val = (user.id, get_setting('economy', 'starting_balance'), get_setting('economy', 'starting_balance'), 0, get_setting('economy', 'starting_tux_pass'), 0, None) cursor.execute(sql, val) db.commit() # saves changes @@ -223,16 +102,6 @@ async def on_started(event): streak INTEGER, date TEXT )''') - cursor.execute('''CREATE TABLE IF NOT EXISTS pokemon ( - id TEXT PRIMARY KEY, - user_id TEXT, - date TEXT, - name TEXT, - pokemon_id INTEGER, - rarity INTEGER, - shiny INTEGER, - favorite INTEGER - )''') cursor.execute('''CREATE TABLE IF NOT EXISTS profile ( user_id TEXT, name TEXT, @@ -240,6 +109,12 @@ async def on_started(event): active INTEGER, PRIMARY KEY (user_id, type, name) )''') + cursor.execute('''CREATE TABLE IF NOT EXISTS fishing ( + user_id TEXT, + bait_id TEXT, + amount INTEGER, + PRIMARY KEY (user_id, bait_id) + )''') # Install uvloop for Linux users if os.name != 'nt': diff --git a/src/extensions/economy/daily.py b/src/extensions/economy/daily.py index 1ae9763..22698b3 100644 --- a/src/extensions/economy/daily.py +++ b/src/extensions/economy/daily.py @@ -2,7 +2,7 @@ import lightbulb import random -from datetime import datetime, timedelta +from datetime import datetime from bot import get_setting, verify_user, register_user from utils.daily.manager import DailyManager diff --git a/src/extensions/economy/update.py b/src/extensions/economy/update.py index d3713d0..57c532f 100644 --- a/src/extensions/economy/update.py +++ b/src/extensions/economy/update.py @@ -1,8 +1,10 @@ import typing as t +import hikari import lightbulb import sqlite3 from lightbulb.ext import tasks +from lightbulb.ext.tasks import CronTrigger from datetime import datetime from bot import get_setting @@ -11,7 +13,7 @@ leaderboardEco = [] leaderboardEcoLastRefresh = None -@tasks.task(m=30, auto_start=True) +@tasks.task(CronTrigger("0,30 * * * *"), auto_start=True) async def update_leaderboard(): global leaderboardEco, leaderboardEcoLastRefresh leaderboardEco = [] @@ -48,6 +50,10 @@ async def update_leaderboard(): cursor.close() db.close() +@plugin.listener(hikari.StartedEvent) +async def on_start(event: hikari.StartedEvent) -> None: + await update_leaderboard() + def getLeaderboard() -> list: return leaderboardEco diff --git a/src/extensions/general/customize.py b/src/extensions/general/customize.py index 05f8597..396717d 100644 --- a/src/extensions/general/customize.py +++ b/src/extensions/general/customize.py @@ -23,7 +23,7 @@ def __init__(self, inventory: Inventory, profile: Card, newPreset: list) -> None self.newPreset = newPreset @miru.button(label='Save', emoji='🖨️', style=hikari.ButtonStyle.SUCCESS, row=1) - async def save(self, button: miru.Button, ctx: miru.ViewContext): + async def save(self, ctx: miru.ViewContext, button: miru.Button): self.cursor.execute('UPDATE profile SET active = 0 WHERE user_id = ?', (ctx.user.id,)) for item in self.newPreset: name, type = str(item).split('-') @@ -36,12 +36,12 @@ async def save(self, button: miru.Button, ctx: miru.ViewContext): self.stop() @miru.button(label='Cancel', emoji='✖️', style=hikari.ButtonStyle.DANGER, row=1) - async def cancel(self, button: miru.Button, ctx: miru.ViewContext): + async def cancel(self, ctx: miru.ViewContext, button: miru.Button): await ctx.edit_response(components=[]) self.stop() @miru.button(label='Preview', emoji='🔍', style=hikari.ButtonStyle.PRIMARY, row=1) - async def preview(self, button: miru.Button, ctx: miru.ViewContext): + async def preview(self, ctx: miru.ViewContext, button: miru.Button): bg = Image.open(f'assets/img/general/profile/banner/{self.newPreset[0]}.png').convert('RGBA') card = Image.open(f'assets/img/general/profile/base/{self.newPreset[1]}.png').convert('RGBA') nametag = Image.open(f'assets/img/general/profile/nametag/{self.newPreset[2]}.png').convert('RGBA') @@ -61,7 +61,7 @@ async def view_check(self, ctx: ViewContext) -> bool: @lightbulb.implements(lightbulb.SlashCommand) async def customize(ctx: lightbulb.Context, banner: str, base: str, nametag: str) -> None: inventory = Inventory(ctx.member) - profile = Card(ctx.member) + profile = Card(ctx.member, ctx) oldPreset = inventory.get_active_customs() activePreset = [banner, base, nametag] @@ -86,8 +86,9 @@ async def customize(ctx: lightbulb.Context, banner: str, base: str, nametag: str .set_footer(text='This action cannot be undone.') ) - message = await ctx.respond(embed, components=view.build(), flags=hikari.MessageFlag.EPHEMERAL) - await view.start(message) + await ctx.respond(embed, components=view.build(), flags=hikari.MessageFlag.EPHEMERAL) + client = ctx.bot.d.get('client') + client.start_view(view) @customize.autocomplete('banner', 'base', 'nametag') async def search_autocomplete(opt: hikari.AutocompleteInteractionOption, ctx: hikari.AutocompleteInteraction): diff --git a/src/extensions/general/fish.py b/src/extensions/general/fish.py new file mode 100644 index 0000000..fb33698 --- /dev/null +++ b/src/extensions/general/fish.py @@ -0,0 +1,265 @@ +import hikari +import hikari.emojis +import lightbulb +import miru +import random + +from lightbulb.ext import tasks +from lightbulb.ext.tasks import CronTrigger +from miru.abc.item import InteractiveViewItem + +from utils.economy.manager import EconomyManager +from utils.fishing.bait import Bait +from utils.fishing.config_loader import FishingConfigLoader +from utils.fishing.fish import Fish +from utils.fishing.inventory import Inventory +from utils.fishing.location import Location +from utils.general.config import get_setting + +plugin = lightbulb.Plugin('Fish') + +economy = EconomyManager() +data = FishingConfigLoader() +todaysWeather = None + +@tasks.task(CronTrigger('0 0 * * *'), auto_start=True) +async def update_weather() -> None: + global todaysWeather + weathers = list(data.weathers.values()) + weights = [weather.weight for weather in weathers] + todaysWeather = random.choices(weathers, weights=weights, k=1)[0] + +@plugin.listener(hikari.StartedEvent) +async def on_start(event: hikari.StartedEvent) -> None: + await update_weather() + +class FishMenuView(miru.View): + def __init__(self, author: hikari.User, inventory: Inventory, location: Location,) -> None: + super().__init__(timeout=300) + self.inventory = inventory + self.author = author + self.location = location + self.add_item( + miru.TextSelect( + custom_id='bait_select', + placeholder='Select a Bait', + options=[ + miru.SelectOption( + label=f'{bait.name} ({self.inventory.get_bait_amount(bait)}x)', + description=bait.tooltip, + emoji=bait.emoji, + value=bait.id + ) for bait in sorted(self.inventory.get_baits(), key=lambda bait: (bait.success_rate_bonus, bait.quantity_bonus, bait.rarity_bonus), reverse=True) + ] + ) + ) + # self.location.print_fish_percentages(self.location.rarity_bonus + todaysWeather.rarity_bonus) + + async def _handle_callback(self, item: InteractiveViewItem, ctx: miru.ViewContext) -> None: + bait = data.baits[ctx.interaction.values[0]] + + if self.inventory.get_bait_amount(bait) == 0: + embed = hikari.Embed( + description='You do not have enough bait to fish with that!', + color=get_setting('general', 'embed_error_color') + ) + return await ctx.respond(embed=embed, flags=hikari.MessageFlag.EPHEMERAL) + + fish = self.location.get_fish(bait, todaysWeather) + self.inventory.update_bait(bait, self.inventory.get_bait_amount(bait) - 1) + + if self.inventory.get_bait_amount(bait) == 0: + self.inventory.delete_bait(bait) + + if fish is None: + embed = hikari.Embed( + title='Oh no! The fish got away!', + description='You left empty-handed this time. Better luck next time!', + color=get_setting('general', 'embed_error_color') + ) + return await ctx.edit_response(embed=embed, components=[], flags=hikari.MessageFlag.EPHEMERAL) + + self.stop() + view = FishCaughtView(ctx.author, self.inventory, bait, self.location, fish) + await ctx.edit_response(embed=view.embed, components=view.build()) + self.client.start_view(view) + + async def view_check(self, ctx: miru.ViewContext) -> bool: + return ctx.user.id == self.author.id + +class FishCaughtView(miru.View): + def __init__(self, author: hikari.User, inventory: Inventory, bait: Bait, location: Location, fish: Fish) -> None: + super().__init__(timeout=300) + self.author = author + self.inventory = inventory + self.location = location + self.fish = fish + self.quantity = fish.get_fish_quantity(bait.quantity_bonus + todaysWeather.quantity_bonus + location.quantity_bonus) + self.salvageDrops = fish.combine_salvages(self.quantity, randomize=True) + self.embed = self.get_message() + self.add_buttons() + + async def _handle_callback(self, item: InteractiveViewItem, ctx: miru.ViewContext) -> None: + option = ctx.interaction.custom_id + + match option: + case 'sell_button': + economy.add_money(ctx.user.id, self.fish.price * self.quantity, True) + embed = hikari.Embed( + title='Money Received!', + description=f'You sold **{self.fish.emoji} {self.fish.name} ({self.quantity}x)** for 🪙 {self.fish.price * self.quantity}.', + color=get_setting('general', 'embed_success_color') + ) + await ctx.edit_response(components=[]) + return await ctx.respond(embed=embed) + case 'salvage_button': + embed = hikari.Embed(color=get_setting('general', 'embed_color')) + if self.salvageDrops == []: + embed.title = 'Salvage Results' + embed.description = f'You did not receive any salvage drops from **{self.fish.emoji} {self.fish.name}**.' + flags = hikari.MessageFlag.EPHEMERAL + elif len(self.inventory.get_baits()) > (25 - len(self.salvageDrops)): + embed.title = 'Salvage Failed!' + embed.description = f'You do not have enough space in your inventory to salvage **{self.fish.emoji} {self.fish.name}**.' + flags = hikari.MessageFlag.EPHEMERAL + else: + for bait, amount in self.salvageDrops: + self.inventory.update_bait(bait, self.inventory.get_bait_amount(bait) + amount) + + baits = [ + f'> **{bait.emoji} {bait.name} ({amount}x)**\n' + f'> -# {bait.description}\n' + for bait, amount in self.salvageDrops + ] + + embed.title = 'Salvage Successful!' + embed.description = ( + f'You salvaged **{self.fish.emoji} {self.fish.name}** and received the following items:\n\n' + f'{str.format("".join(baits))}\n' + '-# Your salvage drops have been added to your inventory.' + ) + embed.color = get_setting('general', 'embed_success_color') + flags = hikari.MessageFlag.NONE + await ctx.edit_response(components=[]) + return await ctx.respond(embed=embed, flags=flags) + case 'info_button': + baits = [ + f'> **{bait.emoji} {bait.name} ({amount}x)**\n' + f'> ⤷ {bait.tooltip}' + for bait, amount in self.salvageDrops + ] + + embed = hikari.Embed( + title='Salvage Information', + description=( + f'**{self.fish.emoji} {self.fish.name}** can be salvaged into the following items:\n\n' + f'{str.format("".join(baits))}\n' + '‎' + ), + color=get_setting('general', 'embed_color') + ) + embed.set_thumbnail(hikari.emojis.Emoji.parse(self.fish.emoji).url) + return await ctx.respond(embed=embed, flags=hikari.MessageFlag.EPHEMERAL) + + def get_message(self) -> hikari.Embed: + embed = hikari.Embed( + title=f'Hooray! You caught a fish!', + description=( + f'I caught a **{self.fish.emoji} {self.fish.name}**! {self.fish.description}\n\n' + f'> **Quantity**: {self.fish.emoji} {self.quantity}x\n' + f'> **Sell Price**: 🪙 {self.fish.price * self.quantity}\n' + f'> **Salvage Drops**: {", ".join(f"{bait.emoji} {bait.name} ({amount}x)" for bait, amount in self.salvageDrops)}\n\n' + '-# What would you like to do with it?' + ), + color=get_setting('general', 'embed_color') + ) + embed.set_thumbnail(hikari.emojis.Emoji.parse(self.fish.emoji).url) + return embed + + def add_buttons(self) -> None: + self.add_item( + miru.Button( + custom_id='sell_button', + label='Sell', + emoji='🪙', + style=hikari.ButtonStyle.PRIMARY + ) + ) + self.add_item( + miru.Button( + custom_id='salvage_button', + label=f'Salvage', + emoji='♻️', + style=hikari.ButtonStyle.DANGER + ) + ) + self.add_item( + miru.Button( + custom_id='info_button', + emoji='❔', + style=hikari.ButtonStyle.SECONDARY + ) + ) + + async def view_check(self, ctx: miru.ViewContext) -> bool: + return ctx.user.id == self.author.id + +@plugin.command +@lightbulb.app_command_permissions(dm_enabled=False) +@lightbulb.option('location', 'The location to fish at.', choices=[location.name for location in data.locations.values()]) +@lightbulb.command('fish', 'Catch a fish.') +@lightbulb.implements(lightbulb.SlashCommand) +async def fish(ctx: lightbulb.Context) -> None: + inventory = Inventory(ctx.author) + + if inventory.get_baits() == []: + embed = hikari.Embed( + description="You're out of bait! Head over to `/shop` to purchase some to continue fishing.", + color=get_setting('general', 'embed_error_color') + ) + return await ctx.respond(embed=embed, flags=hikari.MessageFlag.EPHEMERAL) + + for locationItem in data.locations.values(): + if locationItem.name == ctx.options['location']: + location = locationItem + + sortedFish = sorted(location.fish, key=lambda fish: (fish.weight, -fish.price), reverse=True) + + embed = hikari.Embed( + title=f'Fishing at {location.name}', + description=( + f'{location.description} ' + f'{todaysWeather.description}\n\n' + f'> **Weather**: {todaysWeather.emoji} {todaysWeather.name}\n' + f'> **Catch Bonus**: `{todaysWeather.success_rate_bonus * 100:.0f}%`\n' + f'> **Quantity Bonus**: `{todaysWeather.quantity_bonus * 100:.0f}%`\n' + f'> **Rarity Bonus**: `{todaysWeather.rarity_bonus * 100:.0f}%`\n' + f'‎' + ), + color=get_setting('general', 'embed_color') + ) + + embed.add_field( + name='Available Fish', + value='\n'.join(f'{fish.emoji} {fish.name}' for fish in sortedFish) or 'N/A', + inline=True + ) + embed.add_field( + name='Sell Price', + value='\n'.join(f'🪙 {fish.price}' for fish in sortedFish) or 'N/A', + inline=True + ) + embed.add_field( + name='Rarity', + value='\n'.join(f'{location.get_fish_rarity(fish, todaysWeather)}' for fish in sortedFish) or 'N/A', + inline=True + ) + embed.set_thumbnail(hikari.emojis.Emoji.parse(location.emoji).url) + + view = FishMenuView(ctx.author, inventory, location) + await ctx.respond(embed, components=view.build()) + client = ctx.bot.d.get('client') + client.start_view(view) + +def load(bot): + bot.add_plugin(plugin) \ No newline at end of file diff --git a/src/extensions/general/fishpedia.py b/src/extensions/general/fishpedia.py new file mode 100644 index 0000000..64e537b --- /dev/null +++ b/src/extensions/general/fishpedia.py @@ -0,0 +1,93 @@ +import hikari +import hikari.emojis +import lightbulb +import itertools + +from extensions.general.fish import data +from utils.fishing.bait import Bait +from utils.fishing.fish import Fish +from utils.fishing.location import Location +from utils.fishing.weather import Weather +from utils.general.config import get_setting + +plugin = lightbulb.Plugin('Fish') + +terms = list(itertools.chain(data.baits.values(), data.fishes.values(), data.locations.values(), data.weathers.values())) + +class FishpediaTerm: + def __init__(self, name: str, value: Bait | Fish | Location | Weather): + self.name = name + self.value = f'{value.__class__.__name__},{value.id}' + + def __str__(self) -> str: + return self.name + +@plugin.command +@lightbulb.app_command_permissions(dm_enabled=False) +@lightbulb.option('term', 'The term to search for.', autocomplete=True, required=True) +@lightbulb.command('fishpedia', 'Get information on anything related to fishing.', pass_options=True) +@lightbulb.implements(lightbulb.SlashCommand) +async def fishpedia(ctx: lightbulb.Context, term: str) -> None: + termType, termId = term.split(',') + + data_map = { + 'Bait': data.baits, + 'Fish': data.fishes, + 'Location': data.locations, + 'Weather': data.weathers + } + + if termType not in data_map: + embed = hikari.Embed( + description='Invalid term type.', + color=get_setting('general', 'embed_error_color') + ) + return await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) + + item_dict = data_map[termType] + item = item_dict.get(termId) + + if not item: + embed = hikari.Embed( + description='Item not found.', + color=get_setting('general', 'embed_error_color') + ) + return await ctx.respond("Item not found.", flags=hikari.MessageFlag.EPHEMERAL) + + def create_embed(item, is_fish=False): + if is_fish: + salvage = item.combine_salvages(1, randomize=False) + description = ( + f'{item.description}\n\n' + f'> **Price**: 🪙 {item.price}\n' + f'> **Quantity**: {item.min} - {item.max}\n' + f'> **Salvage Drops**: {", ".join(f"{bait.emoji} {bait.name} ({count}x)" for bait, count in salvage)}' + ) + else: + description = ( + f'{item.description}\n\n' + f'> **Success Rate Bonus**: `{item.success_rate_bonus * 100:.0f}%`\n' + f'> **Quantity Bonus**: `{item.quantity_bonus * 100:.0f}%`\n' + f'> **Rarity Bonus**: `{item.rarity_bonus * 100:.0f}%`' + ) + + return hikari.Embed( + title=f'{item.name}', + description=description, + color=get_setting('general', 'embed_color') + ).set_thumbnail(hikari.emojis.Emoji.parse(item.emoji).url) + + embed = create_embed(item, is_fish=(termType == 'Fish')) + await ctx.respond(embed) + +@fishpedia.autocomplete('term') +async def search_autocomplete(opt: hikari.AutocompleteInteractionOption, inter: hikari.AutocompleteInteraction): + resultList = [] + for term in terms: + if term.name.lower().startswith(opt.value.lower()) and len(resultList) < 25: + option = FishpediaTerm(term.name, term) + resultList.append(option) + return resultList + +def load(bot): + bot.add_plugin(plugin) \ No newline at end of file diff --git a/src/extensions/general/help.py b/src/extensions/general/help.py index 14e38b4..3353b31 100644 --- a/src/extensions/general/help.py +++ b/src/extensions/general/help.py @@ -4,7 +4,7 @@ from miru.ext import nav from bot import get_setting, get_commands -from utils.pokemon.inventory import NavPageInfo +from utils.general.navigator import NavPageInfo VERSION = get_setting('bot', 'version') diff --git a/src/extensions/general/shop.py b/src/extensions/general/shop.py index 2eb70d3..fa1dd40 100644 --- a/src/extensions/general/shop.py +++ b/src/extensions/general/shop.py @@ -2,155 +2,84 @@ import lightbulb import miru -from miru.context import ViewContext -from miru.context.view import ViewContext from miru.ext import nav -from PIL import Image -from bot import get_setting, verify_user, register_user -from utils.pokemon.inventory import NavPageInfo -from utils.economy.manager import EconomyManager +from bot import get_setting +from utils.economy.shop.fishing.view import FishingShopView +from utils.economy.shop.profile.navigator import NavShopSelectView +from utils.economy.shop.profile.checks import ChecksView +from utils.general.navigator import NavPageInfo from utils.profile.inventory import Inventory -from utils.profile.card import Card plugin = lightbulb.Plugin('Shop') -economy = EconomyManager() -class ProfileShopSelect(miru.SelectOption): - def __init__(self, item: tuple) -> None: - super().__init__(label=item[0], description='Click To View', value=item[1]) - -class NavShopSelectView(nav.NavTextSelect): - def __init__(self, inventory:Inventory, items: list, maxItems: int, row: int) -> None: - self.inventory = inventory - self.items = items - self.maxItems = maxItems - self.options = [ProfileShopSelect(item) for item in self.get_items(0)] - - super().__init__( - placeholder='Select a Item', - options=self.options, - row=row, - ) - - async def callback(self, ctx: ViewContext) -> None: - if self.inventory.user.id != ctx.user.id: - return - - profile = Card(plugin.bot.cache.get_member(ctx.get_guild(), ctx.user)) - selected = self.values[0] - name = selected.replace('_', ' ').title().split('-') - - if self.inventory.get_profile_item(selected.split('-')) == False: - bg, card, nametag = self.inventory.get_active_customs() - bg = Image.open(f'assets/img/general/profile/banner/{bg}.png').convert('RGBA') - card = Image.open(f'assets/img/general/profile/base/{card}.png').convert('RGBA') - nametag = Image.open(f'assets/img/general/profile/nametag/{nametag}.png').convert('RGBA') - - match selected.split('-')[1]: - case 'banner': - bg = Image.open(f'assets/img/general/profile/banner/{selected}.png').convert('RGBA') - case 'base': - card = Image.open(f'assets/img/general/profile/base/{selected}.png').convert('RGBA') - case 'nametag': - nametag = Image.open(f'assets/img/general/profile/nametag/{selected}.png').convert('RGBA') - - embed = hikari.Embed(title=f'Do you want to purchase {name[0]} ({name[1]})?', description="This little maneuver's gonna cost you 51 years.", color=get_setting('general', 'embed_color')) - image = await profile.draw_card(bg, card, nametag) - embed.set_image(image) - embed.set_footer(text='This action cannot be undone.') - view = ProfileConfirmView(self.inventory, image, self.items, selected) - message = await ctx.respond(embed, components=view.build(), flags=hikari.MessageFlag.EPHEMERAL) - await view.start(message) - else: - embed = hikari.Embed(description='You already own this item!', color=get_setting('general', 'embed_error_color')) - return await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) - - async def before_page_change(self) -> None: - self.options = [ProfileShopSelect(item) for item in self.get_items(self.view.current_page)] - - def get_items(self, index: int): - pages = [] - for i in range(0, len(self.items), self.maxItems): - end = i + self.maxItems - page = [] - for option in self.items[i:end]: - currency, name, price = option - strName = str(name).replace('_', ' ').capitalize().split('-') - strName = f'{strName[0].title()} ({strName[1]})' - page.append((strName, name)) - pages.append(page) - return pages[index] - -class ProfileConfirmView(miru.View): - def __init__(self, inventory: Inventory, image: bytes, items: list, selected: str) -> None: - super().__init__(timeout=None, autodefer=True) - self.inventory = inventory - self.image = image - self.items = items - self.selected = selected - - @miru.button(label='Yes', style=hikari.ButtonStyle.SUCCESS, row=1) - async def yes(self, button: miru.Button, ctx: miru.ViewContext): - currency, name, price = [item for item in self.items if item[1] == self.selected][0] - - if verify_user(ctx.user) == None: # if user has never been register - register_user(ctx.user) - - if currency == 'coin' and economy.remove_money(ctx.user.id, price, True) == False: # checks if user has enough money - embed = hikari.Embed(description='You do not have enough money!', color=get_setting('general', 'embed_error_color')) - return await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) - elif currency == 'tpass' and economy.remove_ticket(ctx.user.id, price) == False: # checks if user has enough tickets - embed = hikari.Embed(description='You do not have enough tickets!', color=get_setting('general', 'embed_error_color')) - return await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) - - self.inventory.add_item((ctx.user.id, name.split("-")[0], name.split("-")[1], 0)) - - embed = hikari.Embed(title='Thank you for your purchase!', color=get_setting('general', 'embed_success_color')) - embed.set_image(self.image) - await ctx.respond(embed) +class ShopSelectView(miru.View): + def __init__(self, user: hikari.User) -> None: + super().__init__(timeout=None) + self.author = user + self.option = None - @miru.button(label='No', style=hikari.ButtonStyle.DANGER, row=1) - async def no(self, button: miru.Button, ctx: miru.ViewContext): - await ctx.edit_response(components=[]) + @miru.text_select( + custom_id='shop_select', + placeholder='Select a Shop', + options=[ + miru.SelectOption(label='Profile Shop', emoji='🎨', description='Personalize your profile with a wide range of options.', value='profile'), + miru.SelectOption(label='Fishing Shop', emoji='🎣', description='Find the perfect bait to reel in your next big catch.', value='fishing'), + ] + ) + async def shop_select(self, ctx: miru.ViewContext, select: miru.TextSelect) -> None: + self.option = select.values[0] self.stop() -class ChecksView(nav.NavigatorView): - def __init__(self, inventory: Inventory, pages, buttons, timeout, autodefer: bool = True) -> None: - super().__init__(pages=pages, items=buttons, timeout=timeout, autodefer=autodefer) - self.inventory = inventory - async def view_check(self, ctx: miru.ViewContext) -> bool: - return ctx.user.id == self.inventory.user.id + return ctx.user.id == self.author.id @plugin.command -@lightbulb.command('shop', 'Customize your profile using coins or tux passes', pass_options=True) +@lightbulb.command('shop', 'Spend your hard-earned coins and tickets on a variety of items!', pass_options=True) @lightbulb.implements(lightbulb.SlashCommand) async def customize(ctx: lightbulb.Context) -> None: - inventory = Inventory(ctx.member) - profiles = get_setting('profile') - coinItems = [] - tpassItems = [] - - # Iterate through 'coin' and add tuples to result - for item, price in profiles['coin'].items(): - coinItems.append(('coin', item, price)) - coinItems.sort(key=lambda x: x[2], reverse=True) - - # Iterate through 'tpass' and add tuples to result - for item, price in profiles['tpass'].items(): - tpassItems.append(('tpass', item, price)) - tpassItems.sort(key=lambda x: x[2], reverse=True) - - items = coinItems + tpassItems - - pages = inventory.get_pages(items, 10) - buttons = [NavShopSelectView(inventory, items, 10, row=1), nav.PrevButton(emoji='⬅️', row=2), NavPageInfo(len(pages), row=2), nav.NextButton(emoji='➡️', row=2)] - navigator = ChecksView(inventory, pages, buttons, timeout=None) + view = ShopSelectView(ctx.author) + embed = hikari.Embed( + title=f'Select a Shop', + description='Spend your hard-earned coins and tickets on a variety of items!', + color=get_setting('general', 'embed_color') + ) + + await ctx.respond(embed, components=view.build()) client = ctx.bot.d.get('client') - builder = await navigator.build_response_async(client=client, ephemeral=True) - await builder.create_initial_response(ctx.interaction) - client.start_view(navigator) + client.start_view(view) + await view.wait() + + # await ctx.delete_last_response() + + match view.option: + case 'profile': + inventory = Inventory(ctx.member) + profiles = get_setting('profile') + coinItems = [] + tpassItems = [] + + # Iterate through 'coin' and add tuples to result + for item, price in profiles['coin'].items(): + coinItems.append(('coin', item, price)) + coinItems.sort(key=lambda x: x[2], reverse=True) + + # Iterate through 'tpass' and add tuples to result + for item, price in profiles['tpass'].items(): + tpassItems.append(('tpass', item, price)) + tpassItems.sort(key=lambda x: x[2], reverse=True) + + items = coinItems + tpassItems + + pages = inventory.get_pages(items, 10) + buttons = [NavShopSelectView(ctx, inventory, items, 10, row=1), nav.PrevButton(emoji='⬅️', row=2), NavPageInfo(len(pages), row=2), nav.NextButton(emoji='➡️', row=2)] + navigator = ChecksView(inventory, pages, buttons, timeout=None) + await ctx.edit_last_response(embed=pages[0], components=navigator.build()) + client.start_view(navigator) + case 'fishing': + view = FishingShopView(ctx.author) + await ctx.edit_last_response(embed=view.embed, components=view.build()) + client.start_view(view) def load(bot): bot.add_plugin(plugin) \ No newline at end of file diff --git a/src/extensions/owner/ticket.py b/src/extensions/owner/ticket.py index 21420fe..d6820a1 100644 --- a/src/extensions/owner/ticket.py +++ b/src/extensions/owner/ticket.py @@ -26,7 +26,7 @@ async def set_ticket(ctx: lightbulb.Context, user: hikari.User, amount: int): embed = hikari.Embed(description='You are not allowed to set tickets to this user!', color=get_setting('general', 'embed_error_color')) return await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) elif verify_user(user) == None: - register_user(ctx.user) + register_user(user) economy.set_ticket(user.id, amount) embed = (hikari.Embed(description=f"You set {user.global_name}'s ticket amount to 🎟️ {amount:,}.", color=get_setting('general', 'embed_color'))) @@ -43,7 +43,7 @@ async def add(ctx: lightbulb.Context, user: hikari.User, amount: int): embed = hikari.Embed(description='You are not allowed to add tickets to this user!', color=get_setting('general', 'embed_error_color')) return await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) elif verify_user(user) == None: - register_user(ctx.user) + register_user(user) if economy.add_ticket(user.id, amount): embed = (hikari.Embed(description=f"You added {amount:,} 🎟️ to {user.global_name}'s wallet.", color=get_setting('general', 'embed_color'))) @@ -60,7 +60,7 @@ async def remove(ctx: lightbulb.Context, user: hikari.User, amount: int, update: embed = hikari.Embed(description='You are not allowed to take money from this user!', color=get_setting('general', 'embed_error_color')) return await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) elif verify_user(user) == None: - register_user(ctx.user) + register_user(user) if economy.remove_ticket(user.id, amount): embed = (hikari.Embed(description=f"You took {amount:,} 🎟️ from {user.global_name}'s wallet.", color=get_setting('general', 'embed_color'))) diff --git a/src/extensions/owner/wallet.py b/src/extensions/owner/wallet.py index 198d170..73225be 100644 --- a/src/extensions/owner/wallet.py +++ b/src/extensions/owner/wallet.py @@ -26,7 +26,7 @@ async def set_wallet(ctx: lightbulb.Context, user: hikari.User, amount: str): embed = hikari.Embed(description='You are not allowed to set money to this user!', color=get_setting('general', 'embed_error_color')) return await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) elif verify_user(user) == None: - register_user(ctx.user) + register_user(user) economy.set_money(user.id, amount) embed = (hikari.Embed(description=f"You set {user.global_name}'s money to 🪙 {amount:,}.", color=get_setting('general', 'embed_color'))) @@ -44,7 +44,7 @@ async def add(ctx: lightbulb.Context, user: hikari.User, amount: int, update: bo embed = hikari.Embed(description='You are not allowed to add money to this user!', color=get_setting('general', 'embed_error_color')) return await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) elif verify_user(user) == None: - register_user(ctx.user) + register_user(user) if economy.add_money(user.id, amount, update): embed = (hikari.Embed(description=f"You added 🪙 {amount:,} to {user.global_name}'s wallet.", color=get_setting('general', 'embed_color'))) @@ -62,7 +62,7 @@ async def remove(ctx: lightbulb.Context, user: hikari.User, amount: int, update: embed = hikari.Embed(description='You are not allowed to take money from this user!', color=get_setting('general', 'embed_error_color')) return await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) elif verify_user(user) == None: - register_user(ctx.user) + register_user(user) if economy.remove_money(user.id, amount, update): embed = (hikari.Embed(description=f"You took 🪙 {amount:,} from {user.global_name}'s wallet.", color=get_setting('general', 'embed_color'))) diff --git a/src/extensions/pokemon/pokeinfo.py b/src/extensions/pokemon/pokeinfo.py deleted file mode 100644 index 36eaa36..0000000 --- a/src/extensions/pokemon/pokeinfo.py +++ /dev/null @@ -1,193 +0,0 @@ -import hikari -import lightbulb -import miru -import sqlite3 - -from bot import get_setting -from utils.economy.manager import EconomyManager -from utils.pokemon.inventory import Inventory, PromptView - -plugin = lightbulb.Plugin('Pokeinfo') -economy = EconomyManager() - -class InfoMenu(miru.View): - def __init__(self, owner_id: str,) -> None: - super().__init__(timeout=None) - self.owner_id = int(owner_id) - - async def view_check(self, ctx: miru.ViewContext) -> bool: - if ctx.user.id != self.owner_id: - embed = hikari.Embed(description='You are not the owner of this card!', color=get_setting('general', 'embed_error_color')) - await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL, delete_after=10) - return ctx.user.id == self.owner_id - -class FavoriteButton(miru.Button): - def __init__(self, embed: hikari.Embed, indents: str, card_id: str, favorite_symbol: str) -> None: - super().__init__(emoji=favorite_symbol, style=hikari.ButtonStyle.PRIMARY, row=1, custom_id='favorite_button') - self.db = sqlite3.connect(get_setting('general', 'database_data_dir')) - self.cursor = self.db.cursor() - self.embed = embed - self.indents = indents - self.card_id = card_id - - async def callback(self, ctx: miru.ViewContext) -> None: - inventory = Inventory(ctx, ctx.user) - result = inventory.get_item(self.card_id) - - if not result: - embed = hikari.Embed(description='This card does not exist!', color=get_setting('general', 'embed_error_color')) - await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) - return - else: - name, card = result - card_id, userID, date, name, pokemon_id, rarity, shiny, favorite = card - - favorite = not favorite - self.cursor.execute('UPDATE pokemon SET favorite = ? WHERE id = ?', (int(favorite), self.card_id,)) - self.db.commit() - - if favorite: - favorite_symbol = '<:favorite_icon:1265770511858794496>' - else: - favorite_symbol = '<:unfavorite_icon:1265770609732751451>' - - self.embed.title = f'{name} Card {self.indents}{favorite_symbol}' if not shiny else f'Shiny {name} Card {self.indents}{favorite_symbol}' - self.emoji = favorite_symbol - await ctx.edit_response(self.embed, components=self.view.build()) - - def __del__(self): - self.db.close() - self.cursor.close() - -class SellButton(miru.Button): - def __init__(self, card_id: str) -> None: - super().__init__(label='Sell Card', emoji='🗑️', style=hikari.ButtonStyle.DANGER, row=1, custom_id='trash_button') - self.db = sqlite3.connect(get_setting('general', 'database_data_dir')) - self.cursor = self.db.cursor() - self.card_id = card_id - - async def callback(self, ctx: miru.ViewContext) -> None: - inventory = Inventory(ctx, ctx.user) - result = inventory.get_item(self.card_id) - - if not result: - embed = hikari.Embed(description='This card does not exist!', color=get_setting('general', 'embed_error_color')) - await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) - return - else: - name, card = result - card_id, userID, date, name, pokemon_id, rarity, shiny, favorite = card - price = 20 - - if shiny: - price = price * 2 - - if favorite: - embed = hikari.Embed(description=f'You cannot sell favorite cards!', color=get_setting('general', 'embed_error_color')) - await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL, delete_after=10) - return - - view = PromptView() - - embed = hikari.Embed(title=f'Are you sure?', description=f'You will get 🪙 {price} for selling {name}.', color=get_setting('general', 'embed_error_color')) - embed.set_footer('This action is irreversible!') - message = await ctx.respond(embed, components=view.build(), flags=hikari.MessageFlag.EPHEMERAL) - - client = ctx.bot.d.get('client') - client.start_view(view) - await view.wait() - - if view.answer: - if not inventory.get_item(self.card_id): - embed = hikari.Embed(description='This card does not exist!', color=get_setting('general', 'embed_error_color')) - await ctx.edit_response(embed, components=[], flags=hikari.MessageFlag.EPHEMERAL) - return - economy.add_money(userID, price, True) - try: - self.db.execute('DELETE FROM pokemon WHERE id = ?', (card_id,)) - self.db.commit() - except sqlite3.Error as e: - self.db.rollback() - print("Error deleting item from the database:", e) - await ctx.edit_response(hikari.Embed(description=f'You sold {name} for 🪙 {price}!', color=get_setting('general', 'embed_success_color')), components=[], flags=hikari.MessageFlag.EPHEMERAL) - else: - await ctx.edit_response(hikari.Embed(description=f'Selling process has been cancelled.', color=get_setting('general', 'embed_error_color')), components=[], flags=hikari.MessageFlag.EPHEMERAL) - - def __del__(self): - self.db.close() - self.cursor.close() - -@plugin.command -@lightbulb.app_command_permissions(dm_enabled=False) -@lightbulb.option('uuid', 'Enter a pack or card ID to get more info on it.', type=str, required=True) -@lightbulb.command('pokeinfo', 'Obtain additional details on a Pokémon card or pack.', pass_options=True) -@lightbulb.implements(lightbulb.SlashCommand) -async def open(ctx: lightbulb.Context, uuid: str) -> None: - db = sqlite3.connect(get_setting('general', 'database_data_dir')) - cursor = db.cursor() - - result = None - - cursor.execute("SELECT id, user_id, date, name FROM pokemon WHERE id = ? AND name IN (?, ?)", (uuid, 'Standard', 'Premium')) - pack = cursor.fetchone() - if pack: - result = 'Pack', pack - - cursor.execute("SELECT * FROM pokemon WHERE id = ? AND name NOT IN (?, ?)", (uuid, 'Standard', 'Premium')) - card = cursor.fetchone() - if card: - result = 'Card', card - - if not result: - embed = hikari.Embed(description='Card or pack was not found!', color=get_setting('general', 'embed_error_color')) - await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) - return - - name, item = result - if name == 'Pack': - packID, userID, date, pack_type = item - view = InfoMenu(userID) - embed = hikari.Embed(title=f'{pack_type} Pack', description='A standard pack that contains 7 Pokémon cards.' if pack_type == 'Standard' else 'A premium pack that contains 7 high quality Pokémon cards.', color=get_setting('general', 'embed_color')) - embed.set_thumbnail(f'assets/img/pokemon/{pack_type.lower()}_pack_icon.png') - embed.add_field(name='Owner', value=f'<@{userID}>', inline=True) - embed.add_field(name='Obtained', value=date, inline=True) - embed.add_field(name='Pack ID', value=f'`{packID}`', inline=False) - embed.set_footer('Type `/packshop` to buy packs!') - elif name == 'Card': - card_id, userID, date, name, pokemon_id, rarity, is_shiny, favorite = item - view = InfoMenu(userID) - - if is_shiny: - rarity_symbol = '🌟' - shiny = True - indents = '\t\t\t\t\t\t\t\t\t' - else: - rarity_symbol = '⭐' - shiny = False - indents = '\t\t\t\t\t\t\t\t\t\t\t' - - if favorite: - favorite_symbol = '<:favorite_icon:1265770511858794496>' - else: - favorite_symbol = '<:unfavorite_icon:1265770609732751451>' - - embed = hikari.Embed(title=f'{name} Card {indents}{favorite_symbol}' if not shiny else f'Shiny {name} Card {indents}{favorite_symbol}', color=get_setting('general', 'embed_color')) - embed.set_image(f'https://img.pokemondb.net/sprites/home/normal/{name.lower()}.png' if not shiny else f'https://img.pokemondb.net/sprites/home/shiny/{name.lower()}.png') - # embed.set_image(f'https://raw.githubusercontent.com/harshit23897/Pokemon-Sprites/master/assets/imagesHQ/{"{:03d}".format(pokemon_id)}.png') # If you want 2d sprites. This does not support shiny sprites. - embed.add_field(name='Owner', value=f'<@{userID}>', inline=True) - embed.add_field(name='Obtained', value=date, inline=True) - embed.add_field(name='Pokémon ID', value=pokemon_id, inline=True) - embed.add_field(name='Pokémon Name', value=name, inline=True) - embed.add_field(name='Card Quality', value=" ".join([rarity_symbol for i in range(rarity)]), inline=True) - embed.add_field(name='Card ID', value=f'`{card_id}`', inline=False) - embed.set_footer('Type `/packshop` to get Pokémon cards!') - - view.add_item(FavoriteButton(embed, indents, card_id, favorite_symbol)) - view.add_item(SellButton(card_id)) - - message = await ctx.respond(embed, components=view.build()) - client = ctx.bot.d.get('client') - client.start_view(view) - -def load(bot): - bot.add_plugin(plugin) \ No newline at end of file diff --git a/src/extensions/pokemon/pokeinv.py b/src/extensions/pokemon/pokeinv.py deleted file mode 100644 index a0bc64d..0000000 --- a/src/extensions/pokemon/pokeinv.py +++ /dev/null @@ -1,70 +0,0 @@ -import hikari -import lightbulb - -from miru.ext import nav -from typing import Optional - -from bot import get_setting -from utils.economy.manager import EconomyManager -from utils.pokemon.inventory import Inventory, SellView, NavPageInfo - -plugin = lightbulb.Plugin('Pokeinv') -economy = EconomyManager() - -@plugin.command -@lightbulb.app_command_permissions(dm_enabled=False) -@lightbulb.command('pokeinv', 'Organize your Pokémon cards and packs inventory.') -@lightbulb.implements(lightbulb.SlashCommandGroup) -async def inventory(ctx: lightbulb.Context) -> None: - return - -## Inventory View Command ## - -@inventory.child -@lightbulb.option('user', 'The user to get information about.', type=hikari.User, required=False) -@lightbulb.command('view', "Open a server member's pack inventory.", pass_options=True) -@lightbulb.implements(lightbulb.SlashSubCommand) -async def open(ctx: lightbulb.Context, user: Optional[hikari.User] = None) -> None: - if not (guild := ctx.get_guild()): - embed = hikari.Embed(description='This command may only be used in servers.', color=get_setting('general', 'embed_error_color')) - await ctx.respond(embed) - return - elif user != None and user.is_bot: # checks if the user is a bot - embed = hikari.Embed(description="You are not allowed to view this user's inventory!", color=get_setting('general', 'embed_error_color')) - await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) - return - - user = user or ctx.author - user = ctx.bot.cache.get_member(guild, user) - - if not user: - embed = hikari.Embed(description='That user is not in the server.', color=get_setting('general', 'embed_error_color')) - await ctx.respond(embed) - return - - inventory = Inventory(ctx, user) - pages = inventory.show_inventory(10) - buttons = [nav.FirstButton(row=1), nav.PrevButton(emoji='⬅️', row=1), NavPageInfo(len(pages), 1), nav.NextButton(emoji='➡️', row=1), nav.LastButton(row=1)] - navigator = nav.NavigatorView(pages=pages, items=buttons, timeout=None) - client = ctx.bot.d.get('client') - builder = await navigator.build_response_async(client=client, ephemeral=True) - await builder.create_initial_response(ctx.interaction) - client.start_view(navigator) - -## Sell Command ## - -@inventory.child -@lightbulb.command('sell', "Sell your cards.", pass_options=True) -@lightbulb.implements(lightbulb.SlashSubCommand) -async def sell(ctx: lightbulb.Context) -> None: - inventory = Inventory(ctx, ctx.author) - embed = hikari.Embed(title='Sell Menu', description='Favorite cards will **not** be accounted for in the algorithm. \nIn the future, additional selling options may become available. \n\n> Normal = 🪙 20 \n> Shiny = 🪙 40 \n‍', color=get_setting('general', 'embed_error_color')) - embed.set_thumbnail('assets/img/pokemon/convert_icon.png') - embed.set_footer(text='Once you sell cards, the action cannot be undone.') - view = SellView(embed, inventory) - message = await ctx.respond(embed, components=view.build(), flags=hikari.MessageFlag.EPHEMERAL) - - await view.start(message) - -def load(bot): - bot.add_plugin(plugin) \ No newline at end of file diff --git a/src/extensions/pokemon/pokeopen.py b/src/extensions/pokemon/pokeopen.py deleted file mode 100644 index bf64c32..0000000 --- a/src/extensions/pokemon/pokeopen.py +++ /dev/null @@ -1,42 +0,0 @@ -import hikari -import lightbulb - -from bot import get_setting -from utils.pokemon.inventory import Inventory -from utils.pokemon.pack import StandardPokemonCardPack, PremiumPokemonCardPack - -plugin = lightbulb.Plugin('Pokeopen') - -@plugin.command -@lightbulb.app_command_permissions(dm_enabled=False) -@lightbulb.option('uuid', 'Enter a pack ID you want to open.', type=str, required=True) -@lightbulb.command('pokeopen', 'Unbox a Pokémon card pack.', pass_options=True) -@lightbulb.implements(lightbulb.SlashCommand) -async def open(ctx: lightbulb.Context, uuid: str) -> None: - inventory = Inventory(ctx, ctx.user) - result = inventory.get_item(uuid) - - if not result: - embed = hikari.Embed(description='You do not own this pack!', color=get_setting('general', 'embed_error_color')) - return await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) - else: - name, item = result - - if name == 'Card': - embed = hikari.Embed(description='You cannot open a card!', color=get_setting('general', 'embed_error_color')) - return await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) - - packID, user, date, name = item - - if inventory.get_inventory_capacity() + get_setting('pokemon', 'pokemon_pack_amount') > inventory.max: - embed = hikari.Embed(description='You do not have enough inventory space!', color=get_setting('general', 'embed_error_color')) - return await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) - elif name == 'Standard': - pack = StandardPokemonCardPack(ctx.user, ctx) - else: - pack = PremiumPokemonCardPack(ctx.user, ctx) - - await pack.open(packID) - -def load(bot): - bot.add_plugin(plugin) \ No newline at end of file diff --git a/src/extensions/pokemon/pokeshop.py b/src/extensions/pokemon/pokeshop.py deleted file mode 100644 index 42036c8..0000000 --- a/src/extensions/pokemon/pokeshop.py +++ /dev/null @@ -1,62 +0,0 @@ -import hikari -import lightbulb -import miru - -from bot import get_setting, verify_user, register_user -from utils.economy.manager import EconomyManager -from utils.pokemon.pack import StandardPokemonCardPack, PremiumPokemonCardPack - -plugin = lightbulb.Plugin('Pokeshop') -economy = EconomyManager() - -class PackShop(miru.View): - def __init__(self) -> None: - super().__init__(timeout=None) - - @miru.button(label='Standard Pack', emoji='<:standard_booster_pack:1265770591319883919>', style=hikari.ButtonStyle.PRIMARY, custom_id='standard_pack_button') - async def standard(self, ctx: miru.ViewContext, button: miru.Button) -> None: - if verify_user(ctx.user) == None: # if user has never been register - register_user(ctx.user) - - if economy.remove_money(ctx.user.id, 200, True) == False: # checks if user has enough money - embed = hikari.Embed(description='You do not have enough money!', color=get_setting('general', 'embed_error_color')) - await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) - return - - pack = StandardPokemonCardPack(ctx.user, ctx) - await pack.buy() - - @miru.button(label='Premium Pack', emoji='<:premium_booster_pack:1265770581832503316>', style=hikari.ButtonStyle.PRIMARY, custom_id='premium_pack_button') - async def premium(self, ctx: miru.ViewContext, button: miru.Button) -> None: - if verify_user(ctx.user) == None: # if user has never been register - register_user(ctx.user) - - if economy.remove_ticket(ctx.user.id, 1) == False: # checks if user has enough tickets - embed = hikari.Embed(description='You do not have enough tickets!', color=get_setting('general', 'embed_error_color')) - return await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) - - pack = PremiumPokemonCardPack(ctx.user, ctx) - await pack.buy() - - @miru.button(label='What are Booster Packs?', emoji='❔', style=hikari.ButtonStyle.SECONDARY, custom_id='card_pack_info') - async def info(self, ctx: miru.ViewContext, button: miru.Button) -> None: - embed = hikari.Embed(title='What are Booster Packs?', description="Pokémon Booster Packs are packs of 7 randomly selected Pokémon cards, up to Gen 2, that enhance your Pokémon card collection. They include rare cards ranging from 1-5 ⭐, with a chance of finding a shiny Pokémon. You can purchase the standard booster packs using PokéCoins, and premium booster packs require 1 🎟️. Collect and trade cards with other players to build an impressive collection of Gen 2 and earlier Pokémon cards. Perfect for beginners and seasoned collectors alike.", color=get_setting('general', 'embed_color')) - embed.set_thumbnail('assets/img/pokemon/shop_icon.png') - await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) - -@plugin.command -@lightbulb.app_command_permissions(dm_enabled=False) -@lightbulb.command('pokeshop', 'Access the PokéShop menu.') -@lightbulb.implements(lightbulb.SlashCommand) -async def shop(ctx: lightbulb.Context) -> None: - embed = hikari.Embed(title='Welcome to the PokéShop!', description='Get your hands on the newest Pokémon cards at the Pokémon Booster Pack Shop! Simply choose from one of the two options below to start building your collection today. \n\nA standard booster pack costs 🪙 200, while the premium booster pack, which offers a **3x boost** on the chance of getting rare quality cards, can be yours for just 1 🎟️.', color=get_setting('general', 'embed_color')) - embed.set_thumbnail('assets/img/pokemon/shop_icon.png') - - view = PackShop() - await ctx.respond(embed, components=view.build()) - - client = ctx.bot.d.get('client') - client.start_view(view) - -def load(bot): - bot.add_plugin(plugin) \ No newline at end of file diff --git a/src/extensions/pokemon/poketrade.py b/src/extensions/pokemon/poketrade.py deleted file mode 100644 index 9e0aa85..0000000 --- a/src/extensions/pokemon/poketrade.py +++ /dev/null @@ -1,300 +0,0 @@ -import hikari -import lightbulb -import miru -import sqlite3 - -from bot import get_setting -from utils.pokemon.inventory import Inventory - -plugin = lightbulb.Plugin('Poketrade') - -class TradeView(miru.View): - def __init__(self, ctx: lightbulb.Context, embed: hikari.Embed, user1: hikari.User, user2: hikari.User) -> None: - super().__init__(timeout=120) - self.db = sqlite3.connect(get_setting('general', 'database_data_dir')) - self.cursor = self.db.cursor() - self.ctx = ctx - self.embed = embed - self.user1 = user1 - self.user1_ready = False - self.user1_offer = [] - self.user2 = user2 - self.user2_ready = False - self.user2_offer = [] - - @miru.button(label='Item', emoji='➕', style=hikari.ButtonStyle.SUCCESS, row=1, custom_id='add_item') - async def include_item(self, button: miru.Button, ctx: miru.ViewContext) -> None: - user = (self.user1, self.user1_offer) if ctx.user.id == self.user1.id else (self.user2, self.user2_offer) - modal = AddItemModal(user) - await ctx.respond_with_modal(modal) - await modal.wait() - - if not modal.item: - return - - name, item = modal.item - - if name == 'Pack': - itemID, userID, date, name = item - elif name == 'Card': - itemID, userID, date, name, pokemon_id, rarity, shiny, favorite = item - - if ctx.user.id == self.user1.id: - self.user1_ready = False - self.user1_offer.append((name, itemID)) - else: - self.user2_ready = False - self.user2_offer.append((name, itemID)) - - await self.update_trade_display() - - @miru.button(label='Ready', style=hikari.ButtonStyle.SUCCESS, row=1, custom_id='ready') - async def ready(self, button: miru.Button, ctx: miru.ViewContext) -> None: - if ctx.user.id == self.user1.id: - if not self.user1_ready: - self.user1_ready = True - self.embed.edit_field(0, f'{self.embed.fields[0].name} ✅', self.embed.fields[0].value) - else: - self.user1_ready = False - self.embed.edit_field(0, f'{self.user1.global_name} Trade Offer', self.embed.fields[0].value) - else: - if not self.user2_ready: - self.user2_ready = True - self.embed.edit_field(1, f'{self.embed.fields[1].name} ✅', self.embed.fields[1].value) - else: - self.user2_ready = False - self.embed.edit_field(1, f'{self.user2.global_name} Trade Offer', self.embed.fields[1].value) - - if self.user1_ready and self.user2_ready: - user1Inv = Inventory(self.ctx, self.user1) - user2Inv = Inventory(self.ctx, self.user2) - if user1Inv.get_inventory_capacity() + len(self.user2_offer) > user1Inv.max: - self.embed.title = 'Trade Error!' - self.embed.description = f'{self.user1.global_name} does not have enough inventory space!' - self.embed.color = get_setting('general', 'embed_error_color') - self.embed.set_footer(None) - self.stop() - return await self.ctx.edit_last_response(self.embed, components=[]) - elif user2Inv.get_inventory_capacity() + len(self.user1_offer) > user2Inv.max: - self.embed.title = 'Trade Error!' - self.embed.description = f'{self.user2.global_name} does not have enough inventory space!' - self.embed.color = get_setting('general', 'embed_error_color') - self.embed.set_footer(None) - self.stop() - return await self.ctx.edit_last_response(self.embed, components=[]) - else: - for item in self.user1_offer: - name, itemID = item - if user1Inv.get_item(itemID) == None: - self.embed.title = 'Trade Error!' - self.embed.description = f'{self.user1.global_name} no longer owns card(s) or pack(s)!' - self.embed.color = get_setting('general', 'embed_error_color') - self.embed.set_footer(None) - self.stop() - return await self.ctx.edit_last_response(self.embed, components=[]) - for item in self.user2_offer: - name, itemID = item - if user2Inv.get_item(itemID) == None: - self.embed.title = 'Trade Error!' - self.embed.description = f'{self.user2.global_name} no longer owns card(s) or pack(s)!' - self.embed.color = get_setting('general', 'embed_error_color') - self.embed.set_footer(None) - self.stop() - return await self.ctx.edit_last_response(self.embed, components=[]) - self.complete_trade() - self.embed.title = 'Trade has been completed!' - self.embed.color = get_setting('general', 'embed_success_color') - self.stop() - return await ctx.edit_response(self.embed, components=[]) - - await ctx.edit_response(self.embed) - - @miru.button(label='Item', emoji='🗑️', style=hikari.ButtonStyle.DANGER, row=1, custom_id='remove_item') - async def remove_item(self, button: miru.Button, ctx: miru.ViewContext) -> None: - user = (self.user1, self.user1_offer) if ctx.user.id == self.user1.id else (self.user2, self.user2_offer) - modal = RemoveItemModal(user) - await ctx.respond_with_modal(modal) - await modal.wait() - - if not modal.item_index: - return - elif ctx.user.id == self.user1.id: - self.user1_ready = False - self.user1_offer.pop(modal.item_index-1) - else: - self.user2_ready = False - self.user2_offer.pop(modal.item_index-1) - - await self.update_trade_display() - - @miru.button(label='Exit', style=hikari.ButtonStyle.DANGER, row=1, custom_id='exit') - async def exit(self, button: miru.Button, ctx: miru.ViewContext) -> None: - self.embed.title = f'{self.user1.global_name} has declined the trade!' if ctx.user.id == self.user1.id else f'{self.user2.global_name} has declined the trade!' - self.embed.color = get_setting('general', 'embed_error_color') - await ctx.edit_response(self.embed, components=[]) - self.stop() - - async def on_timeout(self) -> None: - self.embed.title = f'Trade menu has timed out!' - await self.ctx.edit_last_response(self.embed, components=[]) - self.stop() - - async def view_check(self, ctx: miru.ViewContext) -> bool: - return ctx.user.id == self.user1.id or ctx.user.id == self.user2.id - - async def update_trade_display(self) -> None: - user1_offer = [] - user2_offer = [] - - for name, itemID in self.user1_offer: - if name in ['Standard', 'Premium']: - self.cursor.execute('SELECT name FROM pokemon WHERE id = ?', (itemID,)) - item = f'• {self.cursor.fetchone()[0]} Pack' - else: - self.cursor.execute('SELECT name, rarity, shiny FROM pokemon WHERE id = ?', (itemID,)) - name, rarity, shiny = self.cursor.fetchone() - if shiny: - rarity_symbol = '🌟' - else: - rarity_symbol = '⭐' - - item = f'• {name} {" ".join([rarity_symbol for i in range(rarity)])}' - user1_offer.append(item) - for name, itemID in self.user2_offer: - if name == 'Pack': - self.cursor.execute('SELECT type FROM pokemon WHERE id = ?', (itemID,)) - item = f'• {self.cursor.fetchone()[0]} Pack' - elif name == 'Card': - self.cursor.execute('SELECT name, rarity, shiny FROM pokemon WHERE id = ?', (itemID,)) - name, rarity, shiny = self.cursor.fetchone() - - if shiny: - rarity_symbol = '🌟' - else: - rarity_symbol = '⭐' - - item = f'• {name} {" ".join([rarity_symbol for i in range(rarity)])}' - user2_offer.append(item) - - user1_offer_string = '\n'.join(user1_offer + ['• -' for i in range(10-len(user1_offer))]) - user2_offer_string = '\n'.join(user2_offer + ['• -' for i in range(10-len(user2_offer))]) - - self.embed.edit_field(0, self.embed.fields[0].name, f'`Item Limit: {len(user1_offer)}/10`\n{user1_offer_string}' if len(user1_offer) > 0 else f'`Item Limit: 0/10`\n' + '\n'.join(['• -' for i in range(10)])) - self.embed.edit_field(1, self.embed.fields[1].name, f'`Item Limit: {len(user2_offer)}/10`\n{user2_offer_string}' if len(user2_offer) > 0 else f'`Item Limit: 0/10`\n' + '\n'.join(['• -' for i in range(10)])) - await self.ctx.edit_last_response(self.embed) - - def complete_trade(self) -> None: - db = sqlite3.connect(get_setting('general', 'database_data_dir')) - cursor = db.cursor() - for item in self.user1_offer: - name, itemID = item - cursor.execute('UPDATE pokemon SET user_id = ? WHERE id = ?', (self.user2.id, itemID)) - db.commit() - for item in self.user2_offer: - name, itemID = item - cursor.execute('UPDATE pokemon SET user_id = ? WHERE id = ?', (self.user1.id, itemID)) - db.commit() - db.close() - -class AddItemModal(miru.Modal): - id = miru.TextInput(label='Enter Item UUID', placeholder='25aea761-7725-41da-969f-5d55a6af1519', custom_id='add_item_input', style=hikari.TextInputStyle.SHORT, required=True) - - def __init__(self, user: tuple) -> None: - super().__init__(title='Add Item Menu', custom_id='add_item_menu', timeout=None) - self.user, self.user_offer = user - self.item = None - - async def callback(self, ctx: miru.ModalContext) -> None: - inventory = Inventory(ctx, ctx.user) - result = inventory.get_item(self.id.value) - - if not result: - embed = hikari.Embed(title='Item Error', description='You do not own this item!', color=get_setting('general', 'embed_error_color')) - embed.set_thumbnail('assets/img/pokemon/trade_icon.png') - await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL, delete_after=10) - self.stop() - return - elif len(self.user_offer) >= 10: - embed = hikari.Embed(title='Item Error', description='You reached the item limit!', color=get_setting('general', 'embed_error_color')) - embed.set_thumbnail('assets/img/pokemon/trade_icon.png') - await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL, delete_after=10) - self.stop() - return - else: - self.item = result - name, item = self.item - - if name == 'Pack': - itemID, userID, date, name = item - name = f'{name} Booster Pack' - elif name == 'Card': - itemID, userID, date, name, pokemon_id, rarity, shiny, favorite = item - - for item in self.user_offer: - if itemID in item: - embed = hikari.Embed(title='Item Error', description='You already added this item!', color=get_setting('general', 'embed_error_color')) - embed.set_thumbnail('assets/img/pokemon/trade_icon.png') - self.item = None - break - else: - embed = hikari.Embed(title='Item Added', description=f'You added {name} (`{itemID}`) to the trade.', color=get_setting('general', 'embed_success_color')) - embed.set_thumbnail('assets/img/pokemon/trade_icon.png') - - await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL, delete_after=10) - -class RemoveItemModal(miru.Modal): - val = miru.TextInput(label='Enter Item Index', placeholder='1 - 10', custom_id='remove_item_input', style=hikari.TextInputStyle.SHORT, required=True) - - def __init__(self, user: tuple) -> None: - super().__init__(title='Remove Item Menu', custom_id='remove_item_menu', timeout=None) - self.user, self.user_offer = user - self.item_index = 0 - - async def callback(self, ctx: miru.ModalContext) -> None: - try: - if int(self.val.value) > len(self.user_offer) or int(self.val.value) < 1: - embed = hikari.Embed(title='Item Error', description='Invalid index!', color=get_setting('general', 'embed_error_color')) - embed.set_thumbnail('assets/img/pokemon/trade_icon.png') - await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL, delete_after=10) - self.item_index = None - self.stop() - return - else: - self.item_index = int(self.val.value) - except ValueError: - embed = hikari.Embed(title='Item Error', description='Input is not a number!', color=get_setting('general', 'embed_error_color')) - embed.set_thumbnail('assets/img/pokemon/trade_icon.png') - await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL, delete_after=10) - self.item_index = None - self.stop() - return - - embed = hikari.Embed(title='Item Removed', description=f'{self.user_offer[self.item_index-1][0]} was removed.', color=get_setting('general', 'embed_error_color')) - embed.set_thumbnail('assets/img/pokemon/trade_icon.png') - await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL, delete_after=10) - -@plugin.command -@lightbulb.app_command_permissions(dm_enabled=False) -@lightbulb.option('user', 'The user to trade with.', type=hikari.User, required=True) -@lightbulb.command('poketrade', 'Initiate a Pokémon item or card trade with a Discord member.', pass_options=True) -@lightbulb.implements(lightbulb.SlashCommand) -async def trade(ctx: lightbulb.Context, user: hikari.User) -> None: - if user.is_bot or ctx.author.id == user.id: # checks if the user is a bot or the sender - embed = hikari.Embed(description='You are not allowed to trade with this user!', color=get_setting('general', 'embed_error_color')) - await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL, delete_after=10) - return - - embed = hikari.Embed(title=f'Trading with {user.global_name}...', description='Use the buttons below to edit the Items/Cards in your trade.', color=get_setting('general', 'embed_color')) - embed.set_thumbnail('assets/img/pokemon/trade_icon.png') - embed.add_field(name=f'{ctx.author.global_name} Trade Offer', value='`Item Limit: 0/10`\n' + '\n'.join(['• -' for i in range(10)]), inline=True) - embed.add_field(name=f'{user.global_name} Trade Offer', value='`Item Limit: 0/10`\n' + '\n'.join(['• -' for i in range(10)]), inline=True) - embed.set_footer('Trade menu will time out in 2 minutes.') - - view = TradeView(ctx, embed, ctx.author, user) - message = await ctx.respond(embed, components=view.build()) - - await view.start(message) - await view.wait() - -def load(bot): - bot.add_plugin(plugin) \ No newline at end of file diff --git a/src/utils/daily/events/chest.py b/src/utils/daily/events/chest.py index e72e2f5..79a19eb 100644 --- a/src/utils/daily/events/chest.py +++ b/src/utils/daily/events/chest.py @@ -36,17 +36,17 @@ async def chests(self, ctx: miru.ViewContext, select: miru.TextSelect) -> None: case '0': chest = 'Old Chest' description = 'With a creaking sound, you gingerly open it. Inside, you find a modest sum of coins, their aged appearance hinting at the wisdom of the past. The Old Chest offers a reminder that even the simplest treasures hold value when viewed through the lens of history.' - img = 'assets/img/emotes/small_chest.png' + img = 'assets/img/emotes/general/small_chest.png' case '1': chest = 'Standard Chest' description = 'With a steady hand, you unlock it. Inside, you discover a sum of coins that neither beguiles nor overwhelms, leaving you with a sense of steady progression. The Standard Chest reaffirms the virtue of reliability in an unpredictable world.' - img = 'assets/img/emotes/medium_chest.png' + img = 'assets/img/emotes/general/medium_chest.png' case '2': chest = 'Luxurious Chest' description = 'Tempted by the allure of immense wealth, you decide to open it. With a dramatic flourish, the chest reveals a dazzling array of coins. Yet, you sense the weight of responsibility that accompanies such wealth, a reminder that great riches come with equally great risks.' - img = 'assets/img/emotes/large_chest.png' + img = 'assets/img/emotes/general/large_chest.png' - embed = hikari.Embed(title=f'You opened the {chest}', description=f'{description}\n\n> You earned 🪙 {amount}!\n> Your daily streak is now **{self.dailyManager.streak}**!\n\nCommand cooldown will reset at 12 AM EDT.', color=get_setting('general', 'embed_color'), timestamp=datetime.now().astimezone()) + embed = hikari.Embed(title=f'You opened the {chest}', description=f'{description}\n\n> You earned 🪙 {amount}!\n> Your daily streak is now **{self.dailyManager.streak}**!\n\nCommand cooldown will reset at 12 AM EST.', color=get_setting('general', 'embed_color'), timestamp=datetime.now().astimezone()) embed.set_footer(text=f'Requested by {ctx.author.global_name}', icon=ctx.author.display_avatar_url) embed.set_thumbnail(img) diff --git a/src/utils/daily/events/fox.py b/src/utils/daily/events/fox.py index 65be7c3..feb33de 100644 --- a/src/utils/daily/events/fox.py +++ b/src/utils/daily/events/fox.py @@ -35,7 +35,7 @@ async def fox(self, ctx: miru.ViewContext, select: miru.TextSelect) -> None: match select.values[0]: case '0': embed.title = 'A Touch of Fate' - embed.description = f'As you extend your hand to gently pet the curious red fox, you initiate a unique connection with the wild.\n\n> You earned 🪙 {amount}!\n> Your daily streak is now **{self.dailyManager.streak}**!\n\nCommand cooldown will reset at 12 AM EDT.' + embed.description = f'As you extend your hand to gently pet the curious red fox, you initiate a unique connection with the wild.\n\n> You earned 🪙 {amount}!\n> Your daily streak is now **{self.dailyManager.streak}**!\n\nCommand cooldown will reset at 12 AM EST.' case '1': embed.title = "Respecting Nature's Rhythm" embed.description = f'As you stand there amidst the tranquil forest, you recognize the importance of allowing nature to unfold at its own pace. The fox, in its natural habitat, is a symbol of the untamed, unscripted beauty of the wilderness.\n\n> You earned 🪙 {amount}!\n> Your daily streak is now **{self.dailyManager.streak}**!\n\nCommand cooldown will reset at 12 AM EDT.' diff --git a/src/utils/daily/events/mystery_box.py b/src/utils/daily/events/mystery_box.py index 1962f3b..c310818 100644 --- a/src/utils/daily/events/mystery_box.py +++ b/src/utils/daily/events/mystery_box.py @@ -43,7 +43,7 @@ async def get_users(self, ctx: miru.ViewContext, select: miru.UserSelect) -> Non economy.add_money(self.user.id, amount, True) economy.add_money(user.id, amount, True) - embed = hikari.Embed(title=f'A Bond Forged Through Sharing', description=f'As you choose to share the enigmatic mystery box with {user.global_name}, a sense of anticipation fills the air. Gently, you pass the box to {user.global_name}, and together, you both open it.\n\n> You and <@{user.id}> earned 🪙 {amount}!\n> Your daily streak is now **{self.dailyManager.streak}**!\n\nCommand cooldown will reset at 12 AM EDT.', color=get_setting('general', 'embed_color'), timestamp=datetime.now().astimezone()) + embed = hikari.Embed(title=f'A Bond Forged Through Sharing', description=f'As you choose to share the enigmatic mystery box with {user.global_name}, a sense of anticipation fills the air. Gently, you pass the box to {user.global_name}, and together, you both open it.\n\n> You and <@{user.id}> earned 🪙 {amount}!\n> Your daily streak is now **{self.dailyManager.streak}**!\n\nCommand cooldown will reset at 12 AM EST.', color=get_setting('general', 'embed_color'), timestamp=datetime.now().astimezone()) embed.set_thumbnail('assets/img/general/daily/question_mark.png') embed.set_footer(text=f'Requested by {ctx.author.global_name}', icon=ctx.author.display_avatar_url) diff --git a/src/utils/economy/shop/fishing/confirm.py b/src/utils/economy/shop/fishing/confirm.py new file mode 100644 index 0000000..30e69c7 --- /dev/null +++ b/src/utils/economy/shop/fishing/confirm.py @@ -0,0 +1,46 @@ +import hikari +import miru +from miru.abc.item import InteractiveViewItem + +from bot import get_setting +from utils.fishing.bait import Bait + + +class FishingConfirmView(miru.View): + def __init__(self, user: hikari.User, bait: Bait, amount: int) -> None: + super().__init__(timeout=None) + self.bait = bait + self.amount = amount + self.embed = hikari.Embed( + title='Purchase Confirmation', + description= f'Are you sure you want to purchase **{bait.emoji} {bait.name} ({amount}x)**?\n ', + color=get_setting('general', 'embed_color') + ) + self.add_item(miru.Button( + custom_id='confirm', + label='Confirm', + style=hikari.ButtonStyle.PRIMARY + )) + self.add_item(miru.Button( + custom_id='cancel', + label='Cancel', + style=hikari.ButtonStyle.DANGER + )) + self.author = user + self.response = None + + async def _handle_callback(self, item: InteractiveViewItem, ctx: miru.ViewContext) -> None: + if item.custom_id == 'confirm': + self.response = 'confirm' + elif item.custom_id == 'cancel': + self.response = 'cancel' + self.embed = hikari.Embed( + title='Purchase Cancelled', + description='You have cancelled the purchase of the bait.', + color=get_setting('general', 'embed_error_color') + ) + await ctx.edit_response(embed=self.embed, components=[]) + self.stop() + + async def view_check(self, ctx: miru.ViewContext) -> bool: + return ctx.user.id == self.author.id \ No newline at end of file diff --git a/src/utils/economy/shop/fishing/view.py b/src/utils/economy/shop/fishing/view.py new file mode 100644 index 0000000..2510f59 --- /dev/null +++ b/src/utils/economy/shop/fishing/view.py @@ -0,0 +1,77 @@ +import hikari +import hikari.emojis +import miru + +from miru.abc.item import InteractiveViewItem + +from bot import get_setting +from utils.economy.manager import EconomyManager +from utils.economy.shop.fishing.confirm import FishingConfirmView +from utils.fishing.config_loader import FishingConfigLoader +from utils.fishing.inventory import Inventory + +economy = EconomyManager() +baits = FishingConfigLoader().baits + +class FishingShopView(miru.View): + def __init__(self, user: hikari.User) -> None: + super().__init__(timeout=None) + self.items = [bait for bait in baits.values() if bait.price > 0] + self.embed = embed = hikari.Embed( + title='Welcome to the Fishing Shop!', + description=( + 'Here, you can purchase bait to help reel in your next catch. Each type of fishing bait is unique, offering different **special effects**. ' + 'While we only sell bait with basic effects, you can discover **rarer** bait while **salvaging** fish, so keep an eye out!\n\n' + "On **Saturday** and **Sunday**, we offer a **30% discount** on all our bait, so plan accordingly to make the most of our offers!" + ), + color=get_setting('general', 'embed_color') + ) + embed.add_field(name='Bait', value='\n'.join([f'{item.emoji} {item.name}' for item in self.items]), inline=True) + embed.add_field(name='Amount', value='\n'.join(['5x'] * len(self.items)), inline=True) + embed.add_field(name='Price', value='\n'.join([f'🪙 {round(item.current_price() * 5)}' for item in self.items]), inline=True) + embed.set_thumbnail(hikari.emojis.Emoji.parse('🎣').url) + self.menu = self.add_item(miru.TextSelect( + custom_id='bait_select', + placeholder='Select a Bait', + options=[ + miru.SelectOption( + label=f'{item.name} (5x)', + emoji=item.emoji, + description=item.tooltip, + value=item.id + ) for item in self.items + ] + )) + self.author = user + + async def _handle_callback(self, item: InteractiveViewItem, ctx: miru.ViewContext) -> None: + bait = baits[ctx.interaction.values[0]] + price = round(bait.current_price() * 5) + + view = FishingConfirmView(ctx.user, bait, 5) + await ctx.respond(view.embed, components=view.build(), flags=hikari.MessageFlag.EPHEMERAL) + client = ctx.client + client.start_view(view) + await view.wait() + + match view.response: + case 'confirm': + if economy.remove_money(ctx.user.id, price, True) is False: + embed = hikari.Embed( + title='Insufficient Funds', + description='You do not have enough money to purchase this bait.', + color=get_setting('general', 'embed_error_color') + ) + return await ctx.respond(embed=embed, flags=hikari.MessageFlag.EPHEMERAL) + else: + inventory = Inventory(ctx.user) + inventory.update_bait(bait, inventory.get_bait_amount(bait) + 5) + embed = hikari.Embed( + title='Purchase Successful', + description=f'You have successfully purchased **{bait.emoji} {bait.name} (5x)** for 🪙 {price}.', + color=get_setting('general', 'embed_success_color') + ) + return await ctx.respond(embed=embed) + + async def view_check(self, ctx: miru.ViewContext) -> bool: + return ctx.user.id == self.author.id \ No newline at end of file diff --git a/src/utils/economy/shop/profile/checks.py b/src/utils/economy/shop/profile/checks.py new file mode 100644 index 0000000..62cad14 --- /dev/null +++ b/src/utils/economy/shop/profile/checks.py @@ -0,0 +1,13 @@ +import miru + +from miru.ext import nav + +from utils.profile.inventory import Inventory + +class ChecksView(nav.NavigatorView): + def __init__(self, inventory: Inventory, pages, buttons, timeout, autodefer: bool = True) -> None: + super().__init__(pages=pages, items=buttons, timeout=timeout, autodefer=autodefer) + self.inventory = inventory + + async def view_check(self, ctx: miru.ViewContext) -> bool: + return ctx.user.id == self.inventory.user.id \ No newline at end of file diff --git a/src/utils/economy/shop/profile/confirm.py b/src/utils/economy/shop/profile/confirm.py new file mode 100644 index 0000000..d12baf1 --- /dev/null +++ b/src/utils/economy/shop/profile/confirm.py @@ -0,0 +1,41 @@ +import hikari +import miru + +from bot import get_setting, register_user, verify_user +from utils.economy.manager import EconomyManager +from utils.profile.inventory import Inventory + +economy = EconomyManager() + +class ProfileConfirmView(miru.View): + def __init__(self, inventory: Inventory, image: bytes, items: list, selected: str) -> None: + super().__init__(timeout=None, autodefer=True) + self.inventory = inventory + self.image = image + self.items = items + self.selected = selected + + @miru.button(label='Yes', style=hikari.ButtonStyle.SUCCESS, row=1) + async def yes(self, ctx: miru.ViewContext, button: miru.Button): + currency, name, price = [item for item in self.items if item[1] == self.selected][0] + + if verify_user(ctx.user) == None: # if user has never been register + register_user(ctx.user) + + if currency == 'coin' and economy.remove_money(ctx.user.id, price, True) == False: # checks if user has enough money + embed = hikari.Embed(description='You do not have enough money!', color=get_setting('general', 'embed_error_color')) + return await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) + elif currency == 'tpass' and economy.remove_ticket(ctx.user.id, price) == False: # checks if user has enough tickets + embed = hikari.Embed(description='You do not have enough tickets!', color=get_setting('general', 'embed_error_color')) + return await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) + + self.inventory.add_item((ctx.user.id, name.split('-')[0], name.split('-')[1], 0)) + + embed = hikari.Embed(title='Thank you for your purchase!', color=get_setting('general', 'embed_success_color')) + embed.set_image(self.image) + await ctx.respond(embed) + + @miru.button(label='No', style=hikari.ButtonStyle.DANGER, row=1) + async def no(self, ctx: miru.ViewContext, button: miru.Button): + await ctx.edit_response(components=[]) + self.stop() \ No newline at end of file diff --git a/src/utils/economy/shop/profile/navigator.py b/src/utils/economy/shop/profile/navigator.py new file mode 100644 index 0000000..8ceda00 --- /dev/null +++ b/src/utils/economy/shop/profile/navigator.py @@ -0,0 +1,77 @@ +import hikari +import lightbulb + +from miru import ViewContext +from miru.ext import nav +from PIL import Image + +from bot import get_setting +from utils.economy.shop.profile.confirm import ProfileConfirmView +from utils.economy.shop.profile.option import ProfileOptionSelect +from utils.profile.card import Card +from utils.profile.inventory import Inventory + + +class NavShopSelectView(nav.NavTextSelect): + def __init__(self, ctx: lightbulb.Context, inventory:Inventory, items: list, maxItems: int, row: int) -> None: + self.inventory = inventory + self.items = items + self.maxItems = maxItems + self.options = [ProfileOptionSelect(item) for item in self.get_items(0)] + self.ctx = ctx + + super().__init__( + placeholder='Select a Item', + options=self.options, + row=row, + ) + + async def callback(self, ctx: ViewContext) -> None: + if self.inventory.user.id != ctx.user.id: + return + + profile = Card(ctx.client.cache.get_member(ctx.get_guild(), ctx.user), self.ctx) + selected = self.values[0] + name = selected.replace('_', ' ').title().split('-') + + if self.inventory.get_profile_item(selected.split('-')) == False: + bg, card, nametag = self.inventory.get_active_customs() + bg = Image.open(f'assets/img/general/profile/banner/{bg}.png').convert('RGBA') + card = Image.open(f'assets/img/general/profile/base/{card}.png').convert('RGBA') + nametag = Image.open(f'assets/img/general/profile/nametag/{nametag}.png').convert('RGBA') + + match selected.split('-')[1]: + case 'banner': + bg = Image.open(f'assets/img/general/profile/banner/{selected}.png').convert('RGBA') + case 'base': + card = Image.open(f'assets/img/general/profile/base/{selected}.png').convert('RGBA') + case 'nametag': + nametag = Image.open(f'assets/img/general/profile/nametag/{selected}.png').convert('RGBA') + + embed = hikari.Embed(title=f'Do you want to purchase {name[0]} ({name[1]})?', color=get_setting('general', 'embed_color')) + image = await profile.draw_card(bg, card, nametag) + embed.set_image(image) + embed.set_footer(text='This action cannot be undone.') + view = ProfileConfirmView(self.inventory, image, self.items, selected) + await ctx.respond(embed, components=view.build(), flags=hikari.MessageFlag.EPHEMERAL) + client = ctx.client + client.start_view(view) + else: + embed = hikari.Embed(description='You already own this item!', color=get_setting('general', 'embed_error_color')) + return await ctx.respond(embed, flags=hikari.MessageFlag.EPHEMERAL) + + async def before_page_change(self) -> None: + self.options = [ProfileOptionSelect(item) for item in self.get_items(self.view.current_page)] + + def get_items(self, index: int): + pages = [] + for i in range(0, len(self.items), self.maxItems): + end = i + self.maxItems + page = [] + for option in self.items[i:end]: + currency, name, price = option + strName = str(name).replace('_', ' ').capitalize().split('-') + strName = f'{strName[0].title()} ({strName[1]})' + page.append((strName, name)) + pages.append(page) + return pages[index] \ No newline at end of file diff --git a/src/utils/economy/shop/profile/option.py b/src/utils/economy/shop/profile/option.py new file mode 100644 index 0000000..3177e8b --- /dev/null +++ b/src/utils/economy/shop/profile/option.py @@ -0,0 +1,5 @@ +import miru + +class ProfileOptionSelect(miru.SelectOption): + def __init__(self, item: tuple) -> None: + super().__init__(label=item[0], description='Click To View', value=item[1]) \ No newline at end of file diff --git a/src/utils/fishing/bait.py b/src/utils/fishing/bait.py new file mode 100644 index 0000000..609e399 --- /dev/null +++ b/src/utils/fishing/bait.py @@ -0,0 +1,75 @@ +from datetime import datetime +from enum import Enum + +class Days(Enum): + SATURDAY = 'Saturday' + SUNDAY = 'Sunday' + +class Bait: + def __init__(self, id: str, name: str, emoji: str, description: str, tooltip: str, price: float, success_rate_bonus: float, quantity_bonus: float, rarity_bonus: float) -> None: + """ + Initialize a new Bait instance. + + Parameters + ---------- + id : str + The ID of the bait. + name : str + The name of the bait. + emoji : str + The emoji representing the bait. + price : float + The price of the bait. + description : str + A description of the bait. + tooltip : str + A tooltip for the bait. + success_rate_bonus : int + The success rate of catching a fish with this bait. + quantity_bonus : int + The bonus quantity of fish caught with this bait. + rarity_bonus : int + The bonus rarity of fish caught with this bait. + """ + self.id = id + self.name = name + self.emoji = emoji + self.price = price + self.description = description + self.tooltip = tooltip + self.success_rate_bonus = success_rate_bonus + self.quantity_bonus = quantity_bonus + self.rarity_bonus = rarity_bonus + + def current_price(self) -> float: + """Calculate the current price, applying a discount if it's Saturday or Sunday.""" + today = datetime.now().strftime('%A') + if today in [Days.SATURDAY.value, Days.SUNDAY.value]: + return round(self.price * 0.7, 2) # Apply 30% discount + return self.price + + @staticmethod + def load_bait_from_config(bait_data): + """Load bait data from a configuration file.""" + return {key: Bait(id=key, **item) for key, item in bait_data.items()} + + def to_dict(self) -> dict: + """Convert the bait to a dictionary.""" + return { + 'id': self.id, + 'name': self.name, + 'emoji': self.emoji, + 'price': self.price, + 'description': self.description, + 'tooltip': self.tooltip, + 'success_rate_bonus': self.success_rate_bonus, + 'quantity_bonus': self.quantity_bonus, + 'rarity_bonus': self.rarity_bonus + } + + def __str__(self) -> str: + return ( + f'[{self.id}] {self.emoji} {self.name}: {self.description}, ' + f'Price: 🪙 {self.current_price()}, Success Rate: {self.success_rate_bonus * 100}%, ' + f'Quantity Bonus: {self.quantity_bonus * 100}%, Rarity Bonus: {self.rarity_bonus * 100}%' + ) \ No newline at end of file diff --git a/src/utils/fishing/config_loader.py b/src/utils/fishing/config_loader.py new file mode 100644 index 0000000..a03c56f --- /dev/null +++ b/src/utils/fishing/config_loader.py @@ -0,0 +1,70 @@ +import json + +from utils.fishing.bait import Bait +from utils.fishing.fish import Fish +from utils.fishing.weather import Weather +from utils.fishing.location import Location + +class FishingConfigLoader: + def __init__(self, path='settings.json'): + self.path = path + self.baits = {} + self.fishes = {} + self.weathers = {} + self.locations = {} + self.load_fishing_data() + + def load_fishing_data(self) -> None: + """Load fishing data from a configuration file.""" + with open('settings.json', 'r') as file: + config = json.load(file) + + bait = config.get('fishing', {}).get('bait', {}) + fish = config.get('fishing', {}).get('fish', {}) + weather = config.get('fishing', {}).get('weather', {}) + location = config.get('fishing', {}).get('location', {}) + + self.baits = Bait.load_bait_from_config(bait) + self.fishes = Fish.load_fish_from_config(fish, self.baits) + self.weathers = Weather.load_weather_from_config(weather) + self.locations = Location.load_location_from_config(location, self.fishes) + + @classmethod + def find_bait_by_id(cls, bait_id) -> Bait: + """Find a bait instance from its ID.""" + for bait in cls().baits.values(): + if bait.id == bait_id: + return bait + + @classmethod + def find_fish_from_id(cls, fish_id) -> Fish: + """Find a fish instance from its ID.""" + for fish in cls().fishes.values(): + if fish.id == fish_id: + return fish + + @classmethod + def find_weather_from_id(cls, weather_id) -> Weather: + """Find a weather instance from its ID.""" + for weather in cls().weathers.values(): + if weather.id == weather_id: + return weather + + @classmethod + def find_location_from_id(cls, location_id) -> Location: + """Find a location instance from its ID.""" + for location in cls().locations.values(): + if location.id == location_id: + return location + + def __str__(self) -> str: + bait = '\n'.join(str(bait) for bait in self.baits.values()) + fish = '\n'.join(str(fish) for fish in self.fishes.values()) + weather = '\n'.join(str(weather) for weather in self.weathers.values()) + locations = '\n'.join(str(location) for location in self.locations.values()) + return ( + f'Bait:\n{bait}\n' + f'Fish:\n{fish}\n' + f'Weather:\n{weather}\n' + f'Locations:\n{locations}' + ) \ No newline at end of file diff --git a/src/utils/fishing/fish.py b/src/utils/fishing/fish.py new file mode 100644 index 0000000..150dac6 --- /dev/null +++ b/src/utils/fishing/fish.py @@ -0,0 +1,118 @@ +import random + +from bot import get_setting +from utils.fishing.bait import Bait + +class Fish(): + def __init__(self, id: str, name: str, emoji: str, description: str, price: float, salvage: list[Bait], weight: int, min: int, max: int) -> None: + """ + Initialize a new Fish instance. + + Parameters + ---------- + id : str + The ID of the fish. + name : str + The name of the fish. + emoji : str + The emoji representing the fish. + description : str + A description of the fish. + price : float + The price of the fish. + salvage : list[Bait] + The bait that can be salvaged from the fish. + rarity : int + The rarity of the fish. + weight : int + The weight of the fish. + min : int + The minimum size of the fish. + max : int + The maximum size of the fish. + """ + self.id = id + self.name = name + self.emoji = emoji + self.description = description + self.price = price + self.salvage = salvage + self.weight = weight + self.min = min + self.max = max + + def get_fish_quantity(self, quantityBonus: float) -> int: + """Get the quantity of fish caught with a specific bait and weather.""" + base = random.randint(self.min, self.max) + bonus = base * quantityBonus + return round(base + bonus) + + def get_salvage_quantity(self, count: int) -> int: + """Get the quantity of bait salvaged from the fish.""" + min = get_setting('fishing', 'salvage_rate_min') + max = get_setting('fishing', 'salvage_rate_max') + salvage_rate = random.uniform(min, max) + return round(count * salvage_rate) + + def get_rarity(self) -> str: + """Get the rarity of the fish based on its weight.""" + if self.weight < 20: + return 'Rare' + elif self.weight < 40: + return 'Uncommon' + return 'Common' + + def combine_salvages(self, quantity: int, randomize: bool = False) -> list[tuple[Bait, int]]: + salvages = [bait for bait in self.salvage for _ in range(quantity)] + combinedSalvages = {} + for bait in salvages: + if bait in combinedSalvages: + combinedSalvages[bait] += 1 + else: + combinedSalvages[bait] = 1 + + if randomize: + combinedSalvages = {bait: self.get_salvage_quantity(count) for bait, count in combinedSalvages.items()} + + return [(bait, count) for bait, count in combinedSalvages.items()] + + @staticmethod + def load_fish_from_config(fish_data, bait_instances): + """Load fish data from a configuration file.""" + return { + key: Fish( + id=key, + name=item['name'], + emoji=item['emoji'], + description=item['description'], + price=item['price'], + salvage=[bait_instances[bait_id] for bait_id in item.get('salvage', []) if bait_id in bait_instances], + weight=item['weight'], + min=item['min'], + max=item['max'] + ) + for key, item in fish_data.items() + } + + def to_dict(self) -> dict: + """Convert the fish to a dictionary.""" + return { + 'id': self.id, + 'name': self.name, + 'emoji': self.emoji, + 'description': self.description, + 'price': self.price, + 'salvage': [bait.id for bait in self.salvage], + 'weight': self.weight, + 'min': self.min, + 'max': self.max + } + + def __str__(self) -> str: + salvage = ', '.join(f'{bait.emoji} {bait.name}' for bait in self.salvage) if self.salvage else 'None' + return ( + f'[{self.id}] {self.emoji} {self.name}: {self.description}, ' + f'Price: 🪙 {self.price}, Weight: {self.weight}, ' + f'Min: {self.min}, Max: {self.max}\n' + f'Salvage: {salvage}' + ) \ No newline at end of file diff --git a/src/utils/fishing/inventory.py b/src/utils/fishing/inventory.py new file mode 100644 index 0000000..4512283 --- /dev/null +++ b/src/utils/fishing/inventory.py @@ -0,0 +1,66 @@ +import sqlite3 +import hikari + +from bot import get_setting +from utils.fishing.bait import Bait +from utils.fishing.config_loader import FishingConfigLoader + +class Inventory: + def __init__(self, user: hikari.User) -> None: + """ + Initialize a new Inventory instance. + + Parameters + ---------- + user : hikari.User + The user whose inventory is being managed. + """ + self.db = sqlite3.connect(get_setting('general', 'database_data_dir')) + self.cursor = self.db.cursor() + self.user = user + + def get_baits(self) -> list[Bait]: + """Get the bait inventory of a user.""" + self.cursor.execute(''' + SELECT bait_id, amount FROM fishing + WHERE user_id = ? + ''', (self.user.id,)) + result = self.cursor.fetchall() + return [FishingConfigLoader.find_bait_by_id(id) for id, _ in result] + + def get_bait_amount(self, bait: Bait) -> int: + """Get the amount of a specific bait in the inventory of a user.""" + self.cursor.execute(''' + SELECT amount FROM fishing + WHERE user_id = ? AND bait_id = ? + ''', (self.user.id, bait.id)) + result = self.cursor.fetchone() + return result[0] if result else 0 + + def update_bait(self, bait: Bait, amount: int) -> None: + """Update the bait from the inventory of a user.""" + try: + self.cursor.execute(''' + INSERT OR REPLACE INTO fishing (user_id, bait_id, amount) + VALUES (?, ?, ?) + ''', (self.user.id, bait.id, amount)) + self.db.commit() + except sqlite3.Error as e: + self.db.rollback() + print("Error inserting item from the database:", e) + + def delete_bait(self, bait: Bait) -> None: + """Delete a bait from the inventory of a user.""" + try: + self.cursor.execute(''' + DELETE FROM fishing + WHERE user_id = ? AND bait_id = ? + ''', (self.user.id, bait.id)) + self.db.commit() + except sqlite3.Error as e: + self.db.rollback() + print("Error inserting item from the database:", e) + + def __delattr__(self) -> None: + """Close the database connection when the object is deleted.""" + self.db.close() \ No newline at end of file diff --git a/src/utils/fishing/location.py b/src/utils/fishing/location.py new file mode 100644 index 0000000..d28973a --- /dev/null +++ b/src/utils/fishing/location.py @@ -0,0 +1,132 @@ +import random + +from utils.fishing.bait import Bait +from utils.fishing.fish import Fish +from utils.fishing.weather import Weather + +class Location: + def __init__(self, id: str, name: str, emoji: str, description: str, fish: list[Fish], success_rate_bonus: float, quantity_bonus: float, rarity_bonus: float) -> None: + """ + Initialize a new Location instance. + + Parameters + ---------- + name : str + The name of the location. + emoji : str + The emoji representing the location. + description : str + A description of the location. + fish : list[Fish] + The fish that can be caught in the location. + weather : list[Weather] + The available weather conditions in the location. + success_rate_bonus : float + The bonus success rate of catching a fish. + quantity_bonus : float + The bonus quantity of fish caught. + rarity_bonus : float + The bonus rarity of fish caught. + """ + self.id = id + self.name = name + self.emoji = emoji + self.description = description + self.fish = sorted(fish, key=lambda fish: (fish.weight, -fish.price), reverse=True) + self.success_rate_bonus = success_rate_bonus + self.quantity_bonus = quantity_bonus + self.rarity_bonus = rarity_bonus + self.baseWeights = [fish.weight for fish in self.fish] + + def get_fish(self, bait: Bait, weather: Weather) -> Fish: + """Get a fish that can be caught with a specific bait and weather.""" + successChance = bait.success_rate_bonus + weather.success_rate_bonus + self.success_rate_bonus + rarityBonus = bait.rarity_bonus + weather.rarity_bonus + self.rarity_bonus + + fishWeights = self.get_fish_adjusted_weights(rarityBonus) + + if random.uniform(0, 1) > successChance or sum(fishWeights.values()) == 0: + return None + + fish = random.choices(self.fish, weights=fishWeights.values(), k=1)[0] + + return fish + + def get_fish_rarity(self, fish: Fish, weather: Weather) -> str: + """Get the rarity of a fish based on the location and weather.""" + rarityBonus = weather.rarity_bonus + self.rarity_bonus + fishWeights = self.get_fish_adjusted_weights(rarityBonus) + totalWeight = sum(fishWeights.values()) + + if totalWeight != 0: + for fishItem, weight in fishWeights.items(): + if fish.id == fishItem.id: + percentage = (weight / totalWeight) * 100 + rarityMap = { + (0, 3): 'Legendary', + (3, 10): 'Rare', + (10, 15): 'Uncommon', + (15, float('inf')): 'Common' + } + for (low, high), rarity in rarityMap.items(): + if low < percentage <= high: + return rarity + + return 'Impossible' + + def get_fish_adjusted_weights(self, rarityBonus: float) -> dict[Fish, list[int]]: + """Get the percentage of each fish that can be caught in the location.""" + rarityBonus = rarityBonus * 100 + rarityAdjustments = [(1 / weight) * rarityBonus for weight in self.baseWeights] + adjustedWeights = [max(0, weight + (weight * adj)) for weight, adj in zip(self.baseWeights, rarityAdjustments)] + return {fish: weight for fish, weight in zip(self.fish, adjustedWeights)} + + def print_fish_percentages(self, rarityBonus: float) -> None: + """Print the percentage of each fish that can be caught in the location.""" + adjustedWeights = self.get_fish_adjusted_weights(rarityBonus).values() + print(f'Fish Percentages ({(rarityBonus * 100) + 100:.2f}%):') + for fish, base, adjusted in zip(self.fish, self.baseWeights, adjustedWeights): + basePercentage = (base / sum(self.baseWeights)) * 100 if sum(self.baseWeights) > 0 else 0 + adjustedPercentage = (adjusted / sum(adjustedWeights)) * 100 if sum(adjustedWeights) > 0 else 0 + print(f'{fish.emoji} {fish.name}: {basePercentage:.2f}% -> {adjustedPercentage:.2f}%', end='\n') + + @staticmethod + def load_location_from_config(location, fish) -> dict[str, 'Location']: + """Load location data from a configuration file.""" + return { + key: Location( + id=key, + name=item['name'], + emoji=item['emoji'], + description=item['description'], + fish=[fish[id] for id in item.get('fish', []) if id in fish], + success_rate_bonus=item['success_rate_bonus'], + quantity_bonus=item['quantity_bonus'], + rarity_bonus=item['rarity_bonus'] + ) + for key, item in location.items() + } + + def to_dict(self) -> dict: + """Convert the location to a dictionary.""" + return { + 'id': self.id, + 'name': self.name, + 'emoji': self.emoji, + 'description': self.description, + 'fish': [fish.id for fish in self.fish], + 'success_rate_bonus': self.success_rate_bonus, + 'quantity_bonus': self.quantity_bonus, + 'rarity_bonus': self.rarity_bonus + } + + def __str__(self) -> str: + fish = ', '.join(f'{fish.emoji} {fish.name}' for fish in self.fish) if self.fish else 'None' + weather = ', '.join(f'{weather.emoji} {weather.name}' for weather in self.weather) if self.weather else 'None' + return ( + f'[{self.id}] {self.emoji} {self.name}: {self.description}\n' + f'Success Rate Bonus: {self.success_rate_bonus * 100}%, Quantity Bonus: {self.quantity_bonus * 100}%, ' + f'Rarity Bonus: {self.rarity_bonus * 100}%\n' + f'Fish: {fish}\n' + f'Weather: {weather}' + ) \ No newline at end of file diff --git a/src/utils/fishing/weather.py b/src/utils/fishing/weather.py new file mode 100644 index 0000000..81050a1 --- /dev/null +++ b/src/utils/fishing/weather.py @@ -0,0 +1,61 @@ +import json + +class Weather: + def __init__(self, id: str, name: str, emoji: str, description: str, weight: int, success_rate_bonus: float, quantity_bonus: float, rarity_bonus: float) -> None: + """ + Initialize a new Weather instance. + + Parameters + ---------- + id : str + The ID of the weather. + name : str + The name of the weather. + emoji : str + The emoji representing the weather. + description : str + A description of the weather. + rarity : Rarity + The rarity of the weather. + weight : int + The weight of the weather. + success_rate_bonus : int + The bonus success rate of catching a fish. + quantity_bonus : int + The bonus quantity of fish caught. + rarity_bonus : int + The bonus rarity of fish caught. + """ + self.id = id + self.name = name + self.emoji = emoji + self.description = description + self.weight = weight + self.success_rate_bonus = success_rate_bonus + self.quantity_bonus = quantity_bonus + self.rarity_bonus = rarity_bonus + + @staticmethod + def load_weather_from_config(weather_data): + """Load weather data from a configuration file.""" + return {key: Weather(id=key, **item) for key, item in weather_data.items()} + + def to_dict(self) -> dict: + """Convert the weather to a dictionary.""" + return { + 'id': self.id, + 'name': self.name, + 'emoji': self.emoji, + 'description': self.description, + 'weight': self.weight, + 'success_rate_bonus': self.success_rate_bonus, + 'quantity_bonus': self.quantity_bonus, + 'rarity_bonus': self.rarity_bonus + } + + def __str__(self) -> str: + return ( + f'[{self.id}] {self.emoji} {self.name}: {self.description}, Weight: {self.weight}, ' + f'Success Rate Bonus: {self.success_rate_bonus * 100}%, Quantity Bonus: {self.quantity_bonus * 100}%, ' + f'Rarity Bonus: {self.rarity_bonus * 100}%' + ) \ No newline at end of file diff --git a/src/utils/general/config.py b/src/utils/general/config.py new file mode 100644 index 0000000..c185ad2 --- /dev/null +++ b/src/utils/general/config.py @@ -0,0 +1,618 @@ +import json + +VERSION = '1.4.0' + +def get_setting_json(): + bot = { + 'version': VERSION, # DO NOT CHANGE + 'token': '', # BOT TOKEN (REQUIRED) + 'owner_id': [], # BOT OWNER IDS (REQUIRED) + 'test_guild_id': [], # APPLICATION COMMAND ENABLED GUILDS (OPTIONAL) + 'wordnik_api_key': '', # WORDNIK API KEY (OPTIONAL) + } + general = { + 'database_data_dir': 'database/database.sqlite', + 'command_cooldown': 5, + 'embed_color': '#249EDB', + 'embed_important_color': 'b03f58', + 'embed_success_color': '#32CD32', + 'embed_error_color': '#FF0000', + 'auto_translate_conf': 0.80, + 'auto_translate_min_relative_distance': 0.90, + } + economy = { + 'starting_balance': 300, + 'starting_tux_pass': 0, + 'daily_max_streak': 30, + } + profile = { + 'coin': { + 'gray-banner': 200, + 'float-nametag': 200, + 'separator-nametag': 200, + 'tuxedo-nametag': 200, + 'apple-base': 500, + 'burgundy-base': 500, + 'blueberry-base': 500, + 'grape-base': 500, + 'snow-base': 1000, + 'snow-nametag': 1000, + 'plastic-banner': 1000, + 'plastic-base': 1000, + 'plastic-nametag': 1000, + 'blue-banner': 2000, + 'orange-banner': 2000, + 'grassiant-banner': 5000, + 'sky-banner': 5000, + 'purp-banner': 5000, + 'purp-base': 5000, + 'purp-nametag': 5000, + 'charged_rose-banner': 5000, + 'rushsite_s3-banner': 7000, + 'france-banner': 10000, + 'usa-banner': 10000, + }, + 'tpass': { + 'nippuwu-banner': 1, + } + } + fishing = { + 'salvage_rate_min': 0.7, + 'salvage_rate_max': 1.3, + 'weather': { + '1': { + 'name': 'Sunny', + 'emoji': '☀️', + 'description': 'Today will be bright and sunny with clear skies and comfortable temperatures. Great conditions for fishing!', + 'weight': 200, + 'success_rate_bonus': 0, + 'quantity_bonus': 0, + 'rarity_bonus': 0, + }, + '2': { + 'name': 'Light Winds', + 'emoji': '🍃', + 'description': 'Expect light winds with clear skies today. Pleasant weather that’s ideal for fishing.', + 'weight': 20, + 'success_rate_bonus': 0.1, + 'quantity_bonus': 0, + 'rarity_bonus': 0, + }, + '3': { + 'name': 'Cloudy', + 'emoji': '☁️', + 'description': 'Today\’s forecast includes light cloud cover with no rain. Good conditions for fishing are expected.', + 'weight': 20, + 'success_rate_bonus': 0, + 'quantity_bonus': 0, + 'rarity_bonus': 0.1, + }, + '4': { + 'name': 'Rainy', + 'emoji': '🌧️', + 'description': 'Light rain showers are on the horizon today. Fish are likely to be more active, making it a promising day for fishing.', + 'weight': 20, + 'success_rate_bonus': 0, + 'quantity_bonus': 0.1, + 'rarity_bonus': 0, + }, + '5': { + 'name': 'Overcast', + 'emoji': '☁️', + 'description': 'Expect thick cloud cover throughout the day with no rain. Favorable conditions for a successful fishing trip.', + 'weight': 15, + 'success_rate_bonus': 0.2, + 'quantity_bonus': 0, + 'rarity_bonus': 0, + }, + '6': { + 'name': 'Foggy', + 'emoji': '🌫️', + 'description': 'Thick fog and low visibility are expected today. Fishing might be challenging but could offer some rewards.', + 'weight': 15, + 'success_rate_bonus': -0.15, + 'quantity_bonus': 0, + 'rarity_bonus': 0.3, + }, + '7': { + 'name': 'Snowy', + 'emoji': '❄️', + 'description': 'Snowfall and cold temperatures are forecasted for today. Fishing may be less productive due to the weather.', + 'weight': 15, + 'success_rate_bonus': -0.1, + 'quantity_bonus': -0.1, + 'rarity_bonus': 0, + }, + '8': { + 'name': 'Windy', + 'emoji': '💨', + 'description': 'Expect strong winds today, which could make fishing conditions difficult.', + 'weight': 10, + 'success_rate_bonus': -0.2, + 'quantity_bonus': 0, + 'rarity_bonus': -0.1, + }, + '9': { + 'name': 'Thunderstorm', + 'emoji': '⛈️', + 'description': 'Thunderstorms and heavy rain are forecasted today. Not ideal for fishing, as conditions will be challenging.', + 'weight': 10, + 'success_rate_bonus': 0, + 'quantity_bonus': -0.1, + 'rarity_bonus': -0.2, + }, + '10': { + 'name': 'Snowstorm', + 'emoji': '🌨️', + 'description': 'Heavy snowfall and blizzard conditions are expected today. Fishing will be severely affected by the extreme weather', + 'weight': 10, + 'success_rate_bonus': 0, + 'quantity_bonus': -0.2, + 'rarity_bonus': -0.1, + }, + '11': { + 'name': 'Tornado', + 'emoji': '🌪️', + 'description': 'Tornadoes and extreme weather conditions are expected today. Fishing will be extremely hazardous and not recommended.', + 'weight': 5, + 'success_rate_bonus': -0.3, + 'quantity_bonus': -0.1, + 'rarity_bonus': -0.2, + }, + }, + 'location': { + '1': { + 'name': 'Lake', + 'emoji': '🏞️', + 'description': 'A peaceful lake where you have a better chance of a successful catch.', + 'fish': ['3', '4', '5', '6', '12', '14', '18', '19'], + 'success_rate_bonus': 0.1, + 'quantity_bonus': 0, + 'rarity_bonus': 0, + }, + '2': { + 'name': 'River', + 'emoji': '🏞️', + 'description': 'A lively river where you might catch more fish, though the waters can be tricky.', + 'fish': ['1', '2', '5', '6', '11', '14', '20'], + 'success_rate_bonus': 0, + 'quantity_bonus': 0.1, + 'rarity_bonus': 0, + }, + '3': { + 'name': 'Ocean', + 'emoji': '🏖️', + 'description': 'A vast ocean offering a chance to find rare fish. Be prepared for a bit of a challenge in these expansive waters.', + 'fish': ['7', '8', '9', '10', '13', '14', '15', '16', '17'], + 'success_rate_bonus': -0.1, + 'quantity_bonus': 0, + 'rarity_bonus': 0.05, + }, + }, + 'bait': { + '1': { + 'name': 'Worms', + 'emoji': '🪱', + 'description': 'A classic choice for anglers, known for their versatility and effectiveness in various fishing conditions.', + 'tooltip': 'Slightly attracts fish with no special bonus.', + 'price': 9, + 'success_rate_bonus': 0.5, + 'quantity_bonus': 0, + 'rarity_bonus': 0, + }, + '2': { + 'name': 'Minnows', + 'emoji': '🐟', + 'description': 'Small fish that attract larger fish by mimicking their natural prey, making them a popular choice for consistent catches.', + 'tooltip': 'Moderately attracts fish with no special bonus.', + 'price': 12, + 'success_rate_bonus': 0.7, + 'quantity_bonus': 0, + 'rarity_bonus': 0, + }, + '3': { + 'name': 'Crickets', + 'emoji': '🦗', + 'description': 'Known for their high activity levels, which can entice fish looking for a lively snack.', + 'tooltip': 'Significantly attracts fish with no special bonus.', + 'price': 17, + 'success_rate_bonus': 0.9, + 'quantity_bonus': 0, + 'rarity_bonus': 0, + }, + '4': { + 'name': 'Bread Crumbs', + 'emoji': '🍞', + 'description': 'Works well in shallow waters, especially to attract smaller, common fish.', + 'tooltip': 'Slightly attracts common fish with increased quantity.', + 'price': 0, + 'success_rate_bonus': 0.4, + 'quantity_bonus': 0.2, + 'rarity_bonus': -0.15, + }, + '5': { + 'name': 'Corn Kernels', + 'emoji': '🌽', + 'description': 'An inexpensive bait option that can be surprisingly effective, particularly for smaller fish species.', + 'tooltip': 'Slightly attracts common fish with increased quantity.', + 'price': 0, + 'success_rate_bonus': 0.35, + 'quantity_bonus': 0.1, + 'rarity_bonus': -0.2, + }, + '6': { + 'name': 'Boiled Eggs', + 'emoji': '🥚', + 'description': 'A traditional bait that can lure fish due to their unique scent and texture.', + 'tooltip': 'Moderately attracts common fish with increased quantity.', + 'price': 0, + 'success_rate_bonus': 0.6, + 'quantity_bonus': 0.2, + 'rarity_bonus': -0.05, + }, + '7': { + 'name': 'Nightcrawlers', + 'emoji': '🪱', + 'description': 'Prized by anglers for their effectiveness, especially during nighttime or in low-light conditions.', + 'tooltip': 'Moderately attracts fish with increased quantity.', + 'price': 0, + 'success_rate_bonus': 0.6, + 'quantity_bonus': 0.2, + 'rarity_bonus': 0, + }, + '8': { + 'name': 'Insects Mix', + 'emoji': '🪰', + 'description': 'Provides a diverse bait option that can cater to different fish preferences and increase your chances of success.', + 'tooltip': 'Significantly attracts fish with increased quantity.', + 'price': 0, + 'success_rate_bonus': 0.7, + 'quantity_bonus': 0.4, + 'rarity_bonus': 0, + }, + '9': { + 'name': 'Shrimp', + 'emoji': '🍤', + 'description': 'Highly appealing for many fish species, especially those that feed on crustaceans.', + 'tooltip': 'Moderately attracts fish with increased quantity.', + 'price': 0, + 'success_rate_bonus': 0.55, + 'quantity_bonus': 0.3, + 'rarity_bonus': 0, + }, + '10': { + 'name': 'Mini Crabs', + 'emoji': '🦀', + 'description': 'A great choice for attracting fish that prey on crustaceans, offering a more substantial bait option.', + 'tooltip': 'Slightly attracts rare fish.', + 'price': 0, + 'success_rate_bonus': 0.45, + 'quantity_bonus': -0.1, + 'rarity_bonus': 0.05, + }, + '11': { + 'name': 'Scented Lures', + 'emoji': '🎣', + 'description': 'Designed to mimic the smell of prey, making them particularly effective for attracting rare fish.', + 'tooltip': 'Significantly attracts rare fish.', + 'price': 0, + 'success_rate_bonus': 0.75, + 'quantity_bonus': -0.1, + 'rarity_bonus': 0.3, + }, + '12': { + 'name': 'Jumbo Crickets', + 'emoji': '🦗', + 'description': 'Larger and more robust than regular crickets, Jumbo Crickets are highly effective for attracting fish due to their increased size and scent.', + 'tooltip': 'Significantly attracts fish with increased quantity.', + 'price': 0, + 'success_rate_bonus': 0.95, + 'quantity_bonus': 0.15, + 'rarity_bonus': 0.05, + }, + '13': { + 'name': 'Squid Strips', + 'emoji': '🦑', + 'description': 'Favored for their strong scent and texture, making them ideal for catching rare and large fish.', + 'tooltip': 'Moderately attracts rare fish.', + 'price': 0, + 'success_rate_bonus': 0.65, + 'quantity_bonus': -0.1, + 'rarity_bonus': 0.15, + }, + '14': { + 'name': 'Silk Moths', + 'emoji': '🦋', + 'description': 'Silk moths are known for their fluttery movement and enticing scent, making them effective for drawing in a variety of fish.', + 'tooltip': 'Significantly attracts fish and increased quantity.', + 'price': 0, + 'success_rate_bonus': 0.8, + 'quantity_bonus': 0.4, + 'rarity_bonus': 0, + }, + '15': { + 'name': 'Golden Minnows', + 'emoji': '✨', + 'description': 'Renowned for their unmatched effectiveness in attracting all types of fish.', + 'tooltip': 'The finest of all baits that no fish can turn down.', + 'price': 0, + 'success_rate_bonus': 0.98, + 'quantity_bonus': 0.8, + 'rarity_bonus': 0.5, + }, + '16': { + 'name': 'Celestial Bait', + 'emoji': '☄️', + 'description': 'A legendary item with extraordinary power, said to attract even the most elusive fish.', + 'tooltip': 'Legendary bait with extraordinary power.', + 'price': 0, + 'success_rate_bonus': 1.2, + 'quantity_bonus': 0.8, + 'rarity_bonus': 0.3, + }, + }, + 'fish': { + '1': { + 'name': 'Bluegill', + 'emoji': '🐟', + 'description': 'Do you think it calls me "pinklung"?', + 'price': 2, + 'salvage': ['4'], # Bread Crumbs + 'weight': 100, + 'min': 1, + 'max': 3, + }, + '2': { + 'name': 'Bass', + 'emoji': '🐟', + 'description': 'If I can catch a drummer, maybe I\'ll form a band!', + 'price': 8, + 'salvage': ['6'], # Boiled Eggs + 'weight': 60, + 'min': 1, + 'max': 3, + }, + '3': { + 'name': 'Tadpole', + 'emoji': '🐟', + 'description': 'It\'s just a tad small.', + 'price': 1, + 'salvage': ['5'], # Corn Kernels + 'weight': 110, + 'min': 2, + 'max': 5, + }, + '4': { + 'name': 'Frog', + 'emoji': '🐸', + 'description': 'Or maybe it\'s a prince in disguise?', + 'price': 12, + 'salvage': ['6'], # Boiled Eggs + 'weight': 50, + 'min': 1, + 'max': 2, + }, + '5': { + 'name': 'Crayfish', + 'emoji': '🦞', + 'description': 'Or else it\'s a lobster, and I\'m a giant!', + 'price': 6, + 'salvage': ['4'], # Bread Crumbs + 'weight': 70, + 'min': 1, + 'max': 3, + }, + '6': { + 'name': 'Catfish', + 'emoji': '🐠', + 'description': 'Do you think it has 9 lives?', + 'price': 14, + 'salvage': ['7', '7'], # Live Worms + 'weight': 40, + 'min': 1, + 'max': 2, + }, + '7': { + 'name': 'Clownfish', + 'emoji': '🐠', + 'description': 'How many can fit in a carfish?', + 'price': 1, + 'salvage': ['5'], # Corn Kernels + 'weight': 110, + 'min': 1, + 'max': 4, + }, + '8': { + 'name': 'Sea Bass', + 'emoji': '🐟', + 'description': 'No, wait- it\'s at least a C+!', + 'price': 10, + 'salvage': ['8'], # Insects Mix + 'weight': 50, + 'min': 1, + 'max': 3, + }, + '9': { + 'name': 'Puffer fish', + 'emoji': '🐡', + 'description': 'I thought you would be tougher, fish!', + 'price': 8, + 'salvage': ['9'], # Shrimp + 'weight': 60, + 'min': 1, + 'max': 2, + }, + '10': { + 'name': 'Horse Mackerel', + 'emoji': '🐠', + 'description': 'Holy mackerel!', + 'price': 8, + 'salvage': ['8'], # Insects Mix + 'weight': 50, + 'min': 1, + 'max': 3, + }, + '11': { + 'name': 'Snapping Turtle', + 'emoji': '🐢', + 'description': 'How can it snap without fingers?', + 'price': 14, + 'salvage': ['14'], # Glow Worms + 'weight': 30, + 'min': 1, + 'max': 3, + }, + '12': { + 'name': 'Sturgeon', + 'emoji': '🐟', + 'description': 'Wonder if it can perform sturgery...', + 'price': 10, + 'salvage': ['11'], # Scented Lures + 'weight': 50, + 'min': 1, + 'max': 3, + }, + '13': { + 'name': 'Barred Knifejaw', + 'emoji': '🐠', + 'description': 'I\'ll have to use it to cut veggies!', + 'price': 5, + 'salvage': ['9'], # Shrimp + 'weight': 90, + 'min': 1, + 'max': 4, + }, + '14': { + 'name': 'Salmon', + 'emoji': '🐟', + 'description': 'I\'ll have to scale back my excitement!', + 'price': 10, + 'salvage': ['12'], # Specialized Crickets + 'weight': 50, + 'min': 1, + 'max': 3, + }, + '15': { + 'name': 'Squid', + 'emoji': '🦑', + 'description': 'Do they... not actually "bloop"?', + 'price': 8, + 'salvage': ['13', '13'], # Squid Strips + 'weight': 60, + 'min': 1, + 'max': 3, + }, + '16': { + 'name': 'Octopus', + 'emoji': '🐙', + 'description': 'I\'m a sucker for these!', + 'price': 12, + 'salvage': ['13', '13', '13'], # Squid Strips + 'weight': 40, + 'min': 1, + 'max': 2, + }, + '17': { + 'name': 'Shark', + 'emoji': '🦈', + 'description': 'There’s just some-fin about you…', + 'price': 50, + 'salvage': ['16', '16', '16'], # Celestial Bait + 'weight': 10, + 'min': 1, + 'max': 2, + }, + '18': { + 'name': 'Carp', + 'emoji': '🐟', + 'description': 'If I catch another they can carpool!', + 'price': 2, + 'salvage': ['5', '5'], # Corn Kernels + 'weight': 100, + 'min': 1, + 'max': 3, + }, + '19': { + 'name': 'Goldfish', + 'emoji': '🐠', + 'description': 'It\'s worth its weight in fish!', + 'price': 7, + 'salvage': ['15'], # Golden Minnows + 'weight': 7, + 'min': 4, + 'max': 8, + }, + '20': { + 'name': 'Mitten Crab', + 'emoji': '🦀', + 'description': 'One more and I\'m ready for winter!', + 'price': 10, + 'salvage': ['10', '10'], # Mini Crabs + 'weight': 50, + 'min': 1, + 'max': 2, + }, + } + } + + json = { + 'bot': bot, + 'general': general, + 'economy': economy, + 'profile': profile, + 'fishing': fishing, + } + + return json + +def update_settings(): + settings = get_setting_json() + with open('settings.json', 'r') as openfile: + data = json.load(openfile) + + # Add or update settings + for section in settings: + if section not in data: + data[section] = settings[section] + else: + for option in settings[section]: + if option not in data[section]: + data[section][option] = settings[section][option] + + # Remove settings not present in get_setting_json() + sections_to_remove = [section for section in data if section not in settings] + for section in sections_to_remove: + del data[section] + + for section in data: + options_to_remove = [option for option in data[section] if option not in settings[section]] + for option in options_to_remove: + del data[section][option] + + with open('settings.json', 'w') as openfile: + json.dump(data, openfile, indent=4) + +def get_setting(section: str, option: str = None): + with open('settings.json', 'r') as openfile: + data = json.load(openfile) + if option: + if section in data and option in data[section]: + return data[section][option] + else: + return None + elif section in data: + return data[section] + else: + return None + +def write_setting(section: str, option: str, value): + with open('settings.json', 'r') as openfile: + data = json.load(openfile) + + if section not in data: + data[section] = {} + + data[section][option] = value + + with open('settings.json', 'w') as openfile: + json.dump(data, openfile, indent=4) \ No newline at end of file diff --git a/src/utils/general/navigator.py b/src/utils/general/navigator.py new file mode 100644 index 0000000..3c7b6ed --- /dev/null +++ b/src/utils/general/navigator.py @@ -0,0 +1,15 @@ +import hikari +import miru + +from miru.ext import nav + +class NavPageInfo(nav.NavButton): + def __init__(self, pages: int, row: int): + super().__init__(label="Page: 1", style=hikari.ButtonStyle.SECONDARY, disabled=True, row=row) + self.pages = pages + + async def callback(self, ctx: miru.ViewContext) -> None: + return + + async def before_page_change(self) -> None: + self.label = f'Page: {self.view.current_page+1}/{self.pages}' \ No newline at end of file diff --git a/src/utils/pokemon/card.py b/src/utils/pokemon/card.py deleted file mode 100644 index 2f32835..0000000 --- a/src/utils/pokemon/card.py +++ /dev/null @@ -1,49 +0,0 @@ -import hikari - -from bot import get_setting - -class PokemonCard: - def __init__(self, user: hikari.User, card_id, date, name, pokemon_id, rarity, shiny=False): - self.user = user - self.card_id = card_id - self.name = name - self.date = date - self.pokemon_id = pokemon_id - self.rarity = rarity - self.shiny = shiny - - def display(self, pages: list) -> list: - if self.shiny: - rarity_symbol = '🌟' - else: - rarity_symbol = '⭐' - - embed = hikari.Embed(title=f'{self.name} Card' if not self.shiny else f'Shiny {self.name} Card', color=get_setting('general', 'embed_color')) - embed.set_image(f'https://img.pokemondb.net/sprites/home/normal/{self.name.lower()}.png' if not self.shiny else f'https://img.pokemondb.net/sprites/home/shiny/{self.name.lower()}.png') - # embed.set_image(f'https://raw.githubusercontent.com/harshit23897/Pokemon-Sprites/master/assets/imagesHQ/{"{:03d}".format(pokemon_id)}.png') # If you want 2d sprites. This does not support shiny sprites. - embed.add_field(name='Pokémon Name', value=self.name, inline=True) - embed.add_field(name='Card Quality', value=" ".join([rarity_symbol for i in range(self.rarity)]), inline=True) - embed.add_field(name='Card ID', value=f'`{self.card_id}`', inline=False) - embed.set_footer('Type `/packinventory view` to view your cards and packs.') - - pages.append(embed) - return pages - - def display_overview(self, embed: hikari.Embed) -> hikari.Embed: - if self.shiny: - name_symbol = '**' - rarity_symbol = '🌟' - else: - name_symbol = '' - rarity_symbol = '⭐' - - if len(embed.fields) == 0: - embed.add_field(name='Pokémon Name', value=f'• {name_symbol}{self.name}{name_symbol}', inline=True) - embed.add_field(name='Card Quality', value=" ".join([rarity_symbol for i in range(self.rarity)]), inline=True) - embed.add_field(name='Card ID', value=f'{self.card_id}', inline=True) - else: - embed.edit_field(0, embed.fields[0].name, f'{embed.fields[0].value}\n• {name_symbol}{self.name}{name_symbol}') - embed.edit_field(1, embed.fields[1].name, f'{embed.fields[1].value}\n{" ".join([rarity_symbol for i in range(self.rarity)])}') - embed.edit_field(2, embed.fields[2].name, f'{embed.fields[2].value}\n{self.card_id}') - - return embed \ No newline at end of file diff --git a/src/utils/pokemon/inventory.py b/src/utils/pokemon/inventory.py deleted file mode 100644 index 8d8cc20..0000000 --- a/src/utils/pokemon/inventory.py +++ /dev/null @@ -1,247 +0,0 @@ -import hikari -import lightbulb -import miru -import sqlite3 - -from miru.ext import nav -from miru.context.view import ViewContext - -from bot import get_setting -from utils.economy.manager import EconomyManager - -economy = EconomyManager() - -class Inventory: - def __init__(self, ctx: lightbulb.Context, user: hikari.User): - self.db = sqlite3.connect(get_setting('general', 'database_data_dir')) - self.cursor = self.db.cursor() - self.user = user - self.ctx = ctx - self.max = get_setting('pokemon', 'pokemon_max_capacity') - - def show_inventory(self, items_per_page): - inventory = self.get_inventory() - - if not inventory: - embed = hikari.Embed(title=f"{self.user.global_name}'s Inventory", description=f'No cards or packs found!', color=get_setting('general', 'embed_color')) - return [embed] - else: - items, cards = inventory - - pages = [] - for i in range(0, len(items), items_per_page): - embed = hikari.Embed(title=f"{self.user.global_name}'s Inventory", description=f'Capacity: `{self.get_inventory_capacity()}/{self.max}`', color=get_setting('general', 'embed_color')) - # embed.set_thumbnail('assets/img/pokemon/inventory_icon.png') - end = i + items_per_page - for pack in items[i:end]: - id, userID, date, name = pack - if len(embed.fields) == 0: - embed.add_field(name='Pack Type', value=f'• {name} Pack', inline=True) - embed.add_field(name='Pack ID', value=f'{id}', inline=True) - else: - embed.edit_field(0, embed.fields[0].name, f'{embed.fields[0].value}\n• {name} Pack') - embed.edit_field(1, embed.fields[1].name, f'{embed.fields[1].value}\n{id}') - pages.append(embed) - for i in range(0, len(cards), items_per_page): - embed = hikari.Embed(title=f"{self.user.global_name}'s Inventory", description=f'Capacity: `{self.get_inventory_capacity()}/{self.max}`', color=get_setting('general', 'embed_color')) - # embed.set_thumbnail('assets/img/pokemon/inventory_icon.png') - end = i + items_per_page - for card in cards[i:end]: - id, userID, date, name, pokemonId, rarity, shiny, favorite = card - emoji_star = '⭐' if not shiny else '🌟' - name_symbol = '' if not shiny else '**' - favorite_symbol = '' if not favorite else '<:favorite_icon:1265770511858794496>' - rarity_emoji = emoji_star * int(rarity) - if len(embed.fields) == 0: - embed.add_field(name='Pokémon Name', value=f'• {name_symbol}{name}{name_symbol} {favorite_symbol}', inline=True) - embed.add_field(name='Card Quality', value=f'{rarity_emoji}', inline=True) - embed.add_field(name='Card ID', value=f'{id}', inline=True) - else: - embed.edit_field(0, embed.fields[0].name, f'{embed.fields[0].value}\n• {name_symbol}{name}{name_symbol} {favorite_symbol}') - embed.edit_field(1, embed.fields[1].name, f'{embed.fields[1].value}\n{rarity_emoji}') - embed.edit_field(2, embed.fields[2].name, f'{embed.fields[2].value}\n{id}') - pages.append(embed) - - return pages - - def get_inventory(self): - self.cursor.execute("SELECT * FROM pokemon WHERE user_id=? AND name NOT IN (?, ?) ORDER BY name ASC, shiny DESC, rarity DESC, favorite DESC, date ASC;", (self.user.id, 'Standard', 'Premium')) - cards = self.cursor.fetchall() - self.cursor.execute("SELECT id, user_id, date, name FROM pokemon WHERE user_id=? AND name IN (?, ?) ORDER BY name ASC, date ASC;", (self.user.id, 'Standard', 'Premium')) - items = self.cursor.fetchall() - - if not cards and not items: - return None - - return items, cards - - def get_item(self, id): - query = "SELECT id, user_id, date, name FROM pokemon WHERE id = ? AND user_id = ? AND name IN (?, ?)" - self.cursor.execute(query, (id, self.user.id, 'Standard', 'Premium')) - pack = self.cursor.fetchone() - - if pack: - return 'Pack', pack - - query = "SELECT * FROM pokemon WHERE id = ? AND user_id = ? AND name NOT IN (?, ?)" - self.cursor.execute(query, (id, self.user.id, 'Standard', 'Premium')) - card = self.cursor.fetchone() - - if card: - return 'Card', card - - return None - - def get_dupe_cards(self, s: bool, all: bool): - cards = self.get_inventory()[1] - - # Removes all unique cards that are favorite - if not all: - uniqueCards = {} - - for card in cards: - cardId = card[0] - userId = card[1] - date = card[2] - name = card[3] - pokemonId = card[4] - rarity = card[5] - shiny = card[6] - favorite = card[7] - - cardKey = (pokemonId, rarity, shiny, favorite) - - if cardKey not in uniqueCards: - uniqueCards[cardKey] = (cardId, userId, date, name, pokemonId, rarity, shiny, favorite) - else: - existing_date = uniqueCards[cardKey][2] - if date < existing_date: - uniqueCards[cardKey] = (cardId, userId, date, name, pokemonId, rarity, shiny, favorite) - - for card in list(uniqueCards.values()): - cards.remove(card) - - # Removes all cards that are favorite - cards = [card for card in cards if card[-1] == 0] - - # Removes all cards that are shiny - cards = [card for card in cards if card[-2] == 0] if not s else cards - - return cards - - def get_inventory_capacity(self): - items, cards = self.get_inventory() - total = len(items) + len(cards) - return total - - async def sell(self, cards: list) -> None: - value = 0 - for card in cards: - cardID = card[0] - shiny = card[6] - favorite = card[7] - if favorite or not self.get_item(cardID): - embed = hikari.Embed(title='Sell Error', description='At least one card is favorite or does not exist.', color=get_setting('general', 'embed_error_color')) - embed.set_thumbnail('assets/img/pokemon/convert_icon.png') - await self.ctx.edit_last_response(embed, components=[]) - return - value += 40 if shiny else 20 - for card in cards: - try: - self.db.execute('DELETE FROM pokemon WHERE id=?', (card[0],)) - self.db.commit() - except sqlite3.Error as e: - self.db.rollback() - print("Error deleting item from the database:", e) - self.db.commit() - economy.add_money(self.user.id, value, True) - - embed = hikari.Embed(title='Success', description=f'You sold {len(cards)} cards for 🪙 {value}!', color=get_setting('general', 'embed_success_color')) - embed.set_thumbnail('assets/img/pokemon/convert_icon.png') - # embed.set_footer('Your balance has been updated.') - await self.ctx.edit_last_response(embed, components=[]) - - def __del__(self): - self.db.close() - -class SellView(miru.View): - def __init__(self, embed: hikari.Embed, inventory: Inventory) -> None: - super().__init__(timeout=None, autodefer=True) - self.embed = embed - self.inventory = inventory - - @miru.text_select( - placeholder='Select A Sell Option', - options=[ - miru.SelectOption(label='Sell Duplicates (Exclude Shinies)', value='1'), - miru.SelectOption(label='Sell Duplicates (Include Shinies)', value='2'), - miru.SelectOption(label='Sell All (Exclude Shinies)', value='3'), - miru.SelectOption(label='Sell All (Include Shinies)', value='4'), - ], - row=1 - ) - async def sell_option(self, ctx: miru.ViewContext, select: miru.TextSelect): - option = select.values[0] - - match option: - case '1': - cards = self.inventory.get_dupe_cards(False, False) - case '2': - cards = self.inventory.get_dupe_cards(True, False) - case '3': - cards = self.inventory.get_dupe_cards(False, True) - case '4': - cards = self.inventory.get_dupe_cards(True, True) - - view = PromptView() - self.embed.title = 'Are you sure?' - self.embed.color = get_setting('general', 'embed_error_color') - self.embed.set_footer('This action is irreversible!') - message = await ctx.edit_response(self.embed, components=view.build(), flags=hikari.MessageFlag.EPHEMERAL) - - await view.start(message) - await view.wait() - - if view.answer: - await self.inventory.sell(cards) - else: - self.embed.title = 'Selling process has been cancelled.' - self.embed.description = 'No cards has been sold.' - self.embed.set_footer(None) - await ctx.edit_response(self.embed, components=[], flags=hikari.MessageFlag.EPHEMERAL) - - async def view_check(self, ctx: ViewContext) -> bool: - return self.inventory.get_inventory() != None - -class ChecksView(nav.NavigatorView): - def __init__(self, user: hikari.User, pages, buttons, timeout, autodefer: bool = True) -> None: - super().__init__(pages=pages, items=buttons, timeout=timeout, autodefer=autodefer) - self.user = user - - async def view_check(self, ctx: miru.ViewContext) -> bool: - return ctx.user.id == self.user.id - -class NavPageInfo(nav.NavButton): - def __init__(self, pages: int, row: int): - super().__init__(label="Page: 1", style=hikari.ButtonStyle.SECONDARY, disabled=True, row=row) - self.pages = pages - - async def callback(self, ctx: miru.ViewContext) -> None: - return - - async def before_page_change(self) -> None: - self.label = f'Page: {self.view.current_page+1}/{self.pages}' - -class PromptView(miru.View): - def __init__(self) -> None: - super().__init__(timeout=None, autodefer=True) - self.answer = False - - @miru.button(label='Confirm', style=hikari.ButtonStyle.SUCCESS) - async def confirm(self, ctx: miru.ViewContext, button: miru.Button) -> None: - self.answer = True - self.stop() - - @miru.button(label='Cancel', style=hikari.ButtonStyle.DANGER) - async def cancel(self, ctx: miru.ViewContext, button: miru.Button) -> None: - self.stop() diff --git a/src/utils/pokemon/pack.py b/src/utils/pokemon/pack.py deleted file mode 100644 index 434aac3..0000000 --- a/src/utils/pokemon/pack.py +++ /dev/null @@ -1,234 +0,0 @@ -import hikari -import miru -import random -import sqlite3 -import requests -import uuid -import datetime -import pytz - -from bot import get_setting -from miru.ext import nav -from utils.pokemon.card import PokemonCard - -timezone = pytz.timezone("America/New_York") - -class PokemonPack: - def __init__(self, user: hikari.User, packID, pack_type): - self.user = user - self.packID = packID - self.pack_type = pack_type - -class StandardPokemonCardPack: - def __init__(self, ctx: miru.ViewContext, user: hikari.User): - self.user = user - self.ctx = ctx - - async def buy(self): - self.db = sqlite3.connect(get_setting('general', 'database_data_dir')) - self.cursor = self.db.cursor() - - packID = str(uuid.uuid4()) - current_date = datetime.datetime.now(timezone) - date = current_date.strftime('%m/%d/%Y') - pack_type = 'Standard' - - try: - self.cursor.execute('INSERT INTO pokemon (id, user_id, date, name, pokemon_id, rarity, shiny, favorite) VALUES (?,?,?,?,?,?,?,?)', (packID, self.user.id, date, pack_type, None, None, None, None)) - self.db.commit() - except sqlite3.Error as e: - self.db.rollback() - print("Error inserting item from the database:", e) - - embed = hikari.Embed(title='Pokémon Booster Pack Shop', description=f'Thank you for your purchase <@{self.user.id}>!\nPack ID: `{packID}`', color=get_setting('general', 'embed_success_color')) - embed.set_thumbnail('assets/img/pokemon/shop_icon.png') - await self.ctx.respond(embed, delete_after=30) - - async def open(self, packID): - self.cards = [] - - self.db = sqlite3.connect(get_setting('general', 'database_data_dir')) - self.cursor = self.db.cursor() - - pokeapi_url = "https://pokeapi.co/api/v2/pokemon?limit=251" - response = requests.get(pokeapi_url) - pokemon_list = response.json()["results"] - - for i in range(7): - pokemon = random.choice(pokemon_list) - pokemon_response = requests.get(pokemon["url"]) - pokemon_data = pokemon_response.json() - rand = random.random() - rarity = 0 - shiny = False - favorite = False - if rand <= 0.01: # 1% chance - rand = random.random() - shiny = True - rarity = 1 - if rand <= 0.1: # 10% (0.1%) chance - rarity = 2 - if rand <= 0.01: # 1% (0.01%) chance - rarity = 3 - else: - rand = random.random() - rarity = 1 - if random.random() <= 0.3: # 30% chance - rarity = 2 - if random.random() <= 0.1: # 10% chance - rarity = 3 - if random.random() <= 0.005: # 0.5% chance - rarity = 4 - if random.random() <= 0.001: # 0.1% chance - rarity = 5 - card_id = str(uuid.uuid4()) - current_date = datetime.datetime.now(timezone) - date = current_date.strftime('%m/%d/%Y') - self.cards.append(PokemonCard(self.user, card_id, date, pokemon_data["name"].capitalize(), pokemon_data["id"], rarity, shiny)) - try: - self.cursor.execute("INSERT INTO pokemon (id, user_id, date, name, pokemon_id, rarity, shiny, favorite) VALUES (?,?,?,?,?,?,?,?)", (card_id, self.user.id, date, pokemon_data["name"].capitalize(), pokemon_data["id"], rarity, int(shiny), int(favorite))) - self.db.commit() - except sqlite3.Error as e: - self.db.rollback() - print("Error inserting item from the database:", e) - try: - self.db.execute('DELETE FROM pokemon WHERE id=?', (packID,)) - self.db.commit() - except sqlite3.Error as e: - self.db.rollback() - print("Error deleting item from the database:", e) - - embed = hikari.Embed(title=f'Standard Booster Pack Overview', color=get_setting('general', 'embed_color')) - embed.set_footer('Type `/packinventory view` to view your cards and packs.') - - pages = [] - for card in self.cards: - pages = card.display(pages) - embed = card.display_overview(embed) - pages.append(embed) - - buttons = [nav.PrevButton(emoji='⬅️', row=1), NavPageInfo(len(pages), row=1), nav.NextButton(emoji='➡️', row=1), nav.LastButton(row=1)] - navigator = ChecksView(self.user, pages, buttons, timeout=None) - builder = await navigator.build_response_async(client=self.ctx.client, ephemeral=True) - await builder.create_initial_response(self.ctx.interaction) - self.ctx.client.start_view(navigator) - - def __del__(self): - self.db.close() - -class PremiumPokemonCardPack: - def __init__(self, user: hikari.User, ctx: miru.ViewContext): - self.user = user - self.ctx = ctx - - async def buy(self): - self.db = sqlite3.connect(get_setting('general', 'database_data_dir')) - self.cursor = self.db.cursor() - - packID = str(uuid.uuid4()) - current_date = datetime.datetime.now(timezone) - date = current_date.strftime('%m/%d/%Y') - pack_type = 'Premium' - - try: - self.cursor.execute("INSERT INTO pokemon (id, user_id, date, name, pokemon_id, rarity, shiny, favorite) VALUES (?,?,?,?,?,?,?,?)", (packID, self.user.id, date, pack_type, None, None, None, None)) - self.db.commit() - except sqlite3.Error as e: - self.db.rollback() - print("Error inserting item from the database:", e) - - embed = hikari.Embed(title='Pokémon Booster Pack Shop', description=f'Thank you for your purchase <@{self.user.id}>!\nPack ID: `{packID}`', color=get_setting('general', 'embed_success_color')) - embed.set_thumbnail('assets/img/pokemon/shop_icon.png') - embed.set_footer('Type `/packinventory view` to see your packs!') - await self.ctx.respond(embed, delete_after=30) - - async def open(self, packID): - self.cards = [] - - self.db = sqlite3.connect(get_setting('general', 'database_data_dir')) - self.cursor = self.db.cursor() - - pokeapi_url = "https://pokeapi.co/api/v2/pokemon?limit=251" - response = requests.get(pokeapi_url) - pokemon_list = response.json()["results"] - - for i in range(7): - pokemon = random.choice(pokemon_list) - pokemon_response = requests.get(pokemon["url"]) - pokemon_data = pokemon_response.json() - rand = random.random() - rarity = 0 - shiny = False - favorite = False - if rand <= 0.03: # 3% chance - rand = random.random() - shiny = True - rarity = 1 - if rand <= 0.3: # 30% (0.9%) chance - rarity = 2 - if rand <= 0.03: # 3% (0.09%) chance - rarity = 3 - else: - rand = random.random() - rarity = 1 - if rand <= 0.9: # 90% chance - rarity = 2 - if rand <= 0.3: # 30% chance - rarity = 3 - if rand <= 0.015: # 0.15% chance - rarity = 4 - if rand <= 0.003: # 0.3% chance - rarity = 5 - card_id = str(uuid.uuid4()) - current_date = datetime.datetime.now(timezone) - date = current_date.strftime('%m/%d/%Y') - self.cards.append(PokemonCard(self.user, card_id, date, pokemon_data["name"].capitalize(), pokemon_data["id"], rarity, shiny)) - try: - self.cursor.execute("INSERT INTO pokemon (id, user_id, date, name, pokemon_id, rarity, shiny, favorite) VALUES (?,?,?,?,?,?,?,?)", (card_id, self.user.id, date, pokemon_data["name"].capitalize(), pokemon_data["id"], rarity, int(shiny), int(favorite))) - self.db.commit() - except sqlite3.Error as e: - self.db.rollback() - print("Error inserting item from the database:", e) - try: - self.db.execute('DELETE FROM pokemon WHERE id=?', (packID,)) - self.db.commit() - except sqlite3.Error as e: - self.db.rollback() - print("Error deleting item from the database:", e) - - embed = hikari.Embed(title=f'Premium Booster Pack Overview', color=get_setting('general', 'embed_color')) - embed.set_footer('Type `/packinventory view` to view your cards and packs.') - - pages = [] - for card in self.cards: - pages = card.display(pages) - embed = card.display_overview(embed) - pages.append(embed) - - buttons = [nav.PrevButton(emoji='⬅️'), NavPageInfo(len(pages)), nav.NextButton(emoji='➡️'), nav.LastButton()] - navigator = nav.NavigatorView(pages=pages, items=buttons, timeout=None) - builder = await navigator.build_response_async(client=self.ctx.client, ephemeral=True) - await builder.create_initial_response(self.ctx.interaction) - self.ctx.client.start_view(navigator) - - def __del__(self): - self.db.close() - -class ChecksView(nav.NavigatorView): - def __init__(self, user: hikari.User, pages, buttons, timeout, autodefer: bool = True) -> None: - super().__init__(pages=pages, items=buttons, timeout=timeout, autodefer=autodefer) - self.user = user - - async def view_check(self, ctx: miru.ViewContext) -> bool: - return ctx.user.id == self.user.id - -class NavPageInfo(nav.NavButton): - def __init__(self, pages: int, row: int): - super().__init__(label="Page: 1", style=hikari.ButtonStyle.SECONDARY, disabled=True, row=row) - self.pages = pages - - async def callback(self, ctx: miru.ViewContext) -> None: - return - - async def before_page_change(self) -> None: - self.label = f'Page: {self.view.current_page+1}/{self.pages}' \ No newline at end of file diff --git a/src/utils/profile/inventory.py b/src/utils/profile/inventory.py index bf00782..cfe3f00 100644 --- a/src/utils/profile/inventory.py +++ b/src/utils/profile/inventory.py @@ -49,7 +49,8 @@ def get_active_customs(self): def get_pages(self, items: list, maxItems: int): pages = [] for i in range(0, len(items), maxItems): - embed = hikari.Embed(title="Profile Customization Shop", description='Welcome to our Profile Customization Shop, where you can transform your online presence and make a lasting impression. Our extensive menu offers a wide range of options to personalize and enhance your profile to truly reflect your unique style and personality.', color=get_setting('general', 'embed_color')) + embed = hikari.Embed(title="Welcome to the Profile Shop!", description='This is where you can purchase a wide range of cosmetic items to customize and enhance your profile card.', color=get_setting('general', 'embed_color')) + embed.set_thumbnail('assets/img/general/profile/palette.png') end = i + maxItems for option in items[i:end]: currency, name, price = option