From e7968929425dd06927012106f4b1df7892a5d136 Mon Sep 17 00:00:00 2001 From: HypoxiE Date: Sun, 6 Jul 2025 19:24:54 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=20=D1=81=20=D1=83=D1=87?= =?UTF-8?q?=D0=B5=D1=82=D0=BE=D0=BC=20.gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 +- README.md | 0 exclude.txt | 9 + requirements.txt | 42 + src/CoreFun.py | 810 ++++++++++++++++ src/cogs/admin.py | 259 +++++ src/cogs/designer.py | 124 +++ src/cogs/economy.py | 453 +++++++++ src/cogs/resetsupcommands.py | 14 + src/cogs/rimagochi.py | 1247 ++++++++++++++++++++++++ src/cogs/roles.py | 927 ++++++++++++++++++ src/constants/global_constants.py | 20 + src/constants/rimagochi_constants.py | 1307 ++++++++++++++++++++++++++ src/database/db_classes.py | 401 ++++++++ src/database/settings/config.py | 17 + src/database/settings/db_settings.py | 5 + src/managers/DataBaseManager.py | 128 +++ src/managers/old_DataBaseManager.py | 239 +++++ src/test.py | 84 ++ src/testbase.py | 74 ++ 20 files changed, 6162 insertions(+), 3 deletions(-) create mode 100644 README.md create mode 100644 exclude.txt create mode 100644 requirements.txt create mode 100644 src/CoreFun.py create mode 100644 src/cogs/admin.py create mode 100644 src/cogs/designer.py create mode 100644 src/cogs/economy.py create mode 100644 src/cogs/resetsupcommands.py create mode 100644 src/cogs/rimagochi.py create mode 100644 src/cogs/roles.py create mode 100644 src/constants/global_constants.py create mode 100644 src/constants/rimagochi_constants.py create mode 100644 src/database/db_classes.py create mode 100644 src/database/settings/config.py create mode 100644 src/database/settings/db_settings.py create mode 100644 src/managers/DataBaseManager.py create mode 100644 src/managers/old_DataBaseManager.py create mode 100644 src/test.py create mode 100644 src/testbase.py diff --git a/.gitignore b/.gitignore index 3cdb62c..f3a5b6b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,12 @@ -# Игнорируем папку виртуального окружения venv/ nb_venv/ pc_venv/ -# Кэш Python +docs/ + __pycache__/ *.pyc -# Файлы с секретами (токены, пароли) .env .secrets src/data/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/exclude.txt b/exclude.txt new file mode 100644 index 0000000..966de08 --- /dev/null +++ b/exclude.txt @@ -0,0 +1,9 @@ +venv/ +nb_venv/ +pc_venv/ +docs/ +__pycache__/ +*.pyc +.env +.secrets +backups/ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..aaf9965 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,42 @@ +aiohappyeyeballs==2.6.1 +aiohttp==3.12.12 +aiosignal==1.3.2 +alabaster==1.0.0 +async-timeout==5.0.1 +asyncpg==0.30.0 +attrs==25.3.0 +babel==2.17.0 +certifi==2025.4.26 +charset-normalizer==3.4.2 +colorama==0.4.6 +disnake==2.10.1 +docutils==0.21.2 +frozenlist==1.7.0 +greenlet==3.2.3 +idna==3.10 +imageio==2.37.0 +imagesize==1.4.1 +Jinja2==3.1.6 +MarkupSafe==3.0.2 +multidict==6.4.4 +numpy==2.2.6 +packaging==25.0 +pillow==11.3.0 +propcache==0.3.2 +Pygments==2.19.2 +requests==2.32.4 +snowballstemmer==3.0.1 +Sphinx==8.1.3 +sphinx-rtd-theme==3.0.2 +sphinxcontrib-applehelp==2.0.0 +sphinxcontrib-devhelp==2.0.0 +sphinxcontrib-htmlhelp==2.1.0 +sphinxcontrib-jquery==4.1 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-qthelp==2.0.0 +sphinxcontrib-serializinghtml==2.0.0 +SQLAlchemy==2.0.41 +tomli==2.2.1 +typing_extensions==4.14.0 +urllib3==2.4.0 +yarl==1.20.1 diff --git a/src/CoreFun.py b/src/CoreFun.py new file mode 100644 index 0000000..bb61622 --- /dev/null +++ b/src/CoreFun.py @@ -0,0 +1,810 @@ +import disnake +from disnake.ext import commands +from disnake.ext import tasks +import requests +import numpy as np +import aiohttp +import asyncio +import sys +import os +import copy +import datetime +import math +import random +import inspect +import json +import shutil +import re + +from constants.rimagochi_constants import * +from constants.global_constants import * +from data.TOKENS import TOKENS +from database.db_classes import all_data as DataBaseClasses +from managers.DataBaseManager import DatabaseManager +from database.settings import config + +from sqlalchemy.orm import declarative_base, relationship +from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker +from sqlalchemy.orm import sessionmaker +from sqlalchemy.schema import CreateTable + +class AnyBots(commands.Bot): + ''' + + + Any bot class + + + ''' + def __init__(self, DataBaseManager): + super().__init__( + command_prefix="=", + intents=disnake.Intents.all() + ) + self.DataBaseManager = DataBaseManager + self.costrolecreate = constants['costrolecreate'] + self.costrolerenewal = constants['costrolerenewal'] + self.dailycrumbs = constants['dailycrumbs'] + self.casinospinslimit = constants['casinospinslimit'] + + async def on_ready(self, inherited = False): + self.krekchat = await self.fetch_guild(constants["krekchat"]) + print(self.krekchat.name) + self.sponsors = [disnake.utils.get(self.krekchat.roles, id=i) for i in constants["sponsors"]] + self.mutes = [disnake.utils.get(self.krekchat.roles, id=i) for i in constants["mutes"]] + self.ban_role = disnake.utils.get(self.krekchat.roles, id=constants["ban_role"]) + self.me = disnake.utils.get(self.krekchat.roles, id=constants["me"]) + self.moder = disnake.utils.get(self.krekchat.roles, id=constants["moder"]) + self.curator = disnake.utils.get(self.krekchat.roles, id=constants["curator"]) + self.everyone = disnake.utils.get(self.krekchat.roles, id=constants["everyone"]) + self.staff = disnake.utils.get(self.krekchat.roles, id=constants["staff"]) + self.level_roles = [disnake.utils.get(self.krekchat.roles, id=i) for i in constants["level_roles"]] + # lists + self.moderators = [disnake.utils.get(self.krekchat.roles, id=i) for i in constants["moderators"]] + # /lists + self.bots_talk_protocol_channel_id = constants["bots_talk_protocol_channel"] + self.databases_backups_channel_id = constants["databases_backups_channel"] + await self.change_presence(status=disnake.Status.online, activity=disnake.Game("Работаю")) + + if not inherited: + print(f"{datetime.datetime.now().strftime('%H:%M:%S %d-%m-%Y')}:: KrekFunBot activated") + + async def RimagochiUserUpdate(self, member, session = None): + if session is None: + raise Exception("Реализация RimagochiUserUpdate без session ещё не сделана") + + else: + await self.UserUpdate(member, session = session) + async with session.begin(): + async with self.DataBaseManager.models['rimagochi_users'] as rimagochi_users_models: + stmt = self.DataBaseManager.select(rimagochi_users_models).where(rimagochi_users_models.id == member.id) + rimagochi_user = (await session.execute(stmt)).scalars().first() + + if rimagochi_user is None: + rimagochi_user = rimagochi_users_models(id = member.id, settings = rimagochi_default_settings) + session.add(rimagochi_user) + + async def CasinoUserUpdate(self, member, session = None): + if session is None: + raise Exception("Реализация CasinoUserUpdate без session ещё не сделана") + + else: + await self.UserUpdate(member, session = session) + async with session.begin(): + async with self.DataBaseManager.models['casino_user_account'] as casino_user_account_models: + stmt = self.DataBaseManager.select(casino_user_account_models).where(casino_user_account_models.id == member.id) + casino_user = (await session.execute(stmt)).scalars().first() + + if casino_user is None: + casino_user = casino_user_account_models(id = member.id) + session.add(casino_user) + + async def UserUpdate(self, member, session = None): + + if session is None: + async with self.DataBaseManager.session() as session: + async with session.begin(): + async with self.DataBaseManager.models['users'] as users_model: + stmt = self.DataBaseManager.select(users_model).where(users_model.id == member.id) + user = (await session.execute(stmt)).scalars().first() + + if user is None: + user = users_model(id = member.id) + session.add(user) + + return 0 + else: + async with session.begin(): + async with self.DataBaseManager.models['users'] as users_model: + stmt = self.DataBaseManager.select(users_model).where(users_model.id == member.id) + user = (await session.execute(stmt)).scalars().first() + + if user is None: + user = users_model(id = member.id) + session.add(user) + return user + + async def DeleteRole(self, role_id: int): + async with self.DataBaseManager.session() as session: + async with session.begin(): + async with self.DataBaseManager.models['roles_custom'] as roles_custom: + stmt = self.DataBaseManager.select(roles_custom).where( + roles_custom.id == role_id + ).with_for_update() + role = (await session.execute(stmt)).scalars().first() + if not role is None: + await session.delete(role) + + krekchat = await self.fetch_guild(constants["krekchat"]) + role = krekchat.get_role(role_id) + if not role: + return [True, "Кастомная роль успешно удалена из базы данных, но не найдена на сервере"] + # deleting from server + await role.delete() + return [True, "Кастомная роль успешно удалена"] + + async with session.begin(): + async with self.DataBaseManager.models['roles_prize'] as roles_prize: + async with self.DataBaseManager.models['roles_static'] as roles_static: + stmt = self.DataBaseManager.select(roles_prize).where( + roles_prize.id == role_id + ).with_for_update() + prize_role = (await session.execute(stmt)).scalars().first() + if not prize_role is None: + await session.delete(prize_role) + stmt = self.DataBaseManager.select(roles_static).where( + roles_static.id == role_id + ).with_for_update() + static_role = (await session.execute(stmt)).scalars().first() + if not static_role is None: + await session.delete(static_role) + krekchat = await self.fetch_guild(constants["krekchat"]) + role = krekchat.get_role(role_id) + if not role: + return [True, "Призовая роль успешно удалена из базы данных, но не найдена на сервере"] + await role.delete() + return [True, "Призовая роль успешно удалена"] + + return [False, "Такой роли не существует в базе данных"] + + def PartitioningEmbeder(self, arr): + result = [] + for i in range(len(arr)): + if i % 5 == 0: + result.append([]) + result[-1].append(arr[i]) + return result + + async def CalculateRimagochiBattleStrength(self, users_animals: list): + sum_strenght = 0 + for animal in users_animals: + if not animal.in_battle_slots: + continue + animal_model = copy.deepcopy(rimagochi_animals[animal.model_animal_id]) + for gene in animal.genes: + genedata = rimagochi_genes[gene] + for effect in genedata['effects'].keys(): + animal_model['params'][effect] += genedata['effects'][effect] + sum_strenght = sum_strenght + ((animal_model['params']['damage']*animal_model['params']['health'])+(animal_model['params']['damage']*animal_model['params']['health']*animal.level/4)) + return sum_strenght + + async def LevelRolesGiver(self, member, level): + try: + c = 0 + if level >= 1: c += 1 + if level >= 5: c += 1 + if level >= 55: c += 1 + if level >= 91: c += 1 + for role in range(1, len(self.level_roles)): + if role == c: + await member.add_roles(self.level_roles[role]) + continue + await member.remove_roles(self.level_roles[role]) + except: + pass + + def HexToRgb(self, value): + value = value.lstrip('#') + lv = len(value) + return tuple(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3)) + + def find_for_arr(self, arr, elem): + for i in range(len(arr)): + if arr[i] == elem: + return i + return -1 + + def AnimalLevelAdder(self, exp, level): + if(level+1<=max(rimagochi_constants["animals_exp_to_levels"].keys())): + if exp>=rimagochi_constants["animals_exp_to_levels"][level+1]: + exp = exp - rimagochi_constants["animals_exp_to_levels"][level+1] + level += 1 + if (level+1<=max(rimagochi_constants["animals_exp_to_levels"].keys())): + return exp, level, rimagochi_constants["animals_exp_to_levels"][level+1] - exp + else: + return exp, level, float('inf') + else: + return exp, level, rimagochi_constants["animals_exp_to_levels"][level+1] - exp + else: + return exp, level, float('inf') + + def CalculateLevel(self, messages, voiceactivity): + n = (messages + (voiceactivity / (180))) / 4 + # return math.log((messages+(voiceactivity/(30)))+1, 1.11) + if n < 40: + return n / 3 + else: + return math.log(n, 1.05) - 62 + + def TimeFormater(self, time_str: str = "", days: float = 0, hours: float = 0, minutes: float = 0, now_timestamp = None): + """ + Форматирует строку времени в timestamp и разложенное время + Поддерживает форматы: 1d2h30m, 1д2ч30мин, 1.5d, 1 день 2 часа 30 минут и т.п. + Возвращает объект класса FormatedTime + """ + + class FormatedTime: + def __init__(self, time_units): + self.time_units = time_units + + self.days, self.hours, self.minutes = time_units['d'], time_units['h'], time_units['m'] + delta = datetime.timedelta(days=self.days, hours=self.hours, minutes=self.minutes) + self.future_time = 0 + if now_timestamp is None: + self.future_time = datetime.datetime.now() + delta + else: + self.future_time = datetime.datetime.fromtimestamp(now_timestamp) + delta + self.timestamp = self.future_time.timestamp() + + def __float__(self): + return self.timestamp + + def __int__(self): + return int(self.timestamp) + + def __repr__(self): + result = [] + if self.days != 0: + result.append(f"{self.days} дней") + if self.hours != 0: + result.append(f"{self.hours} часов") + if self.minutes != 0: + result.append(f"{self.minutes} минут") + + return ", ".join(result) + f" [{self.timestamp}]" + + def to_dict(self): + return self.time_units + + time_units = {'d': 0, 'h': 0, 'm': 0} + if any([days, hours, minutes]): + time_units = {'d': days, 'h': hours, 'm': minutes} + else: + time_str = time_str.lower().replace(' ', '').replace(',', '.') + + replacements = { + r'дней?': 'd', + r'день': 'd', + r'д': 'd', + r'часов?': 'h', + r'час': 'h', + r'ч': 'h', + r'минут?': 'm', + r'мин': 'm', + r'м': 'm', + } + + for pattern, replacement in replacements.items(): + time_str = re.sub(pattern, replacement, time_str, flags=re.IGNORECASE) + + pattern = r'(\d+(\.\d+)?)([dhm])' + matches = re.findall(pattern, time_str) + + for value, _, unit in matches: + time_units[unit] += float(value) + + return FormatedTime(time_units) + + class ErrorOutHelper: + def __init__(self, send_function, err_name: str = "", err_description: str = "", ephemeral: bool = False, echo: bool = False, thumbnail = None): + self.err_name = err_name + self.err_description = err_description + self.send_function = send_function + self.ephemeral = ephemeral + self.echo = echo + self.thumbnail = thumbnail + self.colour = 0xff0000 + + async def out(self, err_description: str = "", err_name: str = "", d: str = "", n: str = ""): + if d: + err_description = d + if n: + err_name = n + + embed = disnake.Embed(title="", description="", colour = self.colour) + if err_name: + embed.title = err_name + else: + embed.title = self.err_name + + if err_description: + embed.description = err_description + else: + embed.description = self.err_description + + if not self.thumbnail is None: + embed.set_thumbnail(url = self.thumbnail) + + if self.echo: + print(f"{embed.title}: {embed.description}") + if 'ephemeral' in inspect.signature(self.send_function).parameters: + await self.send_function(embed = embed, ephemeral = self.ephemeral) + else: + await self.send_function(embed = embed) + +class AdminBot(AnyBots): + ''' + + + Admin`s bot class + + + ''' + def __init__(self, DataBase, stop_event, *, task_start = True): + super().__init__(DataBase) + self.task_start = task_start + self.stop_event = stop_event + + async def on_ready(self): + await super().on_ready(inherited = True) + + if self.task_start: + self.VoiceXpAdder.cancel() + self.CheckDataBase.cancel() + + self.VoiceXpAdder.start() + self.CheckDataBase.start() + + print(f"{datetime.datetime.now().strftime('%H:%M:%S %d-%m-%Y')}:: KrekFunLoopsBot activated") + + async def BotOff(self): + self.VoiceXpAdder.cancel() + self.CheckDataBase.cancel() + + self.stop_event.set() + + async def on_disconnect(self): + if self.stop_event.is_set(): + pass + else: + print(f"{datetime.datetime.now().strftime('%H:%M:%S %d-%m-%Y')}:: Соединение с дискордом разорвано") + await self.BotOff() + + async def check_bt_channel(self): + async def moder_dataparser(data: dict): + if not 'type' in data.keys(): + raise json.JSONDecodeError() + async with self.DataBaseManager.session() as session: + async with session.begin(): + async with self.DataBaseManager.models['users'] as users_model: + if data['type'] == "punishment": + options = data['options'] + ratings = {'textmute': 10, 'voicemute': 10, 'warning': 5, 'ban': 50, 'reprimand': 15, 'newnick': 0} + user = await session.get(users_model, options['member'], with_for_update = True) + if user is None: + user = users_model(id = options['member'], carma = -ratings[options['severity']]) + session.add(user) + else: + user.carma -= ratings[options['severity']] + + elif data['type'] == "unpunishment": + options = data['options'] + ratings = {'textmute': 10, 'voicemute': 10, 'warning': 5, 'ban': 50, 'reprimand': 15, 'newnick': 0} + user = await session.get(users_model, options['member'], with_for_update = True) + if user is None: + user = users_model(id = options['member'], carma = ratings[options['severity']]) + session.add(user) + else: + user.carma += ratings[options['severity']] + + elif data['type'] == "complaint": + options = data['options'] + + attack_member = await session.get(users_model, options['attack_member'], with_for_update = True) + if attack_member is None: + attack_member = users_model(id = options['attack_member'], carma = 10 if options['accepted'] else -1) + session.add(attack_member) + else: + attack_member.carma += 10 if options['accepted'] else -1 + + moderator = await session.get(users_model, options['moderator'], with_for_update = True) + if moderator is None: + moderator = users_model(id = options['moderator'], carma = 10) + session.add(moderator) + else: + moderator.carma += 10 + + else: + return 1 + + return 0 + + + krekchat = await self.fetch_guild(self.krekchat.id) + bt_channel = await krekchat.fetch_channel(self.bots_talk_protocol_channel_id) + last_msg = None + + while True: + stopflag = False + batch = await bt_channel.history(limit=10, before=last_msg).flatten() + for message in batch: + msg_stopflag = False + for reaction in message.reactions: + if reaction.emoji == "✅" or reaction.emoji == "❎": + msg_stopflag = True + break + else: + try: + data = json.loads(message.content) + result = 0 + if not 'sender' in data: + raise json.JSONDecodeError() + if data['sender'] == "ModBot": + result = await moder_dataparser(data) + if result: + raise json.JSONDecodeError() + await message.add_reaction("✅") + except json.JSONDecodeError as e: + await message.add_reaction("❎") + except Exception as exc: + print(data, exc) + if msg_stopflag: + stopflag = True + break + last_msg = message + if stopflag: + break + + async def give_crumbs_counter(self, incoming_crumbs: float, member, sponsor_roles, carma = 0): + modifier = (carma / 100) + (bool(set(sponsor_roles) & set(member.roles)) + 1) + add_crumbs = (incoming_crumbs * modifier) if incoming_crumbs * modifier >= 0 else 0 + return add_crumbs + + async def on_message(self, msg): + if msg.author.id == 479210801891115009 and msg.content == "botsoff": + await msg.reply(embed=disnake.Embed(description=f'Бот отключён', colour=0xff9900)) + await self.BotOff() + if msg.guild is None: + return + if msg.channel.id == self.bots_talk_protocol_channel_id: + await self.check_bt_channel() + if msg.author.bot: + return + crumb_per_word = 1 / 2 + text = msg.content + while " " in text: + text = text.replace(" ", " ") + add_crumbs = len(text.split(" ")) * crumb_per_word + + async with self.DataBaseManager.session() as session: + async with session.begin(): + async with self.DataBaseManager.models['users'] as users_model: + stmt = self.DataBaseManager.select(users_model).where(users_model.id == msg.author.id) + user = (await session.execute(stmt)).scalars().first() + + if user is None: + user = users_model(id = msg.author.id, period_messages = 1, summary_messages = 1, crumbs = (await self.give_crumbs_counter(incoming_crumbs = add_crumbs, sponsor_roles = self.sponsors, member = msg.author))) + session.add(user) + period_messages, period_voice_activity = 1, 0 + + else: + stmt = self.DataBaseManager.update(users_model).where(users_model.id == msg.author.id).values( + period_messages = users_model.period_messages + 1, + summary_messages = users_model.summary_messages + 1, + crumbs = users_model.crumbs + (await self.give_crumbs_counter(incoming_crumbs = add_crumbs, sponsor_roles = self.sponsors, member = msg.author, carma = user.carma)) + ) + await session.execute(stmt) + period_messages, period_voice_activity = user.period_messages + 1, user.period_voice_activity + + await self.LevelRolesGiver(msg.author, self.CalculateLevel(period_messages, period_voice_activity)) + + '''if msg.channel.id == 1228525235024695328: + embed = disnake.Embed( + title='Новые работы', + description="\n".join("## "+str(i) for i in msg.attachments)+"\n**"+str(msg.content)+"**\nАвтор: "+str(msg.author)+"   "+str(msg.author.id), + color=0x2F3136 + ) + if len(msg.attachments)>0: + await msg.attachments[0].save("timelycontent.png") + embed.set_image(file = disnake.File(fp="timelycontent.png")) + mirror = self.get_channel(1228525202107793519) + await mirror.send(embed=embed) + await msg.reply(embed=disnake.Embed(description=f'Ваша работа отправлена на проверку и будет опубликована в течение суток', colour=0x2F3136)) + await msg.delete() + os.remove("timelycontent.png")''' + + @tasks.loop(seconds=60) + async def VoiceXpAdder(self): + try: + channels = await self.krekchat.fetch_channels() + async with self.DataBaseManager.session() as session: + for channel in channels: + if (not isinstance(channel, disnake.VoiceChannel)) or channel.id == 1250314784914669598: + continue + channel = self.get_channel(channel.id) + for member in channel.members: + if member.bot: + continue + add_crumbs = 5 + async with session.begin(): + async with self.DataBaseManager.models['users'] as users_model: + stmt = self.DataBaseManager.select(users_model).where(users_model.id == member.id) + user = (await session.execute(stmt)).scalars().first() + + if user is None: + user = users_model( + id = member.id, + period_voice_activity = 60, + summary_voice_activity = 60, + crumbs = (await self.give_crumbs_counter(incoming_crumbs = add_crumbs, sponsor_roles = self.sponsors, member = member)) + ) + session.add(user) + period_messages, period_voice_activity = 0, 60 + + else: + stmt = self.DataBaseManager.update(users_model).where(users_model.id == member.id).values( + period_voice_activity = users_model.period_voice_activity + 60, + summary_voice_activity = users_model.summary_voice_activity + 60, + crumbs = users_model.crumbs + (await self.give_crumbs_counter(incoming_crumbs = add_crumbs, sponsor_roles = self.sponsors, member = member, carma = user.carma)) + ) + await session.execute(stmt) + period_messages, period_voice_activity = user.period_messages, user.period_voice_activity + 60 + await self.LevelRolesGiver(member, self.CalculateLevel(period_messages, period_voice_activity)) + + except Exception as e: + print(f"{datetime.datetime.now().strftime('%H:%M:%S %d-%m-%Y')}:: err VoiceXpAdder: {e}") + + @tasks.loop(seconds=3600) + async def CheckDataBase(self): + try: + roles_for_delete = [] + async with self.DataBaseManager.session() as session: + async with session.begin(): + async with self.DataBaseManager.models['roles_custom'] as roles_custom: + + stmt = self.DataBaseManager.select(roles_custom).join(roles_custom.creator).options( + self.DataBaseManager.contains_eager(roles_custom.creator) + ).where(roles_custom.renewal_date <= datetime.datetime.now().timestamp()).with_for_update() + roles = (await session.execute(stmt)).scalars().all() + + for role in roles: + if role.renewal_enabled: + if role.creator.crumbs >= self.costrolerenewal: + async with self.DataBaseManager.models['transaction_history_crumbs'] as transaction_history_crumbs: + transaction = transaction_history_crumbs(sender_id = role.creator.id, amount = self.costrolerenewal, description = f"Содержание роли {role.id}") + session.add(transaction) + role.creator.crumbs -= self.costrolerenewal + await role.renewal_date_update(self.TimeFormater) + continue + else: + roles_for_delete.append(role.id) + async with session.begin(): + async with self.DataBaseManager.models['users'] as users_model: + stmt = self.DataBaseManager.update(users_model).where(users_model.staff_salary > 0).values( + crumbs = users_model.crumbs+users_model.staff_salary + ) + await session.execute(stmt) + for role_id in roles_for_delete: + await self.DeleteRole(role_id) + # создание бэкапов + backup_file = await self.DataBaseManager.pg_dump() + krekchat = await self.fetch_guild(self.krekchat.id) + backups_channel = await krekchat.fetch_channel(self.databases_backups_channel_id) + if "Backup failed" in backup_file: + await backups_channel.send(content=backup_file) + else: + await backups_channel.send(content=f"Бэкап бд за {datetime.datetime.now()}:", file=disnake.File(backup_file)) + + except Exception as e: + print(f"err CheckDataBase: {e}") + +async def init_db(): + DataBaseEngine = create_async_engine( + config.Settings().DB_URL, + pool_size=20, + max_overflow=10, + pool_recycle=300, + pool_pre_ping=True, + #echo=True, + ) + async with DataBaseEngine.begin() as conn: + await conn.run_sync(DataBaseClasses['base'].metadata.create_all) + + return DatabaseManager(DataBaseEngine, DataBaseClasses) + +# async def db_migration(DB_MANAGER): +# new_DataBase = DB_MANAGER +# DataBase = await old_DatabaseManager.connect("data/fun.db") +# await DataBase.execute("PRAGMA journal_mode=WAL") +# await DataBase.execute("PRAGMA synchronous=NORMAL") +# try: +# async with new_DataBase.engine.begin() as conn: +# await conn.run_sync(new_DataBase.metadata.drop_all) +# await conn.run_sync(new_DataBase.metadata.create_all) +# async with new_DataBase.session() as session: +# users = [DB_MANAGER.model_classes['users']( +# id = userid, crumbs = crumbs, summary_messages = messages, summary_voice_activity = voiceactivity, +# carma = carma, staff_salary = staffsalary, last_daily_crumbs_date = lastdailycrumbsdate +# ) +# for userid, crumbs, messages, voiceactivity, carma, rolesinventory, staffsalary, sellingroles, ownchannels, timedroles, lastdailycrumbsdate\ +# in await DataBase.SelectBD('users') +# ] +# async with session.begin(): +# session.add_all(users) + +# roles_custom = [DB_MANAGER.model_classes['roles_custom']( +# id = roleid, creator_id = creatorid, cost = cost, renewal_date = renewaldate, renewal_enabled = renewal, date_of_creation = dateofcreation +# ) +# for roleid, creatorid, dateofcreation, cost, sales, renewaldate, renewal in await DataBase.SelectBD('selling_roles') +# ] +# async with session.begin(): +# session.add_all(roles_custom) + +# received_roles_custom = [DB_MANAGER.model_classes['received_roles_custom']( +# role_id = roleid, user_id = int(user) +# ) +# for roleid, creatorid, dateofcreation, cost, sales, renewaldate, renewal in await DataBase.SelectBD('selling_roles') +# for user in sales.split(":") +# ] +# async with session.begin(): +# session.add_all(received_roles_custom) + +# roles_prize = [DB_MANAGER.model_classes['roles_prize']( +# id = roleid +# ) +# for roleid, surrendered in await DataBase.SelectBD('prize_roles') +# ] +# async with session.begin(): +# session.add_all(roles_prize) + +# received_roles_prize = [DB_MANAGER.model_classes['received_roles_prize']( +# role_id = roleid, user_id = int(user) +# ) +# for roleid, surrendered in await DataBase.SelectBD('prize_roles') +# for user in surrendered.split(":") +# ] +# async with session.begin(): +# session.add_all(received_roles_prize) + +# roles_static = [DB_MANAGER.model_classes['roles_static']( +# id = roleid, +# description = description +# ) +# for roleid, description in await DataBase.SelectBD('uncustom_roles') +# ] +# async with session.begin(): +# session.add_all(roles_static) + +# transaction_history_crumbs = [DB_MANAGER.model_classes['transaction_history_crumbs']( +# sender_id = sender if sender != 0 else None, +# recipient_id = recipient if recipient != 0 else None, +# amount = amount, commission_fraction = commission, +# description = comment, transaction_time = transactiontime +# ) +# for sender, recipient, amount, commission, comment, transactiontime in await DataBase.SelectBD('history') +# ] +# async with session.begin(): +# session.add_all(transaction_history_crumbs) + +# casino_user_account = [DB_MANAGER.model_classes['casino_user_account']( +# id = int(userid), spins_today_count = spinstoday, last_reset_time = lastreset +# ) +# for userid, spinstoday, lastreset in await DataBase.SelectBD('casino_limits') +# ] +# async with session.begin(): +# session.add_all(casino_user_account) + +# rimagochi_users = [DB_MANAGER.model_classes['rimagochi_users']( +# id = userid, items = json.loads(items), genes = json.loads(genes), wins = wins, settings = json.loads(settings) +# ) +# for userid, animals, items, genes, battleslots, wins, settings in await DataBase.SelectBD('rimagochi_users') +# if userid != 920423544691761213 and userid != 1354557962168959077 +# ] +# async with session.begin(): +# session.add_all(rimagochi_users) + +# rimagochi_animals = [] +# for userid, animals, items, genes, battleslots, wins, settings in await DataBase.SelectBD('rimagochi_users'): +# rimagochi_animals += [DB_MANAGER.model_classes['rimagochi_animals']( +# id = int(item['id'][:4]), model_animal_id = item['animal_id'], genes = item['genes'], items = item['used_items'], +# last_feeding_time = item['last_feeding'], first_today_feed_time = item['first_feed_today_time'], +# feed_today_count = item['feed_today'], experience = item['exp'], level = item['level'], wins = item['wins'], +# initial_owner_id = int(item['id'].split("_")[1]), owner_id = userid, in_battle_slots = item['id'] in json.loads(battleslots).keys() +# ) +# for key, item in json.loads(animals).items() +# ] +# async with session.begin(): +# session.add_all(rimagochi_animals) + +# finally: +# await DataBase.close() + +# raise Exception("Миграция БД завершена. требуется переименовывание файлов") + + +async def run_bot(bot, token, stop_event): + try: + await bot.start(token) + except Exception as e: + print(f"Бот {bot.user.name if hasattr(bot, 'user') else 'Unknown'} упал с ошибкой: {e}") + stop_event.set() # Сигнализируем об остановке + +async def monitor_stop(stop_event, bots): + await stop_event.wait() + print(f"{datetime.datetime.now().strftime('%H:%M:%S %d-%m-%Y')}:: Получен сигнал остановки, завершаю всех ботов...") + + for bot in bots: + if not bot.is_closed(): + try: + await bot.close() + except Exception as e: + print(f"Ошибка при закрытии бота: {e}") + + await asyncio.sleep(0.1) + +async def main(): + stop_event = asyncio.Event() + DataBase = None + all_bots = [] + admin_bot = None + + try: + DataBase = await init_db() + + # Инициализация ботов + admin_bot = AdminBot(DataBase, stop_event) + economy_bot = AnyBots(DataBase) + rimagochi_bot = AnyBots(DataBase) + all_bots = [economy_bot, admin_bot, rimagochi_bot] + + # Загрузка когов + economy_bot.load_extension("cogs.economy") + economy_bot.load_extension("cogs.roles") + economy_bot.load_extension("cogs.designer") + admin_bot.load_extension("cogs.admin") + rimagochi_bot.load_extension("cogs.rimagochi") + + # Запуск монитора остановки и ботов + monitor_task = asyncio.create_task(monitor_stop(stop_event, all_bots)) + bot_tasks = [ + asyncio.create_task(run_bot(economy_bot, TOKENS["KrekFunBot"], stop_event)), + asyncio.create_task(run_bot(admin_bot, TOKENS["KrekAdminBot"], stop_event)), + asyncio.create_task(run_bot(rimagochi_bot, TOKENS["KrekRimagochiBot"], stop_event)) + ] + + await asyncio.gather(*bot_tasks, monitor_task) + + except KeyboardInterrupt: + print("Боты остановлены по запросу пользователя") + except Exception as e: + print(f"Произошла критическая ошибка: {e}") + finally: + await admin_bot.BotOff() + + for bot in all_bots: + if not bot.is_closed(): + await bot.close() + + await DataBase.close() + + current_task = asyncio.current_task() + pending = [t for t in asyncio.all_tasks() if t is not current_task and not t.done()] + for task in pending: + task.cancel() + await asyncio.gather(*pending, return_exceptions=True) + + await asyncio.sleep(0.1) + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/src/cogs/admin.py b/src/cogs/admin.py new file mode 100644 index 0000000..24d7f3a --- /dev/null +++ b/src/cogs/admin.py @@ -0,0 +1,259 @@ + +import disnake +from disnake.ext import commands +from disnake.ext import tasks +import requests +import numpy as np +import aiohttp + +import asyncio +import sqlite3 +import sys +import os +import copy +import datetime +import math +import random +import json +import shutil +from constants.global_constants import * +from constants.rimagochi_constants import * + + +def setup(bot): + bot.add_cog(MainAdminModule(bot)) + +class MainAdminModule(commands.Cog): + def __init__(self, client): + self.client = client + self.DataBaseManager = client.DataBaseManager + + @commands.Cog.listener() + async def on_ready(self): + print(f'KrekFunBot admin module activated') + self.krekchat = await self.client.fetch_guild(constants["krekchat"]) + self.me = disnake.utils.get(self.krekchat.roles, id=constants["me"]) + + + @commands.slash_command(name="bot_fun_off") + async def BotFunOff(self, ctx: disnake.ApplicationCommandInteraction): + if self.me in ctx.author.roles: + await ctx.send(embed=disnake.Embed(description=f'Бот отключён', colour=0xff9900), ephemeral=True) + await self.client.BotOff() + else: + await ctx.send(embed=disnake.Embed(description=f'Не допустимо', colour=0xff9900), ephemeral=True) + + + @commands.slash_command(name="зарегистрировать_призовую_роль") + async def RegisterAPrizeRoleSlash(self, ctx, role: disnake.Role): + await ctx.response.defer(ephemeral=True) + if not self.me in ctx.author.roles: + await ctx.edit_original_message( + embed=disnake.Embed(description=f'Эта команда доступна только администратору', colour=0x2F3136)) + return + + async with self.DataBaseManager.session() as session: + async with session.begin(): + async with self.DataBaseManager.models['roles_prize'] as roles_prize_model: + stmt = self.DataBaseManager.select(roles_prize_model).where(roles_prize_model.id == role.id) + role_prize = (await session.execute(stmt)).scalars().first() + if role_prize is None: + prize_role = roles_prize_model(id = role.id) + session.add(prize_role) + await ctx.edit_original_message(embed=disnake.Embed(description=f'Теперь роль {role.mention} зарегистрирована!',colour=0x2F3136)) + else: + await ctx.edit_original_message(embed=disnake.Embed(description=f'Эта роль уже зарегистрирована!',colour=0x2F3136)) + + + + @commands.slash_command(name="выдать_призовую_роль") + async def GiveAPrizeRoleSlash(self, ctx, role: disnake.Role, member: disnake.Member): + await ctx.response.defer(ephemeral=True) + if not self.me in ctx.author.roles: + await ctx.edit_original_message(embed=disnake.Embed(description=f'Эта команда доступна только администратору', colour=0x2F3136)) + return + + async with self.DataBaseManager.session() as session: + await self.client.UserUpdate(member, session = session) + async with session.begin(): + async with self.DataBaseManager.models['roles_prize'] as roles_prize_model: + + stmt = self.DataBaseManager.select(roles_prize_model).where(roles_prize_model.id == role.id) + role_prize = (await session.execute(stmt)).scalars().first() + if role_prize is None: + await ctx.edit_original_message(embed=disnake.Embed(description=f'Данная роль не зарегистрирована как призовая', colour=0x2F3136)) + return 1 + + async with self.DataBaseManager.models['received_roles_prize'] as received_roles_prize_model: + stmt = self.DataBaseManager.select(received_roles_prize_model).where( + received_roles_prize_model.role_id == role.id, + received_roles_prize_model.user_id == member.id + ) + received_roles_prize = (await session.execute(stmt)).scalars().first() + if not received_roles_prize is None: + await ctx.edit_original_message(embed=disnake.Embed(description=f'У {member.mention} уже есть роль {role.mention}', colour=0x2F3136)) + return 1 + + received_roles_prize = received_roles_prize_model(role_id = role.id, user_id = member.id) + session.add(received_roles_prize) + + embed = disnake.Embed(description=f'Роль {role.mention} успешно передана {member.mention}!', colour=0x2F3136) + await member.add_roles(role) + await ctx.edit_original_message(embed=embed) + + + @commands.slash_command(name="забрать_призовую_роль") + async def TakeAwayAPrizeRoleSlash(self, ctx, role: disnake.Role, member: disnake.Member): + await ctx.response.defer(ephemeral=True) + if not self.me in ctx.author.roles: + await ctx.edit_original_message(embed=disnake.Embed(description=f'Эта команда доступна только администратору', colour=0x2F3136)) + return + + async with self.DataBaseManager.session() as session: + async with session.begin(): + async with self.DataBaseManager.models['received_roles_prize'] as received_roles_prize_model: + stmt = self.DataBaseManager.select(received_roles_prize_model).where( + received_roles_prize_model.user_id == member.id, + received_roles_prize_model.role_id == role.id + ).with_for_update() + user_role = (await session.execute(stmt)).scalars().first() + if user_role is None: + await ctx.edit_original_message(embed=disnake.Embed(description=f'Данный пользователь не связан с этой ролью', colour=0x2F3136)) + return + + await session.delete(user_role) + embed = disnake.Embed(description=f'Роль {role.mention} удалена из инвентаря {member.mention}!', colour=0x2F3136) + await member.remove_roles(role) + await ctx.edit_original_message(embed=embed) + + + @commands.slash_command(name="удалить_роль") + async def DeleteRoleSlash(self, ctx: disnake.AppCmdInter, roleid: str): + if not self.me in ctx.author.roles: + await ctx.send(embed=disnake.Embed(description=f'Эта команда доступна только администратору', colour=0x2F3136)) + return + try: + res = await self.client.DeleteRole(int(roleid)) + await ctx.send(embed=disnake.Embed(description=f'{res[1]}', colour=0xff9900), ephemeral=True) + except ValueError: + await ctx.send(embed=disnake.Embed(description=f'Введён неверный id', colour=0xff9900), ephemeral=True) + + + @commands.slash_command(name="установить_описание_роли") + async def RoleDescriptoinSetSlash(self, ctx, role: disnake.Role, description: str): + await ctx.response.defer(ephemeral=True) + if not self.me in ctx.author.roles: + await ctx.edit_original_message(embed=disnake.Embed(description=f'Эта команда доступна только администратору', colour=0x2F3136)) + return + + async with self.DataBaseManager.session() as session: + async with session.begin(): + async with self.DataBaseManager.models['roles_static'] as roles_static_model: + stmt = self.DataBaseManager.select(roles_static_model).where( + roles_static_model.id == role.id + ).with_for_update() + role_static = (await session.execute(stmt)).scalars().first() + if role_static is None: + role = roles_static_model(id = role.id, description = description) + session.add(role) + else: + role_static.description = description + await ctx.edit_original_message(embed=disnake.Embed(description=f'Для роли {role.mention} успешно установлено описание\n```{description}```', colour=0x2F3136)) + return + + @commands.slash_command(name = "изменить_параметр") + async def ChangeParamSlash(self, ctx: disnake.AppCmdInter, member: disnake.Member, vector: int, parameter: str = commands.Param(description="Какой параметр хотите изменить?", + name="параметр", + choices=['крошки', 'сообщения', 'секунды в голосовом канале', 'репутация', 'зарплата'])): + if not self.me in ctx.author.roles: + await ctx.edit_original_message(embed=disnake.Embed(description=f'Эта команда доступна только администратору', colour=0x2F3136)) + return + + async with self.DataBaseManager.session() as session: + await self.client.UserUpdate(member, session = session) + async with session.begin(): + async with self.DataBaseManager.models['users'] as user_model: + stmt = self.DataBaseManager.select(user_model).where( + user_model.id == member.id + ).with_for_update() + user = (await session.execute(stmt)).scalars().first() + count = 0 + match parameter: + case 'крошки': + user.crumbs += vector + count = user.crumbs + case 'сообщения': + user.period_messages += vector + user.summary_messages += vector + count = user.period_messages + case 'секунды в голосовом канале': + user.period_voice_activity += vector + user.summary_voice_activity += vector + count = user.period_voice_activity + case 'репутация': + user.carma += vector + count = user.carma + case 'зарплата': + user.staff_salary += vector + count = user.staff_salary + await ctx.send(embed=disnake.Embed(description=f'Количество {parameter} пользователя {member.mention} успешно изменено до `{count}`', colour=0xff9900), ephemeral=True) + + @commands.slash_command(name = "дать_зверя") + async def GiveRimagochiAnimalSlash(self, ctx: disnake.AppCmdInter, member: disnake.Member, animal_id: int): + if not self.me in ctx.author.roles: + await ctx.edit_original_message(embed=disnake.Embed(description=f'Эта команда доступна только администратору', colour=0x2F3136)) + return + + if not animal_id in rimagochi_animals.keys(): + await ctx.send(embed=disnake.Embed(description=f'Некорректный идентификатор животного', colour=0xff9900), ephemeral=True) + return + + async with self.DataBaseManager.session() as session: + await self.client.RimagochiUserUpdate(member, session = session) + async with session.begin(): + async with self.DataBaseManager.models['rimagochi_animals'] as rimagochi_animals_model: + animal = rimagochi_animals_model(model_animal_id = animal_id, initial_owner_id = ctx.author.id, owner_id = member.id) + session.add(animal) + await ctx.send(embed=disnake.Embed(description=f'{member.mention} успешно получил `{rimagochi_animals[animal_id]["name"]}`', colour=0xff9900), ephemeral=True) + + @commands.slash_command(name = "удалить_зверя") + async def RemoveRimagochiAnimalSlash(self, ctx: disnake.AppCmdInter, inventory_id: int): + if not self.me in ctx.author.roles: + await ctx.edit_original_message(embed=disnake.Embed(description=f'Эта команда доступна только администратору', colour=0x2F3136)) + return + + async with self.DataBaseManager.session() as session: + async with session.begin(): + async with self.DataBaseManager.models['rimagochi_animals'] as rimagochi_animals_model: + stmt = self.DataBaseManager.select(rimagochi_animals_model).where( + rimagochi_animals_model.id == inventory_id + ).with_for_update() + animal = (await session.execute(stmt)).scalars().first() + + if animal is None: + await ctx.send(embed=disnake.Embed(description=f'Животного с таким идентификатором не обнаружено', colour=0x2F3136), ephemeral=True) + return 1 + else: + await session.delete(animal) + await ctx.send(embed=disnake.Embed(description=f"<@{animal.owner_id}> лишился `{rimagochi_animals[animal.model_animal_id]['name']}` с id `{inventory_id}`", + colour=0xff9900), ephemeral=True) + return 0 + + @commands.slash_command(name = "температура") + async def RaspberryTemperature(self, ctx: disnake.AppCmdInter): + if not self.me in ctx.author.roles: + await ctx.send(embed=disnake.Embed(description=f'Эта команда доступна только администратору', colour=0x2F3136)) + return + def redgreen(temper, minimum, maximum): + if temper == ((maximum+minimum)/2): + return [255, 255, 0] + if temper > ((maximum+minimum)/2): + return [255, max(0, min(int((1-((temper-minimum)/(maximum-minimum)))*255), 255)), 0] + if temper < ((maximum+minimum)/2): + return [max(0, min(int(((temper-minimum)/(maximum-minimum))*255), 255)), 255, 0] + try: + with open('/sys/class/thermal/thermal_zone0/temp', 'r') as f: + temp = f.read() + except: + temp = "0" + await ctx.send(embed=disnake.Embed(description=f"Температура CPU {str(int(temp) / 1000)} °C", colour=disnake.Color.from_rgb(*redgreen(int(temp)/1000, 30, 70)))) diff --git a/src/cogs/designer.py b/src/cogs/designer.py new file mode 100644 index 0000000..0aeef2c --- /dev/null +++ b/src/cogs/designer.py @@ -0,0 +1,124 @@ +import disnake +from disnake.ext import commands +from disnake.ext import tasks +import requests +import numpy as np +import asyncio +import sys +import os +import copy +import datetime +import math +import random +import json +import shutil +import imageio +from PIL import Image, ImageDraw, ImageFont +from io import BytesIO +import textwrap +from constants.global_constants import * + + +def setup(bot): + bot.add_cog(MainDesignerModule(bot)) + +class MainDesignerModule(commands.Cog): + def __init__(self, client): + self.client = client + self.DataBaseManager = client.DataBaseManager + + @commands.Cog.listener() + async def on_ready(self): + self.krekchat = await self.client.fetch_guild(constants["krekchat"]) + self.sponsors = [disnake.utils.get(self.krekchat.roles, id=i) for i in constants["sponsors"]] + self.me = disnake.utils.get(self.krekchat.roles, id=constants["me"]) + print(f'KrekFunBot designer module activated') + + @commands.slash_command(name = "профиль", description="Ваш профиль на сервере") + async def Profile(self, ctx: disnake.AppCmdInter, + member: disnake.Member = commands.Param(description="Чей профиль хотите посмотреть?", name="участник", default=None), + old_style: bool = commands.Param(description="Если у вас не грузятся картинки, это лучший вариант", name="старый_стиль", default=False)): + if not member: + member = ctx.author + + await ctx.response.defer() + + async with self.DataBaseManager.session() as session: + async with session.begin(): + async with self.DataBaseManager.models['users'] as users_model: + user = await session.get(users_model, member.id) + + if user is None: + await self.client.ErrorOutHelper(send_function = ctx.edit_original_message).out(n="Ошибка профиля", d=f"{'Вас' if member == ctx.author else 'Этого пользователя'} пока нет в базе данных{', напишите хотя бы одно сообщение' if member == ctx.author else ''}") + return + + rating = users_model.period_messages + (users_model.period_voice_activity / 180.0) + subquery = ( + self.DataBaseManager.select( + users_model.id, + self.DataBaseManager.func.row_number().over(order_by=[self.DataBaseManager.desc(rating)]).label('rank') + ) + .subquery() + ) + stmt = ( + self.DataBaseManager.select(subquery.c.rank) + .where(subquery.c.id == member.id) + ) + user_rank = (await session.execute(stmt)).scalar() + + stmt = self.DataBaseManager.select(self.DataBaseManager.models['profile_design_inventory'].m).where( + self.DataBaseManager.models['profile_design_inventory'].m.user_id == member.id, + self.DataBaseManager.models['profile_design_inventory'].m.is_active == True + ) + + design = (await session.execute(stmt)).scalars().first() + if design is None: + design = await session.get(self.DataBaseManager.models['profile_design'].m, 1) + else: + design = design.design + + if old_style: + embed = disnake.Embed(title=f"Профиль **{member.display_name}**", description=f'') + + embed.colour = 0x2F3136 + embed.set_thumbnail(url=member.avatar) + + level = self.client.CalculateLevel(user.period_messages, user.period_voice_activity) + embed.add_field(name=f"Уровень", + value=f"`{int(level)}`\n|{'█' * int((level % 1) * 35) + '░' * (35 - int((level % 1) * 35))}|\n", + inline=False) + if ctx.guild is None: + embed.add_field(name=f"Баланс ", value=f"`{int(user.crumbs)} крошек`", inline=True) + else: + modify = max(0, (bool(await user.in_role(roles = self.sponsors, member = member))+1) + (user.carma / 100)) + embed.add_field(name=f"Баланс " + ("" if modify == 1 else f"(x{float(modify):.02n})"), value=f"`{int(user.crumbs)} крошек`", inline=True) + embed.add_field(name=f"Текстовая активность", value=f"`{user.period_messages} сообщений`", inline=True) + embed.add_field(name=f"Голосовая активность", value=f"`{round(user.period_voice_activity / (60))} минут`", inline=True) + embed.add_field(name=f"Репутация", value=f"`{int(user.carma)}`", inline=True) + embed.add_field(name=f"Топ", value=f"`{user_rank}`", inline=True) + if user.staff_salary != 0: + embed.add_field(name=f"Зарплата", value=f"`{user.staff_salary:.02n} крошек в час`", inline=True) + await ctx.edit_original_message(embed=embed) + else: + data = {} + + if design.render_profile_code is None: + await self.client.ErrorOutHelper(send_function = ctx.edit_original_message).out(n="Ошибка профиля", d=f"Для этой темы профиля не определена функция render. Свяжитесь с разработчиком для решения этой проблемы") + return + + avatar_asset = member.avatar or member.default_avatar + avatar_bytes = await avatar_asset.read() + avatar_buffer = BytesIO(avatar_bytes) + avatar_image = Image.open(avatar_buffer).convert("RGBA") + data['avatar'] = avatar_image + data['user'] = user + data['place_in_top'] = user_rank + data['crumbs_modify'] = max(0, (bool(await user.in_role(roles = self.sponsors, member = member))+1) + (user.carma / 100)) if ctx.guild is not None else 1 + data['nick'] = member.display_name + data['level'] = self.client.CalculateLevel(user.period_messages, user.period_voice_activity) + namespace = globals().copy() + + if design.type == "PNG": + await ctx.edit_original_message(file=disnake.File(fp=design.render_profile(data, namespace), filename="profile.png")) + elif design.type == "GIF": + await ctx.edit_original_message(file=disnake.File(fp=design.render_profile(data, namespace), filename="profile.gif")) \ No newline at end of file diff --git a/src/cogs/economy.py b/src/cogs/economy.py new file mode 100644 index 0000000..ada0240 --- /dev/null +++ b/src/cogs/economy.py @@ -0,0 +1,453 @@ +import disnake +from disnake.ext import commands +from disnake.ext import tasks +import requests +import numpy as np +import asyncio +import sys +import os +import copy +import datetime +import math +import random +import json +import shutil +from constants.global_constants import * + + +def setup(bot): + bot.add_cog(MainEconomyModule(bot)) + +class MainEconomyModule(commands.Cog): + def __init__(self, client): + self.client = client + self.DataBaseManager = client.DataBaseManager + + @commands.Cog.listener() + async def on_ready(self): + self.krekchat = await self.client.fetch_guild(constants["krekchat"]) + self.sponsors = [disnake.utils.get(self.krekchat.roles, id=i) for i in constants["sponsors"]] + self.me = disnake.utils.get(self.krekchat.roles, id=constants["me"]) + print(f'KrekFunBot economy module activated') + + @commands.slash_command(name = "статистика", description="Статистика отображает все данные о пользователе") + async def UserStatistic(self, ctx: disnake.AppCmdInter, + member: disnake.Member = commands.Param(description="Чью статистику хотите посмотреть?", name="участник", default=None)): + if not member: + member = ctx.author + + if ctx.guild is None: + embed = disnake.Embed(title=f"Статистика **{member.name}**", description=f'') + elif member.nick != None: + embed = disnake.Embed(title=f"Статистика **{member.nick}**", description=f'') + else: + embed = disnake.Embed(title=f"Статистика **{member.name}**", description=f'') + + embed.set_thumbnail(url=member.avatar) + + await ctx.response.defer() + + users_model = self.DataBaseManager.models['users'].m + rimagochi_users_model = self.DataBaseManager.models['rimagochi_users'].m + + async with self.DataBaseManager.session() as session: + async with session.begin(): + user = await session.get(users_model, member.id) + if user is None: + await self.client.ErrorOutHelper(send_function = ctx.edit_original_message, err_name = "Ошибка статистики").out(d="Такого пользователя нет в базе данных") + return + + stmt = self.DataBaseManager.select(users_model).options( + self.DataBaseManager.selectinload(users_model.custom_roles), + self.DataBaseManager.joinedload(users_model.creation_role), + self.DataBaseManager.selectinload(users_model.prize_roles), + self.DataBaseManager.selectinload(users_model.sender_in_crumbs_transactions), + self.DataBaseManager.selectinload(users_model.recipient_in_crumbs_transactions), + self.DataBaseManager.joinedload(users_model.casino_account), + ).where( + users_model.id == member.id + ) + user = (await session.execute(stmt)).scalars().first() + + stmt = self.DataBaseManager.select(rimagochi_users_model).options( + self.DataBaseManager.selectinload(rimagochi_users_model.animals), + self.DataBaseManager.selectinload(rimagochi_users_model.battle_slots_animals) + ).where( + rimagochi_users_model.id == member.id + ) + rimagochi_user = (await session.execute(stmt)).scalars().first() + + embed.add_field(name=f"Модуль экономики", value=f"", inline=False) + embed.add_field(name=f"крошки", value=f"{user.crumbs}", inline=False) + embed.add_field(name=f"сообщения (в общем)", value=f"{user.summary_messages}", inline=False) + embed.add_field(name=f"минут в голосовом канале (в общем)", value=f"{user.summary_voice_activity/60}", inline=False) + embed.add_field(name=f"сообщения (после сброса)", value=f"{user.period_messages}", inline=False) + embed.add_field(name=f"минут в голосовом канале (после сброса)", value=f"{user.period_voice_activity/60}", inline=False) + embed.add_field(name=f"репутация", value=f"{user.carma}", inline=False) + embed.add_field(name=f"зарплата", value=f"{user.staff_salary}", inline=False) + embed.add_field(name=f"время получения последней ежедневной награды", value=f"", inline=False) + embed.add_field(name=f"время последней активности", value=f"", inline=False) + embed.add_field(name=f"количество записей в истории об отправлениях (общая сумма)", value=f"{len(user.sender_in_crumbs_transactions)} ({sum([write.amount for write in user.sender_in_crumbs_transactions]+[0])})", inline=False) + embed.add_field(name=f"количество записей в истории о получениях (общая сумма)", value=f"{len(user.recipient_in_crumbs_transactions)} ({sum([write.amount for write in user.recipient_in_crumbs_transactions]+[0])})", inline=False) + embed.add_field(name=f"наличие записей в казино", value=f"{not user.casino_account is None}", inline=False) + if not user.casino_account is None: + embed.add_field(name=f"количество круток сегодня (может быть не актуальным)", value=f"{user.casino_account.spins_today_count}", inline=False) + embed.add_field(name=f"время последнего сброса лимитов", value=f"", inline=False) + + embed.add_field(name=f"Ролевой модуль", value=f"", inline=False) + embed.add_field(name=f"количество кастомных ролей (общая стоимость)", value=f"{len(user.custom_roles)} ({sum([role.cost for role in user.custom_roles]+[0])})", inline=False) + embed.add_field(name=f"количество призовых ролей", value=f"{len(user.prize_roles)}", inline=False) + embed.add_field(name=f"созданная кастомная роль", value=(f"<@&{user.creation_role.id}>" if not user.creation_role is None else "None"), inline=False) + + embed.add_field(name=f"Модуль rimagochi", value=f"", inline=False) + embed.add_field(name=f"наличие аккаунта rimagochi", value=f"{not rimagochi_user is None}", inline=False) + if not rimagochi_user is None: + embed.add_field(name=f"количество зверей", value=f"{len(rimagochi_user.animals)}", inline=False) + embed.add_field(name=f"количество генов", value=f"{sum([int(i) for i in rimagochi_user.genes.values()])}", inline=False) + embed.add_field(name=f"количество предметов", value=f"{sum([int(i) for i in rimagochi_user.items.values()])}", inline=False) + embed.add_field(name=f"количество побед", value=f"{rimagochi_user.wins}", inline=False) + await ctx.edit_original_message(embed=embed) + + @commands.slash_command(description="Показывает топ участников сервера по заданному параметру (по умолчанию - уровень)", name="топ") + async def TopSlash(self, ctx: disnake.AppCmdInter, parameter: str = commands.Param(description="По какому критерию хотите увидеть топ?", + name="критерий", default = 'по уровню', + choices=['по уровню', 'по количеству сообщений', 'по времени в голосовых каналах', 'по количеству крошек'])): + await ctx.response.defer() + async with self.DataBaseManager.session() as session: + async with session.begin(): + async with self.DataBaseManager.models['users'] as users_model: + order_by = None + match parameter: + case "по уровню": + order_by = [self.DataBaseManager.desc(users_model.period_messages + (users_model.period_voice_activity / 180))] + case "по количеству сообщений": + order_by = [self.DataBaseManager.desc(users_model.period_messages), self.DataBaseManager.desc(users_model.period_voice_activity)] + case "по времени в голосовых каналах": + order_by = [self.DataBaseManager.desc(users_model.period_voice_activity), self.DataBaseManager.desc(users_model.period_messages)] + case "по количеству крошек": + order_by = [self.DataBaseManager.desc(users_model.crumbs), self.DataBaseManager.desc(users_model.period_messages), self.DataBaseManager.desc(users_model.period_voice_activity)] + stmt = self.DataBaseManager.select(users_model).order_by(*order_by).limit(20) + users = (await session.execute(stmt)).scalars().all() + + embed = disnake.Embed(title=f"Общий топ {parameter}", description=f'', colour=0x2F3136) + users_counter = 0 + for user in users: + try: + member = await self.krekchat.fetch_member(user.id) + except disnake.NotFound: + continue + member_name = None + if member.nick != None: + member_name = member.nick + elif member.display_name != None: + member_name = member.display_name + else: + member_name = member.name + match parameter: + case "по уровню": + embed.add_field(name=f"{users_counter + 1}) {member_name}", + value=f"{member.mention} - {int(self.client.CalculateLevel(user.period_messages, user.period_voice_activity))} уровень", + inline=False) + case "по количеству сообщений": + embed.add_field(name=f"{users_counter + 1}) {member_name}", + value=f"{member.mention} - {user.period_messages} сообщений", + inline=False) + case "по времени в голосовых каналах": + embed.add_field(name=f"{users_counter + 1}) {member_name}", + value=f"{member.mention} - {int(user.period_voice_activity/60)} минут", + inline=False) + case "по количеству крошек": + embed.add_field(name=f"{users_counter + 1}) {member_name}", + value=f"{member.mention} - {int(user.crumbs)} крошек", + inline=False) + users_counter += 1 + + if users_counter >= 10: + break + + await ctx.edit_original_message(embed=embed) + + + @commands.slash_command(name="ежедневная") + async def DailySlash(self, ctx): + pass + @DailySlash.sub_command( + description=f"Получайте награду в крошках каждый день", + name="награда") + async def DailyCrumbsSub(self, ctx: disnake.AppCmdInter): + await ctx.response.defer() + error_helper = self.client.ErrorOutHelper(send_function = ctx.edit_original_message, err_name = "Ошибка начисления ежедневной награды") + if ctx.guild is None: + await error_helper.out(d="Эта команда не работает в личных сообщениях!") + return + async with self.DataBaseManager.session() as session: + await self.client.UserUpdate(member = ctx.author, session = session) + async with session.begin(): + async with self.DataBaseManager.models['users'] as users_model: + stmt = self.DataBaseManager.select(users_model).where(users_model.id == ctx.author.id).with_for_update() + user = (await session.execute(stmt)).scalars().first() + + result = await user.claim_daily_crumbs(daily_constant = constants['dailycrumbs'], member = ctx.author, sponsor_roles = self.sponsors) + + if result['success']: + history = self.DataBaseManager.models['transaction_history_crumbs'].m(recipient_id = ctx.author.id, amount = result['count'], description = f"Ежедневная награда за {result['date'].strftime('%d.%m.%Y')}") + session.add(history) + await ctx.edit_original_message(embed=disnake.Embed(description=result['output'], colour=0x2F3136)) + return + else: + await error_helper.out(d=result['output']) + return + + + @commands.slash_command(name="история", description="Показывает историю транзакций") + async def HistoryCrumbsSlash(self, ctx: disnake.AppCmdInter): + pass + @HistoryCrumbsSlash.sub_command(description="Показывает историю транзакций", name="транзакций") + async def HistoryCrumbsSub(self, ctx: disnake.AppCmdInter, member: disnake.Member = commands.Param( + description="Чью историю хотите посмотреть?(только для администраторов)", + name="участник", default=None)): + if not (member != None and self.me in ctx.author.roles): + member = ctx.author + class HistoryButtons(disnake.ui.View): + def __init__(self, ctx, transactions, member, embed): + super().__init__(timeout=180) + self.transactions = transactions + self.ctx = ctx + self.embed = embed + self.member = member + self.page = 1 + self.maxpage = len(transactions) if len(transactions) > 0 else 1 + + self.left.disabled = (self.page == 1) + self.right.disabled = (self.page == self.maxpage) + + @disnake.ui.button(label="<", custom_id="left", style=disnake.ButtonStyle.secondary) + async def left(self, button, inter): + if inter.author != self.ctx.author: + return + self.page -= 1 + self.left.disabled = (self.page == 1) + self.right.disabled = (self.page == self.maxpage) + self.embed = await EmbedHistoryChanger(self.transactions, self.embed, self.page, self.member) + await inter.response.edit_message(embed=self.embed, view=self) + + @disnake.ui.button(label=">", custom_id="right", style=disnake.ButtonStyle.secondary) + async def right(self, button, inter): + if inter.author != self.ctx.author: + return + self.page += 1 + self.left.disabled = (self.page == 1) + self.right.disabled = (self.page == self.maxpage) + self.embed = await EmbedHistoryChanger(self.transactions, self.embed, self.page, self.member) + await inter.response.edit_message(embed=self.embed, view=self) + + async def on_timeout(self): + for child in self.children: + if isinstance(child, (disnake.ui.Button, disnake.ui.BaseSelect)): + child.disabled = True + await self.ctx.edit_original_message(view=self) + async def EmbedHistoryChanger(transactions, embed, selfpage, member): + embed.clear_fields() + if len(transactions) == 0: + embed.add_field(name=f"В истории пока ничего нет", value=f"", inline=False) + return embed + embed.add_field(name=f"", value=f"", inline=False) + page = transactions[selfpage - 1] + maxpage = len(transactions) if len(transactions) > 0 else 1 + c = 1 + for transaction in transactions[selfpage - 1]: + if transaction.recipient_id == member.id: + string = ( + f"{c+5*(selfpage-1)}) <:A_g_wplus:606920277443608593> **{int(transaction.amount)} крошек** {f' (комиссия: {int(transaction.commission_fraction * 100)}%)'if transaction.commission_fraction != 0 else ''}\n" + f"{f'Отправитель: <@{transaction.sender_id}>' if not transaction.sender_id is None else ''}\n" + f"Время транзакции: \n" + f"> {transaction.description}" + ) + else: + string = ( + f"{c+5*(selfpage-1)}) <:A_g_wmins:606920287044239362> **{int(transaction.amount)} крошек** {f' (комиссия: {int(transaction.commission_fraction * 100)}%)'if transaction.commission_fraction != 0 else ''}\n" + f"{f'Получатель: <@{transaction.recipient_id}>' if not transaction.recipient_id is None else ''}\n" + f"Время транзакции: \n" + f"> {transaction.description}" + ) + embed.add_field(name=f"", value=string, inline=False) + + c += 1 + embed.add_field(name=f"", value=f"Страница {selfpage}/{maxpage}", inline=False) + return embed + await ctx.response.defer(ephemeral = True) + embed = disnake.Embed(title=f'История транзакций {member.name}', description=f"", colour=0x2F3136) + + async with self.DataBaseManager.session() as session: + async with session.begin(): + async with self.DataBaseManager.models['transaction_history_crumbs'] as transaction_history_crumbs_model: + stmt = self.DataBaseManager.select(transaction_history_crumbs_model).where( + self.DataBaseManager.or_( + transaction_history_crumbs_model.sender_id == member.id, + transaction_history_crumbs_model.recipient_id == member.id + ) + ).order_by(self.DataBaseManager.desc(transaction_history_crumbs_model.id)) + transactions = (await session.execute(stmt)).scalars().all() + + readyarray = self.client.PartitioningEmbeder(transactions) + view = HistoryButtons(ctx, readyarray, member, embed) + + embed = await EmbedHistoryChanger(readyarray, embed, 1, member) + await ctx.edit_original_message(embed=embed, view=view) + + + @commands.slash_command(name="казино", + description="Хотите иметь очень много крошек? Тогда, эта команда точно не для вас)") + async def CasinoSlash(self, ctx: disnake.AppCmdInter, + count: commands.Range[int, 1, ...] = commands.Param(description="Сколько крошек хотите поставить на кон?", name="ставка"), + possibility: commands.Range[int, 1, 99] = commands.Param( + description="Шанс на победу. Чем выше, тем меньше крошек вы получите после победы(целое число от 1 до 99)", + name="шанс", default=50), + quantity: int = commands.Param( + description=f"Количество круток(целое число от 1 до {constants['casinospinslimit']}({constants['casinospinslimit']*2} для спонсоров))", + name="количество", default=1)): + await ctx.response.defer() + error_helper = self.client.ErrorOutHelper(send_function = ctx.edit_original_message, err_name = "Ошибка казино") + if ctx.guild is None: + await error_helper.out(d="Эта команда не работает в личных сообщениях!") + return + def get_dynamic_fee(count: int) -> float: + if count < 1_000: + return 0 + elif count < 5_000: + return 0.1 + elif count < 10_000: + return 0.2 + elif count < 25_000: + return 0.3 + elif count < 50_000: + return 0.4 + elif count < 75_000: + return 0.5 + elif count < 100_000: + return 0.6 + elif count < 125_000: + return 0.7 + elif count < 150_000: + return 0.8 + else: + return 0.9 + def calculate_multiplier(possibility: float, count: int) -> float: + fee = get_dynamic_fee(count) + multiplier = ((0.909/possibility)-0.918)*(1-fee) + return multiplier*count + + async with self.DataBaseManager.session() as session: + await self.client.CasinoUserUpdate(member = ctx.author, session = session) + async with session.begin(): + users_model = self.DataBaseManager.models['users'].m + casino_account_model = self.DataBaseManager.models['casino_user_account'].m + stmt = self.DataBaseManager.select(users_model).where(users_model.id == ctx.author.id).with_for_update() + user = (await session.execute(stmt)).scalars().first() + stmt = self.DataBaseManager.select(casino_account_model).where(casino_account_model.id == ctx.author.id).with_for_update() + casino_user = (await session.execute(stmt)).scalars().first() + user_is_sponsor = await user.in_role(member = ctx.author, roles = self.sponsors) + casinospinslimit = constants['casinospinslimit'] + if quantity <= 0: + await error_helper.out(d=f'Количество круток должно быть > 0') + return + if quantity > casinospinslimit * (user_is_sponsor + 1) and (not self.me in ctx.author.roles): + await error_helper.out(d=f'Вы не можете крутить казино больше {casinospinslimit * (user_is_sponsor + 1)} раз в день!') + return + if not 0 < possibility < 100: + await error_helper.out(d=f'Шанс должен быть от 1% до 99%') + return + possibility = possibility / 100 + if count <= 0: + await error_helper.out(d=f'Ставка должна быть больше 0') + return + + if user.crumbs < count * quantity: + await error_helper.out(d=f'Для такой ставки вам необходимо иметь {quantity*count} крошек') + return + + now = datetime.datetime.now().timestamp() + + if now - casino_user.last_reset_time >= 86400: + casino_user.spins_today_count = 0 + casino_user.last_reset_time = now + + if casino_user.spins_today_count + quantity > casinospinslimit * (user_is_sponsor + 1) and (not self.me in ctx.author.roles): + if casino_user.spins_today_count >= casinospinslimit * (user_is_sponsor + 1): + await error_helper.out(d=f'У вас не осталось круток, возобновление лимита будет ') + else: + await error_helper.out(d=f'У вас осталось только {casinospinslimit * (user_is_sponsor + 1) - (casino_user.spins_today_count)} круток, возобновление лимита будет ') + return + + results = [random.random() < possibility for _ in range(quantity)] + totalwins = sum(results) + totalcrumbs = totalwins * calculate_multiplier(possibility, count) - (quantity - totalwins) * count + + casino_user.spins_today_count += quantity + + user.crumbs += totalcrumbs + if int(totalcrumbs)>0: + history = self.DataBaseManager.models['transaction_history_crumbs'].m(recipient_id = ctx.author.id, amount = int(totalcrumbs), description = f"Выигрыш в казино с шансом {int(possibility * 100)}% и ставкой {count} крошек ({totalwins} побед, {quantity-totalwins} поражений)") + session.add(history) + embed=disnake.Embed(title=f'Вы в плюсе!', description=f"Ваш выигрыш составил {int(totalcrumbs)}!", colour=0x2F3136) + elif int(totalcrumbs) == 0: + embed=disnake.Embed(title=f'Вы вышли в 0',description=f"", colour=0x2F3136) + else: + history = self.DataBaseManager.models['transaction_history_crumbs'].m(sender_id = ctx.author.id, amount = int(abs(totalcrumbs)), description = f"Проигрыш в казино с шансом {int(possibility * 100)}% и ставкой {count} крошек ({totalwins} побед, {quantity-totalwins} поражений)") + session.add(history) + embed=disnake.Embed(title=f'Вы в минусе(', description=f"Ваш проигрыш составил {int(-totalcrumbs)}", colour=0x2F3136) + + embed.description += (f"\n-# У вас осталось {casinospinslimit * (user_is_sponsor + 1) - (casino_user.spins_today_count)} круток") + await ctx.edit_original_message(embed = embed) + + + @commands.slash_command(name="отдать") + async def GiveCrumbsSlash(self, ctx): + pass + @GiveCrumbsSlash.sub_command(description=f"Передайте свои крошки другому участнику сервера (комиссия {int(constants['givecrumbscommission']*100)}%)", name="крошки") + async def GiveCrumbsSub(self, ctx: disnake.AppCmdInter, + member: disnake.Member = commands.Param(description="Кому вы хотите перевести крошки?", + name="адресат"), + count: int = commands.Param(description="Сколько крошек вы хотите перевести?", + name="количество"), + comment: str = commands.Param(description="Комментарий к переводу", name="комментарий", + default="Перевод"), + commission: float = commands.Param(description="Доля комиссии(доступно только админам)", + name="комиссия", default=None)): + error_helper = self.client.ErrorOutHelper(send_function = ctx.response.send_message, err_name = "Ошибка перевода") + if ctx.guild is None: + await error_helper.out(d="Эта команда не работает в личных сообщениях!") + return + if comment != "Перевод": + comment = "Перевод | " + comment + if (commission == None) or (not self.me in ctx.author.roles): + commission = constants['givecrumbscommission'] + if count <= 0: + await error_helper.out(d="Количество крошек должно быть больше 0") + return + users_model = self.DataBaseManager.models['users'].m + async with self.DataBaseManager.session() as session: + async with session.begin(): + author_data = await session.get(users_model, ctx.author.id, with_for_update = True) + member_data = await session.get(users_model, member.id, with_for_update = True) + + if author_data is None: + await error_helper.out(d=f'Вас нет в базе данных') + return + + if member_data is None: + await error_helper.out(d=f'Такого пользователя нет в базе данных') + return + + if author_data.crumbs - count < 0: + await error_helper.out(d=f'У вас не хватает крошек') + return + + history_write = self.DataBaseManager.models["transaction_history_crumbs"].m(sender_id = ctx.author.id, recipient_id = member.id, commission_fraction = commission, description = comment, amount = count) + session.add(history_write) + author_data.crumbs -= count + member_data.crumbs += count * (1 - commission) + + await ctx.response.send_message(embed=disnake.Embed( + description=f'{count} крошек успешно отправлено на счёт {member.mention}, комиссия составила {int(commission * 100)}%({int(count * commission)} крошек)', + colour=0x2F3136)) \ No newline at end of file diff --git a/src/cogs/resetsupcommands.py b/src/cogs/resetsupcommands.py new file mode 100644 index 0000000..3b24100 --- /dev/null +++ b/src/cogs/resetsupcommands.py @@ -0,0 +1,14 @@ +import disnake +from disnake.ext import commands + +def setup(bot: commands.Bot): + bot.add_cog(ExampleCog(bot)) + print("ExampleCog загружен!") + +class ExampleCog(commands.Cog): + def __init__(self, bot: commands.Bot): + self.bot = bot + + @commands.slash_command(name="ping", description="Проверка бота на работоспособность") + async def ping(self, inter: disnake.ApplicationCommandInteraction): + await inter.response.send_message(f"Понг! {round(self.bot.latency * 1000)}мс") \ No newline at end of file diff --git a/src/cogs/rimagochi.py b/src/cogs/rimagochi.py new file mode 100644 index 0000000..3e0ed1a --- /dev/null +++ b/src/cogs/rimagochi.py @@ -0,0 +1,1247 @@ + +import disnake +from disnake.ext import commands +from disnake.ext import tasks +import requests +import numpy as np +import aiohttp + +import asyncio +import sqlite3 +import sys +import os +import copy +import datetime +import math +import random +import json +import shutil +from constants.global_constants import * +from constants.rimagochi_constants import * + +def now(): + return datetime.datetime.now().timestamp() + + +def setup(bot): + bot.add_cog(MainRimagochiModule(bot)) + + +class MainRimagochiModule(commands.Cog): + def __init__(self, client): + self.client = client + self.DataBaseManager = client.DataBaseManager + + @commands.Cog.listener() + async def on_ready(self): + print(f'KrekFunBot rimagochi module activated') + krekchat = await self.client.fetch_guild(constants["krekchat"]) + self.me = disnake.utils.get(krekchat.roles, id=constants["me"]) + + ''' + + + Профили + + + ''' + + @commands.slash_command(description="Группа команд для реализации профилей", name="профиль") + async def RimagochiProfileSlash(self, ctx: disnake.AppCmdInter): + pass + + @RimagochiProfileSlash.sub_command(description="Показывает профиль игрока", name="игрока") + async def RimagochiPlayerProfileSub(self, ctx: disnake.AppCmdInter, + member: disnake.Member = commands.Param(description="Чей профиль хотите посмотреть?", + name="игрок", default=None)): + if member == None: + member = ctx.author + async with self.DataBaseManager.session() as session: + if member == ctx.author: + await self.client.RimagochiUserUpdate(member = member, session = session) + else: + user = await session.get(self.DataBaseManager.models['rimagochi_users'].m, member.id) + if user is None: + await self.client.ErrorOutHelper(send_function = ctx.response.send_message, err_name = "Ошибка профиля", thumbnail = member.avatar).out(d=f"Этот пользователь скрывает свои данные") + return + async with session.begin(): + async with self.DataBaseManager.models['rimagochi_users'] as rimagochi_users_model: + stmt = self.DataBaseManager.select(rimagochi_users_model).options( + self.DataBaseManager.selectinload(rimagochi_users_model.animals), + self.DataBaseManager.selectinload(rimagochi_users_model.battle_slots_animals) + ).where( + rimagochi_users_model.id == member.id + ) + user = (await session.execute(stmt)).scalars().first() + + if member != ctx.author and user.settings['hide_the_animals'] and (not self.client.me in ctx.author.roles): + await self.client.ErrorOutHelper(send_function = ctx.response.send_message, err_name = "Ошибка профиля", thumbnail = member.avatar).out(d=f"Этот пользователь скрывает свои данные") + return + await ctx.response.defer(ephemeral = user.settings['hide_the_animals']) + + strength = await self.client.CalculateRimagochiBattleStrength(user.battle_slots_animals) + embed = disnake.Embed(title=f"", description=f"### Профиль игрока {member.mention}", colour=0x2F3136) + embed.set_thumbnail(url=member.avatar) + embed.add_field(name=f"Победы в битвах", value=f"{user.wins}", inline=False) + embed.add_field(name=f"Cила отряда", value=f"{strength}", inline=True) + embed.add_field(name=f"Животные в отряде", value=", ".join([rimagochi_animals[animal.model_animal_id]['name'] for animal in user.battle_slots_animals]).capitalize(), inline=True) + embed.add_field(name=f"Всего животных", value=f"{len(user.animals)}", inline=True) + + await ctx.edit_original_message(embed=embed) + + ''' + + + Настройки + + + ''' + + @commands.slash_command(description="Тут вы можете поменять некоторые настройки", name="настройки") + async def SettingsSlash(self, ctx: disnake.AppCmdInter): + await ctx.response.defer() + + class SettingsEmbedView(disnake.ui.View): + DataBaseManager = self.DataBaseManager + client = self.client + def __init__(self, settings): + super().__init__(timeout=120) + self.settings = settings + + @disnake.ui.button(label="1", style=disnake.ButtonStyle.primary) + async def button1_callback(self, button: disnake.ui.Button, inter: disnake.MessageInteraction): + if ctx.author != inter.author: + await self.client.ErrorOutHelper(send_function = inter.response.send_message, err_name = "Ошибка доступа", ephemeral = True).out(d=f"Вы не можете менять настройки другого пользователя") + return + await inter.response.defer() + self.settings['hide_the_animals'] = not self.settings['hide_the_animals'] + await self.dump(self.settings) + await ctx.edit_original_message(embed=SettingsEmbedMaker(self.settings)) + + @disnake.ui.button(label="2", style=disnake.ButtonStyle.primary) + async def button2_callback(self, button: disnake.ui.Button, inter: disnake.MessageInteraction): + if ctx.author != inter.author: + await self.client.ErrorOutHelper(send_function = inter.response.send_message, err_name = "Ошибка доступа", ephemeral = True).out(d=f"Вы не можете менять настройки другого пользователя") + return + await inter.response.defer() + self.settings['always_use_crumbs_for_feeding'] = not self.settings['always_use_crumbs_for_feeding'] + await self.dump(self.settings) + await ctx.edit_original_message(embed=SettingsEmbedMaker(self.settings)) + + @disnake.ui.button(label="3", style=disnake.ButtonStyle.primary) + async def button3_callback(self, button: disnake.ui.Button, inter: disnake.MessageInteraction): + if ctx.author != inter.author: + await self.client.ErrorOutHelper(send_function = inter.response.send_message, err_name = "Ошибка доступа", ephemeral = True).out(d=f"Вы не можете менять настройки другого пользователя") + return + await inter.response.defer() + self.settings['accept_the_challenge'] = not self.settings['accept_the_challenge'] + await self.dump(self.settings) + await ctx.edit_original_message(embed=SettingsEmbedMaker(self.settings)) + + async def dump(self, settings): + async with self.DataBaseManager.session() as session: + async with session.begin(): + async with self.DataBaseManager.models["rimagochi_users"] as r_users_model: + stmt = self.DataBaseManager.update(r_users_model).where(r_users_model.id == ctx.author.id).values(settings = settings) + await session.execute(stmt) + + async def on_timeout(self): + await ctx.edit_original_message(view=None) + + def SettingsEmbedMaker(settings): + embed = disnake.Embed(title=f"Глобальные настройки", description=f'') + embed.set_thumbnail(url=ctx.author.avatar) + embed.add_field(name=f"1) Скрывать свои данные", value="Отключено" if not settings['hide_the_animals'] else "Включено", inline=False) + embed.add_field(name=f"2) Всегда использовать крошки для кормления животных", value="Отключено" if not settings['always_use_crumbs_for_feeding'] else "Включено", inline=False) + embed.add_field(name=f"3) Получать вызовы на бой", value="Отключено" if not settings['accept_the_challenge'] else "Включено", inline=False) + return embed + + async with self.DataBaseManager.session() as session: + await self.client.RimagochiUserUpdate(member = ctx.author, session = session) + async with session.begin(): + stmt = self.DataBaseManager.select(self.DataBaseManager.models["rimagochi_users"].m.settings).where( + self.DataBaseManager.models["rimagochi_users"].m.id == ctx.author.id + ) + settings = (await session.execute(stmt)).scalars().first() + await ctx.edit_original_message(embed=SettingsEmbedMaker(settings), view=SettingsEmbedView(settings)) + + ''' + + + Гача + + + ''' + + @commands.slash_command(name="открыть") + async def OpenGachaSlash(self, ctx: disnake.AppCmdInter): + pass + + @OpenGachaSlash.sub_command(description="После открытия капсулы, вы получаете боевого питомца", name="капсулу") + async def OpenGachaSub(self, ctx: disnake.AppCmdInter, + capsule: int = commands.Param(description="Название капсулы", + name="капсула", + choices=[disnake.OptionChoice(name=f"{i['name']} - {i['cost']} крошек", value=i['id']) for i in rimagochi_capsules.values()])): + def get_random_rarity(chances): + rarity_list = [] + probability_list = [] + + for chance_data in chances: + rarity_list.append(chance_data["rarity"]) + probability_list.append(chance_data["chance"]) + + chosen_rarity = random.choices( + population=rarity_list, + weights=probability_list, + k=1 + )[0] + + return chosen_rarity + + async with self.DataBaseManager.session() as session: + await self.client.RimagochiUserUpdate(member = ctx.author, session = session) + async with session.begin(): + stmt = self.DataBaseManager.select(self.DataBaseManager.models['users'].m).options( + self.DataBaseManager.selectinload(self.DataBaseManager.models['users'].m.rimagochi_account) + ).where(self.DataBaseManager.models['users'].m.id == ctx.author.id).with_for_update() + user = (await session.execute(stmt)).scalars().first() + + await ctx.response.defer(ephemeral = user.rimagochi_account.settings['hide_the_animals']) + error_helper = self.client.ErrorOutHelper(send_function = ctx.edit_original_message, err_name = "Ошибка открытия капсулы") + capsule = rimagochi_capsules[capsule] + + if user.crumbs < capsule['cost']: + await error_helper.out(d=f"Для открытия этой капсулы необходимо иметь {capsule['cost']} крошек") + return + + timed_animals = {} + for animal in rimagochi_animals.keys(): + timed_animals.setdefault(rimagochi_animals[animal]["params"]["rarity"]["id"], []) + timed_animals[rimagochi_animals[animal]["params"]["rarity"]["id"]].append(animal) + + rarity = get_random_rarity(capsule['chances']) + animal = rimagochi_animals[random.choices(timed_animals[rarity['id']])[0]] + + chance = 0 + for chance_data in capsule['chances']: + if rarity == chance_data["rarity"]: + chance = chance_data["chance"] + break + + new_animal = self.DataBaseManager.models['rimagochi_animals'].m(model_animal_id = animal['id'], initial_owner_id = ctx.author.id, owner_id = ctx.author.id) + history_write = self.DataBaseManager.models['transaction_history_crumbs'].m(sender_id = ctx.author.id, amount = capsule['cost'], description = f"Открыта капсула \"{capsule['name']}\", получен {animal['name']}({(chance*100):.02f}%)") + user.crumbs -= capsule['cost'] + session.add_all([new_animal, history_write]) + await session.flush() + + new_animal_id = new_animal.id + + embed = disnake.Embed(title=f"{rarity['emoji']} {animal['name'].capitalize()} ({(chance*100):.02f}%)", description=f"{animal['description']}", colour=0x2F3136) + embed.set_thumbnail(url = animal['params']['image_url']) + + class SlaughterView(disnake.ui.View): + client = self.client + DataBaseManager = self.DataBaseManager + def __init__(self): + super().__init__(timeout=30) + + @disnake.ui.button(label="Оставить", style=disnake.ButtonStyle.success, emoji="<:A_risworld_rislove:759009763840622622>") + async def button1_callback(self, button: disnake.ui.Button, inter: disnake.MessageInteraction): + if ctx.author != inter.author: + await self.client.ErrorOutHelper(send_function = inter.response.send_message, err_name = "Ошибка доступа", ephemeral = True).out(d=f"Вы не можете выбирать за другого игрока") + return + self.stop() + await self.on_timeout() + + @disnake.ui.button(label="Забить", style=disnake.ButtonStyle.danger, emoji="<:A_risworld_risantilove:760082727356727297>") + async def button2_callback(self, button: disnake.ui.Button, inter: disnake.MessageInteraction): + if ctx.author != inter.author: + await self.client.ErrorOutHelper(send_function = inter.response.send_message, err_name = "Ошибка доступа", ephemeral = True).out(d=f"Вы не можете выбирать за другого игрока") + return + await ctx.edit_original_message(view=None) + self.stop() + + async with self.DataBaseManager.session() as session: + async with session.begin(): + stmt = self.DataBaseManager.select(self.DataBaseManager.models['rimagochi_animals'].m).where( + self.DataBaseManager.models['rimagochi_animals'].m.id == new_animal_id + ).with_for_update() + + new_animal = (await session.execute(stmt)).scalars().first() + + if new_animal is None: + await self.client.ErrorOutHelper(send_function = inter.response.send_message, err_name = "Ошибка операции", ephemeral = True).out(d=f"Животное не найдено") + await self.on_timeout() + return + + stmt = self.DataBaseManager.select(self.DataBaseManager.models['users'].m).options( + self.DataBaseManager.selectinload(self.DataBaseManager.models['users'].m.rimagochi_account) + ).where(self.DataBaseManager.models['users'].m.id == ctx.author.id).with_for_update() + user = (await session.execute(stmt)).scalars().first() + + items = copy.deepcopy(user.rimagochi_account.items) + receiveditems = {} + for item in animal['params']['after_death']: + items.setdefault(str(item['item']['id']), 0) + receive=random.randint(*item['count']) + receiveditems.setdefault(item['item']['name'], receive) + items[str(item['item']['id'])] += receive + + await session.delete(new_animal) + user.rimagochi_account.items = items + + embed = disnake.Embed(title=f"~~{rarity['emoji']} {animal['name'].capitalize()} ({(chance*100):.02f}%)~~", description=",\n".join(f"{i} - {receiveditems[i]}" for i in receiveditems.keys()), colour=0x2F3136) + embed.set_thumbnail(url = "https://static.wikia.nocookie.net/rimworld/images/a/ad/Мясо.png/revision/latest?cb=20190130152311&path-prefix=ru") + await ctx.edit_original_message(embed=embed) + + + async def on_timeout(self): + await ctx.edit_original_message(view=None) + + await ctx.edit_original_message(embed=embed, view=SlaughterView()) + + ''' + + + Продать + + + ''' + + @commands.slash_command(description="Продать предметы", name="продать") + async def SellSlash(self, ctx: disnake.AppCmdInter, num: commands.Range[int, 1, ...] = commands.Param(description="Укажите количество", name="количество"), + item: int = commands.Param(description="Какой предмет хотите продать?", name="предмет", choices=[disnake.OptionChoice(name=f"{i['name']} - {i['sell_cost']} крошек/шт", value=i['id']) for i in rimagochi_items.values()])): + + async with self.DataBaseManager.session() as session: + await self.client.RimagochiUserUpdate(member = ctx.author, session = session) + async with session.begin(): + stmt = self.DataBaseManager.select(self.DataBaseManager.models['users'].m).options( + self.DataBaseManager.selectinload(self.DataBaseManager.models['users'].m.rimagochi_account) + ).where(self.DataBaseManager.models['users'].m.id == ctx.author.id).with_for_update() + user = (await session.execute(stmt)).scalars().first() + + await ctx.response.defer(ephemeral = user.rimagochi_account.settings['hide_the_animals']) + error_helper = self.client.ErrorOutHelper(send_function = ctx.edit_original_message, err_name = "Ошибка продажи") + + if not (str(item) in user.rimagochi_account.items.keys() and user.rimagochi_account.items[str(item)] >= num): + await error_helper.out(d=f"У вас недостаточно {rimagochi_items[item]['name']}") + return + items = copy.deepcopy(user.rimagochi_account.items) + items[str(item)] -= num + user.rimagochi_account.items = items + user.crumbs += num * rimagochi_items[item]["sell_cost"] + history_write = self.DataBaseManager.models['transaction_history_crumbs'].m(recipient_id = ctx.author.id, amount = num * rimagochi_items[item]["sell_cost"], description = f"Продажа {rimagochi_items[item]['name']}({num} шт)") + session.add(history_write) + + embed = disnake.Embed(title=f"{rimagochi_items[item]['name'].capitalize()}(x{num}) продано! Вы получили {num * rimagochi_items[item]['sell_cost']} крошек", description=f"", colour=0x2F3136) + embed.set_thumbnail(url=ctx.author.avatar) + await ctx.edit_original_message(embed=embed) + + ''' + + + Магазины + + + ''' + + @commands.slash_command(name="магазин") + async def ShopSlash(self, ctx: disnake.AppCmdInter): + pass + + @ShopSlash.sub_command(name="генов", description="Купите гены, чтобы улучшить ваших зверей! Информация о генах: \"/информация гены\"") + async def GenesShopSub(self, ctx: disnake.AppCmdInter, + gene: int = commands.Param(description="Какой ген желаете приобрести?", name="ген", + choices=[disnake.OptionChoice(name=f"{i['name']} - {i['cost']} крошек", value=i['id']) for i in rimagochi_genes.values()])): + gene = rimagochi_genes[gene] + + async with self.DataBaseManager.session() as session: + await self.client.RimagochiUserUpdate(member = ctx.author, session = session) + async with session.begin(): + stmt = self.DataBaseManager.select(self.DataBaseManager.models['users'].m).options( + self.DataBaseManager.selectinload(self.DataBaseManager.models['users'].m.rimagochi_account) + ).where(self.DataBaseManager.models['users'].m.id == ctx.author.id).with_for_update() + user = (await session.execute(stmt)).scalars().first() + + await ctx.response.defer(ephemeral = user.rimagochi_account.settings['hide_the_animals']) + + if user.crumbs < gene['cost']: + error_helper = self.client.ErrorOutHelper(send_function = ctx.edit_original_message, err_name = "Ошибка покупки гена") + await error_helper.out(d=f"Для покупки этого гена необходимо иметь {gene['cost']} крошек") + return + + genes = copy.deepcopy(user.rimagochi_account.genes) + genes.setdefault(str(gene['id']), 0) + genes[str(gene['id'])] += 1 + user.rimagochi_account.genes = genes + + user.crumbs -= gene['cost'] + + history_write = self.DataBaseManager.models['transaction_history_crumbs'].m(sender_id = ctx.author.id, amount = gene['cost'], description = f"Покупка гена \"{gene['name']}\"") + session.add(history_write) + + embed = disnake.Embed(title=f"Ген \"{gene['name']}\" успешно приобретён!", description=f"Описание:\n{gene['description']}", colour=0x2F3136) + embed.set_thumbnail(url = "https://static.wikia.nocookie.net/rimworld/images/f/fc/GeneBackground_Xenogene.png/revision/latest?cb=20221216070153&path-prefix=ru") + await ctx.edit_original_message(embed=embed) + + + ''' + + + Корм + + + ''' + + @commands.slash_command(name="кормить") + async def FeedSlash(self, ctx: disnake.AppCmdInter): + pass + + @FeedSlash.sub_command(description="Покормите всех животных, которых возможно (используются только крошки)", name="всех") + async def RimagochiFeedAllSub(self, ctx: disnake.AppCmdInter): + async with self.DataBaseManager.session() as session: + await self.client.RimagochiUserUpdate(member = ctx.author, session = session) + async with session.begin(): + user = await session.get(self.DataBaseManager.models['users'].m, ctx.author.id, with_for_update=True) + + async def feed_animal(animal_model, animaldata, food = None): + food = rimagochi_items[food] if food else None + animal_model = copy.deepcopy(animal_model) + + if animaldata.feed_today_count >= 4 and (now() - animaldata.first_today_feed_time) < (24*60*60): + return None + + if (now()-animaldata.last_feeding_time)<(3*60*60): + return None + + genes = [] + for gene in animaldata.genes: + genedata = rimagochi_genes[int(gene)] + genes.append(genedata['name']) + for effect in genedata['effects'].keys(): + animal_model['params'][effect] += genedata['effects'][effect] + + hunger = (animal_model['params']['hunger'] if animal_model['params']['hunger']>0 else 0)*rimagochi_constants['hanger_multiplayer']/4 + + if user.crumbs < hunger: + return None + + user.crumbs -= hunger + + if (now() - animaldata.first_today_feed_time) >= (24*60*60): + animaldata.feed_today_count=0 + animaldata.first_today_feed_time=now() + + animaldata.feed_today_count+=1 + animaldata.last_feeding_time=now() + animaldata.experience, animaldata.level, remains = self.client.AnimalLevelAdder(animaldata.experience+100, animaldata.level) + + return hunger + + stmt = self.DataBaseManager.select(self.DataBaseManager.models['rimagochi_animals'].m).where(self.DataBaseManager.models['rimagochi_animals'].m.owner_id == ctx.author.id).with_for_update() + animals = (await session.execute(stmt)).scalars().all() + crumbs_amout = 0 + feeded_animals = [] + for animal in animals: + animal_model = rimagochi_animals[animal.model_animal_id] + new_feed = await feed_animal(animal_model, animal) + if new_feed != None: + crumbs_amout += new_feed + feeded_animals.append(animal_model['name']) + + if crumbs_amout!=0: + history_write = self.DataBaseManager.models['transaction_history_crumbs'].m(sender_id = ctx.author.id, amount = crumbs_amout, description = f"Корм для " + ", ".join(feeded_animals)) + session.add(history_write) + embed = disnake.Embed(title=f"Всего удалось потратить {crumbs_amout:.0f} крошек на {len(feeded_animals)} животных.", description="", colour=0x2F3136) + stmt = self.DataBaseManager.select(self.DataBaseManager.models['rimagochi_users'].m.settings).where(self.DataBaseManager.models['rimagochi_users'].m.id == ctx.author.id) + settings = (await session.execute(stmt)).scalars().first() + if not settings['hide_the_animals']: embed.description = f"Покормлены:\n"+",\n".join(feeded_animals) + embed.set_thumbnail(url=ctx.author.avatar) + await ctx.response.send_message(embed=embed) + + ''' + + + Инвентари + + + ''' + + @commands.slash_command(name="инвентарь") + async def InventorySlash(self, ctx: disnake.AppCmdInter): + pass + + @InventorySlash.sub_command(description="Покажет все ваши предметы", name="предметы") + async def RimagochiItemsInventorySub(self, ctx: disnake.AppCmdInter, + member: disnake.Member = commands.Param(description="Чей инвентарь хотите посмотреть?", + name="игрок", default=None)): + if member == None: + member = ctx.author + + async with self.DataBaseManager.session() as session: + await self.client.RimagochiUserUpdate(member = member, session = session) + async with session.begin(): + user = await session.get(self.DataBaseManager.models['rimagochi_users'].m, member.id) + + if user.settings['hide_the_animals'] and member != ctx.author and (not self.client.me in ctx.author.roles): + error_helper = self.client.ErrorOutHelper(send_function = ctx.response.send_message, err_name = "Ошибка инвентаря", thumbnail = member.avatar) + await error_helper.out(d=f"Пользователь {member.mention} скрыл свой инвентарь") + return + await ctx.response.defer(ephemeral = user.settings['hide_the_animals']) + + embed = disnake.Embed(title=f"", description=f"### Инвентарь {member.mention}\n", colour=0x2F3136) + embed.set_thumbnail(url=member.avatar) + + if len([i for i in user.items.keys() if user.items[i]>0]) > 0: + embed.add_field(name=f"Предметы:", value=f"", inline=False) + for i in [i for i in user.items.keys() if user.items[i]>0]: + embed.add_field(name=f"{rimagochi_items[int(i)]['name']} - {int(user.items[i])} штук", value=f"", inline=False) + if len([i for i in user.genes.keys() if user.genes[i]>0]) > 0: + embed.add_field(name=f"Гены:", value=f"", inline=False) + for i in [i for i in user.genes.keys() if user.genes[i]>0]: + embed.add_field(name=f"{rimagochi_genes[int(i)]['name']} - {int(user.genes[i])} штук", value=f"", inline=False) + if len(user.items.keys()) == 0 and len(user.genes.keys()) == 0: + embed.add_field(name=f"Тут пока ничего нет", value=f"", inline=True) + await ctx.edit_original_message(embed=embed) + + + + @InventorySlash.sub_command(description="Покажет всех ваших животных, из него их можно покормить или применить генопаки", name="животные") + async def RimagochiAnimalsInventorySub(self, ctx: disnake.AppCmdInter, + member: disnake.Member = commands.Param(description="Чей инвентарь хотите посмотреть?", + name="игрок", default=None)): + if member == None: + member = ctx.author + + rimagochi_users_model = self.DataBaseManager.models['rimagochi_users'].m + rimagochi_animals_model = self.DataBaseManager.models['rimagochi_animals'].m + users_model = self.DataBaseManager.models['users'].m + + async with self.DataBaseManager.session() as session: + await self.client.RimagochiUserUpdate(member = member, session = session) + async with session.begin(): + stmt = self.DataBaseManager.select(rimagochi_users_model).options( + self.DataBaseManager.selectinload(rimagochi_users_model.animals) + ).where(rimagochi_users_model.id == member.id) + + user = (await session.execute(stmt)).scalars().first() + user.animals.sort(key=lambda animal: animal.creation_time, reverse=True) + + await ctx.response.defer(ephemeral = user.settings['hide_the_animals']) + error_helper = self.client.ErrorOutHelper(send_function = ctx.edit_original_message, err_name = "Ошибка профиля", thumbnail = member.avatar) + + if user.settings['hide_the_animals'] and member != ctx.author and (not self.client.me in ctx.author.roles): + await error_helper.out(d=f"Пользователь {member.mention} скрыл свой профиль") + return + + if len(user.animals) == 0: + await error_helper.out(d=f"Кажется, тут зверей пока нет") + return + + async def AnimalsEmbeder(num: int, review = False): + + animal_data = user.animals[num-1] + animal = rimagochi_animals[animal_data.model_animal_id] + + embed = disnake.Embed(title=f"Инвентарь {member.name}", description=f"\n### {animal['params']['rarity']['emoji']} {animal['name'].capitalize()}", colour=0x2F3136) + embed.set_footer(text = f"id: {animal_data.id}") + embed.set_thumbnail(url=animal['params']['image_url']) + animal = copy.deepcopy(animal) + genes = [] + exp, level, to_next_level = self.client.AnimalLevelAdder(animal_data.experience, animal_data.level) + for gene in animal_data.genes: + genedata = rimagochi_genes[int(gene)] + genes.append(genedata['name']) + for effect in genedata['effects'].keys(): + animal['params'][effect] += genedata['effects'][effect] + + + embed.add_field(name=f"Уровень", + value=f"`{animal_data.level}`\n|{'█' * int(((exp/(exp+to_next_level)) % 1) * 35) + '░' * (35 - int(((exp/(exp+to_next_level)) % 1) * 35))}|\n", + inline=False) + embed.add_field(name=f"Урон:", + value=f"{animal['params']['damage']*rimagochi_constants['damage_multiplayer']}", + inline=True) + embed.add_field(name=f"Здоровье:", + value=f"{animal['params']['health']*rimagochi_constants['health_multiplayer']}", + inline=True) + embed.add_field(name=f"Состоит в отряде?\n(занимает {animal['params']['required_slots']}/{rimagochi_constants['max_battle_slots']} мест)", + value=f"{'Да' if animal_data.in_battle_slots else 'Нет'}", + inline=True) + embed.add_field(name=f"Количество потребляемых крошек(шт/день):", + value=f"{int((animal['params']['hunger'] if animal['params']['hunger']>0 else 0)*rimagochi_constants['hanger_multiplayer'])}", + inline=False) + + if (now()-animal_data.first_today_feed_time) >= (24 * 60 * 60): + animal_data.feed_today_count=0 + animal_data.first_today_feed_time=now() + + if animal_data.feed_today_count >= 4 and (now() - animal_data.first_today_feed_time) < (24 * 60 * 60) and (animal_data.first_today_feed_time+(24 * 60 * 60) > animal_data.last_feeding_time + (3 * 60 * 60)): + embed.add_field(name=f"Покормлено за сегодня:", + value=f"{animal_data.feed_today_count} раз (следующий раз )", + inline=True) + elif (now()-animal_data.last_feeding_time)<(3*60*60): + embed.add_field(name=f"Покормлено за сегодня:", + value=f"{animal_data.feed_today_count} раз (следующий раз )", + inline=True) + else: + embed.add_field(name=f"Покормлено за сегодня:", + value=f"{animal_data.feed_today_count} раз (уже доступно)", + inline=True) + + embed.add_field(name=f"Побед:", + value=f"{animal_data.wins}", + inline=True) + + if len(genes) != 0: + embed.add_field(name=f"Гены", value=f", ".join(genes), inline=False) + + if review: + await ctx.edit_original_message(embed=embed, view=ButtonsView(animal, animal_data, user.settings, len(user.animals))) + else: + await ctx.edit_original_message(embed=embed) + + return animal, animal_data, user.settings + + async def feed_animal(inter, animal, animal_id, food = None): + error_helper = self.client.ErrorOutHelper(send_function = inter.response.send_message, err_name = "Ошибка инвентаря", ephemeral=True) + async with self.DataBaseManager.session() as session: + async with session.begin(): + stmt = self.DataBaseManager.select(rimagochi_users_model).options( + self.DataBaseManager.selectinload(rimagochi_users_model.user) + ).where(rimagochi_users_model.id == member.id).with_for_update() + rimagochi_user = (await session.execute(stmt)).scalars().first() + + animal_data = await session.get(rimagochi_animals_model, animal_id, with_for_update=True) + + food = rimagochi_items[food] if food else None + animal = copy.deepcopy(animal) + + if animal_data.feed_today_count >= 4 and (now() - animal_data.first_today_feed_time) < (24 * 60 * 60): + await error_helper.out(d=f"Вы уже покормили своего зверя 4 раза за сегодня, обновление лимитов будет ") + return + + if (now() - animal_data.last_feeding_time) < (3 * 60 * 60): + await error_helper.out(d=f"Следующий раз можно покормить ") + return + + genes = [] + for gene in animal_data.genes: + genedata = rimagochi_genes[int(gene)] + genes.append(genedata['name']) + for effect in genedata['effects'].keys(): + animal['params'][effect] += genedata['effects'][effect] + + hunger = (animal['params']['hunger'] if animal['params']['hunger']>0 else 0)*rimagochi_constants['hanger_multiplayer']/4 + + + if food: + items = copy.deepcopy(rimagochi_user.items) + if (not str(food['id']) in items.keys()) or items[str(food['id'])] < hunger / 10: + await error_helper.out(d=f"У вас недостаточно {food['name']}") + return + + items[str(food['id'])] = items[str(food['id'])] - hunger / 10 + rimagochi_user.items = items + else: + if rimagochi_user.user.crumbs < hunger: + await error_helper.out(d=f"У вас недостаточно крошек") + return + + rimagochi_user.user.crumbs -= hunger + history_write = self.DataBaseManager.models['transaction_history_crumbs'].m(sender_id = ctx.author.id, amount = hunger, description = f"Корм для {animal['name']}") + session.add(history_write) + + if (now() - animal_data.first_today_feed_time) >= (24 * 60 * 60): + animal_data.feed_today_count = 1 + animal_data.first_today_feed_time = now() + else: + animal_data.feed_today_count += 1 + + animal_data.last_feeding_time = now() + animal_data.experience, animal_data.level, remains = self.client.AnimalLevelAdder(animal_data.experience+100, animal_data.level) + + embed = disnake.Embed(description=f"{animal['name'].capitalize()} покормлен, до следующего уровня ему осталось всего {remains} опыта", title=f"", colour=0x2F3136) + await inter.response.send_message(embed = embed, ephemeral=True) + + nonlocal user + + stmt = self.DataBaseManager.select(rimagochi_users_model).options( + self.DataBaseManager.selectinload(rimagochi_users_model.animals) + ).where(rimagochi_users_model.id == member.id) + + user = (await session.execute(stmt)).scalars().first() + user.animals.sort(key=lambda animal: animal.creation_time, reverse=True) + + + class FeedModal(disnake.ui.Modal): + def __init__(self, buttons): + self.animal = buttons.animal + can_eate = ['крошки']+[i['item']['name'] for i in self.animal['params']['can_eate']] + string = [] + for i in range(len(can_eate)): + string.append(f"{i+1} - {can_eate[i]}") + components = [disnake.ui.TextInput(label=f"Чем кормить?", custom_id="feed_animal", style=disnake.TextInputStyle.short, placeholder=f"Введите число: {', '.join(string)}", max_length=1)] + super().__init__(title=f"Выберите, чем хотите покормить", components=components) + self.buttons = buttons + self.animal_id = buttons.animal_data.id + self.done = asyncio.Event() + + async def callback(self, inter: disnake.ModalInteraction): + feedback = int(inter.text_values["feed_animal"]) + if feedback == 1: + await feed_animal(inter, self.animal, self.animal_id) + else: + item = self.animal['params']['can_eate'][feedback-2]['item'] + await feed_animal(inter, self.animal, self.animal_id, item['id']) + self.done.set() + async def wait(self): + await self.done.wait() + + + class ButtonsView(disnake.ui.View): + DataBaseManager = self.DataBaseManager + client = self.client + def __init__(self, animal, animal_data, settings, max_page): + super().__init__(timeout = 180) + self.animal = animal + self.animal_data = animal_data + self.settings = settings + self.page = 1 + self.max_page = max_page + self.update_button_labels() + + @disnake.ui.button(label="Покормить", style=disnake.ButtonStyle.success, row=1) + async def feed_modal(self, button: disnake.ui.Button, inter: disnake.MessageInteraction): + if member != inter.author: + await self.client.ErrorOutHelper(send_function = inter.response.send_message, ephemeral=True).out(n="Ошибка доступа", d=f"Нельзя кормить чужих животных!!!") + return + if len(self.animal["params"]["can_eate"])==0 or self.settings['always_use_crumbs_for_feeding']: + await feed_animal(inter, self.animal, self.animal_data.id) + else: + modal = FeedModal(self) + await inter.response.send_modal(modal) + await modal.wait() + + self.animal, self.animal_data, self.settings = await AnimalsEmbeder(self.page) + + @disnake.ui.button(label="Забить", style=disnake.ButtonStyle.danger, row=1) + async def kill_button(self, button: disnake.ui.Button, inter: disnake.MessageInteraction): + if member != inter.author: + await self.client.ErrorOutHelper(send_function = inter.response.send_message, ephemeral=True).out(n="Ошибка доступа", d=f"Нельзя убивать чужих животных!!!") + return + await inter.response.defer() + self.stop() + class MixedView(disnake.ui.View): + client = self.client + DataBaseManager = self.DataBaseManager + animal_data = self.animal_data + animal = self.animal + def __init__(self): + super().__init__(timeout = 60) + self.add_item(disnake.ui.Button(label="Забить!!!", style=disnake.ButtonStyle.danger, custom_id="accept")) + self.add_item(disnake.ui.Button(label="Нет, отменяй", style=disnake.ButtonStyle.success, custom_id="reject")) + async def interaction_check(self, inter): + if member != inter.author: + await self.client.ErrorOutHelper(send_function = inter.response.send_message, ephemeral=True).out(n="Ошибка доступа", d=f"Нельзя убивать чужих животных!!!") + return + if inter.data.custom_id == "accept": + async with self.DataBaseManager.session() as session: + async with session.begin(): + animal = await session.get(rimagochi_animals_model, self.animal_data.id, with_for_update = True) + if animal is None: + return + await session.delete(animal) + + user = await session.get(rimagochi_users_model, member.id, with_for_update = True) + + items = copy.deepcopy(user.items) + receiveditems = {} + for item in self.animal['params']['after_death']: + items.setdefault(str(item['item']['id']), 0) + receive=random.randint(*item['count']) + receiveditems.setdefault(item['item']['name'], receive) + items[str(item['item']['id'])] += receive + user.items = items + + embed = disnake.Embed(title=f"~~{self.animal['name'].capitalize()}~~", description=",\n".join(f"{i} - {receiveditems[i]}" for i in receiveditems.keys()), colour=0x2F3136) + embed.set_thumbnail(url = "https://static.wikia.nocookie.net/rimworld/images/a/ad/Мясо.png/revision/latest?cb=20190130152311&path-prefix=ru") + await ctx.edit_original_message(embed=embed, view=None) + + if inter.data.custom_id == "reject": + await inter.response.defer() + embed = disnake.Embed(title=f"", description=f"В этот раз {self.animal['name']} остался в живых", colour=0x2F3136) + await ctx.edit_original_message(embed=embed, view=None) + + self.stop() + async def on_timeout(self): + embed = disnake.Embed(title=f"Выбор не сделан", description=f"В этот раз {self.animal['name']} остался в живых", colour=0x2F3136) + await ctx.edit_original_message(embed=embed, view=None) + + embed = disnake.Embed(title=f"Вы вменяемы?", description=f"{self.animal['name']} умрёт и вы потеряете все гены и опыт с него!!!", colour=0x2F3136) + await ctx.edit_original_message(embed=embed, view=MixedView()) + + @disnake.ui.button(label = ".", style=disnake.ButtonStyle.primary, row=1) + async def battle_button(self, button: disnake.ui.Button, inter: disnake.MessageInteraction): + if member != inter.author: + await self.client.ErrorOutHelper(send_function = inter.response.send_message, ephemeral=True).out(n="Ошибка доступа", d=f"Вы не можете управлять чужим отрядом") + return + async with self.DataBaseManager.session() as session: + async with session.begin(): + stmt = self.DataBaseManager.select(rimagochi_users_model).options( + self.DataBaseManager.selectinload(rimagochi_users_model.battle_slots_animals) + ).where(rimagochi_users_model.id == member.id).with_for_update() + rimagochi_user = (await session.execute(stmt)).scalars().first() + animal_data = await session.get(rimagochi_animals_model, self.animal_data.id, with_for_update = True) + + busy_battle_slots = sum( + [ + rimagochi_animals[i.model_animal_id]['params']['required_slots'] + for i in rimagochi_user.battle_slots_animals + ] + [0] + ) + if animal_data.in_battle_slots: + animal_data.in_battle_slots = False + + else: + if busy_battle_slots + self.animal['params']['required_slots'] > rimagochi_constants['max_battle_slots']: + embed = disnake.Embed(title=f"у вас нет столько свободного места в отряде!!!", description=f"", colour=0x2F3136) + await inter.response.send_message(embed=embed, ephemeral=True) + return + animal_data.in_battle_slots = True + + nonlocal user + + stmt = self.DataBaseManager.select(rimagochi_users_model).options( + self.DataBaseManager.selectinload(rimagochi_users_model.animals) + ).where(rimagochi_users_model.id == member.id) + + user = (await session.execute(stmt)).scalars().first() + user.animals.sort(key=lambda animal: animal.creation_time, reverse=True) + + self.animal, self.animal_data, self.settings = await AnimalsEmbeder(self.page) + self.update_button_labels() + await inter.response.defer() + await inter.edit_original_message(view=self) + + @disnake.ui.button(label="Применить ген", style=disnake.ButtonStyle.primary, row=1) + async def apply_genes_button(self, button: disnake.ui.Button, inter: disnake.MessageInteraction): + if member != inter.author: + await self.client.ErrorOutHelper(send_function = inter.response.send_message, ephemeral=True).out(n="Ошибка доступа", d=f"Вы не можете управлять чужим отрядом") + return + class ApplyGenesView(disnake.ui.View): + client = self.client + DataBaseManager = self.DataBaseManager + animal_data = self.animal_data + animal = self.animal + def __init__(self, suitable_genes): + super().__init__(timeout = 60) + self.add_item(disnake.ui.Select( + placeholder="Выберите вариант", + options=[ + disnake.SelectOption(label=rimagochi_genes[int(i)]['name'], value=i) for i in suitable_genes + ] + )) + async def interaction_check(self, interaction: disnake.MessageInteraction): + if member != inter.author: + await self.client.ErrorOutHelper(send_function = inter.response.send_message, ephemeral=True).out(n="Ошибка доступа", d=f"Вы не можете управлять чужим отрядом") + return + selected_value = interaction.data.values[0] + + async with self.DataBaseManager.session() as session: + async with session.begin(): + rimagochi_user = await session.get(rimagochi_users_model, ctx.author.id, with_for_update = True) + self.animal_data = await session.get(rimagochi_animals_model, self.animal_data.id, with_for_update = True) + + suitable_genes = [i for i in rimagochi_user.genes.keys() if (not int(i) in animal_data.genes) and rimagochi_user.genes[i] > 0] + + if not selected_value in suitable_genes: + await self.client.ErrorOutHelper(send_function = inter.response.send_message, ephemeral=True).out(n="Ошибка применения генов", d="У вас больше нет этого гена или ген уже был применён") + return + + animal_genes = copy.deepcopy(self.animal_data.genes) + animal_genes.append(int(selected_value)) + self.animal_data.genes = animal_genes + + user_genes = copy.deepcopy(rimagochi_user.genes) + user_genes[selected_value]-=1 + rimagochi_user.genes = user_genes + + suitable_genes.remove(selected_value) + + embed = disnake.Embed(title=f"", description=f"В животное `{self.animal['name']}` успешно вставлен ген {rimagochi_genes[int(selected_value)]['name']}", colour=0x2F3136) + await interaction.response.send_message(embed = embed, ephemeral=True) + if len(suitable_genes) != 0: + await ctx.edit_original_message(view=ApplyGenesView(suitable_genes)) + else: + await ctx.edit_original_message(view=None) + + async def on_timeout(self): + await ctx.edit_original_message(view=None) + + async with self.DataBaseManager.session() as session: + async with session.begin(): + rimagochi_user = await session.get(rimagochi_users_model, ctx.author.id) + animal_data = await session.get(rimagochi_animals_model, self.animal_data.id) + + suitable_genes = [i for i in rimagochi_user.genes.keys() if (not int(i) in animal_data.genes) and rimagochi_user.genes[i] > 0] + + if len(suitable_genes) == 0: + await self.client.ErrorOutHelper(send_function = inter.response.send_message, ephemeral=True).out(n="Ошибка применения генов", d=f"У вас пока нет генов, которые можно применить на {self.animal['name']}") + return + + self.stop() + await inter.response.defer() + embed = disnake.Embed(title=f"Какие гены хотите применить на животное {self.animal['name']}?", description=f"Будьте внимательны, вернуть использованные генопаки уже не выйдет", colour=0x2F3136) + await ctx.edit_original_message(embed=embed, view=ApplyGenesView(suitable_genes)) + + @disnake.ui.button(label="<", style=disnake.ButtonStyle.secondary, row=2) + async def backward_button(self, button: disnake.ui.Button, inter: disnake.MessageInteraction): + if ctx.author != inter.author: + await self.client.ErrorOutHelper(send_function = inter.response.send_message, ephemeral=True).out(n="Ошибка доступа", d=f"Вы не можете управлять чужим инвентарём") + return + await inter.response.defer() + self.page = max(self.page-1, 1) + self.animal, self.animal_data, self.settings = await AnimalsEmbeder(self.page) + self.update_button_labels() + await inter.edit_original_message(view=self) + + @disnake.ui.button(label=">", style=disnake.ButtonStyle.secondary, row=2) + async def forward_button(self, button: disnake.ui.Button, inter: disnake.MessageInteraction): + if ctx.author != inter.author: + await self.client.ErrorOutHelper(send_function = inter.response.send_message, ephemeral=True).out(n="Ошибка доступа", d=f"Вы не можете управлять чужим инвентарём") + return + await inter.response.defer() + self.page = min(self.page+1, self.max_page) + self.animal, self.animal_data, self.settings = await AnimalsEmbeder(self.page) + self.update_button_labels() + await inter.edit_original_message(view=self) + + async def on_timeout(self): + await ctx.edit_original_message(view=None) + + def update_button_labels(self): + for child in self.children: + if isinstance(child, disnake.ui.Button): + if child.custom_id == self.battle_button.custom_id: + child.label = "Взять в бой" if not self.animal_data.in_battle_slots else "Убрать из отряда" + elif child.label == ">": + child.disabled = (self.page >= self.max_page) + elif child.label == "<": + child.disabled = (self.page <= 1) + + await AnimalsEmbeder(1, review = True) + + ''' + + + Сражения + + + ''' + + @commands.slash_command(name="бросить") + async def ChallengeSlash(self, ctx: disnake.AppCmdInter): + pass + + @ChallengeSlash.sub_command(description="Можно бросить вызов адресно (заполнив поле 'игрок'), а можно любому, кто захочет его принять", name="вызов") + async def ChallengeSub(self, ctx: disnake.AppCmdInter, bet: commands.Range[int, 1000, ...] = commands.Param(description="Введите ставку", + name="ставка"), + member: disnake.Member = commands.Param(description="Кому хотите бросить вызов?", + name="игрок", default=None)): + error_helper = self.client.ErrorOutHelper(send_function = ctx.response.send_message, err_name = "Ошибка вызова", ephemeral = True) + if ctx.author == member: + await error_helper.out(d=f"Нельзя бросить вызов самому себе") + return + + rimagochi_users_model = self.DataBaseManager.models['rimagochi_users'].m + async with self.DataBaseManager.session() as session: + await self.client.RimagochiUserUpdate(member = ctx.author, session = session) + async with session.begin(): + stmt = self.DataBaseManager.select(rimagochi_users_model).options( + self.DataBaseManager.joinedload(rimagochi_users_model.user), + self.DataBaseManager.selectinload(rimagochi_users_model.battle_slots_animals) + ).where(rimagochi_users_model.id == ctx.author.id) + author_user = (await session.execute(stmt)).scalars().first() + + if author_user.user.crumbs < bet: + await error_helper.out(d=f"Вам не хватает крошек на такую ставку") + return + + if len(author_user.battle_slots_animals)==0: + await error_helper.out(d=f"У вас нет экипированных зверей") + return + + if not author_user.settings['accept_the_challenge']: + await error_helper.out(d=f"Чтобы бросить вызов, у вас должна быть включена настройка 'получать вызовы'") + return + + if not member is None: + stmt = self.DataBaseManager.select(rimagochi_users_model).options( + self.DataBaseManager.joinedload(rimagochi_users_model.user), + self.DataBaseManager.selectinload(rimagochi_users_model.battle_slots_animals) + ).where(rimagochi_users_model.id == member.id) + member_user = (await session.execute(stmt)).scalars().first() + + if member_user is None: + await error_helper.out(d=f"У этого пользователя пока нет профиля") + return + + if member_user.user.crumbs < bet: + await error_helper.out(d=f"Вашему оппоненту не хватает крошек на такую ставку") + return + + if len(member_user.battle_slots_animals)==0: + await error_helper.out(d=f"У вашего оппонента нет экипированных зверей") + return + + if not member_user.settings['accept_the_challenge']: + await error_helper.out(d=f"Этот пользователь не принимает вызовы") + return + + class ButtonsView(disnake.ui.View): + client = self.client + DataBaseManager = self.DataBaseManager + CalculateRimagochiBattleStrength = self.client.CalculateRimagochiBattleStrength + def __init__(self, author, member, bet): + super().__init__(timeout = 180) + self.member = member + self.author = author + self.bet = bet + + @disnake.ui.button(label="Принять вызов", style=disnake.ButtonStyle.success, row=1) + async def take_challenge(self, button: disnake.ui.Button, inter: disnake.MessageInteraction): + error_helper = self.client.ErrorOutHelper(send_function = inter.response.send_message, err_name = "Ошибка", ephemeral = True) + if self.member: + if self.member != inter.author: + await error_helper.out(d=f"Этот вызов был брошен не вам, к сожалению, вы не сможете его принять") + return + elif inter.author == self.author: + await error_helper.out(d=f"Вы не можете принять свой же вызов!!!") + return + else: + self.member = inter.author + rimagochi_users_model = self.DataBaseManager.models['rimagochi_users'].m + async with self.DataBaseManager.session() as session: + async with session.begin(): + stmt = self.DataBaseManager.select(rimagochi_users_model).options( + self.DataBaseManager.selectinload(rimagochi_users_model.user), + self.DataBaseManager.selectinload(rimagochi_users_model.animals) + ).where(rimagochi_users_model.id == ctx.author.id).with_for_update() + author_user = (await session.execute(stmt)).scalars().first() + + stmt = self.DataBaseManager.select(rimagochi_users_model).options( + self.DataBaseManager.selectinload(rimagochi_users_model.user), + self.DataBaseManager.selectinload(rimagochi_users_model.animals) + ).where(rimagochi_users_model.id == self.member.id).with_for_update() + member_user = (await session.execute(stmt)).scalars().first() + + if member_user is None: + await error_helper.out(d=f"У вас пока нет профиля, напишите хотя бы одно сообщение") + return + + if not member_user.settings['accept_the_challenge']: + await error_helper.out(d=f"Чтобы принять вызов, у вас должна быть включена настройка 'получать вызовы'") + return + + if member_user.user.crumbs < self.bet: + await error_helper.out(d=f"Вам не хватает крошек, чтобы принять этот вызов") + return + + if len([animal for animal in member_user.animals if animal.in_battle_slots]) == 0: + await error_helper.out(d=f"У вас нет экипированных зверей") + return + + self.stop() + await ctx.edit_original_message("", view = None) + + if author_user.user.crumbs < self.bet: + await self.client.ErrorOutHelper(send_function = ctx.edit_original_message, err_name = "Ошибка").out(d=f"{self.author.mention} не хватает крошек на ставку, бой отменяется") + return + + players_list = [self.author, self.member] + db_players_list = [author_user, member_user] + weights_list = [await self.CalculateRimagochiBattleStrength(author_user.animals), await self.CalculateRimagochiBattleStrength(member_user.animals)] + + chosen_player = random.choices( + population=[0, 1], + weights=weights_list, + k=1 + )[0] + + db_players_list[chosen_player].wins += 1 + for animal in db_players_list[chosen_player].animals: + if animal.in_battle_slots: + animal.wins += 1 + + db_players_list[chosen_player].user.crumbs += self.bet * 0.9 + db_players_list[1-chosen_player].user.crumbs -= self.bet + + history_write = self.DataBaseManager.models['transaction_history_crumbs'].m(sender_id = players_list[1-chosen_player].id, recipient_id = players_list[chosen_player].id, amount = self.bet, commission_fraction = 0.1, description = f"Бой зверей") + session.add(history_write) + + await inter.response.defer() + + for i in range(10, 0, -1): + embed = disnake.Embed(title=f"", description=f"### Вызов принят {self.member.mention}\nИдёт подсчёт сил и рассчёт победителя...\n-# Результат будет через {i} секунд", colour=0x2F3136) + embed.set_thumbnail(url="https://media.discordapp.net/attachments/887735863734313000/1357768401195634728/confused-cat-confused.gif?ex=67f167dc&is=67f0165c&hm=bebc160fac83107c143b93c825b926cbf83caf9d5eb56535c4c11ada09e57103&=") + await ctx.edit_original_message(embed=embed) + await asyncio.sleep(1) + embed = disnake.Embed(title=f"", description=f"### Победил {players_list[chosen_player].mention}!\n\nИздалека все могли видеть сражающихся животных:\n" + f"На стороне {self.author.mention}: {', '.join(rimagochi_animals[animal.model_animal_id]['name'] for animal in author_user.animals if animal.in_battle_slots)}\n" + f"На стороне {self.member.mention}: {', '.join(rimagochi_animals[animal.model_animal_id]['name'] for animal in member_user.animals if animal.in_battle_slots)}", colour=0x2F3136) + embed.set_thumbnail(url=players_list[chosen_player].avatar) + await ctx.edit_original_message(embed=embed) + + @disnake.ui.button(label="Отменить вызов", style=disnake.ButtonStyle.danger, row=1) + async def off_challenge(self, button: disnake.ui.Button, inter: disnake.MessageInteraction): + if inter.author != self.author: + await self.client.ErrorOutHelper(send_function = inter.response.send_message, err_name = "Ошибка доступа", ephemeral = True).out(d=f"Отменить этот вызов может только {self.author.mention}") + return + embed = disnake.Embed(title=f"", description=f"### {self.author.mention} отменил вызов на бой", colour=0x2F3136) + embed.set_thumbnail(url=self.author.avatar) + self.stop() + await ctx.edit_original_message("", embed = embed, view = None) + + async def on_timeout(self): + embed = disnake.Embed(title=f"", description=f"\n### Вызов от {ctx.author.mention}(просрочено)\nВы уже не можете принять вызов, нажав на кнопку ниже.\n-# Ставка `{bet}` крошек", colour=0x2F3136) + embed.set_thumbnail(url=ctx.author.avatar) + await ctx.edit_original_message(embed=embed, view=None) + + embed = disnake.Embed(title=f"", description=f"### Вызов от {ctx.author.mention}\nВы можете принять вызов, нажав на кнопку ниже!\n-# Ставка `{bet}` крошек", colour=0x2F3136) + embed.set_thumbnail(url=ctx.author.avatar) + await ctx.response.send_message(f"{member.mention}" if member else None, embed=embed, view = ButtonsView(ctx.author, member, bet)) + + ''' + + + Википедия + + + ''' + + @commands.slash_command(name="информация") + async def InformationSubGroup(self, ctx: disnake.AppCmdInter): + pass + + async def animal_autocomplete(inter: disnake.ApplicationCommandInteraction, user_input: str): + animals = [ + animal["name"] + for animal in rimagochi_animals.values() + if user_input.lower() in animal["name"].lower() + ][:25] + return animals + + @InformationSubGroup.sub_command(description="Показывает информацию о выбранном животном", name="животные") + async def RimagochiBestiarySub(self, ctx: disnake.AppCmdInter, + animal: str = commands.Param(description="Название животного или его ID", name="животное", + autocomplete=animal_autocomplete)): + matches = {} + animal = animal.lower() + finded_flag = False + if animal.isnumeric() and int(animal) in rimagochi_animals.keys(): + animal = rimagochi_animals[int(animal)] + finded_flag = True + else: + for key, value in rimagochi_animals.items(): + if animal in value['name'].lower(): + matches.setdefault(key, value['name']) + if animal == value['name'].lower(): + animal = rimagochi_animals[key] + finded_flag = True + break + if (not finded_flag) and (not matches): + for key, value in rimagochi_animals.items(): + if animal in value['description'].lower(): + matches.setdefault(key, value['name']) + if len(matches)==1 and (not finded_flag): + animal = rimagochi_animals[list(matches.keys())[0]] + finded_flag = True + + if not finded_flag and len(matches)==0: + embed = disnake.Embed(title=f"", description=f"Мы искали как могли, но не смогли найти такого зверя", colour=0x2F3136) + await ctx.response.send_message(embed = embed) + return + elif not finded_flag: + embed = disnake.Embed(title=f"", description=f"Мы нашли много совпадений, но ничего конкретного, уточните запрос\n-# Найдено совпадений: {', '.join([i for i in matches.values()])}", colour=0x2F3136) + await ctx.response.send_message(embed = embed) + return + + await ctx.response.defer() + embed = disnake.Embed(title=f"{animal['name'].capitalize()}", description=f"{animal['description']}", colour=0x2F3136) + embed.set_thumbnail(url = animal['params']['image_url']) + embed.set_footer(text = f"id: {animal['id']}") + embed.add_field(name=f"Редкость:", + value=f"{animal['params']['rarity']['emoji']} {animal['params']['rarity']['name']}", + inline=False) + embed.add_field(name=f"Основные характеристики".center(40, '-'),value=f"",inline=False) + embed.add_field(name=f"Количество потребляемых крошек(шт/день):", + value=f"{int(animal['params']['hunger']*rimagochi_constants['hanger_multiplayer'])}", + inline=True) + embed.add_field(name=f"Может питаться:", + value=f"{', '.join(['крошки']+[i['item']['name'] for i in animal['params']['can_eate']])}", + inline=True) + embed.add_field(name=f"Ресурсы при смерти:", + value=', \n'.join([f"{i['item']['name']} ≈x{int(sum(i['count'])/2)}" for i in animal['params']['after_death']]), + inline=True) + embed.add_field(name=f"Боевые характеристики".center(40, '-'),value=f"",inline=False) + embed.add_field(name=f"Занимает слотов в бою:", + value=f"{animal['params']['required_slots']}/{rimagochi_constants['max_battle_slots']}", + inline=True) + embed.add_field(name=f"Урон:", + value=f"{animal['params']['damage']*rimagochi_constants['damage_multiplayer']}", + inline=True) + embed.add_field(name=f"Здоровье:", + value=f"{animal['params']['health']*rimagochi_constants['health_multiplayer']}", + inline=True) + + await ctx.edit_original_message(embed=embed) + + @InformationSubGroup.sub_command(description="Показывает информацию о выбранном предмете", name="предметы") + async def RimagochiItemsInfoSub(self, ctx: disnake.AppCmdInter, + item: str = commands.Param(description="Название предмета", + name="предмет", + choices=[disnake.OptionChoice(name=f"{i['name']}", value=str(i['id'])) for i in rimagochi_items.values()])): + item = rimagochi_items[int(item)] + await ctx.response.defer() + embed = disnake.Embed(title=f"{item['name'].capitalize()}", description=f"{item['description']}", colour=0x2F3136) + embed.set_footer(text = f"id: {item['id']}") + embed.add_field(name=f"Основные характеристики".center(40, '-'),value=f"",inline=False) + embed.add_field(name=f"Стоимость при продаже:", + value=f"{item['sell_cost']}", + inline=True) + '''embed.add_field(name=f"Стоимость при покупке на рынке:", + value=f"{item['buy_cost']}", + inline=True) + embed.add_field(name=f"Стоимость при покупке в магазине:", + value=f"{item['shop_cost']}", + inline=True)''' + + await ctx.edit_original_message(embed=embed) + + @InformationSubGroup.sub_command(description="Показывает информацию о выбранном генопаке", name="генопаки") + async def RimagochiGenesInfoSub(self, ctx: disnake.AppCmdInter, + gene: str = commands.Param(description="Название гена", + name="ген", + choices=[disnake.OptionChoice(name=f"{i['name']}", value=str(i['id'])) for i in rimagochi_genes.values()])): + gene = rimagochi_genes[int(gene)] + await ctx.response.defer() + embed = disnake.Embed(title=f"{gene['name'].capitalize()}", description=f"{gene['description']}", colour=0x2F3136) + embed.set_footer(text = f"id: {gene['id']}") + embed.set_thumbnail(url = "https://static.wikia.nocookie.net/rimworld/images/f/fc/GeneBackground_Xenogene.png/revision/latest?cb=20221216070153&path-prefix=ru") + + await ctx.edit_original_message(embed=embed) + + @InformationSubGroup.sub_command(description="Показывает информацию о выбранной капсуле криптосна", name="капсулы") + async def RimagochiCapsuleInfoSub(self, ctx: disnake.AppCmdInter, + capsule: str = commands.Param(description="Название капсулы", + name="капсула", + choices=[disnake.OptionChoice(name=f"{i['name']}", value=str(i['id'])) for i in rimagochi_capsules.values()])): + capsule = rimagochi_capsules[int(capsule)] + await ctx.response.defer() + embed = disnake.Embed(title=f"{capsule['name'].capitalize()}", description=f"Цена: {capsule['cost']}", colour=0x2F3136) + embed.set_footer(text = f"id: {capsule['id']}") + for i in capsule['chances']: + embed.add_field(name=f"",value=f"{i['rarity']['emoji']} {i['rarity']['name']} - {i['chance']*100:.02f}%",inline=False) + + await ctx.edit_original_message(embed=embed) \ No newline at end of file diff --git a/src/cogs/roles.py b/src/cogs/roles.py new file mode 100644 index 0000000..ac8b41a --- /dev/null +++ b/src/cogs/roles.py @@ -0,0 +1,927 @@ + +import disnake +from disnake.ext import commands +from disnake.ext import tasks +import requests +import numpy as np +import aiohttp + +import asyncio +import sqlite3 +import sys +import os +import copy +import datetime +import math +import random +import json +import shutil +from constants.global_constants import * + + +def setup(bot): + bot.add_cog(MainRolesModule(bot)) + + +class MainRolesModule(commands.Cog): + def __init__(self, client): + self.client = client + self.DataBaseManager = client.DataBaseManager + self.costrolecreate = client.costrolecreate + self.costrolerenewal = client.costrolerenewal + + @commands.Cog.listener() + async def on_ready(self): + print(f'KrekFunBot roles module activated') + krekchat = await self.client.fetch_guild(constants["krekchat"]) + self.me = disnake.utils.get(krekchat.roles, id=constants["me"]) + + + @commands.slash_command(name="создать") + async def CreateSlash(self, ctx): + pass + @CreateSlash.sub_command( + description=f"создайте свою роль за {constants['costrolecreate']} крошек (ежемесячная оплата за роль:{constants['costrolerenewal']})", + name="роль") + async def CreateRoleSub(self, ctx: disnake.AppCmdInter, + name: str = commands.Param(description="Название роли", name="название"), + cost: int = commands.Param(description="цена роли для других участников в магазине", name="цена"), + colour: disnake.Colour = commands.Param( + description="цвет роли (указывается в hex формате, например: #ff9900)", + name="цвет", default=0xff9900)): + await ctx.response.defer() + error_helper = self.client.ErrorOutHelper(send_function = ctx.edit_original_message, err_name = "Ошибка создания роли") + if ctx.guild is None: + await error_helper.out(d="Эта команда не работает в личных сообщениях!") + return + async with self.DataBaseManager.session() as session: + await self.client.UserUpdate(member = ctx.author, session = session) + received_roles_custom_model = self.DataBaseManager.model_classes['received_roles_custom'] + roles_custom_model = self.DataBaseManager.model_classes['roles_custom'] + users_model = self.DataBaseManager.model_classes['users'] + transaction_history_crumbs = self.DataBaseManager.model_classes['transaction_history_crumbs'] + async with session.begin(): + stmt = self.DataBaseManager.select(roles_custom_model).where(roles_custom_model.creator_id == ctx.author.id) + custom_role = (await session.execute(stmt)).scalars().first() + + if not custom_role is None: + await error_helper.out(d=f'Вы уже владеете кастомной ролью') + return + + krekchat = await self.client.fetch_guild(constants["krekchat"]) + bordertop = krekchat.get_role(1221400083736690698) + borderbottom = krekchat.get_role(1221400071908753480) + + if not (100 < cost < 2147483647): + await error_helper.out(d=f"Минимальная стоимость: 100 крошек") + return + + if disnake.utils.get(ctx.guild.roles, name=name): + await error_helper.out(d=f"Роль с таким названием уже есть на сервере") + return + + stmt = self.DataBaseManager.select(self.DataBaseManager.func.count()).select_from(roles_custom_model) + count = (await session.execute(stmt)).scalars().first() + if count >= 50: + await error_helper.out(d=f"На сервере сейчас максимальное количество кастомных ролей ({count}), попробуйте позже") + return + + async with session.begin(): + stmt = self.DataBaseManager.select(users_model).where(users_model.id == ctx.author.id).with_for_update() + user = (await session.execute(stmt)).scalars().first() + if user.crumbs < self.costrolecreate: + await error_helper.out(d=f"Для создания роли необходимо заплатить {self.costrolecreate} крошек, а у вас есть только {round(user.crumbs)}") + return + + role = await ctx.guild.create_role(name=name, colour=colour) + await role.edit(position=bordertop.position - 1) + await ctx.author.add_roles(role) + custom_role = roles_custom_model(id = role.id, creator_id = ctx.author.id, cost = cost, renewal_date = 0) + await custom_role.renewal_date_update(self.client.TimeFormater) + session.add(custom_role) + transaction = transaction_history_crumbs(sender_id = ctx.author.id, amount = self.costrolecreate, description = f"Создание кастомной роли {role.mention}") + session.add(transaction) + user.crumbs -= self.costrolecreate + await session.flush() + received_role_custom = received_roles_custom_model(role_id = role.id, user_id = ctx.author.id) + session.add(received_role_custom) + await ctx.edit_original_message(embed=disnake.Embed( + title=f"Роль успешно создана! Вы можете проверить её статус или поменять настройки через команду /изменить роль", + description=f'', colour=0x2F3136)) + + + @commands.slash_command(name="роль") + async def RoleInfoSlash(self, ctx): + pass + @RoleInfoSlash.sub_command(description="Показывает информацию о ролях", name="инфо") + async def RoleInfoSub(self, ctx: disnake.AppCmdInter, + role: disnake.Role = commands.Param(description="О какой роли хотите получить информацию?", + name="роль", default=None)): + await ctx.response.defer() + error_helper = self.client.ErrorOutHelper(send_function = ctx.edit_original_message, err_name = "Ошибка") + krekchat = await self.client.fetch_guild(constants["krekchat"]) + roles_custom_model = self.DataBaseManager.model_classes['roles_custom'] + roles_static_model = self.DataBaseManager.model_classes['roles_static'] + custom_role = None + async with self.DataBaseManager.session() as session: + async with session.begin(): + if role is None: + stmt = self.DataBaseManager.select(roles_custom_model).options(self.DataBaseManager.selectinload(roles_custom_model.users)).where(roles_custom_model.creator_id == ctx.author.id) + custom_role = (await session.execute(stmt)).scalars().first() + if custom_role is None: + await error_helper.out(d=f'Вы не создавали кастомных ролей.\nЧтобы получить информацию о статичной роли, введите целевую роль в поле "роль" при вызове команды "/роль инфо"') + return + role = krekchat.get_role(custom_role.id) + if role is None: + await error_helper.out(d=f'Вашей роли нет на сервере, вероятно, произошла ошибка, обратитесь к администратору') + return + + else: + stmt = self.DataBaseManager.select(roles_static_model).where(roles_static_model.id == role.id) + static_role = (await session.execute(stmt)).scalars().first() + if not static_role is None: + embed = disnake.Embed(title=f"", description=f'### Информация о статичной роли {role.mention}', colour=0x2F3136) + embed.add_field(name=f"Описание", value=f"> {static_role.description}", inline=True) + await ctx.edit_original_message(embed=embed) + return + else: + stmt = self.DataBaseManager.select(roles_custom_model).options(self.DataBaseManager.selectinload(roles_custom_model.users)).where(roles_custom_model.id == role.id) + custom_role = (await session.execute(stmt)).scalars().first() + if custom_role is None: + await error_helper.out(d=f'Описания этой роли пока не существует.') + return + + embed = disnake.Embed(title=f"", description=f'### Информация о кастомной роли {role.mention}', colour=0x2F3136) + embed.add_field(name=f"Время продления", value=f"> ", inline=True) + embed.add_field(name=f"Цена продления", value=f"> {constants['costrolerenewal']}", inline=True) + embed.add_field(name=f"", value=f"", inline=False) + embed.add_field(name=f"Количество покупок", value=f"> {len(custom_role.users)-1}", inline=True) + embed.add_field(name=f"Цена роли", value=f"> {custom_role.cost}", inline=True) + embed.add_field(name=f"", value=f"", inline=False) + try: + creator = await krekchat.fetch_member(custom_role.creator_id) + except: + creator = custom_role.creator_id + embed.add_field(name=f"Создатель", value=f"> {creator.mention if creator else custom_role.creator_id}", inline=True) + await ctx.edit_original_message(embed=embed) + + + @commands.slash_command(name="изменить") + async def RoleChangeSlash(self, ctx): + pass + @RoleChangeSlash.sub_command(description="Позволяет изменить вашу роль", name="роль") + async def RoleChangeSub(self, ctx: disnake.AppCmdInter): + await ctx.response.defer() + error_helper = self.client.ErrorOutHelper(send_function = ctx.edit_original_message, err_name = "Ошибка изменения роли") + + if ctx.guild is None: + await error_helper.out(d="Эта команда не работает в личных сообщениях!") + return + + krekchat = await self.client.fetch_guild(constants["krekchat"]) + client = self.client + HexToRgb = self.client.HexToRgb + + class ButtonsEditRole(disnake.ui.View): + client = self.client + def __init__(self, ctx, role, embed): + super().__init__(timeout=180) + self.ctx = ctx + self.role = role + self.embed = embed + + @disnake.ui.button(label="Стоимость", custom_id="cost", style=disnake.ButtonStyle.blurple) + async def cost(self, button, inter): + if inter.author != self.ctx.author: + error = self.client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете редактировать эту роль", ephemeral = True) + await error.out() + return + modal = ActionModal(self.role, self.ctx, self.embed, "cost") + await inter.response.send_modal(modal) + + @disnake.ui.button(label="Цвет", custom_id="color", style=disnake.ButtonStyle.blurple) + async def color(self, button, inter): + if inter.author != self.ctx.author: + error = self.client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете редактировать эту роль", ephemeral = True) + await error.out() + return + modal = ActionModal(self.role, self.ctx, self.embed, "color") + await inter.response.send_modal(modal) + + @disnake.ui.button(label="Название", custom_id="name", style=disnake.ButtonStyle.blurple) + async def name(self, button, inter): + if inter.author != self.ctx.author: + error = self.client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете редактировать эту роль", ephemeral = True) + await error.out() + return + modal = ActionModal(self.role, self.ctx, self.embed, "name") + await inter.response.send_modal(modal) + + @disnake.ui.button(label="Иконку", custom_id="icon", style=disnake.ButtonStyle.blurple) + async def icon(self, button, inter): + if inter.author != self.ctx.author: + error = self.client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете редактировать эту роль", ephemeral = True) + await error.out() + return + modal = ActionModal(self.role, self.ctx, self.embed, "icon") + await inter.response.send_modal(modal) + + async def on_timeout(self): + for child in self.children: + if isinstance(child, (disnake.ui.Button, disnake.ui.BaseSelect)): + child.disabled = True + await self.ctx.edit_original_message(view=self) + + class ActionModal(disnake.ui.Modal): + DataBaseManager = self.DataBaseManager + def __init__(self, role, ctx, embed, operation): + self.role = role + self.ctx = ctx + self.embed = embed + self.operation = operation + if operation == "name": + components = [ + disnake.ui.TextInput(label="Новое название", placeholder="Например: abc", custom_id="name", + style=disnake.TextInputStyle.short, max_length=25) + ] + if operation == "cost": + components = [ + disnake.ui.TextInput(label="Новая стоимость", placeholder="Например: 123", custom_id="cost", + style=disnake.TextInputStyle.short, min_length=3) + ] + if operation == "color": + components = [ + disnake.ui.TextInput(label="Новый цвет", placeholder="Например: #ff9900", custom_id="color", + style=disnake.TextInputStyle.short) + ] + if operation == "icon": + components = [ + disnake.ui.TextInput(label="Введите ссылку на новую иконку", placeholder="Например: https://i.imgur.com/zWsCJun.png", custom_id="icon", + style=disnake.TextInputStyle.short) + ] + + super().__init__(title=operation, components=components, timeout=300) + + async def callback(self, interaction: disnake.Interaction): + ctx = self.ctx + error_helper.send_function = interaction.send + async with self.DataBaseManager.session() as session: + key, value, = list(interaction.text_values.items())[0] + await interaction.response.defer(ephemeral=True) + if self.operation == "name": + await self.role.edit(name=value) + if self.operation == "color": + rgb = HexToRgb(value) + await self.role.edit(color=disnake.Colour.from_rgb(rgb[0], rgb[1], rgb[2])) + if self.operation == "cost": + cost = abs(int(value)) + if not 100 < cost < 2147483647: + await error_helper.out(d = "Нельзя ставить стоимость роли меньше 100") + return + async with session.begin(): + async with self.DataBaseManager.models['roles_custom'] as roles_custom_model: + stmt = self.DataBaseManager.update(roles_custom_model).where(roles_custom_model.creator_id == ctx.author.id).values(cost = cost) + await session.execute(stmt) + if self.operation == "icon": + image_url = value + TRUSTED_DOMAINS = ["imgur.com", "cdn.discordapp.com", "i.imgur.com"] + if ctx.guild.premium_tier < 2: + await error_helper.out(d = "Этот сервер должен быть уровня 2 буста для изменения иконок ролей") + return + if not any(domain in image_url for domain in TRUSTED_DOMAINS): + await error_helper.out(d = "Загрузка изображений разрешена только с ресурса https://imgur.com/") + return + if not image_url.lower().endswith((".png", ".jpg", ".jpeg")): + await error_helper.out(d = "Разрешены только файлы с расширением .png, .jpg или .jpeg") + return + async with aiohttp.ClientSession() as session: + try: + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" + } + async with session.get(image_url, headers=headers) as resp: + if resp.status != 200: + await error_helper.out(d = "Не удалось загрузить изображение") + return + + content_type = resp.headers.get("Content-Type", "").lower() + if content_type not in ("image/png", "image/jpeg"): + await error_helper.out(d = "Файл должен быть изображением в формате PNG или JPEG") + return + + # Проверка размера файла (не более 5 МБ) + content_length = int(resp.headers.get("Content-Length", 0)) + if content_length > 5 * 1024 * 1024: # 5 МБ + await error_helper.out(d = "Размер файла не должен превышать 5 МБ") + return + + image_data = await resp.read() # Получаем изображение в виде bytes + + except aiohttp.ClientError as e: + await error_helper.out(d = f"Ошибка при загрузке изображения: {e}") + return + try: + await self.role.edit(icon=image_data, reason=f"Иконка изменена пользователем {ctx.author}") + except disnake.Forbidden: + await error_helper.out(d = "У бота недостаточно прав для изменения роли") + return + except disnake.HTTPException as e: + await error_helper.out(d = f"Ошибка при изменении роли: {e}") + return + + krekchat = await client.fetch_guild(constants["krekchat"]) + + role = krekchat.get_role(self.role.id) + + async with session.begin(): + async with self.DataBaseManager.models['roles_custom'] as roles_custom_model: + stmt = self.DataBaseManager.select(roles_custom_model).where(roles_custom_model.id == self.role.id) + custom_role = (await session.execute(stmt)).scalars().first() + self.embed.description = f"**Укажите, какой параметр вы хотите изменить у роли {role.mention}:**\n\n**1) Стоимость: **```{custom_role.cost} крошек```\n**2) Цвет: **```{role.color}```\n**3) Название: **```{role.name}```" + + await interaction.edit_original_message(embed=disnake.Embed(title="Роль успешно изменена!", description=f'', colour=0x2F3136)) + + await self.ctx.edit_original_message(embed=self.embed) + + async with self.DataBaseManager.session() as session: + async with session.begin(): + async with self.DataBaseManager.models['users'] as users_model: + stmt = self.DataBaseManager.select(users_model).options(self.DataBaseManager.joinedload(users_model.creation_role)).where(users_model.id == ctx.author.id) + user = (await session.execute(stmt)).scalars().first() + if user.creation_role is None: + await error_helper.out(d=f"Вы не создавали кастомных ролей") + return + + role = krekchat.get_role(user.creation_role.id) + if not role: + await error_helper.out(d=f'Вашей роли нет на сервере') + return + + embed = disnake.Embed(title=f"Управление ролью", + description=f'**Укажите, какой параметр вы хотите изменить у роли {role.mention}:**\n\n**1) Стоимость: **```{user.creation_role.cost} крошек```\n**2) Цвет: **```{role.color}```\n**3) Название: **```{role.name}```', + colour=0x2F3136) + embed.set_thumbnail(ctx.author.avatar) + EditRole = ButtonsEditRole(ctx, role, embed) + krekchat = await self.client.fetch_guild(constants["krekchat"]) + await ctx.edit_original_message(embed=embed, view=EditRole) + + + + @commands.slash_command(name="инвентарь") + async def RoleInventorySlash(self, ctx): + pass + @RoleInventorySlash.sub_command(description="Показывает все роли, которыми вы владеете", name="ролей") + async def RoleInventorySub(self, ctx: disnake.AppCmdInter): + await ctx.response.defer() + error_helper = self.client.ErrorOutHelper(send_function = ctx.edit_original_message, err_name = "Ошибка инвентаря") + if ctx.guild is None: + await error_helper.out(d="Эта команда не работает в личных сообщениях!") + return + krekchat = await self.client.fetch_guild(constants["krekchat"]) + + class RolesInventoryButtons(disnake.ui.View): + DataBaseManager = self.DataBaseManager + client = self.client + def __init__(self, ctx, inventory, embed): + super().__init__(timeout=180) + self.inventory = inventory + self.ctx = ctx + self.embed = embed + self.page = 1 + self.maxpage = len(inventory) if len(inventory) > 0 else 1 + + self.left.disabled = (self.page == 1) + self.right.disabled = (self.page == self.maxpage) + + @disnake.ui.button(label="1", custom_id="1", style=disnake.ButtonStyle.blurple) + async def one(self, button, inter): + if inter.author != self.ctx.author: + error = self.client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете управлять ролями другого участника", ephemeral = True) + await error.out() + return + await self.ToggleRoles(inter, button) + + @disnake.ui.button(label="2", custom_id="2", style=disnake.ButtonStyle.blurple) + async def two(self, button, inter): + if inter.author != self.ctx.author: + error = self.client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете управлять ролями другого участника", ephemeral = True) + await error.out() + return + await self.ToggleRoles(inter, button) + + @disnake.ui.button(label="3", custom_id="3", style=disnake.ButtonStyle.blurple) + async def three(self, button, inter): + if inter.author != self.ctx.author: + error = self.client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете управлять ролями другого участника", ephemeral = True) + await error.out() + return + await self.ToggleRoles(inter, button) + + @disnake.ui.button(label="4", custom_id="4", style=disnake.ButtonStyle.blurple) + async def four(self, button, inter): + if inter.author != self.ctx.author: + error = self.client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете управлять ролями другого участника", ephemeral = True) + await error.out() + return + await self.ToggleRoles(inter, button) + + @disnake.ui.button(label="5", custom_id="5", style=disnake.ButtonStyle.blurple) + async def five(self, button, inter): + if inter.author != self.ctx.author: + error = self.client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете управлять ролями другого участника", ephemeral = True) + await error.out() + return + await self.ToggleRoles(inter, button) + + @disnake.ui.button(label="<", custom_id="left", style=disnake.ButtonStyle.secondary) + async def left(self, button, inter): + if inter.author != self.ctx.author: + error = self.client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете управлять ролями другого участника", ephemeral = True) + await error.out() + return + self.page -= 1 + self.left.disabled = (self.page == 1) + self.right.disabled = (self.page == self.maxpage) + self.embed = await EmbedRoleInventoryChanger(self.inventory, self.embed, self.page, self.ctx.author) + await inter.response.edit_message(embed=self.embed, view=self) + + @disnake.ui.button(label=">", custom_id="right", style=disnake.ButtonStyle.secondary) + async def right(self, button, inter): + if inter.author != self.ctx.author: + error = self.client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете управлять ролями другого участника", ephemeral = True) + await error.out() + return + self.page += 1 + self.left.disabled = (self.page == 1) + self.right.disabled = (self.page == self.maxpage) + self.embed = await EmbedRoleInventoryChanger(self.inventory, self.embed, self.page, self.ctx.author) + await inter.response.edit_message(embed=self.embed, view=self) + + async def ToggleRoles(self, inter, button): + error_helper = self.client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка инвентаря", ephemeral = True) + + if len(self.inventory) < 1: + await error_helper.out(d=f'Роли с таким номером нет на странице') + return + if len(self.inventory[self.page - 1]) < int(button.custom_id): + await error_helper.out(d=f'Роли с таким номером нет на странице') + return + role = self.inventory[self.page - 1][int(button.custom_id) - 1] + role = krekchat.get_role(role) + if not role: + return + if role in self.ctx.author.roles: + await self.ctx.author.remove_roles(role) + else: + roles_inventory_ids = [] + async with self.DataBaseManager.session() as session: + async with session.begin(): + async with self.DataBaseManager.models['received_roles_custom'] as received_roles_custom_model: + stmt = self.DataBaseManager.select(received_roles_custom_model).where( + received_roles_custom_model.user_id == ctx.author.id + ) + custom_roles = (await session.execute(stmt)).scalars().all() + roles_inventory_ids += [role.role_id for role in custom_roles if not role.role_id in roles_inventory_ids] + + async with self.DataBaseManager.models['received_roles_prize'] as received_roles_prize_model: + stmt = self.DataBaseManager.select(received_roles_prize_model).where( + received_roles_prize_model.user_id == ctx.author.id + ) + prize_roles = (await session.execute(stmt)).scalars().all() + roles_inventory_ids += [role.role_id for role in prize_roles if not role.role_id in roles_inventory_ids] + + if not role.id in roles_inventory_ids: + await error_helper.out(d=f"Эта роль пропала из вашего инвентаря") + return + await self.ctx.author.add_roles(role) + self.embed = await EmbedRoleInventoryChanger(self.inventory, self.embed, self.page, self.ctx.author) + await inter.response.edit_message(embed=self.embed, view=self) + + async def on_timeout(self): + for child in self.children: + if isinstance(child, (disnake.ui.Button, disnake.ui.BaseSelect)): + child.disabled = True + await self.ctx.edit_original_message(view=self) + + async def EmbedRoleInventoryChanger(inventory, embed, selfpage, member): + embed.clear_fields() + if len(inventory) == 0: + embed.add_field(name=f"В инвентаре пока ничего нет", value=f"", inline=False) + return embed + page = inventory[selfpage - 1] + c = 1 + for roleid in page: + role = krekchat.get_role(roleid) + + wearing = role in member.roles + embed.add_field(name=f"", + value=f"**{c})** {role.mention if role else roleid} {':green_circle:' if wearing else ':red_circle:'}", + inline=False) + c += 1 + return embed + + roles_inventory_sorted_ids = [] + async with self.DataBaseManager.session() as session: + async with session.begin(): + async with self.DataBaseManager.models['roles_custom'] as roles_custom_model: + stmt = self.DataBaseManager.select(roles_custom_model).where( + roles_custom_model.creator_id == ctx.author.id + ) + creation_role = (await session.execute(stmt)).scalars().first() + if not creation_role is None: + roles_inventory_sorted_ids.append(creation_role.id) + + async with self.DataBaseManager.models['received_roles_prize'] as received_roles_prize_model: + stmt = self.DataBaseManager.select(received_roles_prize_model).where( + received_roles_prize_model.user_id == ctx.author.id + ) + prize_roles = (await session.execute(stmt)).scalars().all() + roles_inventory_sorted_ids += [role.role_id for role in prize_roles if not role.role_id in roles_inventory_sorted_ids] + + async with self.DataBaseManager.models['received_roles_custom'] as received_roles_custom_model: + stmt = self.DataBaseManager.select(received_roles_custom_model).where( + received_roles_custom_model.user_id == ctx.author.id + ) + custom_roles = (await session.execute(stmt)).scalars().all() + roles_inventory_sorted_ids += [role.role_id for role in custom_roles if not role.role_id in roles_inventory_sorted_ids] + + ready_array = self.client.PartitioningEmbeder(roles_inventory_sorted_ids) + embed = disnake.Embed(title=f"Инвентарь ролей", description=f'', colour=0x2F3136) + embed.set_thumbnail(ctx.author.avatar) + view = RolesInventoryButtons(ctx, ready_array, embed) + + embed = await EmbedRoleInventoryChanger(ready_array, embed, 1, ctx.author) + await ctx.edit_original_message(embed=embed, view=view) + + + @commands.slash_command(name="магазин") + async def ShopSlash(self, ctx): + pass + @ShopSlash.sub_command(description="Тут вы можете купить себе роль", name="ролей") + async def RolesShopSub(self, ctx: disnake.AppCmdInter): + ''' + + + магазин ролей + + + ''' + await ctx.response.defer() + error_helper = self.client.ErrorOutHelper(send_function = ctx.edit_original_message, err_name = "Ошибка магазина") + DataBaseManager = self.DataBaseManager + + if ctx.guild is None: + await error_helper.out(d="Эта команда не работает в личных сообщениях!") + return + PartitioningEmbeder = self.client.PartitioningEmbeder + client = self.client + krekchat = await client.fetch_guild(constants["krekchat"]) + + class ShopView(disnake.ui.View): + def __init__(self, ctx, embed): + super().__init__(timeout=180) + self.ctx = ctx + self.embed = embed + + async def initialize(self): + async with DataBaseManager.session() as session: + async with session.begin(): + async with DataBaseManager.models['roles_custom'] as roles_custom_model: + + stmt = DataBaseManager.select(roles_custom_model).order_by(DataBaseManager.asc(roles_custom_model.date_of_creation)) + self.sortbydateofcreation = (await session.execute(stmt)).scalars().all() + + stmt = DataBaseManager.select(roles_custom_model).order_by(DataBaseManager.asc(roles_custom_model.cost)) + self.sortbycost = (await session.execute(stmt)).scalars().all() + + stmt = DataBaseManager.select(roles_custom_model).order_by(DataBaseManager.desc(roles_custom_model.cost)) + self.sortbycostreversed = (await session.execute(stmt)).scalars().all() + + stmt = DataBaseManager.select(roles_custom_model).order_by(DataBaseManager.desc(roles_custom_model.date_of_creation)) + self.sortbydateofcreationreversed = (await session.execute(stmt)).scalars().all() + + stmt = DataBaseManager.select(roles_custom_model).outerjoin(roles_custom_model.users).group_by(roles_custom_model.id).order_by(DataBaseManager.func.count(DataBaseManager.models['users'].model.id).desc()) + self.sortbypopularityreversed = (await session.execute(stmt)).scalars().all() + + stmt = DataBaseManager.select(roles_custom_model).outerjoin(roles_custom_model.users).group_by(roles_custom_model.id).order_by(DataBaseManager.func.count(DataBaseManager.models['users'].model.id).asc()) + self.sortbypopularity = (await session.execute(stmt)).scalars().all() + + self.sortvalues = {"sortbydateofcreation": self.sortbydateofcreation, "sortbycost": self.sortbycost, + "sortbycostreversed": self.sortbycostreversed, + "sortbydateofcreationreversed": self.sortbydateofcreationreversed, + "sortbypopularityreversed": self.sortbypopularity, + "sortbypopularity": self.sortbypopularityreversed} + self.maxpage = len(PartitioningEmbeder([i for i in self.sortbydateofcreation])) + + self.page = 1 + + @disnake.ui.button(label="<<", custom_id="leftmax") + async def leftmax(self, button, inter): + await inter.response.defer() + if inter.author != self.ctx.author: + error = client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете покупать роли в магазине другого участника. Используйте команду `/магазин ролей`, чтобы купить роли", ephemeral = True) + await error.out() + return + self.page = 1 + self.leftmax.disabled = (self.page == 1) + self.left.disabled = (self.page == 1) + self.rightmax.disabled = (self.page == self.maxpage) + self.right.disabled = (self.page == self.maxpage) + + self.embed = await EmbedShopChanger(self.sort.values, self.embed, self.page, self.sortvalues) + + await inter.message.edit(view=self, embed=self.embed) + + @disnake.ui.button(label="<", custom_id="left") + async def left(self, button, inter): + await inter.response.defer() + if inter.author != self.ctx.author: + error = client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете покупать роли в магазине другого участника. Используйте команду `/магазин ролей`, чтобы купить роли", ephemeral = True) + await error.out() + return + self.page = max(self.page - 1, 1) + self.leftmax.disabled = (self.page == 1) + self.left.disabled = (self.page == 1) + self.rightmax.disabled = (self.page == self.maxpage) + self.right.disabled = (self.page == self.maxpage) + + self.embed = await EmbedShopChanger(self.sort.values, self.embed, self.page, self.sortvalues) + + await inter.message.edit(view=self, embed=self.embed) + + @disnake.ui.button(label=">", custom_id="right") + async def right(self, button, inter): + await inter.response.defer() + if inter.author != self.ctx.author: + error = client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете покупать роли в магазине другого участника. Используйте команду `/магазин ролей`, чтобы купить роли", ephemeral = True) + await error.out() + return + self.page = min(self.page + 1, self.maxpage) + self.leftmax.disabled = (self.page == 1) + self.left.disabled = (self.page == 1) + self.rightmax.disabled = (self.page == self.maxpage) + self.right.disabled = (self.page == self.maxpage) + + self.embed = await EmbedShopChanger(self.sort.values, self.embed, self.page, self.sortvalues) + + await inter.message.edit(view=self, embed=self.embed) + + @disnake.ui.button(label=">>", custom_id="rightmax") + async def rightmax(self, button, inter): + await inter.response.defer() + if inter.author != self.ctx.author: + error = client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете покупать роли в магазине другого участника. Используйте команду `/магазин ролей`, чтобы купить роли", ephemeral = True) + await error.out() + return + self.page = self.maxpage + self.leftmax.disabled = (self.page == 1) + self.left.disabled = (self.page == 1) + self.rightmax.disabled = (self.page == self.maxpage) + self.right.disabled = (self.page == self.maxpage) + + self.embed = await EmbedShopChanger(self.sort.values, self.embed, self.page, self.sortvalues) + + await inter.message.edit(view=self, embed=self.embed) + + @disnake.ui.string_select( + placeholder="Сначала новые", + options=[ + disnake.SelectOption(label="Сначала новые", value="sortbydateofcreationreversed", default=True), + disnake.SelectOption(label="Сначала старые", value="sortbydateofcreation", default=False), + disnake.SelectOption(label="Сначала дешёвые", value="sortbycost", default=False), + disnake.SelectOption(label="Сначала дорогие", value="sortbycostreversed", default=False), + disnake.SelectOption(label="Сначала популярные", value="sortbypopularity", default=False), + disnake.SelectOption(label="Сначала не популярные", value="sortbypopularityreversed", default=False), + ], + min_values=1, + max_values=1, + ) + async def sort(self, select: disnake.ui.StringSelect, inter: disnake.MessageInteraction): + await inter.response.defer() + if inter.author != self.ctx.author: + error = client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете покупать роли в магазине другого участника. Используйте команду `/магазин ролей`, чтобы купить роли", ephemeral = True) + await error.out() + return + self.page = 1 + self.leftmax.disabled = (self.page == 1) + self.left.disabled = (self.page == 1) + self.rightmax.disabled = (self.page == self.maxpage) + self.right.disabled = (self.page == self.maxpage) + + self.embed = await EmbedShopChanger(self.sort.values, self.embed, self.page, self.sortvalues) + + for opt in range(len(self.sort.options)): + if self.sort.options[opt].value == self.sort.values[0]: + self.sort.options[opt].default = True + else: + self.sort.options[opt].default = False + + await inter.message.edit(view=self, embed=self.embed) + + @disnake.ui.button(label="1", custom_id="1", style=disnake.ButtonStyle.blurple, row=3) + async def one(self, button, inter): + if inter.author != self.ctx.author: + error = client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете покупать роли в магазине другого участника. Используйте команду `/магазин ролей`, чтобы купить роли", ephemeral = True) + await error.out() + return + await self.BuyRoles(inter, button) + + @disnake.ui.button(label="2", custom_id="2", style=disnake.ButtonStyle.blurple, row=3) + async def two(self, button, inter): + if inter.author != self.ctx.author: + error = client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете покупать роли в магазине другого участника. Используйте команду `/магазин ролей`, чтобы купить роли", ephemeral = True) + await error.out() + return + await self.BuyRoles(inter, button) + + @disnake.ui.button(label="3", custom_id="3", style=disnake.ButtonStyle.blurple, row=3) + async def three(self, button, inter): + if inter.author != self.ctx.author: + error = client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете покупать роли в магазине другого участника. Используйте команду `/магазин ролей`, чтобы купить роли", ephemeral = True) + await error.out() + return + await self.BuyRoles(inter, button) + + @disnake.ui.button(label="4", custom_id="4", style=disnake.ButtonStyle.blurple, row=3) + async def four(self, button, inter): + if inter.author != self.ctx.author: + error = client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете покупать роли в магазине другого участника. Используйте команду `/магазин ролей`, чтобы купить роли", ephemeral = True) + await error.out() + return + await self.BuyRoles(inter, button) + + @disnake.ui.button(label="5", custom_id="5", style=disnake.ButtonStyle.blurple, row=3) + async def five(self, button, inter): + if inter.author != self.ctx.author: + error = client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете покупать роли в магазине другого участника. Используйте команду `/магазин ролей`, чтобы купить роли", ephemeral = True) + await error.out() + return + await self.BuyRoles(inter, button) + + async def on_timeout(self): + for child in self.children: + if isinstance(child, (disnake.ui.Button, disnake.ui.BaseSelect)): + child.disabled = True + await self.ctx.edit_original_message(view=self) + + async def BuyRoles(self, inter, button): + error_helper = client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка покупки", ephemeral = True) + async with DataBaseManager.session() as session: + async with session.begin(): + async with DataBaseManager.models['users'] as users_model: + stmt = DataBaseManager.select(users_model).options(DataBaseManager.selectinload(users_model.custom_roles)).where(users_model.id == inter.author.id) + user = (await session.execute(stmt)).scalars().first() + + page = (PartitioningEmbeder( + self.sortvalues[self.sort.values[0] if len(self.sort.values) == 1 else "sortbydateofcreationreversed"]))[ + self.page - 1] + + if len(page) < int(button.custom_id): + await error_helper.out(d=f'Роли с таким номером нет на странице') + return + + custom_role = page[int(button.custom_id) - 1] + if user.crumbs < custom_role.cost: + await error_helper.out(d=f'У вас недостаточно крошек') + return + + if custom_role.id in [role.id for role in user.custom_roles]: + await error_helper.out(d=f'У вас уже есть эта роль') + return + + role = krekchat.get_role(custom_role.id) + confirmembed = disnake.Embed(description=f'Вы уверены, что хотите преобрести роль {role.mention if role else custom_role.id} за {custom_role.cost} крошек?', + colour=0x2F3136) + ConfMessage = ConfirmView(self.ctx, role, custom_role) + await inter.response.edit_message(view=ConfMessage, embed=confirmembed) + class ConfirmView(disnake.ui.View): + def __init__(self, ctx, role, custom_role): + super().__init__(timeout=180) + self.ctx = ctx + self.role = role + self.custom_role = custom_role + + @disnake.ui.button(label="Да", custom_id="yes", style=disnake.ButtonStyle.green) + async def yes(self, button, inter): + if inter.author != self.ctx.author: + error = client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете покупать роли за другого участника. Используйте команду `/магазин ролей`, чтобы купить роли", ephemeral = True) + await error.out() + return + if not self.role: + error = client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка покупки", err_description = "Такая роль не найдена на сервере. Возможно, это ошибка базы данных", ephemeral = True) + await error.out() + return + + ctx = self.ctx + async with DataBaseManager.session() as session: + async with session.begin(): + async with DataBaseManager.models['users'] as users_model: + stmt = DataBaseManager.select(users_model).where(users_model.id == inter.author.id).with_for_update() + user = (await session.execute(stmt)).scalars().first() + stmt = DataBaseManager.select(users_model).where(users_model.id == self.custom_role.creator_id).with_for_update() + creator = (await session.execute(stmt)).scalars().first() + user.crumbs -= self.custom_role.cost + creator.crumbs += self.custom_role.cost + receive = DataBaseManager.models['received_roles_custom'].m(role_id = self.custom_role.id, user_id = user.id) + session.add(receive) + history = DataBaseManager.models['transaction_history_crumbs'].m(sender_id = user.id, recipient_id = creator.id, amount = self.custom_role.cost, description = f"Покупка роли {self.role.mention}") + await self.ctx.author.add_roles(self.role) + embed = disnake.Embed(description=f'Роль {self.role.mention} успешно преобретена!', colour=0x2F3136) + await self.ctx.edit_original_message(embed=embed, view=None) + + @disnake.ui.button(label="Нет", custom_id="no", style=disnake.ButtonStyle.red) + async def no(self, button, inter): + if inter.author != self.ctx.author: + error = client.ErrorOutHelper(send_function = inter.send, err_name = "Ошибка доступа", err_description = "Вы не можете покупать роли за другого участника. Используйте команду `/магазин ролей`, чтобы купить роли", ephemeral = True) + await error.out() + return + + ctx = self.ctx + + embed = disnake.Embed(title=f"Магазин ролей", description=f'', colour=0x2F3136) + ShopMessage = ShopView(ctx, embed) + await ShopMessage.initialize() + ShopMessage.leftmax.disabled = (ShopMessage.page == 1) + ShopMessage.left.disabled = (ShopMessage.page == 1) + ShopMessage.rightmax.disabled = (ShopMessage.page == ShopMessage.maxpage) + ShopMessage.right.disabled = (ShopMessage.page == ShopMessage.maxpage) + + embed = await EmbedShopChanger([], embed, 1) + + embed.set_thumbnail(ctx.author.avatar) + + await ctx.edit_original_message(view=ShopMessage, embed=embed) + + async def on_timeout(self): + for child in self.children: + if isinstance(child, (disnake.ui.Button, disnake.ui.BaseSelect)): + child.disabled = True + await self.ctx.edit_original_message(view=self) + async def EmbedShopChanger(sortvalues, embed, selfpage, constvalues=None): + if not constvalues: + async with DataBaseManager.session() as session: + async with session.begin(): + async with DataBaseManager.models['roles_custom'] as roles_custom_model: + + stmt = DataBaseManager.select(roles_custom_model).order_by(DataBaseManager.asc(roles_custom_model.date_of_creation)) + sortbydateofcreation = (await session.execute(stmt)).scalars().all() + + stmt = DataBaseManager.select(roles_custom_model).order_by(DataBaseManager.asc(roles_custom_model.cost)) + sortbycost = (await session.execute(stmt)).scalars().all() + + stmt = DataBaseManager.select(roles_custom_model).order_by(DataBaseManager.desc(roles_custom_model.cost)) + sortbycostreversed = (await session.execute(stmt)).scalars().all() + + stmt = DataBaseManager.select(roles_custom_model).order_by(DataBaseManager.desc(roles_custom_model.date_of_creation)) + sortbydateofcreationreversed = (await session.execute(stmt)).scalars().all() + + stmt = DataBaseManager.select(roles_custom_model).outerjoin(roles_custom_model.users).group_by(roles_custom_model.id).order_by(DataBaseManager.func.count(DataBaseManager.models['users'].model.id).desc()) + sortbypopularityreversed = (await session.execute(stmt)).scalars().all() + + stmt = DataBaseManager.select(roles_custom_model).outerjoin(roles_custom_model.users).group_by(roles_custom_model.id).order_by(DataBaseManager.func.count(DataBaseManager.models['users'].model.id).asc()) + sortbypopularity = (await session.execute(stmt)).scalars().all() + + constvalues = {"sortbydateofcreation": sortbydateofcreation, "sortbycost": sortbycost, + "sortbycostreversed": sortbycostreversed, + "sortbydateofcreationreversed": sortbydateofcreationreversed, + "sortbypopularityreversed": sortbypopularity, + "sortbypopularity": sortbypopularityreversed} + embed.clear_fields() + page = \ + (PartitioningEmbeder(constvalues[sortvalues[0] if len(sortvalues) == 1 else "sortbydateofcreationreversed"]))[ + selfpage - 1] + c = 1 + for custom_role in page: + try: + creator = await krekchat.fetch_member(custom_role.creator_id) + except: + creator = custom_role.creator_id + role = krekchat.get_role(custom_role.id) + embed.add_field(name=f"", + value=f"**{c})** {role.mention if role else custom_role.id}\n**Цена: **{custom_role.cost} крошек\n**Создатель: **{creator.mention if type(creator)!=int else creator}", + inline=False) + c += 1 + return embed + embed = disnake.Embed(title=f"Магазин ролей", description=f'', colour=0x2F3136) + async with DataBaseManager.session() as session: + await self.client.UserUpdate(ctx.author, session = session) + async with session.begin(): + async with DataBaseManager.models['roles_custom'] as roles_custom_model: + stmt = DataBaseManager.select(roles_custom_model) + roles = (await session.execute(stmt)).scalars().all() + if len(roles) == 0: + embed.description = "В магазине пока нет ни одной роли" + embed.set_thumbnail(ctx.author.avatar) + await ctx.edit_original_message(embed=embed) + return + ShopMessage = ShopView(ctx, embed) + await ShopMessage.initialize() + ShopMessage.leftmax.disabled = (ShopMessage.page == 1) + ShopMessage.left.disabled = (ShopMessage.page == 1) + ShopMessage.rightmax.disabled = (ShopMessage.page == ShopMessage.maxpage) + ShopMessage.right.disabled = (ShopMessage.page == ShopMessage.maxpage) + + + embed = await EmbedShopChanger([], embed, 1) + embed.set_thumbnail(ctx.author.avatar) + await ctx.edit_original_message(view=ShopMessage, embed=embed) \ No newline at end of file diff --git a/src/constants/global_constants.py b/src/constants/global_constants.py new file mode 100644 index 0000000..7ce2461 --- /dev/null +++ b/src/constants/global_constants.py @@ -0,0 +1,20 @@ +constants = { + "costrolecreate": 10000, "costrolerenewal": 5000, "dailycrumbs": 300, "casinospinslimit": 20, + "krekchat": 490445877903622144, + "sponsors": [648932777755934740, 926021742151999509, 959416622144180265, 995602360325902386, 995602360325902387, + 995602360325902388, 995602360325902389, 712751688741814343, 1244009309000437780, 1285667409365176372], + "mutes": [1219615791750709268, #text + 1219615979185770597],#voice + "ban_role": 1219276355875639407, + "me": 887696340920963072, + "moder": 490712181927837722, + "curator": 490712205445169162, + "everyone": 490445877903622144, + "staff": 1229765545310818377, + "level_roles": [None, 1379836872653799515, 1379837914837549197, 1379838164394577980, 490707634710642700], + "moderators": [490712181927837722, 490712205445169162], + "hierarchy": [490712205445169162, 490712181927837722, 490445877903622144], + "givecrumbscommission": 0.05, + "bots_talk_protocol_channel": 1376233239202758827, + "databases_backups_channel": 1382363252683706448, + } \ No newline at end of file diff --git a/src/constants/rimagochi_constants.py b/src/constants/rimagochi_constants.py new file mode 100644 index 0000000..572fe24 --- /dev/null +++ b/src/constants/rimagochi_constants.py @@ -0,0 +1,1307 @@ +rimagochi_default_settings = { + 'hide_the_animals': False, + 'always_use_crumbs_for_feeding':False, + 'accept_the_challenge': True, +} +rimagochi_constants = { + "health_multiplayer": 1, + "damage_multiplayer": 1, + "hanger_multiplayer": 100, + "max_battle_slots": 8, + "animals_exp_to_levels": {1: 1000, 2: 1500, 3: 2500, 4: 5000, 5: 10_000, 6: 20_000, 7: 40_000, 8: 80_000, 9: 160_000, 10: 320_000} +} +rimagochi_items = { + 11:{ + "id": 11, + "name": "мясо", + "description": "Будет расходоваться вместо крошек для кормления животных, которые его едят (заменяет 10 крошек)", + "buy_cost": 9, "sell_cost": 5, + "shop_cost": 15, + }, + 12:{ + "id": 12, + "name": "кожа", + "description": "Можно продать, ну или, может быть, использовать на что-нибудь ещё...", + "buy_cost": 15, "sell_cost": 10, + "shop_cost": 25, + }, + 13:{ + "id": 13, + "name": "рог трумбо", + "description": "Можно продать, ну или, может быть, использовать на что-нибудь ещё...", + "buy_cost": 20_000, "sell_cost": 10_000, + "shop_cost": 25_000, + }, + 14:{ + "id": 14, + "name": "древесина", + "description": "Будет расходоваться вместо крошек для кормления животных, которые её едят (заменяет 10 крошек)", + "buy_cost": 9, "sell_cost": 5, + "shop_cost": 15, + }, + 15:{ + "id": 15, + "name": "рог носорога", + "description": "Можно продать, ну или, может быть, использовать на что-нибудь ещё...", + "buy_cost": 6_000, "sell_cost": 3_000, + "shop_cost": 8_000, + }, + 16:{ + "id": 16, + "name": "бивень слона", + "description": "Можно продать, ну или, может быть, использовать на что-нибудь ещё...", + "buy_cost": 3_000, "sell_cost": 1_500, + "shop_cost": 4_000, + }, + 17:{ + "id": 17, + "name": "химическое топливо", + "description": "Можно продать, ну или, может быть, использовать на что-нибудь ещё...", + "buy_cost": 30, "sell_cost": 20, + "shop_cost": 50, + }, + 18:{ + "id": 18, + "name": "хитин", + "description": "Можно продать, ну или, может быть, использовать на что-нибудь ещё...", + "buy_cost": 30, "sell_cost": 20, + "shop_cost": 50, + }, + 19:{ + "id": 19, + "name": "биоферрит", + "description": "Можно продать, ну или, может быть, использовать на что-нибудь ещё...", + "buy_cost": 30, "sell_cost": 20, + "shop_cost": 50, + }, + 110:{ + "id": 110, + "name": "позвоночник ревенанта", + "description": "Можно продать, ну или, может быть, использовать на что-нибудь ещё...", + "buy_cost": 5_000, "sell_cost": 3_000, + "shop_cost": 8_000, + }, +} +rimagochi_genes = { + 41: {"id": 41, "name": "железная кожа", "description": f"Здоровье носителя увеличивается на {2*rimagochi_constants['health_multiplayer']} единицы", "effects": {"health": 2}, "cost": 10_000}, + 42: {"id": 42, "name": "улучшенный метаболизм", "description": f"Потребление пищи носителя снижается на {int(0.5*rimagochi_constants['hanger_multiplayer'])} единиц(не ниже 0)", "effects": {"hunger": -0.5}, "cost": 2250}, + 43: {"id": 43, "name": "ядерный желудок", "description": f"Снижает потребление пищи на {int(10*rimagochi_constants['hanger_multiplayer'])} единиц(не ниже 0)", "effects": {"hunger": -10}, "cost": 30_000}, + 44: {"id": 44, "name": "сильный урон в ближнем бою", "description": f"Урон носителя увеличивается на {2*rimagochi_constants['damage_multiplayer']} единицы", "effects": {"damage": 2}, "cost": 10_000} +} +rimagochi_rarity = { + 31: {"id": 31, "name": "мифический", "emoji": "🟣", "standart_chance": 0.002}, + 32: {"id": 32, "name": "легендарный", "emoji": "🟠", "standart_chance": 0.02}, + 33: {"id": 33, "name": "эпический", "emoji": "🔴", "standart_chance": 0.139}, + 34: {"id": 34, "name": "редкий", "emoji": "🔵", "standart_chance": 0.239}, + 35: {"id": 35, "name": "обычный", "emoji": "🟢", "standart_chance": 0.4}, + 36: {"id": 36, "name": "дно", "emoji": "⚫", "standart_chance": 0.2}, + + #лимитки + 37: {"id": 37, "name": "аномальный", "emoji": "<:Golden_cube:1368690052435279992>", "standart_chance": 0}, +} +rimagochi_capsules = { + 51: { + 'id': 51, + 'name': 'обычный саркофаг криптосна', + 'cost': 2000, + 'chances': [ + {"rarity": rimagochi_rarity[31], "chance": rimagochi_rarity[31]["standart_chance"]}, + {"rarity": rimagochi_rarity[32], "chance": rimagochi_rarity[32]["standart_chance"]}, + {"rarity": rimagochi_rarity[33], "chance": rimagochi_rarity[33]["standart_chance"]}, + {"rarity": rimagochi_rarity[34], "chance": rimagochi_rarity[34]["standart_chance"]}, + {"rarity": rimagochi_rarity[35], "chance": rimagochi_rarity[35]["standart_chance"]}, + {"rarity": rimagochi_rarity[36], "chance": rimagochi_rarity[36]["standart_chance"]}, + ], + }, + 52: { + 'id': 52, + 'name': 'аномальный саркофаг криптосна', + 'cost': 3000, + 'chances': [ + {"rarity": rimagochi_rarity[31], "chance": 0.001}, + {"rarity": rimagochi_rarity[37], "chance": 0.04}, + {"rarity": rimagochi_rarity[32], "chance": 0.02}, + {"rarity": rimagochi_rarity[33], "chance": 0.2}, + {"rarity": rimagochi_rarity[34], "chance": 0.3}, + {"rarity": rimagochi_rarity[35], "chance": 0.439}, + ], + }, +} +rimagochi_animals = { + 261: { + "id": 261, + "name": "ревенант", + "description": "В фольклоре чужеземцев описывается невидимый призрак, который завладевает разумом грешников, помещая их в настоящий ад. Согласно легенде, жертв можно вернуть к жизни, только выследив существо во время его сна и убив его.", + "params":{ + "hunger": 0, + "can_eate": [], + "damage": 30, + "health": 35, + "after_death": [ + {"item": rimagochi_items[19], "count": (50, 85)}, + {"item": rimagochi_items[110], "count": (1, 1)} + ], + "rarity": rimagochi_rarity[37], + "required_slots" : 8, + "image_url": "https://rimworldwiki.com/images/thumb/0/07/Revenant.png/72px-Revenant.png", + } + }, + 262: { + "id": 262, + "name": "золотой куб", + "description": "Кубик кажется неуязвимым для большинства повреждений.", + "params":{ + "hunger": 0, + "can_eate": [], + "damage": 0.5, + "health": 70, + "after_death": [ + {"item": rimagochi_items[19], "count": (0, 0)} + ], + "rarity": rimagochi_rarity[37], + "required_slots" : 1, + "image_url": "https://rimworldwiki.com/images/d/d2/Golden_cube.png", + } + }, + + + #стандарт + 21: { + "id": 21, + "name": "трумбо", + "description": "Медленный гигант с кожей, непробиваемой для большинства снарядов. Его рог дробит скалы, а упрямство сравнимо только с толщиной шкуры. Не ждите от него тактики — Трумбо просто идёт вперёд, пока что-то не рухнет. И это «что-то» обычно не он.", + "params":{ + "hunger": 4.5, + "can_eate": [ + {"item": rimagochi_items[14]} + ], + "damage": 30, + "health": 30, + "after_death": [ + {"item": rimagochi_items[11], "count": (250, 260)}, + {"item": rimagochi_items[12], "count": (50, 66)}, + {"item": rimagochi_items[13], "count": (1, 1)} + ], + "rarity": rimagochi_rarity[31], + "required_slots" : 5, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/9/90/Трумбо.png/revision/latest?cb=20160716092442&path-prefix=ru", + } + }, + 22: { + "id": 22, + "name": "слон", + "description": "Исполин прерий, чьи удары сбивают с ног даже бронированных противников. Его бивни — это природные копья, а хобот запросто ломает рёбра. Медлителен, но один точный удар может переломить ход битвы.", + "params":{ + "hunger": 3.5, + "can_eate": [], + "damage": 20, + "health": 25, + "after_death": [ + {"item": rimagochi_items[11], "count": (350, 370)}, + {"item": rimagochi_items[12], "count": (75, 85)}, + {"item": rimagochi_items[16], "count": (1, 2)} + ], + "rarity": rimagochi_rarity[32], + "required_slots" : 4, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/f/f8/Слон.png/revision/latest/scale-to-width-down/51?cb=20161122173318&path-prefix=ru", + } + }, + 23: { + "id": 23, + "name": "носорог", + "description": "Живой таран в броне. Его рог пробивает даже пласталевые двери, а разгону позавидует любой механоид. Не ищите тактику — носорог знает лишь один манёвр: «Ломись, круши, добеги».", + "params":{ + "hunger": 3.3, + "can_eate": [], + "damage": 24, + "health": 21, + "after_death": [ + {"item": rimagochi_items[11], "count": (260, 280)}, + {"item": rimagochi_items[12], "count": (58, 62)}, + {"item": rimagochi_items[15], "count": (1, 1)} + ], + "rarity": rimagochi_rarity[32], + "required_slots" : 4, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/a/ae/Носорог.png/revision/latest/scale-to-width-down/51?cb=20150613162727&path-prefix=ru", + } + }, + 238: { + "id": 238, + "name": "мегапаук", + "description": "Мегапаук - агрессивное существо, которое обитает в пещерах. Не являясь на самом деле пауком, мегапаук - генно-модифицированное насекомое размером с медведя, созданное для тяжёлой работы и боя. Толстая хитиновая броня хорошо защищает паука, а длинные потрошащие клинки опасны для его врагов.", + "params": { + "hunger": 3.4, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 22, + "health": 22, + "after_death": [ + {"item": rimagochi_items[11], "count": (120, 140)}, + {"item": rimagochi_items[18], "count": (100, 120)} + ], + "rarity": rimagochi_rarity[32], + "required_slots": 4, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/d/d5/Мегапаук.png/revision/latest/scale-to-width-down/51?cb=20180418143103&path-prefix=ru" + } + }, + 24: { + "id": 24, + "name": "гигантский ленивец", + "description": "Крупный медлительный зверь с толстой шкурой. Наносит слабый урон из-за низкой скорости атак, но благодаря живучести может долго держаться в бою. Опасен в ближнем бою — его мощные когти наносят дробящие повреждения.", + "params":{ + "hunger": 2.5, + "can_eate": [], + "damage": 15.6, + "health": 17, + "after_death": [ + {"item": rimagochi_items[11], "count": (350, 370)}, + {"item": rimagochi_items[12], "count": (70, 80)} + ], + "rarity": rimagochi_rarity[33], + "required_slots" : 3, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/7/7c/Гигантский_ленивец.png/revision/latest/scale-to-width-down/51?cb=20161215005219&path-prefix=ru", + } + }, + 211: { + "id": 211, + "name": "белый медведь", + "description": "Король ледяных пустошей. Его лапы бьют с силой айсберга, а шкура не боится ни пуль, ни мороза. Медлителен, но каждый его удар сносит головы — если цель выживет, то лишь потому, что медведь решил пощадить.", + "params":{ + "hunger": 2.7, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 18, + "health": 15, + "after_death": [ + {"item": rimagochi_items[11], "count": (180, 200)}, + {"item": rimagochi_items[12], "count": (40, 46)} + ], + "rarity": rimagochi_rarity[33], + "required_slots" : 3, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/f/fe/Белый_медведь.png/revision/latest/scale-to-width-down/51?cb=20170624170811&path-prefix=ru", + } + }, + 219: { + "id": 219, + "name": "гризли", + "description": "Медвежий ужас лесов. Его когти рвут плоть, как бумагу, а рёв парализует слабонервных. Не ждите тактики — гризли просто идёт напролом, снося всё на своём пути. Единственная стратегия против него — не попадаться на глаза.", + "params": { + "hunger": 2.2, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 17, + "health": 16, + "after_death": [ + {"item": rimagochi_items[11], "count": (190, 200)}, + {"item": rimagochi_items[12], "count": (40, 46)}, + ], + "rarity": rimagochi_rarity[33], + "required_slots": 3, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/6/60/Медведь.png/revision/latest/scale-to-width-down/51?cb=20170709181213&path-prefix=ru" + } + }, + 239: { + "id": 239, + "name": "мегаскарабей", + "description": "Большой генно-модифицированный жук. Когда-то они были рабочей кастой искусственной экосистемы инсектоидов, специально выведенной для борьбы с вторжением механоидов. Теперь их обычно видят без более смертоносных инсектоидных собратьев. Тем не менее, размер и твердый панцирь делают их опасными противниками в бою.", + "params": { + "hunger": 0.8, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 11, + "health": 9, + "after_death": [ + {"item": rimagochi_items[11], "count": (18, 18)}, + {"item": rimagochi_items[18], "count": (25, 30)} + ], + "rarity": rimagochi_rarity[33], + "required_slots": 1, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/8/83/Мегаскарабей.png/revision/latest/scale-to-width-down/51?cb=20180418142807&path-prefix=ru" + } + }, + 240: { + "id": 240, + "name": "муффало", + "description": "Мохнатый гигант тундры. Его рога — природные тараны, а шкура не боится даже арктических морозов. В ярости сносит всё на своём пути, но требует огромного количества пищи.", + "params": { + "hunger": 2.0, + "can_eate": [], + "damage": 16.5, + "health": 16, + "after_death": [ + {"item": rimagochi_items[11], "count": (170, 190)}, + {"item": rimagochi_items[12], "count": (39, 41)} + ], + "rarity": rimagochi_rarity[33], + "required_slots": 3, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/e/eb/Муффало.png/revision/latest/scale-to-width-down/51?cb=20131103180147&path-prefix=ru" + } + }, + 260: { + "id": 260, + "name": "зубр", + "description": "Крупное травоядное, которое при атаке превращается в живой таран. Его шкура достаточно толстая, чтобы игнорировать ножи, а рога оставляют рваные раны. Главный недостаток — требует много места и еды.", + "params": { + "hunger": 2.8, + "can_eate": [], + "damage": 16, + "health": 17, + "after_death": [ + {"item": rimagochi_items[11], "count": (180, 200)}, + {"item": rimagochi_items[12], "count": (60, 66)} + ], + "rarity": rimagochi_rarity[33], + "required_slots": 3, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/6/64/Зубр.png/revision/latest/scale-to-width-down/51?cb=20200529154650&path-prefix=ru" + } + }, + 25: { + "id": 25, + "name": "пума", + "description": "Быстрый и смертоносный хищник. Атакует первым благодаря высокой скорости, нанося кровавые рваные раны когтями. Хрупкая, но опасная в первой линии боя — лучше убить её до того, как она совершит фатальный прыжок.", + "params":{ + "hunger": 1.4, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 19, + "health": 9, + "after_death": [ + {"item": rimagochi_items[11], "count": (120, 136)}, + {"item": rimagochi_items[12], "count": (26, 30)} + ], + "rarity": rimagochi_rarity[34], + "required_slots" : 3, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/9/9f/Пума.png/revision/latest/scale-to-width-down/51?cb=20161121084144&path-prefix=ru", + } + }, + 215: { + "id": 215, + "name": "варг", + "description": "Когда-то его предков боялись целые планеты. Теперь же он всего лишь дорогая мясорубка: за те же крошки можно нанять двух свиней, но они не выглядят так угрожающе. Уровень повышает только его самомнение.", + "params":{ + "hunger": 1.4, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 20, + "health": 9, + "after_death": [ + {"item": rimagochi_items[11], "count": (89, 91)}, + {"item": rimagochi_items[12], "count": (20, 20)}, + ], + "rarity": rimagochi_rarity[34], + "required_slots" : 3, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/4/40/Варг.png/revision/latest/scale-to-width-down/51?cb=20160715194624&path-prefix=ru", + } + }, + 213: { + "id": 213, + "name": "бумалопа", + "description": "Генетический эксперимент с печальными последствиями. Накапливает в жировых отложениях легковоспламеняющееся биохимическое топливо.", + "params":{ + "hunger": 1.2, + "can_eate": [], + "damage": 10, + "health": 10, + "after_death": [ + {"item": rimagochi_items[11], "count": (130, 140)}, + {"item": rimagochi_items[12], "count": (28, 32)}, + {"item": rimagochi_items[17], "count": (5, 15)} + ], + "rarity": rimagochi_rarity[34], + "required_slots" : 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/2/27/Boomalope-0.png/revision/latest/scale-to-width-down/51?cb=20200910181859&path-prefix=ru", + } + }, + 217: { + "id": 217, + "name": "волк", + "description": "Стайный хищник, привыкший разрывать добычу в координированных атаках. В бою действует хитро: атакует первым, нанося глубокие рваные раны, а затем отступает, чтобы избежать контрудара. Идеален для фланговых манёвров и добивания ослабленных врагов.", + "params": { + "hunger": 1.5, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 14, + "health": 9, + "after_death": [ + {"item": rimagochi_items[11], "count": (70, 86)}, + {"item": rimagochi_items[12], "count": (20, 26)} + ], + "rarity": rimagochi_rarity[34], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/9/99/Волк.png/revision/latest/scale-to-width-down/51?cb=20170126162033&path-prefix=ru" + } + }, + 227: { + "id": 227, + "name": "капибара", + "description": "Легендарный носитель дзена. Капибара не сражается — она существует. В бою вызывает недоумение у врага и восторг у союзников. Никто не знает, зачем она здесь, но все уверены: она делает это важно. Почти неуязвима к стрессу… и к урону тоже.", + "params": { + "hunger": 1, + "can_eate": [], + "damage": 2, + "health": 45, + "after_death": [ + {"item": rimagochi_items[11], "count": (48, 52)}, + {"item": rimagochi_items[12], "count": (10, 12)} + ], + "rarity": rimagochi_rarity[34], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/b/b8/Капибара.png/revision/latest/scale-to-width-down/51?cb=20161125110014&path-prefix=ru" + } + }, + 230: { + "id": 230, + "name": "кобра", + "description": "Смертоносная рептилия, чей яд парализует жертву за секунды. В бою атакует молниеносно, но крайне уязвима к ответным ударам.", + "params": { + "hunger": 0.6, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 15, + "health": 4, + "after_death": [ + {"item": rimagochi_items[11], "count": (23, 23)}, + {"item": rimagochi_items[12], "count": (5, 5)} + ], + "rarity": rimagochi_rarity[34], + "required_slots": 1, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/7/77/Кобра.png/revision/latest/scale-to-width-down/51?cb=20150613162522&path-prefix=ru" + } + }, + 237: { + "id": 237, + "name": "лиса", + "description": "Хитрый мелкий хищник, предпочитающий атаковать исподтишка. Её укусы не смертельны, но она мастерски отвлекает противников, заставляя их бегать за собой по всему полю боя.", + "params": { + "hunger": 1.2, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 9, + "health": 7, + "after_death": [ + {"item": rimagochi_items[11], "count": (50, 50)}, + {"item": rimagochi_items[12], "count": (18, 18)} + ], + "rarity": rimagochi_rarity[34], + "required_slots": 1, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/8/83/Лисица.png/revision/latest/scale-to-width-down/51?cb=20170604232620&path-prefix=ru" + } + }, + 243: { + "id": 243, + "name": "пантера", + "description": "Идеальный убийца — быстрый, тихий и безжалостный. Её атаки нацелены в горло, а чёрная шкура делает её почти невидимой в темноте. Боится огня и громких звуков.", + "params": { + "hunger": 1.5, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 15, + "health": 8, + "after_death": [ + {"item": rimagochi_items[11], "count": (120, 132)}, + {"item": rimagochi_items[12], "count": (30, 32)} + ], + "rarity": rimagochi_rarity[34], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/7/72/Пантера.png/revision/latest/scale-to-width-down/51?cb=20180422111902&path-prefix=ru" + } + }, + 244: { + "id": 244, + "name": "полярный волк", + "description": "Белоснежный хищник арктических пустошей. Его густая шерсть защищает от лютых морозов, а стайная тактика делает смертельно опасным даже для более крупных противников.", + "params": { + "hunger": 1.5, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 14, + "health": 9, + "after_death": [ + {"item": rimagochi_items[11], "count": (70, 86)}, + {"item": rimagochi_items[12], "count": (20, 26)} + ], + "rarity": rimagochi_rarity[34], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/8/82/Полярный_волк.png/revision/latest/scale-to-width-down/51?cb=20170126163932&path-prefix=ru" + } + }, + 245: { + "id": 245, + "name": "полярная лисица", + "description": "Мелкий, но невероятно живучий хищник. Меняет окрас шерсти в зависимости от сезона, идеально маскируясь в снегах. Предпочитает атаковать исподтишка.", + "params": { + "hunger": 1.2, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 9, + "health": 7, + "after_death": [ + {"item": rimagochi_items[11], "count": (50, 50)}, + {"item": rimagochi_items[12], "count": (18, 18)} + ], + "rarity": rimagochi_rarity[34], + "required_slots": 1, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/8/8f/Песец.png/revision/latest/scale-to-width-down/51?cb=20220206124138&path-prefix=ru" + } + }, + 246: { + "id": 246, + "name": "рысь", + "description": "Одинокий охотник с молниеносной реакцией. Её кисточки на ушах - не просто украшение, а природный радар. Атакует прыжком, целясь в шею жертвы.", + "params": { + "hunger": 1.5, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 14, + "health": 9, + "after_death": [ + {"item": rimagochi_items[11], "count": (70, 74)}, + {"item": rimagochi_items[12], "count": (15, 17)} + ], + "rarity": rimagochi_rarity[34], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/b/bd/Рысь.png/revision/latest/scale-to-width-down/51?cb=20170624164203&path-prefix=ru" + } + }, + 257: { + "id": 257, + "name": "конь", + "description": "Галопом — в атаку, копытом — в лицо. Идеален для стремительных прорывов, но в ближнем бою мечется.", + "params": { + "hunger": 1.2, + "can_eate": [], + "damage": 10, + "health": 11, + "after_death": [ + {"item": rimagochi_items[11], "count": (160, 200)}, + {"item": rimagochi_items[12], "count": (50, 70)} + ], + "rarity": rimagochi_rarity[34], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/6/65/Лошадь.png/revision/latest/scale-to-width-down/51?cb=20200602055837&path-prefix=ru" + } + }, + 259: { + "id": 259, + "name": "як", + "description": "Мохнатый гигант с нравом бульдозера. Его рога — природные копья, а выносливость позволяет тащить на себе полбатальона.", + "params": { + "hunger": 1.3, + "can_eate": [], + "damage": 12, + "health": 14, + "after_death": [ + {"item": rimagochi_items[11], "count": (160, 180)}, + {"item": rimagochi_items[12], "count": (55, 60)} + ], + "rarity": rimagochi_rarity[34], + "required_slots": 3, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/a/aa/Як.png/revision/latest/scale-to-width-down/51?cb=20200601124520&path-prefix=ru" + } + }, + 26: { + "id": 26, + "name": "свинья", + "description": "Жирный и агрессивный боец ближнего боя. Наносит слабый урон, но устойчив к кровотечениям. Неплохой 'танк' для отвлечения врагов в первых рядах.", + "params":{ + "hunger": 1, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 6, + "health": 10, + "after_death": [ + {"item": rimagochi_items[11], "count": (68, 76)}, + {"item": rimagochi_items[12], "count": (14, 18)} + ], + "rarity": rimagochi_rarity[35], + "required_slots" : 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/5/50/Свинья.png/revision/latest/scale-to-width-down/51?cb=20161029112449&path-prefix=ru", + } + }, + 28: { + "id": 28, + "name": "альпака", + "description": "Мирное на вид существо, способное яростно защищаться. Бьёт врагов копытами и плюётся желудочным соком. Не наносит серьёзного урона, но может долго держаться в бою благодаря выносливости.", + "params":{ + "hunger": 0.9, + "can_eate": [], + "damage": 5, + "health": 12, + "after_death": [ + {"item": rimagochi_items[11], "count": (85, 95)}, + {"item": rimagochi_items[12], "count": (18, 22)} + ], + "rarity": rimagochi_rarity[35], + "required_slots" : 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/7/77/Альпака.png/revision/latest/scale-to-width-down/51?cb=20160419074343&path-prefix=ru", + } + }, + 218: { + "id": 218, + "name": "газель", + "description": "Стремительный и пугливый бегун, чья тактика сводится к одному — ударил, убежал, повторил. Её рога скорее царапают, чем ранят, но в группе они могут измотать противника. После смерти оставляет нежное мясо и тонкую кожу — идеально для начинающих фермеров-бойцов.", + "params": { + "hunger": 0.8, + "can_eate": [], + "damage": 7, + "health": 9, + "after_death": [ + {"item": rimagochi_items[11], "count": (60, 66)}, + {"item": rimagochi_items[12], "count": (14, 14)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/1/18/Газель.png/revision/latest/scale-to-width-down/51?cb=20170624175956&path-prefix=ru" + } + }, + 29: { + "id": 29, + "name": "альфабобер", + "description": "Генетически модифицированный бобр-переросток с усиленными резцами. Стремительно грызёт врагов, нанося высокий урон для своего класса, но крайне уязвим к ответным атакам.", + "params":{ + "hunger": 1, + "can_eate": [ + {"item": rimagochi_items[14]} + ], + "damage": 10, + "health": 7, + "after_death": [ + {"item": rimagochi_items[11], "count": (52, 56)}, + {"item": rimagochi_items[12], "count": (10, 12)} + ], + "rarity": rimagochi_rarity[35], + "required_slots" : 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/e/ed/Альфабобёр.png/revision/latest/scale-to-width-down/51?cb=20150613162449&path-prefix=ru", + } + }, + 214: { + "id": 214, + "name": "бумкрыса", + "description": "Гибрид крысы и бочки с горючим. Постоянно дрожит и нервно озирается — видимо, догадывается о своей... нестабильной природе. Кусается больно, но главная тактика: запустить её в толпу врагов и надеяться, что те запаникуют от одного её вида.", + "params":{ + "hunger": 0.8, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 6, + "health": 5, + "after_death": [ + {"item": rimagochi_items[11], "count": (18, 18)}, + {"item": rimagochi_items[12], "count": (4, 4)}, + {"item": rimagochi_items[17], "count": (2, 6)} + ], + "rarity": rimagochi_rarity[35], + "required_slots" : 1, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/0/05/Boomrat.png/revision/latest/scale-to-width-down/51?cb=20200910182241&path-prefix=ru", + } + }, + 216: { + "id": 216, + "name": "верблюд", + "description": "Выносливый корабль пустыни, который в бою ведёт себя как живая баррикада. Его плевки не наносят серьёзного урона, но способны деморализовать даже самых стойких противников. Медлителен, но терпелив — идеален для изматывания врагов в долгих стычках. После смерти оставляет внушительные запасы мяса и шкуры, словно напоминая, что даже в смерти он полезнее, чем иные существа в жизни.", + "params":{ + "hunger": 1, + "can_eate": [], + "damage": 6, + "health": 10, + "after_death": [ + {"item": rimagochi_items[11], "count": (169, 172)}, + {"item": rimagochi_items[12], "count": (37, 41)} + ], + "rarity": rimagochi_rarity[35], + "required_slots" : 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/1/1b/Верблюд.png/revision/latest/scale-to-width-down/51?cb=20190105160837&path-prefix=ru", + } + }, + 221: { + "id": 221, + "name": "енот", + "description": "Мелкий хитрый воришка, который в бою полагается на внезапность и грязные приёмы. Не надейтесь на его силу — его козырь это умение ударить в спину и быстро скрыться. После смерти оставляет лишь клочья меха, но зато какой мягкий!", + "params": { + "hunger": 0.7, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 6, + "health": 5, + "after_death": [ + {"item": rimagochi_items[11], "count": (20, 25)}, + {"item": rimagochi_items[12], "count": (8, 13)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 1, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/c/c2/Енот.png/revision/latest/scale-to-width-down/51?cb=20170624165320&path-prefix=ru" + } + }, + 224: { + "id": 224, + "name": "индейка", + "description": "Птица, которая выглядит как шутка, но может неожиданно клюнуть в самый неожиданный момент. Не отличается силой или выносливостью, но зато потребляет минимум ресурсов. Хорошо подходит тем, кто хочет поднять армию из пернатых за копейки.", + "params": { + "hunger": 0.6, + "can_eate": [], + "damage": 4, + "health": 7, + "after_death": [ + {"item": rimagochi_items[11], "count": (60, 66)}, + {"item": rimagochi_items[12], "count": (20, 22)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 1, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/a/a0/Индейка.png/revision/latest/scale-to-width-down/51?cb=20200311182418&path-prefix=ru" + } + }, + 225: { + "id": 225, + "name": "кабан", + "description": "Упрямый и яростный зверь, который прёт напролом. Кабан не умеет сдаваться — он способен продырявить строй противника своими клыками. Идеален для мясных таранов и врывов с разбега. Лучше дружи с ним, чем злись.", + "params": { + "hunger": 0.9, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 10, + "health": 7, + "after_death": [ + {"item": rimagochi_items[11], "count": (60, 66)}, + {"item": rimagochi_items[12], "count": (12, 16)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/b/bd/Кабан.png/revision/latest/scale-to-width-down/51?cb=20161122141442&path-prefix=ru" + } + }, + 226: { + "id": 226, + "name": "казуар", + "description": "Говорят, что это просто птица. Но каждый, кто сталкивался с казуаром, знает: это ураган на двух лапах с клювом и когтями. Мгновенный рывок и серия ударов — пока враг моргает, он уже валяется в пыли. Нестабилен, но страшен.", + "params": { + "hunger": 0.8, + "can_eate": [], + "damage": 9, + "health": 7, + "after_death": [ + {"item": rimagochi_items[11], "count": (80, 82)}, + {"item": rimagochi_items[12], "count": (20, 28)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/b/b0/Казуар.png/revision/latest/scale-to-width-down/51?cb=20170624173810&path-prefix=ru" + } + }, + 228: { + "id": 228, + "name": "карибу", + "description": "Выносливый кочевник тундры. Его рога — грозное оружие в сезон гона, но в бою он полагается скорее на стойкость, чем на агрессию. После смерти оставляет ценную шкуру и нежное оленину — желанную добычу для любого охотника.", + "params": { + "hunger": 1, + "can_eate": [], + "damage": 8, + "health": 8, + "after_death": [ + {"item": rimagochi_items[11], "count": (165, 180)}, + {"item": rimagochi_items[12], "count": (36, 41)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/0/08/Северный_олень.png/revision/latest/scale-to-width-down/51?cb=20170624174911&path-prefix=ru" + } + }, + 229: { + "id": 229, + "name": "лось", + "description": "Атакует стремительным наскоком, сметая всё на пути. Не так силён, как медведь, но его удары копыт способны переломать рёбра даже бронированному противнику. После смерти оставляет огромное количество мяса и прочную шкуру.", + "params": { + "hunger": 1, + "can_eate": [], + "damage": 8, + "health": 8, + "after_death": [ + {"item": rimagochi_items[11], "count": (165, 180)}, + {"item": rimagochi_items[12], "count": (36, 41)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/8/80/Лось.png/revision/latest/scale-to-width-down/51?cb=20220523154624&path-prefix=ru" + } + }, + 231: { + "id": 231, + "name": "горный козёл", + "description": "Проворный скалолаз, способный атаковать с неожиданных углов. Его рога - грозное оружие в горной местности, а цепкие копыта позволяют занимать выгодные позиции. После смерти оставляет крепкую шкуру и нежное мясо.", + "params": { + "hunger": 1, + "can_eate": [], + "damage": 8, + "health": 8, + "after_death": [ + {"item": rimagochi_items[11], "count": (75, 77)}, + {"item": rimagochi_items[12], "count": (23, 23)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/f/f0/Горный_козёл.png/revision/latest/scale-to-width-down/51?cb=20170127064950&path-prefix=ru" + } + }, + 232: { + "id": 232, + "name": "корова", + "description": "Мирное создание, чья главная боевая тактика — занимать место. Её удары копытами слабы, но неожиданно болезненны для тех, кто недооценивает её массу.", + "params": { + "hunger": 0.9, + "can_eate": [], + "damage": 7, + "health": 8, + "after_death": [ + {"item": rimagochi_items[11], "count": (160, 164)}, + {"item": rimagochi_items[12], "count": (36, 36)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/8/88/Корова.png/revision/latest/scale-to-width-down/51?cb=20160717123309&path-prefix=ru" + } + }, + 233: { + "id": 233, + "name": "кот", + "description": "Независимый хищник, который сражается только когда сам этого захочет. Его атаки — стремительные царапины, от которых противник истекает кровью. Чаще просто наблюдает за битвой с высокомерием существа, которое знает: люди — временные, а кошки — вечные.", + "params": { + "hunger": 0.7, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 7, + "health": 3.8, + "after_death": [ + {"item": rimagochi_items[11], "count": (22, 23)}, + {"item": rimagochi_items[12], "count": (5, 5)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 1, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/6/62/Кошка.png/revision/latest/scale-to-width-down/51?cb=20161029110411&path-prefix=ru" + } + }, + 236: { + "id": 236, + "name": "лабрадор ретривер", + "description": "Преданный пёс, который защищает хозяина ценой своей жизни. Не самый сильный боец, но его укусы целенаправленны, а преданность делает его живым щитом. После битвы может притащить брошенное оружие... или хотя бы палку.", + "params": { + "hunger": 0.8, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 9, + "health": 6, + "after_death": [ + {"item": rimagochi_items[11], "count": (85, 95)}, + {"item": rimagochi_items[12], "count": (20, 22)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/d/d9/Лабрадор-ретривер.png/revision/latest/scale-to-width-down/51?cb=20161121091243&path-prefix=ru" + } + }, + 242: { + "id": 242, + "name": "олень", + "description": "Грациозный бегун, который предпочитает избегать боя. Его рога — скорее украшение, чем оружие, но в отчаянии он может нанести болезненный удар. Главная ценность — нежное мясо и прочная шкура.", + "params": { + "hunger": 1, + "can_eate": [], + "damage": 8, + "health": 8, + "after_death": [ + {"item": rimagochi_items[11], "count": (79, 82)}, + {"item": rimagochi_items[12], "count": (17, 19)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/e/ec/Олень.png/revision/latest/scale-to-width-down/51?cb=20161122181902&path-prefix=ru" + } + }, + 247: { + "id": 247, + "name": "страус", + "description": "Нелетающая птица-рекордсмен. Может разогнаться до 70 км/ч, а удар её ноги ломает рёбра. В бою использует тактику 'ударил-убежал'.", + "params": { + "hunger": 1, + "can_eate": [], + "damage": 8, + "health": 7, + "after_death": [ + {"item": rimagochi_items[11], "count": (90, 108)}, + {"item": rimagochi_items[12], "count": (25, 28)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/f/fb/Страус.png/revision/latest/scale-to-width-down/51?cb=20170605224933&path-prefix=ru" + } + }, + 248: { + "id": 248, + "name": "эму", + "description": "Менее агрессивный, но более выносливый собрат обычного страуса. В бою полагается на свою скорость и долгие ноги, изматывая противника.", + "params": { + "hunger": 1, + "can_eate": [], + "damage": 7, + "health": 8, + "after_death": [ + {"item": rimagochi_items[11], "count": (90, 108)}, + {"item": rimagochi_items[12], "count": (25, 28)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/0/07/Эму.png/revision/latest/scale-to-width-down/51?cb=20170624172722&path-prefix=ru" + } + }, + 249: { + "id": 249, + "name": "фенек", + "description": "Мелкая пустынная лисица с огромными ушами. Невероятно проворная, но хрупкая. В бою полагается на скорость и внезапные атаки.", + "params": { + "hunger": 0.5, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 6, + "health": 5, + "after_death": [ + {"item": rimagochi_items[11], "count": (50, 50)}, + {"item": rimagochi_items[12], "count": (18, 18)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 1, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/2/22/Фенек.png/revision/latest/scale-to-width-down/51?cb=20170624171740&path-prefix=ru" + } + }, + 250: { + "id": 250, + "name": "хаски", + "description": "Верный пёс, чья преданность компенсирует отсутствие тактического гения. В бою полагается на стайный инстинкт.", + "params": { + "hunger": 0.8, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 9, + "health": 8, + "after_death": [ + {"item": rimagochi_items[11], "count": (90, 90)}, + {"item": rimagochi_items[12], "count": (18, 22)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/f/f5/Хаски.png/revision/latest/scale-to-width-down/51?cb=20161122135832&path-prefix=ru" + } + }, + 251: { + "id": 251, + "name": "черепаха", + "description": "Живой щит с философским отношением к жизни. Её панцирь игнорирует половину урона, но атакует так медленно, что противники успевают заскучать.", + "params": { + "hunger": 0.5, + "can_eate": [], + "damage": 3, + "health": 20, + "after_death": [ + {"item": rimagochi_items[11], "count": (45, 45)}, + {"item": rimagochi_items[12], "count": (8, 12)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/5/5a/Черепаха.png/revision/latest/scale-to-width-down/51?cb=20150613162739&path-prefix=ru" + } + }, + 253: { + "id": 253, + "name": "осёл", + "description": "Упрямый таскер, который в бою ведёт себя как живая баррикада. Его пинки ломают кости, но главная сила — терпение.", + "params": { + "hunger": 1, + "can_eate": [], + "damage": 8, + "health": 8, + "after_death": [ + {"item": rimagochi_items[11], "count": (130, 140)}, + {"item": rimagochi_items[12], "count": (40, 50)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/6/66/Осел.png/revision/latest/scale-to-width-down/51?cb=20200602064232&path-prefix=ru" + } + }, + 254: { + "id": 254, + "name": "селезень", + "description": "Агрессивная птица, чьи шипящие атаки снижают боевой дух врагов. Клюв бьёт точнее, чем кажется.", + "params": { + "hunger": 0.6, + "can_eate": [], + "damage": 6, + "health": 5, + "after_death": [ + {"item": rimagochi_items[11], "count": (16, 16)}, + {"item": rimagochi_items[12], "count": (5, 5)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 1, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/f/fa/Утка.png/revision/latest?cb=20200601121206&path-prefix=ru" + } + }, + 255: { + "id": 255, + "name": "козёл", + "description": "Неукротимый горный боец. Бьётся рогами в стиле «танк-ролл», снося противников с ног.", + "params": { + "hunger": 0.9, + "can_eate": [], + "damage": 8, + "health": 8, + "after_death": [ + {"item": rimagochi_items[11], "count": (66, 70)}, + {"item": rimagochi_items[12], "count": (26, 28)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/b/b8/Козел.png/revision/latest/scale-to-width-down/51?cb=20200602061648&path-prefix=ru" + } + }, + 256: { + "id": 256, + "name": "гусь", + "description": "Пернатый террор. В стае превращает поле боя в хаос: шипение, хлопанье крыльев и укусы в самые неожиданные места.", + "params": { + "hunger": 0.7, + "can_eate": [], + "damage": 5, + "health": 6, + "after_death": [ + {"item": rimagochi_items[11], "count": (36, 38)}, + {"item": rimagochi_items[12], "count": (20, 22)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 1, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/c/c6/Гусь.png/revision/latest/scale-to-width-down/51?cb=20200602052723&path-prefix=ru" + } + }, + 258: { + "id": 258, + "name": "баран", + "description": "Живая таранная установка. Разгоняется медленно, но удар его лба сбивает с ног даже медведей.", + "params": { + "hunger": 1, + "can_eate": [], + "damage": 9, + "health": 7, + "after_death": [ + {"item": rimagochi_items[11], "count": (175, 185)}, + {"item": rimagochi_items[12], "count": (65, 71)} + ], + "rarity": rimagochi_rarity[35], + "required_slots": 2, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/2/27/Баран.png/revision/latest/scale-to-width-down/51?cb=20200602054539&path-prefix=ru" + } + }, + 27: { + "id": 27, + "name": "морская свинка", + "description": "Единственное тактическое преимущество — способность умереть так громко, что противник на секунду задумается: 'А не слишком ли я жесток'?", + "params":{ + "hunger": 0.5, + "can_eate": [], + "damage": 3, + "health": 4, + "after_death": [ + {"item": rimagochi_items[11], "count": (28, 30)}, + {"item": rimagochi_items[12], "count": (15, 17)} + ], + "rarity": rimagochi_rarity[36], + "required_slots" : 1, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/d/d2/Морская_свинка.png/revision/latest?cb=20200602065852&path-prefix=ru", + } + }, + 210: { + "id": 210, + "name": "белка", + "description": "Юркий, но абсолютно бесполезный в бою грызун. Мечется по полю, хаотично царапая врагов, и тут же погибает от любого удара. Единственное применение — сбивает противников с толку своей нелепой анимацией смерти.", + "params":{ + "hunger": 0.6, + "can_eate": [], + "damage": 5, + "health": 3, + "after_death": [ + {"item": rimagochi_items[11], "count": (14, 14)}, + {"item": rimagochi_items[12], "count": (3, 3)} + ], + "rarity": rimagochi_rarity[36], + "required_slots" : 1, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/d/da/Белка.png/revision/latest/scale-to-width-down/51?cb=20161121160342&path-prefix=ru", + } + }, + 212: { + "id": 212, + "name": "беляк", + "description": "Трусливый комок шерсти с единственной тактикой — драпать при первой опасности. Его 'атаки' (робкие тычки лапками) не пугают даже мышей. Чаще всего просто служит живой консервой для хищников.", + "params":{ + "hunger": 0.5, + "can_eate": [], + "damage": 4, + "health": 3, + "after_death": [ + {"item": rimagochi_items[11], "count": (18, 18)}, + {"item": rimagochi_items[12], "count": (4, 4)} + ], + "rarity": rimagochi_rarity[36], + "required_slots" : 1, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/e/e1/Заяц-беляк.png/revision/latest/scale-to-width-down/51?cb=20180422112010&path-prefix=ru", + } + }, + 220: { + "id": 220, + "name": "заяц", + "description": "Трусливый комок шерсти с единственной тактикой — драпать при первой опасности. Его 'атаки' (робкие тычки лапками) не пугают даже мышей. Чаще всего просто служит живой консервой для хищников.", + "params":{ + "hunger": 0.5, + "can_eate": [], + "damage": 4, + "health": 3, + "after_death": [ + {"item": rimagochi_items[11], "count": (18, 18)}, + {"item": rimagochi_items[12], "count": (4, 4)} + ], + "rarity": rimagochi_rarity[36], + "required_slots" : 1, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/d/d6/Заяц.png/revision/latest/scale-to-width-down/51?cb=20150613162547&path-prefix=ru", + } + }, + 222: { + "id": 222, + "name": "игуана", + "description": "Медлительная, но удивительно живучая рептилия. Её укусы слабы, но она может часами изматывать противников, прячась в укрытиях.", + "params": { + "hunger": 0.6, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 1, + "health": 12, + "after_death": [ + {"item": rimagochi_items[11], "count": (24, 26)}, + {"item": rimagochi_items[12], "count": (6, 6)} + ], + "rarity": rimagochi_rarity[36], + "required_slots": 1, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/c/ca/Игуана.png/revision/latest/scale-to-width-down/51?cb=20150613162556&path-prefix=ru" + } + }, + 223: { + "id": 223, + "name": "йоркширский терьер", + "description": "Крошечный пёсик с комплексом Наполеона. Его лай страшнее укусов, но зато он отвлекает врагов своей истеричной суетой. Идеальный живой щит — противники теряют время, пытаясь попасть по этой юркой мишени.", + "params": { + "hunger": 0.4, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 2, + "health": 5, + "after_death": [ + {"item": rimagochi_items[11], "count": (26, 28)}, + {"item": rimagochi_items[12], "count": (6, 6)} + ], + "rarity": rimagochi_rarity[36], + "required_slots": 1, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/a/a3/Йоркширский_терьер.png/revision/latest/scale-to-width-down/51?cb=20161124131207&path-prefix=ru" + } + }, + 234: { + "id": 234, + "name": "крыса", + "description": "Рой этих тварей способен обглодать врага до костей за минуты. Поодиночке абсолютно беспомощны, но в массовом порядке становятся живой бензопилой. Главный козырь — противник теряет рассудок от их писка и шевелящейся массы.", + "params": { + "hunger": 0.4, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 3, + "health": 4, + "after_death": [ + {"item": rimagochi_items[11], "count": (20, 20)}, + {"item": rimagochi_items[12], "count": (8, 8)} + ], + "rarity": rimagochi_rarity[36], + "required_slots": 1, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/b/bc/Крыса.png/revision/latest/scale-to-width-down/51?cb=20170812161851&path-prefix=ru" + } + }, + 235: { + "id": 235, + "name": "курица", + "description": "Беспорядочно мечется по полю боя, создавая хаос. Её клевки почти безвредны, но стая куриц может затоптать противника в прямом смысле.", + "params": { + "hunger": 0.2, + "can_eate": [], + "damage": 2, + "health": 4, + "after_death": [ + {"item": rimagochi_items[11], "count": (23, 23)} + ], + "rarity": rimagochi_rarity[36], + "required_slots": 1, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/3/34/Курица.png/revision/latest/scale-to-width-down/51?cb=20160715183052&path-prefix=ru" + } + }, + 241: { + "id": 241, + "name": "обезьяна", + "description": "Проворный боец, использующий примитивное оружие и тактику 'бей-беги'. Может швырять в врагов камни и экскременты, снижая их мораль.", + "params": { + "hunger": 0.6, + "can_eate": [ + {"item": rimagochi_items[11]} + ], + "damage": 5, + "health": 3, + "after_death": [ + {"item": rimagochi_items[11], "count": (30, 34)}, + {"item": rimagochi_items[12], "count": (7, 7)} + ], + "rarity": rimagochi_rarity[36], + "required_slots": 1, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/2/26/Обезьяна.png/revision/latest/scale-to-width-down/51?cb=20150613162702&path-prefix=ru" + } + }, + 252: { + "id": 252, + "name": "шиншилла", + "description": "Пушистый шарик с тактикой «отвлечь милотой». Её укусы безвредны, но противники теряют ход, пытаясь понять: это боец или декоративный аксессуар?", + "params": { + "hunger": 0.4, + "can_eate": [], + "damage": 3, + "health": 3, + "after_death": [ + {"item": rimagochi_items[11], "count": (18, 22)}, + {"item": rimagochi_items[12], "count": (5, 7)} + ], + "rarity": rimagochi_rarity[36], + "required_slots": 1, + "image_url": "https://static.wikia.nocookie.net/rimworld/images/c/cc/Шиншилла.png/revision/latest/scale-to-width-down/51?cb=20200523125620&path-prefix=ru" + } + }, +} \ No newline at end of file diff --git a/src/database/db_classes.py b/src/database/db_classes.py new file mode 100644 index 0000000..d001594 --- /dev/null +++ b/src/database/db_classes.py @@ -0,0 +1,401 @@ +from sqlalchemy import Column, Integer, BigInteger, Text, Float, ForeignKey, UniqueConstraint, MetaData, Boolean, JSON, String, ARRAY, CheckConstraint, func, Index +from sqlalchemy.orm import declarative_base, relationship, Mapped, mapped_column +from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession +from sqlalchemy.orm import sessionmaker, relationship, DeclarativeBase +from sqlalchemy.schema import CreateTable +from sqlalchemy.sql import text +import datetime +from zoneinfo import ZoneInfo +from typing import Annotated, List +import disnake + +class Base(DeclarativeBase): + + def get_table_name(self): + return self.__tablename__ + + def to_dict(self, exclude: list[str] = None): + """Конвертирует модель в словарь, исключая указанные поля.""" + if exclude is None: + exclude = [] + + return { + c.name: getattr(self, c.name) + for c in self.__table__.columns + if c.name not in exclude + } + + +discord_identificator_pk = Annotated[int, mapped_column(BigInteger, primary_key=True, nullable=False, index = True)] +identificator_pk = Annotated[int, mapped_column(Integer, primary_key=True, nullable=False, autoincrement=True, index = True)] +discord_identificator = Annotated[int, mapped_column(BigInteger, nullable=False, index=True)] + +class User(Base): + __tablename__ = 'users' + + id: Mapped[discord_identificator_pk] + crumbs: Mapped[float] = mapped_column(Float, nullable=False, default=0) + period_messages: Mapped[int] = mapped_column(Integer, nullable=False, default=0) + summary_messages: Mapped[int] = mapped_column(Integer, nullable=False, default=0) + period_voice_activity: Mapped[float] = mapped_column(Float, nullable=False, default=0) + summary_voice_activity: Mapped[float] = mapped_column(Float, nullable=False, default=0) + carma: Mapped[float] = mapped_column(Float, nullable=False, default=0) + staff_salary: Mapped[float] = mapped_column(Float, nullable=False, default=0) + last_daily_crumbs_date: Mapped[float] = mapped_column(Float, nullable=False, default=0) + last_activity_date: Mapped[float] = mapped_column(Float, nullable=False, onupdate=func.extract('epoch', func.now()), server_onupdate=text("EXTRACT(EPOCH FROM NOW())"), server_default=text("EXTRACT(EPOCH FROM NOW())")) + + custom_roles: Mapped[list["CustomRole"]] = relationship( + secondary="received_roles_custom", + back_populates="users", + #lazy="selectin" + ) + + creation_role: Mapped["CustomRole"] = relationship( + back_populates="creator", + uselist=False, + #lazy="joined" + ) + + prize_roles: Mapped[list["PrizeRole"]] = relationship( + secondary="received_roles_prize", + back_populates="users", + #lazy="selectin" + ) + + sender_in_crumbs_transactions: Mapped[list["CrumbsTransactionHistory"]] = relationship( + back_populates="sender", + uselist=True, + foreign_keys="[CrumbsTransactionHistory.sender_id]", + #lazy="selectin" + ) + + recipient_in_crumbs_transactions: Mapped[list["CrumbsTransactionHistory"]] = relationship( + back_populates="recipient", + uselist=True, + foreign_keys="[CrumbsTransactionHistory.recipient_id]", + #lazy="selectin" + ) + + casino_account: Mapped["CasinoUser"] = relationship( + back_populates="user", + uselist=False, + #lazy="joined" + ) + + rimagochi_account: Mapped["RimagochiUser"] = relationship( + back_populates="user", + uselist=False, + #lazy="joined" + ) + + async def get_member(self, client = None, guild = None, guild_id = None): + + if not guild is None: + try: + member = await guild.fetch_member(self.id) + except disnake.NotFound: + member = None + return member + + if client is None: + raise TypeError("get_member() не хватает обязательного аргумента: 'client'") + + if guild_id is None: + raise TypeError("get_member() не хватает обязательного аргумента: 'guild_id'") + else: + guild = await client.fetch_guild(guild_id) + return self.get_member(guild = guild, client = None, guild_id = None) + + async def in_role(self, roles = None, member = None, boolean = True, guild = None, guild_id = None, client = None): + ''' + Находит пересечение между ролями пользователя и входным списком ролей + ''' + if member is None: + await self.get_member(guild = guild, guild_id = guild_id, client = client) + + if roles is None: + raise TypeError("in_role() не хватает обязательного аргумента: 'roles'") + + member_roles = set(member.roles) + roles = set(roles) + + if boolean: + return bool(member_roles & roles) + else: + return member_roles & roles + + async def crumbs_adder(self, incoming_crumbs: float, sponsor = None, member = None, sponsor_roles = None): + if sponsor is None: + sponsor = await self.in_role(roles = sponsor_roles, member = member) + + modifier = (self.carma / 100) + (sponsor+1) + + add_crumbs = (incoming_crumbs * modifier) if incoming_crumbs * modifier >= 0 else 0 + self.crumbs += add_crumbs + return add_crumbs + + async def give_salary(self): + self.crumbs += self.staff_salary + + async def claim_daily_crumbs(self, sponsor = None, member = None, sponsor_roles = None, daily_constant = 300): + + moscow_tz = ZoneInfo("Europe/Moscow") + last_daily_time = datetime.datetime.fromtimestamp(self.last_daily_crumbs_date, tz=moscow_tz) + now_time = datetime.datetime.now(tz=moscow_tz) + if now_time.date() > last_daily_time.date(): + daily = await self.crumbs_adder(incoming_crumbs = daily_constant, sponsor = sponsor, sponsor_roles = sponsor_roles, member = member) + self.last_daily_crumbs_date = datetime.datetime.now().timestamp() + return {'success': True, 'output': f"Вы получили ежедневную награду в размере {int(daily)} крошек!", 'count': daily, 'date': now_time.date()} + else: + next_day = (now_time + datetime.timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0).timestamp() + return {'success': False, 'output': f"Вы уже получали ежедневную награду сегодня, следующая будет доступна "} + +#роли +class CustomRole(Base): + __tablename__ = 'roles_custom' + + id: Mapped[discord_identificator_pk] + creator_id: Mapped[int] = mapped_column(ForeignKey('users.id', ondelete='CASCADE'), nullable=False, unique=True) + date_of_creation: Mapped[float] = mapped_column(Float, nullable=False, server_default=text("EXTRACT(EPOCH FROM NOW())")) + cost: Mapped[int] = mapped_column(Integer, nullable=False) + renewal_date: Mapped[float] = mapped_column(Float, nullable=False) + renewal_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) + + users: Mapped[list["User"]] = relationship( + secondary="received_roles_custom", + back_populates="custom_roles", + #lazy="selectin" + ) + + creator: Mapped["User"] = relationship( + back_populates="creation_role", + foreign_keys = [creator_id], + uselist=False, + #lazy="joined" + ) + + async def renewal_date_update(self, time_formater_function, days = 30): + self.renewal_date = float(time_formater_function(days = days)) + +class PrizeRole(Base): + __tablename__ = 'roles_prize' + + id: Mapped[discord_identificator_pk] + + users: Mapped[list["User"]] = relationship( + secondary="received_roles_prize", + back_populates="prize_roles", + #lazy="selectin" + ) + +class StaticRole(Base): + __tablename__ = 'roles_static' + + id: Mapped[discord_identificator_pk] + description: Mapped[str | None] = mapped_column(String, nullable=True) + +class ReceivedCustomRoles(Base): + __tablename__ = 'received_roles_custom' + + role_id: Mapped[int] = mapped_column(ForeignKey('roles_custom.id', ondelete='CASCADE'), primary_key=True) + user_id: Mapped[int] = mapped_column(ForeignKey('users.id', ondelete='CASCADE'), primary_key=True) + + __table_args__ = ( + UniqueConstraint('user_id', 'role_id', name='received_roles_custom_user_role'), + ) + +class ReceivedPrizeRoles(Base): + __tablename__ = 'received_roles_prize' + + role_id: Mapped[int] = mapped_column(ForeignKey('roles_prize.id', ondelete='CASCADE'), primary_key=True) + user_id: Mapped[int] = mapped_column(ForeignKey('users.id', ondelete='CASCADE'), primary_key=True) + + __table_args__ = ( + UniqueConstraint('user_id', 'role_id', name='received_roles_prize_user_role'), + ) + +#профили +class ProfileDesign(Base): + __tablename__ = 'profile_design' + + id: Mapped[identificator_pk] + file_name: Mapped[str] = mapped_column(String, nullable=False) + type: Mapped[str | None] = mapped_column(String, server_default=text("'PNG'"), nullable=False) + border_color: Mapped[list[int]] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[0, 0, 0, 255]"), nullable=False) + border_width: Mapped[int | None] = mapped_column(Integer, server_default=text("3"), nullable=False) + scale: Mapped[int | None] = mapped_column(Integer, server_default=text("4"), nullable=False) + + blackout_background_size: Mapped[list[int] | None] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[452, 226]"), nullable=False) + blackout_background_position: Mapped[list[int] | None] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[30, 15]"), nullable=False) + blackout_background_color: Mapped[list[int] | None] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[0, 0, 0, 100]"), nullable=False) + blackout_background_radius: Mapped[int | None] = mapped_column(Integer, server_default=text("15"), nullable=False) + + avatar_size: Mapped[list[int] | None] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[100, 100]"), nullable=False) + avatar_position: Mapped[list[int] | None] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[50, 25]"), nullable=False) + + nick_size: Mapped[list[int] | None] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[145, 65]"), nullable=False) + nick_position: Mapped[list[int] | None] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[30, 110]"), nullable=False) + nick_color: Mapped[list[int] | None] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[255, 255, 255, 255]"), nullable=False) + nick_stroke_width: Mapped[int | None] = mapped_column(Integer, server_default=text("6"), nullable=False) + nick_stroke_color: Mapped[list[int] | None] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[0, 0, 0, 255]"), nullable=False) + + progress_bar_size: Mapped[list[int] | None] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[400, 40]"), nullable=False) + progress_bar_position: Mapped[list[int] | None] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[50, 175]"), nullable=False) + progress_bar_fill_color: Mapped[list[int] | None] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[0, 255, 0, 255]"), nullable=False) + progress_bar_empty_color: Mapped[list[int] | None] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[50, 50, 50, 255]"), nullable=False) + progress_bar_radius: Mapped[int | None] = mapped_column(Integer, server_default=text("15"), nullable=False) + progress_bar_text_position: Mapped[list[int] | None] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[15, 7]"), nullable=False) + progress_bar_text_color: Mapped[list[int] | None] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[255, 255, 255, 255]"), nullable=False) + progress_bar_text_stroke_width: Mapped[int | None] = mapped_column(Integer, server_default=text("6"), nullable=False) + progress_bar_text_stroke_color: Mapped[list[int] | None] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[0, 0, 0, 255]"), nullable=False) + + info_bar_size: Mapped[list[int] | None] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[275, 135]"), nullable=False) + info_bar_position: Mapped[list[int] | None] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[175, 30]"), nullable=False) + info_bar_color: Mapped[list[int] | None] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[255, 255, 255, 0]"), nullable=False) + info_bar_radius: Mapped[int | None] = mapped_column(Integer, server_default=text("15"), nullable=False) + info_bar_text_color: Mapped[list[int] | None] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[0, 0, 0, 255]"), nullable=False) + info_bar_text_position: Mapped[list[int] | None] = mapped_column(ARRAY(Integer), server_default=text("ARRAY[15, 10]"), nullable=False) + + render_profile_code: Mapped[str | None] = mapped_column(Text, nullable=True) + + def render_profile(self, data, namespace): + #{user, place_in_top, avatar, nick, level, crumbs_modify} in data + exec(self.render_profile_code, namespace) + render_func = namespace['render'] + + return render_func(data, design = self) + + usage: Mapped[list["ProfileDesignInventory"]] = relationship( + back_populates="design", + #lazy="selectin" + ) + +class ProfileDesignInventory(Base): + __tablename__ = 'profile_design_inventory' + + user_id: Mapped[int] = mapped_column(ForeignKey('users.id', ondelete='CASCADE'), primary_key=True) + design_id: Mapped[int] = mapped_column(ForeignKey('profile_design.id', ondelete='CASCADE'), primary_key=True) + is_active: Mapped[bool | None] = mapped_column(Boolean, server_default=text("FALSE"), nullable=False) + + design: Mapped["ProfileDesign"] = relationship( + back_populates="usage", + foreign_keys=[design_id], + lazy="joined" + ) + + __table_args__ = ( + UniqueConstraint('user_id', 'design_id', name='received_profile_design_inventory_user_design'), + Index( + 'received_profile_design_inventory_user_only_one_active', + 'user_id', + unique=True, + postgresql_where=text('is_active') + ), + ) + +#экономика +class CrumbsTransactionHistory(Base): + __tablename__ = 'transaction_history_crumbs' + + id: Mapped[identificator_pk] + sender_id: Mapped[int | None] = mapped_column(ForeignKey('users.id', ondelete='CASCADE'), nullable=True, default=None) + recipient_id: Mapped[int | None] = mapped_column(ForeignKey('users.id', ondelete='CASCADE'), nullable=True, default=None) + amount: Mapped[int] = mapped_column(Integer, nullable=False) + commission_fraction: Mapped[float] = mapped_column(Float, default=0, nullable=False) + description: Mapped[str | None] = mapped_column(String, default=None, nullable=True) + transaction_time: Mapped[float] = mapped_column(Float, nullable=False, server_default=text("EXTRACT(EPOCH FROM NOW())")) + + sender: Mapped["User | None"] = relationship( + back_populates="sender_in_crumbs_transactions", + foreign_keys=[sender_id], + uselist=False, + #lazy="joined" + ) + + recipient: Mapped["User | None"] = relationship( + back_populates="recipient_in_crumbs_transactions", + foreign_keys=[recipient_id], + uselist=False, + #lazy="joined" + ) + + __table_args__ = ( + CheckConstraint('NOT (sender_id IS NULL AND recipient_id IS NULL)'), + ) + +class CasinoUser(Base): + __tablename__ = 'casino_user_account' + + id: Mapped[int] = mapped_column(ForeignKey('users.id', ondelete='CASCADE'), primary_key=True, nullable=False) + spins_today_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False) + last_reset_time: Mapped[int] = mapped_column(Integer, default=0, nullable=False) + + user: Mapped["User"] = relationship( + back_populates="casino_account", + uselist=False, + #lazy="joined" + ) + +#rimagochi +class RimagochiUser(Base): + __tablename__ = 'rimagochi_users' + + id: Mapped[int] = mapped_column(ForeignKey('users.id', ondelete='CASCADE'), primary_key=True, nullable=False, index = True) + items: Mapped[dict] = mapped_column(JSON, default={}, nullable=False) + genes: Mapped[dict] = mapped_column(JSON, default={}, nullable=False) + wins: Mapped[int] = mapped_column(Integer, default=0, nullable=False) + settings: Mapped[dict] = mapped_column(JSON, nullable=False) + + user: Mapped["User"] = relationship( + back_populates="rimagochi_account", + uselist=False, + #lazy="joined" + ) + + animals: Mapped[list["RimagochiAnimal"]] = relationship( + back_populates="owner", + uselist=True, + #lazy="selectin" + ) + + battle_slots_animals: Mapped[list["RimagochiAnimal"]] = relationship( + primaryjoin=( + "and_(" + "RimagochiAnimal.owner_id == RimagochiUser.id, " + "RimagochiAnimal.in_battle_slots == True" + ")" + ), + uselist=True, + viewonly=True, + #lazy="selectin" + ) + +class RimagochiAnimal(Base): + __tablename__ = 'rimagochi_animals' + + id: Mapped[identificator_pk] + model_animal_id: Mapped[int] = mapped_column(Integer, nullable=False) + genes: Mapped[list[int]] = mapped_column(ARRAY(Integer), default=[], nullable=False) + items: Mapped[list[int]] = mapped_column(ARRAY(Integer), default=[], nullable=False) + last_feeding_time: Mapped[float] = mapped_column(Float, default=0, nullable=False) + first_today_feed_time: Mapped[float] = mapped_column(Float, default=0, nullable=False) + feed_today_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False) + experience: Mapped[int] = mapped_column(Integer, default=0, nullable=False) + level: Mapped[int] = mapped_column(Integer, default=1, nullable=False) + wins: Mapped[int] = mapped_column(Integer, default=0, nullable=False) + initial_owner_id: Mapped[discord_identificator] + owner_id: Mapped[int] = mapped_column(ForeignKey('rimagochi_users.id', ondelete='CASCADE'), nullable=False, index = True) + in_battle_slots: Mapped[bool] = mapped_column(Boolean, default = False, nullable = False) + creation_time: Mapped[float] = mapped_column(Float, nullable=False, server_default=text("EXTRACT(EPOCH FROM NOW())")) + + owner: Mapped["RimagochiUser"] = relationship( + back_populates="animals", + foreign_keys = [owner_id], + uselist=False, + #lazy="joined" + ) + + +all_data = { + 'base': Base +} \ No newline at end of file diff --git a/src/database/settings/config.py b/src/database/settings/config.py new file mode 100644 index 0000000..b64e250 --- /dev/null +++ b/src/database/settings/config.py @@ -0,0 +1,17 @@ +from database.settings.db_settings import * + +class Settings: + + def __init__(self): + self.DB_HOST = DB_HOST + self.DB_PORT = DB_PORT + self.DB_USER = DB_USER + self.DB_PASS = DB_PASS + self.DB_NAME = DB_NAME + + @property + def DB_URL(self): + return f"postgresql+asyncpg://{self.DB_USER}:{self.DB_PASS}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}" + +settings = Settings() + \ No newline at end of file diff --git a/src/database/settings/db_settings.py b/src/database/settings/db_settings.py new file mode 100644 index 0000000..ee5bf5e --- /dev/null +++ b/src/database/settings/db_settings.py @@ -0,0 +1,5 @@ +DB_HOST='localhost' +DB_PORT=5432 +DB_USER='discord_economy_bot' +DB_PASS='economy_bot' +DB_NAME='discord_economy_bot_db' \ No newline at end of file diff --git a/src/managers/DataBaseManager.py b/src/managers/DataBaseManager.py new file mode 100644 index 0000000..a82eb43 --- /dev/null +++ b/src/managers/DataBaseManager.py @@ -0,0 +1,128 @@ +import disnake +from disnake.ext import commands +from disnake.ext import tasks +from typing import Optional, Union, List, Dict, Any, AsyncIterator, Tuple +import datetime +from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker +from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm import joinedload, selectinload, contains_eager +from sqlalchemy import select, delete, insert, update, func, asc, desc +from sqlalchemy import and_, or_, not_ +import subprocess +import os +from types import SimpleNamespace + +class Model: + def __init__(self, model): + self.model = model + self.m = model + + async def __aenter__(self): + return self.model + + async def __aexit__(self, exc_type, exc_val, exc_tb): + return None + +class DatabaseManager: + + def __init__(self, engine, tables_data): + self.engine = engine + self.session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) + self.metadata = tables_data['base'].metadata + + self.model_classes = {} + tables = self.metadata.tables + for table_name, table in tables.items(): + model_class = next((cls for cls in tables_data['base'].__subclasses__() if cls.__tablename__ == table_name), None) + if model_class: + #print(f"Table: {table_name}, Model Class: {model_class.__name__}") + self.model_classes[table_name] = model_class + + self.models = {table_name: Model(model) for table_name, model in self.model_classes.items()} + + self.tables_data = tables_data + + #функции sqlalchemy + self.joinedload = joinedload + self.selectinload = selectinload + self.contains_eager = contains_eager + self.select = select + self.delete = delete + self.insert = insert + self.update = update + self.func = func + self.desc = desc + self.asc = asc + self.and_ = and_ + self.or_ = or_ + self.not_ = not_ + + #ошибки sqlalchemy + exceptions = SimpleNamespace( + IntegrityError = IntegrityError + ) + + async def close(self): + await self.engine.dispose() + + async def pg_dump(self, echo=False, backup_file='src/backups/discord_economy_bot_backup.sql'): + conn = await self.engine.connect() + db_name = self.engine.url.database + user = self.engine.url.username + host = self.engine.url.host + port = self.engine.url.port + password = self.engine.url.password + + os.environ['PGPASSWORD'] = password + command = [ + 'pg_dump', + '-h', host, + '-p', str(port), + '-U', user, + '-F', 'p', # <-- plain text SQL + ] + (['-v'] if echo else []) + [ + '-f', backup_file, + db_name + ] + + try: + subprocess.run(command, check=True) + if echo: + print(f"{datetime.datetime.now():%H:%M:%S %d-%m-%Y} :: SQL backup of '{db_name}' created.") + return backup_file + except subprocess.CalledProcessError as e: + print(f"{datetime.datetime.now():%H:%M:%S %d-%m-%Y} :: Backup failed: {e}") + return f"{datetime.datetime.now():%H:%M:%S %d-%m-%Y} :: Backup failed: {e}" + finally: + await conn.close() + + async def pg_restore(self, echo = False, backup_file = 'src/backups/backup_file.backup'): + conn = await self.DataBaseManager.engine.connect() + db_name = self.DataBaseManager.engine.url.database + user = self.DataBaseManager.engine.url.username + host = self.DataBaseManager.engine.url.host + port = self.DataBaseManager.engine.url.port + password = self.DataBaseManager.engine.url.password + + os.environ['PGPASSWORD'] = password # Установка пароля для подключения + command = [ + 'pg_restore', + '-h', host, + '-p', str(port), + '-U', user, + '-d', db_name, # Имя базы данных, в которую будет восстановлено + ] + ([ + '-v' # Подробный вывод + ] if echo else []) + [ + backup_file # Путь к файлу резервной копии + ] + + try: + subprocess.run(command, check=True) + if echo: + print(f"{datetime.datetime.now().strftime('%H:%M:%S %d-%m-%Y')}:: Database '{db_name}' restored successfully from '{backup_file}'.") + except subprocess.CalledProcessError as e: + print(f"{datetime.datetime.now().strftime('%H:%M:%S %d-%m-%Y')}:: Error during restore: {e}") + finally: + await conn.close() + \ No newline at end of file diff --git a/src/managers/old_DataBaseManager.py b/src/managers/old_DataBaseManager.py new file mode 100644 index 0000000..7012dda --- /dev/null +++ b/src/managers/old_DataBaseManager.py @@ -0,0 +1,239 @@ +try: + import aiosqlite + import disnake + from disnake.ext import commands + from disnake.ext import tasks +except: + import pip + + pip.main(['install', 'disnake']) + pip.main(['install', 'aiosqlite']) + import disnake + from disnake.ext import commands + from disnake.ext import tasks + import aiosqlite +from typing import Optional, Union, List, Dict, Any, AsyncIterator, Tuple +from asyncio import Lock +import datetime + +class DatabaseManager: + """Расширенное соединение с БД с поддержкой транзакций и удобных методов""" + + def __init__(self, connection: aiosqlite.Connection): + self._connection = connection + self._transaction_depth = 0 + self._closed = False + self._transaction_lock = Lock() + self.last_error = None + + @classmethod + async def connect(cls, database: str, **kwargs) -> 'DatabaseManager': + """Альтернатива конструктору для подключения""" + connection = await aiosqlite.connect(database, **kwargs) + return cls(connection) + + async def UpdateBD(self, table: str, *, change: dict, where: dict, whereandor = "AND"): + request = () + + change_request = [] + for i in change.keys(): + change_request.append(f"{i} = ?") + request = request + (change[i],) + + where_request = [] + for i in where.keys(): + where_request.append(f"{i} = ?") + request = request + (where[i],) + + await self.execute('UPDATE {table} SET {change} WHERE {where}' + .format(table = table, change = ", ".join(change_request), where = f" {whereandor} ".join(where_request)), request) + return 0 + + async def SelectBD(self, table: str, *, select: list = ["*"], where: dict = None, where_ops: dict = None, whereandor = "AND", order_by: str = None, limit: int = None): + + where_combined = {} + if where: + where_combined.update({f"{k} =": v for k, v in where.items()}) + if where_ops: + where_combined.update(where_ops) + + request = () + where_clauses = "" + + for condition, value in where_combined.items(): + field_op = condition.split() + field = field_op[0] + op = "=" if len(field_op) == 1 else field_op[1] + if where_clauses == "": + where_clauses= where_clauses + f"{field} {op} ? " + else: + where_clauses= where_clauses + f"{whereandor} {field} {op} ? " + request += (value,) + + query = "SELECT {select} FROM {table}".format( + select=", ".join(select), + table=table + ) + + if where_clauses: + query += f" WHERE {where_clauses}" + + if order_by: + query += f" ORDER BY {order_by}" + + if limit: + query += f" LIMIT {limit}" + + async with await self.execute(query, request) as cursor: + return [i for i in await cursor.fetchall()] + + async def GetStaffJoins(): + query = \ + """ + SELECT sur.userid, sur.roleid, sur.description, sur.starttime, sr.staffsalary, sbr.layer as rolelayer, sbr.branchid, sb.layer as branchlayer, sb.purpose + FROM staff_users_roles AS sur + JOIN staff_roles as sr ON sr.roleid = sur.roleid + JOIN staff_branches_roles as sbr ON sbr.roleid = sur.roleid + JOIN staff_branches as sb ON sb.branchid = sbr.branchid + ORDER BY branchlayer ASC, rolelayer ASC; + """ + async with await self.execute(query, request) as cursor: + answer = [i for i in await cursor.fetchall()] + result = [{'userid': userid, 'roleid': roleid, 'description': description, 'starttime': starttime, 'staffsalary': staffsalary, 'rolelayer': rolelayer, 'branchid': branchid, 'branchlayer': branchlayer, 'purpose': purpose} for userid, roleid, description, starttime, staffsalary, rolelayer, branchid, branchlayer, purpose in answer] + return result + + async def DeleteBD(self, table: str, *, where: dict, whereandor = "AND"): + request = () + where_request = [] + for i in where.keys(): + where_request.append(f"{i} = ?") + request = request + (where[i],) + await self.execute("DELETE FROM {table} where {where}" + .format(table = table, where = f" {whereandor} ".join(where_request)), request) + return 0 + + async def InsertBD(self, table: str, *, data: dict): + request = () + keys = list(data.keys()) + qstring = [] + for i in keys: + request = request + (data[i],) + qstring.append("?") + await self.execute("INSERT INTO {table}({keys}) VALUES({values})" + .format(table = table, keys = ", ".join(keys), values = ", ".join(qstring)), request) + return 0 + + async def execute(self, sql: str, parameters: Optional[Union[Tuple, Dict]] = None, **kwargs) -> aiosqlite.Cursor: + """ + Универсальный execute, который автоматически определяет: + - Нужно ли начинать транзакцию (для INSERT/UPDATE/DELETE вне транзакции) + - Работает ли уже внутри транзакции (не создаёт вложенные транзакции) + """ + is_modifying = sql.strip().upper().startswith(("INSERT", "UPDATE", "DELETE")) + + # Если это модифицирующий запрос И мы НЕ внутри транзакции + if is_modifying and self._transaction_depth == 0: + async with self: # Автоматические begin/commit + cursor = await self._connection.execute(sql, parameters or (), **kwargs) + await cursor.close() # Важно: закрываем курсор для COMMIT + return cursor + else: + # Для SELECT или работы внутри существующей транзакции + return await self._connection.execute(sql, parameters or (), **kwargs) + + async def fetch_all(self, sql: str, parameters: Optional[Union[Tuple, Dict]] = None) -> List[Tuple]: + """Выполняет запрос и возвращает все строки""" + async with await self.execute(sql, parameters) as cursor: + return await cursor.fetchall() + + async def fetch_one(self, sql: str, parameters: Optional[Union[Tuple, Dict]] = None) -> Optional[Tuple]: + """Выполняет запрос и возвращает первую строку""" + async with await self.execute(sql, parameters) as cursor: + return await cursor.fetchone() + + async def fetch_val(self, sql: str, parameters: Optional[Union[Tuple, Dict]] = None, column: int = 0) -> Any: + """Возвращает значение из первого столбца""" + row = await self.fetch_one(sql, parameters) + return row[column] if row else None + + async def insert(self, table: str, data: Dict[str, Any], on_conflict: str = None) -> int: + """Упрощенный INSERT с поддержкой ON CONFLICT""" + keys = data.keys() + values = list(data.values()) + + sql = f""" + INSERT INTO {table} ({', '.join(keys)}) + VALUES ({', '.join(['?']*len(keys))}) + """ + + if on_conflict: + sql += f" ON CONFLICT {on_conflict}" + + await self.execute(sql, values) + return self.lastrowid + + async def update(self, table: str, where: Dict[str, Any], changes: Dict[str, Any], where_operator: str = "AND") -> int: + """Упрощенный UPDATE с автоматическим построением WHERE""" + set_clause = ", ".join([f"{k} = ?" for k in changes.keys()]) + where_clause = f" {where_operator} ".join([f"{k} = ?" for k in where.keys()]) + + sql = f""" + UPDATE {table} + SET {set_clause} + WHERE {where_clause} + """ + + result = await self.execute(sql, [*changes.values(), *where.values()]) + return result.rowcount + + async def begin(self): + """Начать транзакцию (с поддержкой вложенности)""" + async with self._transaction_lock: + if self._transaction_depth == 0: + await self._connection.execute("BEGIN IMMEDIATE") + self._transaction_depth += 1 + + async def commit(self): + """Зафиксировать транзакцию""" + async with self._transaction_lock: + if self._transaction_depth == 1: + await self._connection.commit() + self._transaction_depth = max(0, self._transaction_depth - 1) + + async def rollback(self): + """Откатить транзакцию""" + async with self._transaction_lock: + if self._transaction_depth > 0: + await self._connection.rollback() + self._transaction_depth = 0 + + async def close(self) -> None: + """Безопасное закрытие соединения с учётом транзакций""" + async with self._transaction_lock: + try: + # Откатываем активную транзакцию, если есть + if self._transaction_depth > 0: + await self._connection.rollback() + self._transaction_depth = 0 + + # Закрываем соединение + if hasattr(self._connection, '_connection'): # Проверка внутреннего состояния + await self._connection.close() + except Exception as e: + self.last_error = f"{datetime.datetime.now().strftime('%H:%M:%S %d-%m-%Y')}:: Ошибка при закрытии соединения: {e}" + print(f"{datetime.datetime.now().strftime('%H:%M:%S %d-%m-%Y')}:: Ошибка при закрытии соединения: {e}") + finally: + # Помечаем соединение как закрытое + self._closed = True + + async def __aenter__(self): + await self.begin() # Используем собственный метод begin + return self # Возвращаем сам менеджер, а не соединение + + async def __aexit__(self, exc_type, exc_val, exc_tb): + if exc_type is None: + await self.commit() + else: + self.last_error = f"{datetime.datetime.now().strftime('%H:%M:%S %d-%m-%Y')}:: Во время записи в бд произошла ошибка: {exc_type}({exc_val}): {exc_tb.tb_frame.f_code.co_filename}(строка {exc_tb.tb_lineno})!" + print(f"{datetime.datetime.now().strftime('%H:%M:%S %d-%m-%Y')}:: Во время записи в бд произошла ошибка: {exc_type}({exc_val}): {exc_tb.tb_frame.f_code.co_filename}(строка {exc_tb.tb_lineno})!") + await self.rollback() \ No newline at end of file diff --git a/src/test.py b/src/test.py new file mode 100644 index 0000000..1f94732 --- /dev/null +++ b/src/test.py @@ -0,0 +1,84 @@ + +import disnake +from disnake.ext import commands +from disnake.ext import tasks +import requests +import numpy as np +import aiohttp +import asyncio +import sys +import os +import copy +import datetime +import math +import random +import json +import re +import shutil +from constants.global_constants import * +from data.TOKENS import TOKENS + +import CoreFun + + +async def main(): + stop_event = asyncio.Event() + sup_bot = None + DataBase = None + all_bots = [] + + try: + DataBase = await CoreFun.init_db() + #await CoreFun.db_migration(DataBase) + + ''' + async with DataBase.session() as session: + async with session.begin(): + stmt = DataBase.select(DataBase.model_classes['users']).where( + DataBase.model_classes['users'].id == 479210801891115009 + ).options( + DataBase.selectinload(DataBase.model_classes['users'].custom_roles) + .selectinload(DataBase.model_classes['roles_custom'].creator) + ) + user = (await session.execute(stmt)).scalars().first() + for role in user.custom_roles: + print("role: " + str(role.to_dict()), "creator: " + str(role.creator.to_dict()), sep = "\n") + + ''' + + + sup_bot = CoreFun.AdminBot(DataBase, stop_event, task_start = False) + all_bots = [sup_bot] + + # Загрузка когов + sup_bot.load_extension("cogs.resetsupcommands") + #sup_bot.load_extension("cogs.economy") + sup_bot.load_extension("cogs.designer") + #sup_bot.load_extension("cogs.roles") + #sup_bot.load_extension("cogs.admin") + #sup_bot.load_extension("cogs.rimagochi") + + # Запуск монитора остановки и ботов + monitor_task = asyncio.create_task(CoreFun.monitor_stop(stop_event, all_bots)) + bot_tasks = [ + asyncio.create_task(CoreFun.run_bot(sup_bot, TOKENS["KrekSupBot"], stop_event)), + ] + + await asyncio.gather(*bot_tasks, monitor_task) + + except KeyboardInterrupt: + print(f"{datetime.datetime.now().strftime('%H:%M:%S %d-%m-%Y')}:: Боты остановлены по запросу пользователя") + except Exception as e: + print(f"{datetime.datetime.now().strftime('%H:%M:%S %d-%m-%Y')}:: Произошла критическая ошибка: {e}") + finally: + + # Остановка всех ботов + stop_event.set() + for bot in all_bots: + if not bot.is_closed(): + await bot.close() + + await DataBase.close() + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/src/testbase.py b/src/testbase.py new file mode 100644 index 0000000..48612ff --- /dev/null +++ b/src/testbase.py @@ -0,0 +1,74 @@ +from constants.rimagochi_constants import * +import itertools +# Данные из файла constants.py +animals = rimagochi_animals + +# Рассчитываем эффективность для каждого животного +animal_stats = [] +for animal_id, animal_data in animals.items(): + #if not animal_data["params"]["rarity"] == rimagochi_rarity[31]: + health = animal_data["params"]["health"] + damage = animal_data["params"]["damage"] + slots = animal_data["params"]["required_slots"] + efficiency = (health * damage) / slots + animal_stats.append({ + "id": animal_id, + "name": animal_data["name"], + "health": health, + "damage": damage, + "slots": slots, + "efficiency": efficiency + }) + +# Сортируем по убыванию эффективности +animal_stats.sort(key=lambda x: -x["efficiency"]) + +for animal_data in animal_stats: + print(f"{animal_data['name'].replace(' ', '_')} {animal_data['damage']} {animal_data['health']} {animal_data['slots']} {animal_data['efficiency']}".replace(".", ",")) + +best_animals = {} + +for animal in animal_stats: + best_animals.setdefault(animal["slots"], animal) + + +the_best = [[1]] + +for i in range(8): + for animals in itertools.product(best_animals.keys(), repeat = i): + if sum(animals) == 8: + sum_damage = 0 + for i in animals: + sum_damage += best_animals[i]['efficiency']*best_animals[i]['slots'] + sum_best_damage = 0 + for i in the_best[0]: + sum_best_damage += best_animals[i]['efficiency']*best_animals[i]['slots'] + if sum_damage > sum_best_damage: + the_best = [sorted(animals, reverse = True)] + elif sum_damage == sum_best_damage: + if not sorted(animals, reverse = True) in the_best: + the_best.append(sorted(animals, reverse = True)) + + +# Вывод результатов +print("\n\nНайденные комбинации:") +i=0 +for animals in the_best: + total_slots = 0 + total_health = 0 + total_damage = 0 + total_efficiency = 0 + i+=1 + print(f"Комбинация №{i}") + for animal in animals: + total_slots += best_animals[animal]['slots'] + total_health += best_animals[animal]['health'] + total_damage += best_animals[animal]['damage'] + total_efficiency += best_animals[animal]['efficiency']*best_animals[animal]['slots'] + print(f"{best_animals[animal]['name']} (Здоровье: {best_animals[animal]['health']}, Урон: {best_animals[animal]['damage']}, Слоты: {best_animals[animal]['slots']})") + print(f"\nСуммарное здоровье: {total_health}") + print(f"Суммарный урон: {total_damage}") + print(f"Суммарная эффективность: {total_efficiency}") + print(f"Занято слотов: {total_slots}\n"+"--"*40) + +print(max(rimagochi_animals.keys())) \ No newline at end of file