From 58051933fa10b8b53a8c3ce3e60832b17e2d1a92 Mon Sep 17 00:00:00 2001 From: Komorebi <110453675+KomoriDev@users.noreply.github.com> Date: Thu, 23 Jan 2025 14:45:07 +0800 Subject: [PATCH] =?UTF-8?q?:boom:=20=E8=B0=83=E6=95=B4=20jsondata=20?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E7=BB=93=E6=9E=84=20(#9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- marisa/__init__.py | 1 - marisa/configs/configs/level_up.py | 8 + marisa/exception.py | 15 ++ .../migrations/6bf5eefd0cbd_first_revision.py | 49 +++++ .../migrations/aab5a786b5de_first_revision.py | 168 ------------------ marisa/models/__init__.py | 1 - marisa/models/backpack.py | 10 +- marisa/models/buff.py | 38 ---- marisa/models/user.py | 31 +++- marisa/plugins/base.py | 43 +++-- marisa/plugins/info/__init__.py | 100 ++++------- marisa/schemas/__init__.py | 16 +- marisa/schemas/buff.py | 113 +++--------- marisa/schemas/enums/__init__.py | 79 ++++++++ marisa/schemas/item.py | 37 ++++ marisa/schemas/level.py | 19 -- marisa/schemas/limit.py | 20 +++ marisa/schemas/quest.py | 33 ++++ marisa/schemas/rift.py | 11 ++ marisa/schemas/root.py | 19 +- marisa/schemas/sect.py | 9 +- marisa/utils/annotated.py | 1 - marisa/utils/jsondata.py | 147 +++++---------- marisa/utils/manager/__init__.py | 3 + marisa/utils/manager/backpack.py | 32 ++++ marisa/utils/manager/buff.py | 14 ++ marisa/utils/manager/item.py | 69 +++++++ marisa/utils/resource.py | 2 +- 28 files changed, 557 insertions(+), 531 deletions(-) create mode 100644 marisa/exception.py create mode 100644 marisa/migrations/6bf5eefd0cbd_first_revision.py delete mode 100644 marisa/migrations/aab5a786b5de_first_revision.py delete mode 100644 marisa/models/buff.py create mode 100644 marisa/schemas/enums/__init__.py create mode 100644 marisa/schemas/item.py delete mode 100644 marisa/schemas/level.py create mode 100644 marisa/schemas/limit.py create mode 100644 marisa/schemas/quest.py create mode 100644 marisa/schemas/rift.py create mode 100644 marisa/utils/manager/__init__.py create mode 100644 marisa/utils/manager/backpack.py create mode 100644 marisa/utils/manager/buff.py create mode 100644 marisa/utils/manager/item.py diff --git a/marisa/__init__.py b/marisa/__init__.py index fea9108..5a895bf 100644 --- a/marisa/__init__.py +++ b/marisa/__init__.py @@ -9,7 +9,6 @@ require("nonebot_plugin_htmlrender") from . import migrations -from .models import Buff as Buff from .models import Sect as Sect from .models import User as User from .plugins import base as base diff --git a/marisa/configs/configs/level_up.py b/marisa/configs/configs/level_up.py index 49882e6..570ff27 100644 --- a/marisa/configs/configs/level_up.py +++ b/marisa/configs/configs/level_up.py @@ -6,6 +6,14 @@ class LevelUpConfig(BaseConfig): """突破""" + theme: str = Field(default="default", alias="境界名称沿用") + """境界名称""" + base_exp: int = Field(default=100, alias="基础经验") + """基础经验""" + max_level: int = Field(default=200, alias="最高等级") + """最高等级""" + cardinality: float = Field(default=1.1, alias="经验系数") + """经验系数""" cd: int = Field(default=0, alias="冷却时间") """突破 CD (分钟)""" punishment: list[int] = Field(default=[10, 35], alias="失败后扣除修为范围") diff --git a/marisa/exception.py b/marisa/exception.py new file mode 100644 index 0000000..91ba716 --- /dev/null +++ b/marisa/exception.py @@ -0,0 +1,15 @@ +from nonebot.exception import NoneBotException + + +class MarisaException(NoneBotException): + """异常基类""" + + +class ItemNotFoundError(MarisaException): + """物品未找到异常""" + + def __init__(self, item_id: int) -> None: + self.item_id = item_id + + def __repr__(self) -> str: + return f"ItemNotFoundError(item_id={self.item_id})" diff --git a/marisa/migrations/6bf5eefd0cbd_first_revision.py b/marisa/migrations/6bf5eefd0cbd_first_revision.py new file mode 100644 index 0000000..6a9b922 --- /dev/null +++ b/marisa/migrations/6bf5eefd0cbd_first_revision.py @@ -0,0 +1,49 @@ +"""first revision + +迁移 ID: 6bf5eefd0cbd +父迁移: 0cdb9777e97d +创建时间: 2025-01-22 19:22:54.513452 + +""" + +from __future__ import annotations + +from collections.abc import Sequence + +import sqlalchemy as sa +from alembic import op + +revision: str = "6bf5eefd0cbd" +down_revision: str | Sequence[str] | None = "0cdb9777e97d" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade(name: str = "") -> None: + if name: + return + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("backpack", schema=None) as batch_op: + batch_op.add_column(sa.Column("is_bundle", sa.Boolean(), nullable=False)) + batch_op.add_column(sa.Column("is_equipped", sa.Boolean(), nullable=False)) + batch_op.drop_column("item_amount") + batch_op.drop_column("item_type") + batch_op.drop_column("bundle_item_amount") + + # ### end Alembic commands ### + + +def downgrade(name: str = "") -> None: + if name: + return + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("backpack", schema=None) as batch_op: + batch_op.add_column( + sa.Column("bundle_item_amount", sa.INTEGER(), nullable=False) + ) + batch_op.add_column(sa.Column("item_type", sa.VARCHAR(), nullable=False)) + batch_op.add_column(sa.Column("item_amount", sa.INTEGER(), nullable=False)) + batch_op.drop_column("is_equipped") + batch_op.drop_column("is_bundle") + + # ### end Alembic commands ### diff --git a/marisa/migrations/aab5a786b5de_first_revision.py b/marisa/migrations/aab5a786b5de_first_revision.py deleted file mode 100644 index 8363b70..0000000 --- a/marisa/migrations/aab5a786b5de_first_revision.py +++ /dev/null @@ -1,168 +0,0 @@ -"""first revision - -迁移 ID: aab5a786b5de -父迁移: -创建时间: 2025-01-18 10:30:21.873855 - -""" - -from __future__ import annotations - -from collections.abc import Sequence - -import sqlalchemy as sa -from alembic import op - -revision: str = "aab5a786b5de" -down_revision: str | Sequence[str] | None = None -branch_labels: str | Sequence[str] | None = ("marisa",) -depends_on: str | Sequence[str] | None = None - - -def upgrade(name: str = "") -> None: - if name: - return - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "sect", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sa.String(), nullable=False), - sa.Column("owner", sa.String(), nullable=False), - sa.Column("scale", sa.Integer(), nullable=True), - sa.Column("stone_amount", sa.Integer(), nullable=False), - sa.Column("fairyland", sa.Integer(), nullable=True), - sa.Column("materials", sa.Integer(), nullable=False), - sa.Column("main_buff", sa.String(), nullable=True), - sa.Column("sec_buff", sa.String(), nullable=True), - sa.Column("elixir_room_level", sa.Integer(), nullable=False), - sa.PrimaryKeyConstraint("id", name=op.f("pk_sect")), - info={"bind_key": "marisa"}, - ) - op.create_table( - "user", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("user_name", sa.String(length=15), nullable=False), - sa.Column("user_title", sa.String(), nullable=True), - sa.Column("root", sa.String(), nullable=False), - sa.Column("root_type", sa.String(), nullable=False), - sa.Column("level", sa.String(), nullable=False), - sa.Column("stone", sa.Integer(), nullable=False), - sa.Column("create_time", sa.DateTime(), nullable=False), - sa.Column("last_check_time", sa.DateTime(), nullable=False), - sa.PrimaryKeyConstraint("id", name=op.f("pk_user")), - sa.UniqueConstraint("user_name"), - info={"bind_key": "marisa"}, - ) - op.create_table( - "backpack", - sa.Column("id", sa.String(), nullable=False), - sa.Column("user_id", sa.Integer(), nullable=False), - sa.Column("item_id", sa.Integer(), nullable=False), - sa.Column("item_name", sa.String(), nullable=False), - sa.Column("item_type", sa.String(), nullable=False), - sa.Column("item_amount", sa.Integer(), nullable=False), - sa.Column("bundle_item_amount", sa.Integer(), nullable=False), - sa.Column("create_time", sa.DateTime(), nullable=False), - sa.Column("update_time", sa.DateTime(), nullable=False), - sa.ForeignKeyConstraint( - ["user_id"], - ["user.id"], - name=op.f("fk_backpack_user_id_user"), - ondelete="CASCADE", - ), - sa.PrimaryKeyConstraint("id", name=op.f("pk_backpack")), - info={"bind_key": "marisa"}, - ) - op.create_table( - "buff", - sa.Column("id", sa.String(), nullable=False), - sa.Column("user_id", sa.Integer(), nullable=False), - sa.Column("hp", sa.Integer(), nullable=False), - sa.Column("mp", sa.Integer(), nullable=False), - sa.Column("atk", sa.Integer(), nullable=False), - sa.Column("vit", sa.Integer(), nullable=False), - sa.Column("exp", sa.Integer(), nullable=False), - sa.Column("atk_level", sa.Integer(), nullable=False), - sa.Column("atk_buff", sa.Integer(), nullable=False), - sa.Column("main_buff", sa.Integer(), nullable=False), - sa.Column("sec_buff", sa.Integer(), nullable=False), - sa.Column("sub_buff", sa.Integer(), nullable=False), - sa.Column("dharma", sa.Integer(), nullable=False), - sa.Column("armor", sa.Integer(), nullable=False), - sa.ForeignKeyConstraint( - ["user_id"], - ["user.id"], - name=op.f("fk_buff_user_id_user"), - ondelete="CASCADE", - ), - sa.PrimaryKeyConstraint("id", name=op.f("pk_buff")), - info={"bind_key": "marisa"}, - ) - op.create_table( - "user_sect", - sa.Column("id", sa.String(), nullable=False), - sa.Column("user_id", sa.Integer(), nullable=False), - sa.Column("sect_id", sa.Integer(), nullable=True), - sa.Column("position", sa.String(), nullable=True), - sa.Column("task", sa.Integer(), nullable=False), - sa.Column("contribution", sa.Integer(), nullable=False), - sa.Column("elixir_get", sa.Integer(), nullable=False), - sa.ForeignKeyConstraint( - ["sect_id"], ["sect.id"], name=op.f("fk_user_sect_sect_id_sect") - ), - sa.ForeignKeyConstraint( - ["user_id"], - ["user.id"], - name=op.f("fk_user_sect_user_id_user"), - ondelete="CASCADE", - ), - sa.PrimaryKeyConstraint("id", name=op.f("pk_user_sect")), - info={"bind_key": "marisa"}, - ) - op.create_table( - "user_status", - sa.Column("id", sa.String(), nullable=False), - sa.Column("user_id", sa.Integer(), nullable=False), - sa.Column( - "status", - sa.Enum( - "NONE", - "MEDITATING", - "WORKING", - "EXPLORING", - "PRACTICING", - name="userstatustype", - ), - nullable=False, - ), - sa.Column("create_time", sa.DateTime(), nullable=False), - sa.Column("scheduled_time", sa.Integer(), nullable=True), - sa.Column("is_sign", sa.Boolean(), nullable=False), - sa.Column("is_beg", sa.Boolean(), nullable=False), - sa.Column("is_ban", sa.Boolean(), nullable=False), - sa.Column("level_up_cd", sa.DateTime(), nullable=True), - sa.Column("level_up_rate", sa.Integer(), nullable=True), - sa.Column("work_refresh_times", sa.Integer(), nullable=False), - sa.ForeignKeyConstraint( - ["user_id"], - ["user.id"], - name=op.f("fk_user_status_user_id_user"), - ondelete="CASCADE", - ), - sa.PrimaryKeyConstraint("id", name=op.f("pk_user_status")), - info={"bind_key": "marisa"}, - ) - # ### end Alembic commands ### - - -def downgrade(name: str = "") -> None: - if name: - return - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("user_status") - op.drop_table("user_sect") - op.drop_table("buff") - op.drop_table("backpack") - op.drop_table("user") - op.drop_table("sect") - # ### end Alembic commands ### diff --git a/marisa/models/__init__.py b/marisa/models/__init__.py index fa89cdd..dc6c226 100644 --- a/marisa/models/__init__.py +++ b/marisa/models/__init__.py @@ -1,4 +1,3 @@ -from .buff import Buff as Buff from .sect import Sect as Sect from .user import User as User from .backpack import Backpack as Backpack diff --git a/marisa/models/backpack.py b/marisa/models/backpack.py index d390e87..1422d0d 100644 --- a/marisa/models/backpack.py +++ b/marisa/models/backpack.py @@ -16,12 +16,10 @@ class Backpack(Model): """物品 ID""" item_name: Mapped[str] """物品名称""" - item_type: Mapped[str] - """物品类型""" - item_amount: Mapped[int] - """物品数量""" - bundle_item_amount: Mapped[int] = mapped_column(default=0) - """绑物数量""" + is_bundle: Mapped[bool] + """是否为绑物""" + is_equipped: Mapped[bool] = mapped_column(default=False) + """是否装备""" create_time: Mapped[DateTime] = mapped_column(DateTime, default=func.now()) """创建时间""" update_time: Mapped[DateTime] = mapped_column( diff --git a/marisa/models/buff.py b/marisa/models/buff.py deleted file mode 100644 index 5c1a1bf..0000000 --- a/marisa/models/buff.py +++ /dev/null @@ -1,38 +0,0 @@ -from sqlalchemy import ForeignKey -from nonebot_plugin_orm import Model -from sqlalchemy.orm import Mapped, mapped_column - - -class Buff(Model): - """Buff 信息""" - - __tablename__ = "buff" - - id: Mapped[str] = mapped_column(primary_key=True) - - user_id: Mapped[int] = mapped_column(ForeignKey("user.id", ondelete="CASCADE")) - """用户 ID""" - hp: Mapped[int] = mapped_column(default=0) - """生命值""" - mp: Mapped[int] = mapped_column(default=0) - """真元""" - atk: Mapped[int] = mapped_column(default=0) - """攻击力""" - vit: Mapped[int] = mapped_column(default=240) - """体力""" - exp: Mapped[int] = mapped_column(default=0) - """经验""" - atk_level: Mapped[int] = mapped_column(default=0) - """攻击修炼等级""" - atk_buff: Mapped[int] = mapped_column(default=0) - """攻击加成""" - main_buff: Mapped[int] = mapped_column(default=0) - """主功法""" - sec_buff: Mapped[int] = mapped_column(default=0) - """神通""" - sub_buff: Mapped[int] = mapped_column(default=0) - """辅修""" - dharma: Mapped[int] = mapped_column(default=0) - """法宝""" - armor: Mapped[int] = mapped_column(default=0) - """防具""" diff --git a/marisa/models/user.py b/marisa/models/user.py index 6f3b1fd..86b0221 100644 --- a/marisa/models/user.py +++ b/marisa/models/user.py @@ -3,11 +3,11 @@ from typing_extensions import Self from nonebot_plugin_orm import Model, get_session -from sqlalchemy.orm import Mapped, relationship, mapped_column +from sqlalchemy.orm import Mapped, relationship, selectinload, mapped_column from sqlalchemy import String, Boolean, DateTime, ForeignKey, and_, func, select if TYPE_CHECKING: - from . import Buff, Sect, Backpack + from . import Sect, Backpack class UserStatusType(Enum): @@ -40,8 +40,10 @@ class User(Model): """灵根""" root_type: Mapped[str] """灵根类型""" - level: Mapped[str] + level: Mapped[int] """等级""" + exp: Mapped[int] + """经验""" stone: Mapped[int] """灵石""" create_time: Mapped[DateTime] = mapped_column(DateTime, default=func.now()) @@ -51,7 +53,6 @@ class User(Model): ) """上次检查时间""" - buff: Mapped["Buff"] = relationship("Buff", back_populates=None, uselist=False) sect: Mapped["UserSect"] = relationship( "UserSect", back_populates=None, uselist=False ) @@ -63,13 +64,10 @@ class User(Model): @classmethod async def create_user(cls, user: Self): """创建用户""" - from . import Buff - sect = UserSect(id=user.id, user_id=user.id) status = UserStatus(id=user.id, user_id=user.id) - buff = Buff(id=user.id, user_id=user.id) - objects = [user, sect, status, buff] + objects = [user, sect, status] session = get_session() async with session.begin(): @@ -80,7 +78,22 @@ async def delete_user(cls, user: Self): """删除用户""" session = get_session() async with session.begin(): - await session.delete(user) + stmt = ( + select(cls).options( + selectinload(cls.sect), + selectinload(cls.backpack), + selectinload(cls.status), + ) + ).where(cls.id == user.id) + obj = (await session.execute(stmt)).scalar() + if obj: + if obj.sect: + await session.delete(obj.sect) + if obj.status: + await session.delete(obj.status) + for backpack_item in obj.backpack: + await session.delete(backpack_item) + await session.delete(obj) @classmethod async def is_user_exist(cls, id: int, user_name: str) -> bool: diff --git a/marisa/plugins/base.py b/marisa/plugins/base.py index fb7c9b3..496acb1 100644 --- a/marisa/plugins/base.py +++ b/marisa/plugins/base.py @@ -1,4 +1,5 @@ import random +from typing import Literal from nonebot_plugin_uninfo import Uninfo from nonebot_plugin_waiter import waiter @@ -9,7 +10,6 @@ from nonebot_plugin_alconna import Match, Button, Command, UniMessage, FallbackStrategy from ..models import User -from ..schemas import Root from ..configs import config from ..utils.jsondata import jsondata from ..utils.annotated import UserInfo @@ -25,19 +25,24 @@ async def _(session: Uninfo, user_session: UserSession): user_name = session.user.name or "无名客" - root_data = jsondata.get_all_root_data() - root, root_type = jsondata.select_root() - power: int = int(100 * float(Root.model_validate(root_data[root_type]).speeds)) + root = jsondata.select_root() + exp_buff = next( + (buff.value for buff in root.buff if buff.type.value == "exp"), None + ) + exp: float | Literal[0] = 100 * exp_buff if exp_buff else 0 user = User( id=user_session.user.id, user_name=user_name, - root=root, - root_type=root_type, - level="江湖好手", + root=root.name, + root_type=root.type, + level=1, + exp=exp, stone=0, ) + level_name = jsondata.get_level_name(1) + if not await User.is_user_exist(user.id, user_name): try: await User.create_user(user) @@ -62,7 +67,7 @@ async def check(event: Event): await User.create_user(user) await UniMessage( - f"欢迎来到修真界。你的灵根为:{root},类型是:{root_type}。你的战力为:{power}。当前境界:江湖好手" + f"欢迎来到修真界。你的灵根为:{root.name},类型是:{root.type}。你的战力为:{exp}。当前境界:{level_name}" ).finish(at_sender=True) await ( UniMessage.text("你已迈入修仙世界,输入 『 /我的修仙信息 』查看信息吧") @@ -72,28 +77,32 @@ async def check(event: Event): @rebirth_ascension.handle() -async def _(user_info: UserInfo, session: async_scoped_session): +async def _(user_info: UserInfo): if user_info.stone < config.rebirth.cost: await UniMessage("你的灵石还不够呢,快去赚点灵石吧!").finish(at_sender=True) - root_data = jsondata.get_all_root_data() - root, root_type = jsondata.select_root() - power: int = int(100 * float(root_data[root_type].speeds)) + root = jsondata.select_root() + exp_buff = next( + (buff.value for buff in root.buff if buff.type.value == "exp"), None + ) + exp: float | Literal[0] = 100 * exp_buff if exp_buff else 0 user = User( id=user_info.id, user_name=user_info.user_name, - root=root, - root_type=root_type, - level="江湖好手", + root=root.name, + root_type=root.type, + level=1, + exp=exp, stone=0, ) + level_name = jsondata.get_level_name(1) + await User.delete_user(user_info) await User.create_user(user) - await session.commit() await UniMessage( - f"欢迎来到修真界。你的灵根为:{root},类型是:{root_type}。你的战力为:{power}。当前境界:江湖好手" + f"欢迎来到修真界。你的灵根为:{root.name},类型是:{root.type}。你的战力为:{exp}。当前境界:{level_name}" ).finish(at_sender=True) diff --git a/marisa/plugins/info/__init__.py b/marisa/plugins/info/__init__.py index cc5abc6..dfb7d4f 100644 --- a/marisa/plugins/info/__init__.py +++ b/marisa/plugins/info/__init__.py @@ -13,8 +13,10 @@ FallbackStrategy, ) -from marisa.models import Buff, User +from marisa.models import User from marisa.utils.jsondata import jsondata +from marisa.utils.manager import BuffManager +from marisa.utils.manager.backpack import BackpackManager from .render import render @@ -46,14 +48,12 @@ async def _( else: name = "你" ident = user_session.user_id - stmt = ( select(User) - .options(selectinload(User.sect), selectinload(User.buff)) + .options(selectinload(User.sect), selectinload(User.backpack)) .where(or_(User.id == ident, User.user_name == ident)) ) user_info = (await db_session.execute(stmt)).scalar() - if user_info is None: await ( UniMessage.text( @@ -62,7 +62,6 @@ async def _( .keyboard(Button("input", "我要修仙", text="/我要修仙")) .finish(at_sender=True, fallback=FallbackStrategy.ignore) ) - session = await get_session(bot, event) if session is None: return @@ -74,60 +73,41 @@ async def _( ) # Level Up Info - level_rate = jsondata.get_level_data(user_info.level).spend - last_level = jsondata.get_level_data("-1") - + level_rate = jsondata.get_level_up_probability(user_info.level) + last_level = list(jsondata._get_level_data().keys())[-1] if user_info.level == last_level: level_up_status = "位面至高" else: - need_exp = ( - jsondata.get_next_level_data(user_info.level).power - user_info.buff.exp - ) + need_exp = jsondata.get_level_exp(user_info.level + 1) - user_info.exp if need_exp > 0: level_up_status = f"还需 {need_exp} 修为可突破!" else: level_up_status = "即刻突破!" # Buff Info - buff_info = await db_session.get(Buff, user_info.id) - if buff_info is None: - mainbuff_name = subuff_name = secbuff_name = dharma_name = armor_name = "无" - else: - mainbuff_name = ( - f""" - {jsondata.get_mainbuff_data(buff_info.main_buff).name}({jsondata.get_mainbuff_data(buff_info.main_buff).level}) - """ - if buff_info.main_buff != 0 - else "无" - ) - subuff_name = ( - f""" - {jsondata.get_subuff_data(buff_info.sub_buff).name}({jsondata.get_subuff_data(buff_info.sub_buff).level}) - """ - if buff_info.sub_buff != 0 - else "无" - ) - secbuff_name = ( - f""" - {jsondata.get_secbuff_data(buff_info.sec_buff).name}({jsondata.get_secbuff_data(buff_info.sec_buff).level}) - """ - if buff_info.sec_buff != 0 - else "无" - ) - dharma_name = ( - f""" - {jsondata.get_dharma_data(buff_info.dharma).name}({jsondata.get_dharma_data(buff_info.dharma).level}) - """ - if buff_info.dharma != 0 - else "无" - ) - armor_name = ( - f""" - {jsondata.get_armor_data(buff_info.armor).name}({jsondata.get_armor_data(buff_info.armor).level}) - """ - if buff_info.armor != 0 - else "无" - ) + buff = BuffManager(user_info) + + # Backpack + backpack = BackpackManager(user_info) + main_tech = backpack.filter( + lambda item: item.type == "main_tech" + and bool(item.model_dump().get("is_equipped")) + ) + second_tech = backpack.filter( + lambda item: item.type == "second_tech" + and bool(item.model_dump().get("is_equipped")) + ) + divine_tech = backpack.filter( + lambda item: item.type == "divine_tech" + and bool(item.model_dump().get("is_equipped")) + ) + dharma = backpack.filter( + lambda item: item.type == "dharma" + and bool(item.model_dump().get("is_equipped")) + ) + armor = backpack.filter( + lambda item: item.type == "armor" and bool(item.model_dump().get("is_equipped")) + ) # Sect Info sect_id = user_info.sect.sect_id @@ -148,8 +128,7 @@ async def _( ), None, ) - - exp_query = select(User.id, Buff.exp).join(Buff).order_by(Buff.exp.desc()) + exp_query = select(User.id, User.exp).order_by(User.exp.desc()) exp_rank = await db_session.execute(exp_query) user_exp_rank = next( ( @@ -159,7 +138,6 @@ async def _( ), None, ) - stone_query = select(User.id, User.stone).order_by(User.stone.desc()) stone_rank = await db_session.execute(stone_query) user_stone_rank = next( @@ -170,29 +148,27 @@ async def _( ), None, ) - info_map = { "avatar": user_avatar, "title": user_info.user_title if user_info.user_title else "暂无", "name": user_info.user_name, "level": user_info.level, - "exp": user_info.buff.exp, + "exp": user_info.exp, "stone": user_info.stone, "root": f"{user_info.root}({user_info.root_type} + {level_rate * 100} %)", "level_up_status": f"{level_up_status}", - "mainbuff": mainbuff_name, - "secbuff": secbuff_name, - "subuff": subuff_name, - "atk": f"{user_info.buff.atk},攻修等级:{user_info.buff.atk_level} 级", - "dharma": dharma_name, - "armor": armor_name, + "mainbuff": main_tech[0].name if main_tech else "无", + "secbuff": second_tech[0].name if second_tech else "无", + "subuff": divine_tech[0].name if divine_tech else "无", + "atk": str(buff.atk), + "dharma": dharma[0].name if dharma else "无", + "armor": armor[0].name if armor else "无", "sect_name": sect_name, "sect_position": sect_position, "register_rank": f"道友是踏入修仙世界的第 {user_register_rank} 人", "exp_rank": f"第 {user_exp_rank} 名", "stone_rank": f"第 {user_stone_rank} 名", } - img = await render(info_map) await db_session.commit() await UniMessage.image(raw=img).finish(at_sender=True) diff --git a/marisa/schemas/__init__.py b/marisa/schemas/__init__.py index ceb9bbb..70552f5 100644 --- a/marisa/schemas/__init__.py +++ b/marisa/schemas/__init__.py @@ -1,8 +1,10 @@ +from .buff import Buff as Buff +from .item import Item as Item +from .rift import Rift as Rift from .root import Root as Root -from .buff import SecBuff as SecBuff -from .buff import SubBuff as SubBuff -from .buff import MainBuff as MainBuff -from .buff import ArmorBuff as ArmorBuff -from .level import LevelInfo as LevelInfo -from .buff import DharmaBuff as DharmaBuff -from .sect import SectPosition as SectPosition +from .sect import Sect as Sect +from .limit import Limit as Limit +from .enums import BuffType as BuffType +from .enums import LevelType as LevelType +from .enums import QualityType as QualityType +from .enums import DistributionMethod as DistributionMethod diff --git a/marisa/schemas/buff.py b/marisa/schemas/buff.py index 84124af..5727ab7 100644 --- a/marisa/schemas/buff.py +++ b/marisa/schemas/buff.py @@ -1,87 +1,30 @@ -from pydantic import Field, BaseModel - - -class MainBuff(BaseModel): - """主功法""" - - name: str - hpbuff: float - mpbuff: float - atkbuff: float - ratebuff: float - crit_buff: int - def_buff: int - dan_exp: int - reap_buff: int - exp_buff: int - critatk: int - two_buff: int - number: int - clo_exp: int - clo_rs: int - random_buff: int - ew: int - desc: str - rank: str - level: str - - -class SecBuff(BaseModel): - """神通""" - - name: str - skill_type: int - atkvalue: list[float] - hpcost: float - mpcost: float - turncost: int - desc: str - rate: int - rank: str - level: str - +from typing import Literal -class SubBuff(BaseModel): - """辅修""" - - name: str - buff_type: str - buff: str - buff2: str - stone: int - integral: int - jin: int - drop: int - fan: int - abreak: int = Field(..., alias="break") - exp: int - desc: str - rank: str - level: str - - -class DharmaBuff(BaseModel): - """法器""" - - name: str - atk_buff: int | float - crit_buff: float - def_buff: float - critatk: float - zw: int - mp_buff: int - rank: int - level: str - type: str - - -class ArmorBuff(BaseModel): - """防具""" +from pydantic import Field, BaseModel - name: str - def_buff: float - atk_buff: int | float - crit_buff: float - rank: int - level: str - type: str +from marisa.schemas.enums import BuffType + + +class Buff(BaseModel): + """增益效果""" + + type: BuffType + """类型""" + calc: Literal["add", "mul"] + """ + 计算方式 + - `add`: 加法 + - `mul`: 乘法 + """ + value: float + """数值""" + duration: int = Field(-1) + """持续时间""" + state: Literal["active", "inactive"] = Field("inactive") + """状态""" + + @property + def desc(self): + duration = f"在 {self.duration}s 内" if self.duration != -1 else "" + unit = "点" if self.calc == "add" else "%" + return duration + f"{self.type.value} + {self.value}{unit}" diff --git a/marisa/schemas/enums/__init__.py b/marisa/schemas/enums/__init__.py new file mode 100644 index 0000000..e53281a --- /dev/null +++ b/marisa/schemas/enums/__init__.py @@ -0,0 +1,79 @@ +from enum import Enum, IntEnum + + +class BuffType(str, Enum): + """Buff 类型""" + + atk = "atk" + """攻击力""" + ac = "ac" + """防御值""" + boost = "boost" + """倍率""" + cr = "cr" + """暴击率""" + crd = "crd" + """暴击伤害""" + cp = "cp" + """战力""" + dm = "dm" + """免伤""" + dr = "dr" + """掉落率""" + ew = "ew" + """专属武器""" + exp = "exp" + """经验值""" + hp = "hp" + """生命值""" + mp = "mp" + """真元""" + sp = "sp" + """体力""" + + +class LevelType(IntEnum): + """装备等级""" + + INFERIOR = 1 + """下品""" + SUPERIOR = 2 + """上品""" + + +class QualityType(IntEnum): + """装备品质""" + + NORMAL = 1 + """普通""" + RARE = 2 + """稀有""" + EPIC = 3 + """史诗""" + LEGENDARY = 4 + """传说""" + + +class DistributionMethod(str, Enum): + """发放途径""" + + none = "none" + """不发放""" + all = "all" + """所有途径""" + quest = "quest" + """悬赏令""" + shop = "shop" + """商店""" + rift = "rift" + """秘境""" + gift = "gift" + """礼物""" + exchange = "exchange" + """兑换""" + craft = "craft" + """合成""" + reward = "reward" + """奖励""" + sect = "sect" + """宗门""" diff --git a/marisa/schemas/item.py b/marisa/schemas/item.py new file mode 100644 index 0000000..578bb0d --- /dev/null +++ b/marisa/schemas/item.py @@ -0,0 +1,37 @@ +from typing import Literal, TypeAlias + +from pydantic import BaseModel, ConfigDict + +from marisa.schemas.buff import Buff +from marisa.schemas.limit import Limit +from marisa.schemas.enums import LevelType, QualityType + +ItemType: TypeAlias = Literal[ + "dharma", # 法器 + "armor", # 防具 + "main_tech", # 主功法 + "second_tech", # 辅修 + "divine_tech", # 神通 + "elixir", # 丹药 + "drug_material", # 药材 +] + + +class Item(BaseModel): + """物品""" + + model_config = ConfigDict(extra="allow") + + id: int | None = None + name: str + type: ItemType + + level: LevelType | None = None + """等级""" + quality: QualityType | None = None + """品质""" + + buff: list[Buff] | None = None + """增幅""" + limit: Limit | None = None + """限制""" diff --git a/marisa/schemas/level.py b/marisa/schemas/level.py deleted file mode 100644 index ad9ef78..0000000 --- a/marisa/schemas/level.py +++ /dev/null @@ -1,19 +0,0 @@ -from pydantic import Field, BaseModel - - -class LevelInfo(BaseModel): - """境界信息""" - - power: int - """战力""" - atk: int = Field(..., alias="ATK") - ac: int = Field(..., alias="AC") - spend: int | float - hp: int = Field(..., alias="HP") - mp: int = Field(..., alias="MP") - comment: int - rate: int - exp: int - """修为""" - sp: int = Field(..., alias="SP") - sp_ra: float = Field(..., alias="SP_RA") diff --git a/marisa/schemas/limit.py b/marisa/schemas/limit.py new file mode 100644 index 0000000..bd78106 --- /dev/null +++ b/marisa/schemas/limit.py @@ -0,0 +1,20 @@ +from pydantic import Field, BaseModel + +from marisa.schemas.enums import DistributionMethod + + +class Limit(BaseModel): + """物品限制""" + + level: int | None = None + """限制等级""" + stone: int | None = None + """花费灵石""" + cooldown: int | None = None + """冷却时间""" + count: int | None = None + """使用次数""" + distribution: DistributionMethod | list[DistributionMethod] = Field( + DistributionMethod.all + ) + """发放途径""" diff --git a/marisa/schemas/quest.py b/marisa/schemas/quest.py new file mode 100644 index 0000000..e42cf8b --- /dev/null +++ b/marisa/schemas/quest.py @@ -0,0 +1,33 @@ +from typing import Literal + +from pydantic import BaseModel + +from marisa.schemas import Buff, Item + + +class Reward(BaseModel): + """奖励""" + + stone: int | None + """灵石""" + item: list[Item] | None + """物品""" + buff: Buff | None + """增幅""" + + +class Ending(BaseModel): + """结局""" + + description: str + """描述""" + reward: Reward + """奖励""" + + +class Quest(BaseModel): + """悬赏令""" + + name: str + type: Literal["assassin", "picking", "repress"] + ending: Ending diff --git a/marisa/schemas/rift.py b/marisa/schemas/rift.py new file mode 100644 index 0000000..dbcda5c --- /dev/null +++ b/marisa/schemas/rift.py @@ -0,0 +1,11 @@ +from pydantic import BaseModel + + +class Rift(BaseModel): + """秘境""" + + name: str + rank: int + rate: int + count: int + duration: int diff --git a/marisa/schemas/root.py b/marisa/schemas/root.py index 0509895..d8db701 100644 --- a/marisa/schemas/root.py +++ b/marisa/schemas/root.py @@ -1,13 +1,12 @@ -from pydantic import Field, BaseModel +from pydantic import BaseModel + +from marisa.schemas.buff import Buff class Root(BaseModel): - """灵根类型""" - - rate: int = Field(..., alias="type_rate") - """抽取概率""" - type_list: list[str] - """灵根类型""" - speeds: float = Field(..., alias="type_speeds") - """闭关的修为倍率""" - flag: list[int | None] = Field(..., alias="type_flag") + """灵根""" + + name: str + type: str | list[str] + count: int + buff: list[Buff] diff --git a/marisa/schemas/sect.py b/marisa/schemas/sect.py index ffb0cee..7cdf74b 100644 --- a/marisa/schemas/sect.py +++ b/marisa/schemas/sect.py @@ -1,9 +1,10 @@ from pydantic import BaseModel -class SectPosition(BaseModel): - """宗门职务""" +class Sect(BaseModel): + """宗门""" - title: str - speeds: str + position: str + """职位""" max_exp: int + """完成任务获取的修为上限""" diff --git a/marisa/utils/annotated.py b/marisa/utils/annotated.py index f9c2cc6..999848e 100644 --- a/marisa/utils/annotated.py +++ b/marisa/utils/annotated.py @@ -14,7 +14,6 @@ select(_User) .options( selectinload(_User.sect), - selectinload(_User.buff), selectinload(_User.backpack), selectinload(_User.status), ) diff --git a/marisa/utils/jsondata.py b/marisa/utils/jsondata.py index bd7d9d6..cc262f7 100644 --- a/marisa/utils/jsondata.py +++ b/marisa/utils/jsondata.py @@ -1,127 +1,70 @@ import random -from typing import Any from pathlib import Path import ujson as json -from ..configs import DATA_DIR -from ..schemas import ( - Root, - SecBuff, - SubBuff, - MainBuff, - ArmorBuff, - LevelInfo, - DharmaBuff, - SectPosition, -) +from marisa.schemas import Buff + +from ..schemas import Root, BuffType +from ..configs import DATA_DIR, config class JsonData: """Ascension Json Data""" - def __init__(self): - self.root_path: Path = DATA_DIR / "灵根.json" - self.level_path: Path = DATA_DIR / "境界.json" - self.sect_path: Path = DATA_DIR / "宗门.json" - self.level_up_rate_path: Path = DATA_DIR / "突破概率.json" - self.mainbuff_path: Path = DATA_DIR / "功法" / "主功法.json" - self.subuff_path: Path = DATA_DIR / "功法" / "辅修功法.json" - self.secbuff_path: Path = DATA_DIR / "功法" / "神通.json" - self.dharma_path: Path = DATA_DIR / "装备" / "法器.json" - self.armor_path: Path = DATA_DIR / "装备" / "防具.json" - - def _get_level_data(self) -> dict[str, LevelInfo]: + def __init__(self) -> None: + self.root_path: Path = DATA_DIR / "root.json" + self.level_path: Path = DATA_DIR / "level" / f"{config.level_up.theme}.json" + self.sect_path: Path = DATA_DIR / "sect.json" + + def _get_level_data(self) -> dict[str, str]: """获取境界数据""" return json.loads(self.level_path.read_text("utf-8")) - def _get_sect_data(self) -> dict[str, SectPosition]: - """获取宗门数据""" - return json.loads(self.sect_path.read_text("utf-8")) - - def get_level_data(self, level: str) -> LevelInfo: - """ - 获取境界数据 - 参数: - level: 境界(若填入 `-1`,则返回最后一个境界信息) - """ - if level == "-1": - all_level_name: list = list(self._get_level_data().keys()) - last_level_name: str = all_level_name[-1] - return LevelInfo.model_validate(self._get_level_data()[last_level_name]) - return LevelInfo.model_validate(self._get_level_data()[level]) - - def get_next_level_data(self, level: str) -> LevelInfo: - """获取用户下一境界数据""" - all_level_name = list(self._get_level_data().keys()) - level_index = all_level_name.index(level) - return LevelInfo.model_validate( - self.get_level_data(all_level_name[level_index + 1]) - ) - - def get_all_root_data(self) -> dict[str, Root]: + def _get_all_root_data(self) -> list[Root]: """获取全部灵根数据""" - return json.loads(self.root_path.read_text("utf-8")) + data = json.loads(self.root_path.read_text("utf-8"))["root"] + return [Root(**root) for root in data] + + def get_level_name(self, level: int) -> str: + """获取对应境界名称""" + return self._get_level_data()[str(level)] + + def get_level_exp(self, level: int) -> int: + """获取对应境界所需经验""" + cfg = config.level_up + return round(int(cfg.base_exp * (cfg.cardinality**level)) / 10) * 10 + + def get_level_up_probability(self, level: int) -> float: + """获取对应境界突破概率""" + return max(0, 100 - 2 * level) def get_root_data(self, name: str) -> Root: """获取灵根数据""" - return Root.model_validate(self.get_all_root_data()[name]) - - def get_mainbuff_data(self, key: str | int) -> MainBuff: - """获取功法数据""" - return MainBuff.model_validate( - json.loads(self.mainbuff_path.read_text("utf-8"))[str(key)] - ) - - def get_secbuff_data(self, key: str | int) -> SecBuff: - """获取神通数据""" - return SecBuff.model_validate( - json.loads(self.secbuff_path.read_text("utf-8"))[str(key)] - ) - - def get_subuff_data(self, key: str | int) -> SubBuff: - """获取辅修数据""" - return SubBuff.model_validate( - json.loads(self.subuff_path.read_text("utf-8"))[str(key)] - ) - - def get_dharma_data(self, key: str | int) -> DharmaBuff: - """获取法器数据""" - return DharmaBuff.model_validate( - json.loads(self.dharma_path.read_text("utf-8"))[str(key)] - ) - - def get_armor_data(self, key: str | int) -> ArmorBuff: - """获取防具数据""" - return ArmorBuff.model_validate( - json.loads(self.armor_path.read_text("utf-8"))[str(key)] - ) - - def select_root(self) -> tuple[str, str]: - data = self.get_all_root_data() - root_types: list[str] = list(data.keys()) - root_rate = [ - Root.model_validate(data[root_type]).rate for root_type in root_types + roots: list[Root] = self._get_all_root_data() + return list(filter(lambda root: root.name == name, roots))[0] + + def select_root(self) -> Root: + """随机选择灵根""" + roots: list[Root] = self._get_all_root_data() + roots_with_dr: list[Root] = [ + root + for root in roots + if any(buff.type == BuffType.dr for buff in root.buff) ] - select_root_type: str = random.choices(root_types, weights=root_rate, k=1)[0] - select_root: str = random.choice( - Root.model_validate(data[select_root_type]).type_list - ) + weighted_roots = [] + for root in roots_with_dr: + dr_buff: Buff = next(buff for buff in root.buff if buff.type == BuffType.dr) + weight: float = dr_buff.value + weighted_roots.extend([root] * int(weight * 10)) - return select_root, select_root_type + chosen_root: Root = random.choice(weighted_roots) + chosen_type: str = random.choice(chosen_root.type) - def _get_level_up_rate_data(self) -> dict[str, Any]: - """获取境界突破概率数据""" - return json.loads(self.level_up_rate_path.read_text("utf-8")) + chosen_root.type = chosen_type - def get_user_level_up_rate(self, level) -> str: - """ - 获取用户突破概率 - 参数: - level: 当前等级 - """ - return self._get_level_up_rate_data()[level] + return chosen_root jsondata = JsonData() diff --git a/marisa/utils/manager/__init__.py b/marisa/utils/manager/__init__.py new file mode 100644 index 0000000..a93cf6c --- /dev/null +++ b/marisa/utils/manager/__init__.py @@ -0,0 +1,3 @@ +from .buff import BuffManager as BuffManager +from .item import item_manager as item_manager +from .backpack import BackpackManager as BackpackManager diff --git a/marisa/utils/manager/backpack.py b/marisa/utils/manager/backpack.py new file mode 100644 index 0000000..b8f244f --- /dev/null +++ b/marisa/utils/manager/backpack.py @@ -0,0 +1,32 @@ +from typing import Generic, TypeVar + +from marisa.schemas.item import Item +from marisa.models import User, Backpack + +from . import item_manager + +T = TypeVar("T", bound=User) + + +class BackpackManager(Generic[T]): + def __init__(self, user: T) -> None: + self.user = user + self.backpack: list[Backpack] = user.backpack + self.items: list[Item] = [ + self._add_extra_fields(backpack_item) for backpack_item in self.backpack + ] + + def _add_extra_fields(self, backpack_item: Backpack) -> Item: + item = item_manager.get(backpack_item.item_id) + + item_dict = item.model_dump() + item_dict.update( + { + "is_bundle": backpack_item.is_bundle, + "is_equipped": backpack_item.is_equipped, + } + ) + + return Item(**item_dict) + + filter = item_manager.filter diff --git a/marisa/utils/manager/buff.py b/marisa/utils/manager/buff.py new file mode 100644 index 0000000..7b0267e --- /dev/null +++ b/marisa/utils/manager/buff.py @@ -0,0 +1,14 @@ +from typing import Generic, TypeVar + +from marisa.models import User + +T = TypeVar("T", bound=User) + + +class BuffManager(Generic[T]): + def __init__(self, user: T) -> None: + self.user = user + + @property + def atk(self) -> float: + return self.user.exp * 0.1 diff --git a/marisa/utils/manager/item.py b/marisa/utils/manager/item.py new file mode 100644 index 0000000..a563358 --- /dev/null +++ b/marisa/utils/manager/item.py @@ -0,0 +1,69 @@ +from pathlib import Path +from typing import overload +from collections.abc import Callable + +import ujson as json +from nonebot.log import logger +from pydantic import TypeAdapter + +from marisa.configs import DATA_DIR +from marisa.exception import ItemNotFoundError +from marisa.schemas.item import Item, ItemType + + +class ItemManager: + def __init__(self) -> None: + self.file_path: Path = DATA_DIR / "items.json" + self.items: list[Item] = self.load() + + def load(self) -> list[Item]: + with open(self.file_path, encoding="UTF-8") as f: + json_data = json.load(f) + + items = TypeAdapter(list[Item]).validate_python(json_data["items"]) + + for item_type in ItemType.__args__: + loaded_count = len(list(filter(lambda item: item.type == item_type, items))) + logger.debug(f"Loaded {loaded_count} {item_type} items successfully.") + + logger.info(f"Loaded {len(items)} items successfully.") + return items + + def save(self) -> None: + json_data = {"items": [item.model_dump() for item in self.items]} + with open(self.file_path, "w", encoding="UTF-8") as f: + json.dump(json_data, f, ensure_ascii=False, indent=4) + + def add(self, item: Item) -> None: + if not self.items: + max_id = 0 + else: + max_id = max([item.id for item in self.items if item.id is not None]) + + item.id = max_id + 1 + self.items.append(item) + self.save() + + def get(self, item_id: int) -> Item: + for item in self.items: + if item.id == item_id: + return item + raise ItemNotFoundError(item_id) + + @overload + def filter(self, target: str) -> list[Item] | None: ... + + @overload + def filter(self, target: Callable[[Item], bool]) -> list[Item] | None: ... + + def filter(self, target: str | Callable[[Item], bool]) -> list[Item] | None: + if isinstance(target, str): + filtered_items = filter( + lambda item: target.lower() in item.name.lower(), self.items + ) + elif callable(target): + filtered_items = filter(target, self.items) + return list(filtered_items) + + +item_manager = ItemManager() diff --git a/marisa/utils/resource.py b/marisa/utils/resource.py index 43703f8..7d07b3e 100644 --- a/marisa/utils/resource.py +++ b/marisa/utils/resource.py @@ -27,7 +27,7 @@ async def check_resource(): file_count = len([f for f in DATA_DIR.iterdir() if f.is_file()]) subfolders_count = len([f for f in DATA_DIR.iterdir() if f.is_dir()]) - if (file_count + subfolders_count) < 11: + if (file_count + subfolders_count) < 8: logger.info("资源缺失,即刻下载") try: async with httpx.AsyncClient(timeout=10.0).stream(