class Guild(Model): guild_id = BigIntField(unique=True) prefix = CharField(max_length=5, null=True, default="?") clear_spam = BooleanField(default=False) aggregate_clans = BooleanField(default=True) track_sherpas = BooleanField(default=False) admin_channel = BigIntField(unique=True, null=True) announcement_channel = BigIntField(unique=True, null=True)
class MonsterDrop(Model): item = ForeignKeyField("models.Item", related_name="drops") monster = ForeignKeyField("models.Monster", related_name="drops") rate = IntField() pickpocket_only = BooleanField(default=False) no_pickpocket = BooleanField(default=False) conditional = BooleanField(default=False) fixed = BooleanField(default=False) stealable_accordion = BooleanField(default=False)
class Skill(Model, metaclass=SkillMeta): id = IntField(pk=True, generated=False) name = CharField(max_length=255) image = CharField(max_length=255) level_required = IntField(default=0) mp_cost = IntField(default=0) passive: bool = BooleanField(default=False) # type: ignore noncombat: bool = BooleanField(default=False) # type: ignore shruggable: bool = BooleanField(default=False) # type: ignore combat: bool = BooleanField(default=False) # type: ignore healing: bool = BooleanField(default=False) # type: ignore summon: bool = BooleanField(default=False) # type: ignore expression: bool = BooleanField(default=False) # type: ignore walk: bool = BooleanField(default=False) # type: ignore mutex_song: bool = BooleanField(default=False) # type: ignore @property def buff(self) -> bool: return self.shruggable def have(self): return self in self.kol.state.skills async def cast(self, times: int = 1): return await request.skill_use(self.kol, self, times).parse()
class User(Model): id = UUIDField(pk=True, index=True) # 0 == Free, 10 == Premium tier = IntField(default=0) username = CharField(max_length=255, unique=True, index=True) hashed_password = TextField(null=False) encrypted = BooleanField(default=False) admin = BooleanField(default=False) journals: ReverseRelation["Journal"]
class ClanMember(Model): platform_id = IntField() join_date = DatetimeField() is_active = BooleanField(default=True) last_active = DatetimeField(null=True) is_sherpa = BooleanField(default=False) member_type = IntField(null=True, validators=[ClanMemberRankValidator()]) clan: ForeignKeyRelation[Clan] = ForeignKeyField( "seraphsix.Clan", related_name="members", to_field="id" ) member: ForeignKeyRelation[Member] = ForeignKeyField( "seraphsix.Member", related_name="clans", to_field="id" )
class Role(Model): role_id = BigIntField() platform_id = IntField(null=True) is_sherpa = BooleanField(null=True) is_clanmember = BooleanField(null=True) is_new_clanmember = BooleanField(null=True) is_non_clanmember = BooleanField(null=True) is_protected_clanmember = BooleanField(null=True) guild: ForeignKeyRelation[Guild] = ForeignKeyField( "seraphsix.Guild", related_name="roles", to_field="id" ) class Meta: indexes = ("guild", "role_id")
class Dataset(Model): id = IntField(pk=True) collection = ForeignKeyField('models.Collection', related_name='datasets') discarded = BooleanField(default=False) name = TextField() status = IntField(default=0) time_added = IntField() # ms since epoch timestamp = IntField() # ms since epoch setid = custom.SetIDField(unique=True) tags = ManyToManyField('models.Tag', related_name='datasets') acn = ForeignKeyField('models.Acn', related_name='datasets', on_delete=RESTRICT) # Populated by backreferences: comments, files error = make_status_property(1) missing = make_status_property(2) outdated = make_status_property(4) pending = make_status_property(8) def __repr__(self): return f'<{type(self).__name__} {self.setid} {self.name}>'
class Guild(Model): id = BigIntField(pk=True, generated=False) mute_role = BigIntField(null=True) mod_logs = BigIntField(null=True) mod_users = ArrayField(int, default=list) mod_roles = ArrayField(int, default=list) disabled_commands = ArrayField(str, default=list) disabled_channels = ArrayField(int, default=list) disabled_users = ArrayField(int, default=list) ignored_users = disabled_users = ArrayField(int) ignored_channels = disabled_users = ArrayField(int) tags_locked = BooleanField(defualt=False) tags_mod_only = BooleanField(defualt=False) language = CharField(max_length=20, null=False, default="en") # I am related stuff: iam_roles = ArrayField(int, null=True) iam_disabled_users = ArrayField(int, null=True)
class User(Model): id = IntField(pk=True) username = CharField(20, unique=True) password_hash = BinaryField() is_admin = BooleanField(default=False) def check_password(self, to_check: str) -> bool: return check_password_hash(self.password_hash.decode(), to_check) def set_password(self, new_password: str): self.password_hash = generate_password_hash(new_password).encode()
class File(Model): id = IntField(pk=True) dataset = ForeignKeyField('models.Dataset', related_name='files') idx = IntField() # files are counted per dataset missing = BooleanField(default=False) mtime = IntField() # ms since epoch path = TextField() size = IntField() def __repr__(self): return f"<{type(self).__name__} '{self.path}'>"
class User(Model): id = IntField(pk=True) name = CharField(max_length=255, unique=True) password = TextField(null=True) given_name = TextField(null=True) family_name = TextField(null=True) email = TextField(null=True) realm = TextField() realmuid = TextField(null=True) active = BooleanField() time_created = DatetimeField(auto_now_add=True) time_updated = DatetimeField(auto_now=True) groups = ManyToManyField('models.Group', related_name='users')
class Clan(Model): clan_id = BigIntField(unique=True) name = CharField(max_length=255) callsign = CharField(max_length=4) platform = IntField(null=True, validators=[PlatformValidator()]) the100_group_id = IntField(unique=True, null=True) activity_tracking = BooleanField(default=True) guild: ForeignKeyRelation[Guild] = ForeignKeyField( "seraphsix.Guild", related_name="clans", to_field="id" ) members: ReverseRelation["ClanMember"]
class Guild(Model): id = BigIntField(pk=True) prefix = TextField(null=True) mute_role = BigIntField(null=True) level_up_messages = BooleanField(default=True) moderators: ReverseRelation["Moderator"] mutes: ReverseRelation["Mute"] warns: ReverseRelation["Warn"] class Meta: table = "guilds" def __str__(self): return f"<Guild id:{self.id} prefix:{self.prefix} muterole:{self.mute_role}>"
class Modifier(Model): item = ForeignKeyField("models.Item", related_name="modifiers", null=True) item_id: Optional[int] effect = ForeignKeyField("models.Effect", related_name="modifiers", null=True) effect_id: Optional[int] key = CharField(max_length=255) numeric_value = IntField(null=True) string_value = CharField(max_length=255, null=True) expression_value = CharField(max_length=255, null=True) percentage = BooleanField(default=False)
class ApiJob(Model): """API job model.""" requestor: ForeignKeyRelation[ApiUser] = ForeignKeyField( "models.ApiUser", related_name="jobs") request_time = DatetimeField(auto_now=True) complete_time = DatetimeField(null=True) in_progress = BooleanField(default=False) detail = TextField(null=True) class Meta: """Tortoise ORM Config.""" table = "api_jobs"
class GameMember(Model): time_played = FloatField(null=True) completed = BooleanField(null=True) member: ForeignKeyRelation[Member] = ForeignKeyField( "seraphsix.Member", related_name="games", to_field="id" ) game: ForeignKeyRelation[Game] = ForeignKeyField( "seraphsix.Game", related_name="members", to_field="id" ) class Meta: indexes = ("member", "game")
class ClanMemberApplication(Model): approved = BooleanField(default=False) message_id = BigIntField(unique=True) guild: ForeignKeyRelation[Guild] = ForeignKeyField( "seraphsix.Guild", related_name="clanmemberapplications", to_field="id" ) member: ForeignKeyRelation[Member] = ForeignKeyField( "seraphsix.Member", related_name="clanmemberapplications_created", to_field="id" ) approved_by: ForeignKeyRelation[Member] = ForeignKeyField( "seraphsix.Member", related_name="clanmemberapplications_approved", to_field="id", )
class Mute(Model): id = IntField(pk=True) moderator = BigIntField() reason = TextField(null=True) start = DatetimeField(auto_now_add=True) end = DatetimeField() active = BooleanField(default=True) user: ForeignKeyRelation[User] = ForeignKeyField("models.User", related_name="mutes") guild: ForeignKeyRelation[Guild] = ForeignKeyField("models.Guild", related_name="mutes") class Meta: table = "mutes" def __str__(self): return ( f"<Mute id:{self.id} moderator:{self.moderator} " f"reason:'{self.reason}' start:{self.start} end:{self.end} " f"active:{self.active} user:{self.user.id} guild:{self.guild.id}>")
class Member(Model): discord_id = BigIntField(null=True) bungie_id = BigIntField(null=True) bungie_username = CharField(max_length=255, null=True) xbox_id = BigIntField(null=True) xbox_username = CharField(max_length=255, unique=True, null=True) psn_id = BigIntField(null=True) psn_username = CharField(max_length=255, unique=True, null=True) blizzard_id = BigIntField(null=True) blizzard_username = CharField(max_length=255, unique=True, null=True) steam_id = BigIntField(null=True) steam_username = CharField(max_length=255, unique=True, null=True) stadia_id = BigIntField(null=True) stadia_username = CharField(max_length=255, unique=True, null=True) the100_id = BigIntField(unique=True, null=True) the100_username = CharField(max_length=255, unique=True, null=True) timezone = CharField(max_length=255, null=True) bungie_access_token = CharField(max_length=360, unique=True, null=True) bungie_refresh_token = CharField(max_length=360, unique=True, null=True) is_cross_save = BooleanField(default=False) primary_membership_id = BigIntField(unique=True, null=True) clan: ReverseRelation["ClanMember"] games: ReverseRelation["GameMember"] class Meta: indexes = [ PostgreSQLIndex( fields={ "discord_id", "bungie_id", "xbox_id", "psn_id", "blizzard_id", "steam_id", "stadia_id", "the100_id", } ) ]
class Item(Model, metaclass=ItemMeta): id = IntField() name = CharField(max_length=255) desc_id = IntField() plural = CharField(max_length=255, null=True) image = CharField(max_length=255) autosell = IntField(default=0) level_required = IntField(default=0) # Level required # Consumables food = BooleanField(default=False) fullness = IntField(default=0) booze = BooleanField(default=False) inebriety = IntField(default=0) spleen = BooleanField(default=False) spleenhit = IntField(default=0) quality = CharField(max_length=255, null=True) gained_adventures_min = IntField(default=0) gained_adventures_max = IntField(default=0) gained_muscle_min = IntField(default=0) gained_muscle_max = IntField(default=0) gained_mysticality_min = IntField(default=0) gained_mysticality_max = IntField(default=0) gained_moxie_min = IntField(default=0) gained_moxie_max = IntField(default=0) # Usability usable = BooleanField(default=False) multiusable = BooleanField(default=False) combat_usable = BooleanField(default=False) reusable = BooleanField(default=False) combat_reusable = BooleanField(default=False) curse = BooleanField(default=False) # Can be used on others # Equipment hat = BooleanField(default=False) pants = BooleanField(default=False) shirt = BooleanField(default=False) weapon = BooleanField(default=False) weapon_hands = IntField(null=True) weapon_type = CharField(max_length=255, null=True) offhand = BooleanField(default=False) offhand_type = CharField(max_length=255, null=True) accessory = BooleanField(default=False) container = BooleanField(default=False) sixgun = BooleanField(default=False) familiar_equipment = BooleanField(default=False) power = IntField(null=True) required_muscle = IntField(default=0) required_mysticality = IntField(default=0) required_moxie = IntField(default=0) # Collections foldgroup = ForeignKeyField("models.FoldGroup", related_name="items", null=True) foldgroup_id: Optional[int] zapgroup = ForeignKeyField("models.ZapGroup", related_name="items", null=True) zapgroup_id: Optional[int] # NPC Store Info store_row = IntField(null=True) store_price = IntField(null=True) store = ForeignKeyField("models.Store", related_name="items", null=True) store_id: Optional[int] # Flags hatchling = BooleanField(default=False) pokepill = BooleanField(default=False) sticker = BooleanField(default=False) card = BooleanField(default=False) folder = BooleanField(default=False) bootspur = BooleanField(default=False) bootskin = BooleanField(default=False) food_helper = BooleanField(default=False) drink_helper = BooleanField(default=False) guardian = BooleanField(default=False) bounty = BooleanField(default=False) # Can appear as a bounty item candy = IntField() # 0: n/a, 1: simple, 2: complex sphere = BooleanField(default=False) # What is this for? quest = BooleanField(default=False) # is a quest item gift = BooleanField(default=False) # is a gift item tradeable = BooleanField(default=False) # is tradeable discardable = BooleanField(default=False) # is discardable def pluralize(self): return "{}s".format(self.name) if self.plural is None else self.plural @classmethod async def get_or_discover(cls, *args, **kwargs) -> "Item": result = await cls.filter(*args, **kwargs).first() if result is None: id: int = kwargs.get("id", None) desc_id: int = kwargs.get("desc_id", None) return await cls.discover(id=id, desc_id=desc_id) return result @classmethod async def discover(cls, id: int = None, desc_id: int = None): """ Discover this item using its id or description id. The description id is preferred as it provides more information, so if only an id is provided, libkol will first determine the desc_id. Note that this Returns an Item object but it is not automatically committed to the database. It is not sufficient to run `await item.save()` to do this however as tortoise-orm will attempt to `UPDATE` the row because it already has an `id` set. Instead you need to run `awaititem._insert_instance()` explicitly. :param id: Id of the item to discover :param desc_id: Description id of the item to discover """ if id is not None: desc_id = (await request.item_information(cls.kol, id).parse()).descid if desc_id is None: raise ItemNotFoundError( "Cannot discover an item without either an id or a desc_id") info = await request.item_description(cls.kol, desc_id).parse() return Item(**{k: v for k, v in info.items() if v is not None}) async def get_mall_price(self, limited: bool = False) -> int: """ Get the lowest price for this item in the mall :param limited: Include limited sales in this search """ prices = await request.mall_price(self.kol, self).parse() if limited: return prices.limited[0].price return prices.unlimited[0].price async def get_mall_listings(self, **kwargs) -> List["types.Listing"]: return await request.mall_search(self.kol, query=self, **kwargs).parse() async def buy_from_mall( self, listing: "types.Listing" = None, store_id: int = None, price: int = 0, quantity: int = 1, ): if listing is None and store_id is None: listings = await self.get_mall_listings(num_results=quantity, max_price=price) tasks = [ request.mall_purchase(self.kol, item=self, listing=l).parse() for l in listings ] return await asyncio.gather(*tasks) return await request.mall_purchase( self.kol, item=self, listing=listing, store_id=store_id, price=price, quantity=quantity, ).parse() def amount(self): return self.kol.state["inventory"][self] async def use(self, quantity: int = 1, multi_use: bool = True): if self.usable is False: raise WrongKindOfItemError("This item cannot be used") if self.multiusable and multi_use: await request.item_multi_use(self.kol, self, quantity).parse() return tasks = [ request.item_use(self.kol, self).parse() for _ in range(quantity) ] return await asyncio.gather(*tasks)
class Familiar(Model, metaclass=FamiliarMeta): id = IntField(pk=True, generated=False) name = CharField(max_length=255) image = CharField(max_length=255) # Behaviour stat_volley = BooleanField(default=False) stat_sombrero = BooleanField(default=False) item_drop = BooleanField(default=False) meat_drop = BooleanField(default=False) physical_attack = BooleanField(default=False) elemental_attack = BooleanField(default=False) drop = BooleanField(default=False) block = BooleanField(default=False) delevel = BooleanField(default=False) combat_hp = BooleanField(default=False) combat_mp = BooleanField(default=False) combat_meat = BooleanField(default=False) combat_stat = BooleanField(default=False) combat_other = BooleanField(default=False) post_hp = BooleanField(default=False) post_mp = BooleanField(default=False) post_meat = BooleanField(default=False) post_stat = BooleanField(default=False) post_other = BooleanField(default=False) passive = BooleanField(default=False) underwater = BooleanField(default=False) variable = BooleanField(default=False) # Related items hatchling = ForeignKeyField("models.Item", related_name="grows_into", null=True) hatchling_id: Optional[int] equipment = ForeignKeyField("models.Item", related_name="equipment_for", null=True) equipment_id: Optional[int] # Arena skills cave_match_skill = IntField(default=0) scavenger_hunt_skill = IntField(default=0) obstacle_course_skill = IntField(default=0) hide_and_seek_skill = IntField(default=0) # Pokefam pokefam = BooleanField(default=False) # Attributes bites = BooleanField(default=False) has_eyes = BooleanField(default=False) has_hands = BooleanField(default=False) has_wings = BooleanField(default=False) is_animal = BooleanField(default=False) is_bug = BooleanField(default=False) is_flying = BooleanField(default=False) is_hot = BooleanField(default=False) is_mechanical = BooleanField(default=False) is_quick = BooleanField(default=False) is_slayer = BooleanField(default=False) is_sleazy = BooleanField(default=False) is_undead = BooleanField(default=False) wears_clothes = BooleanField(default=False) def __ge__(self, other): if isinstance(other, str): return self.name == other elif isinstance(other, int): return self.id == other else: return self == other @property def have(self) -> bool: return self in self.kol.familiars @property def weight(self) -> Optional[int]: if self.have is False: return None return self.kol.familiars[self].weight
class Trophy(Model): id = IntField() name = CharField(max_length=255) image = CharField(max_length=255) before_ascending = BooleanField(default=False) stateful = BooleanField(default=False)
class Monster(Model): id = IntField(pk=True, generated=False) name = CharField(max_length=255) # Flags boss = BooleanField(default=False) free = BooleanField(default=False) nobanish = BooleanField(default=False) nocopy = BooleanField(default=False) nomanuel = BooleanField(default=False) semirare = BooleanField(default=False) superlikely = BooleanField(default=False) ultrarare = BooleanField(default=False) wanderer = BooleanField(default=False) nowander = BooleanField(default=False) dummy = BooleanField(default=False) ghost = BooleanField(default=False) # Variable Stats _attack = PickleField() _cap = PickleField(default=10000) _defence = PickleField() _experience = PickleField(default=0) _floor = PickleField(default=10) _hp = PickleField() _initiative = PickleField() _ml_factor = PickleField(default=1) _scale = PickleField(default=0) _sprinkle_max = PickleField(default=0) _sprinkle_min = PickleField(default=0) # Static stats attack_element = EnumField(enum_type=Element, null=True) defence_element = EnumField(enum_type=Element, null=True) meat = IntField(default=0) phylum = EnumField(enum_type=Phylum, null=True) physical_resistance = IntField(default=0) async def get_cap(self) -> int: return await expression.evaluate(self.kol, self._cap) async def get_floor(self) -> int: return await expression.evaluate(self.kol, self._floor) async def get_scale(self) -> int: return await expression.evaluate(self.kol, self._scale) async def get_attack(self) -> int: return await expression.evaluate(self.kol, self._attack) async def get_defence(self) -> int: return await expression.evaluate(self.kol, self._defence) async def get_hp(self) -> int: if self._hp is not None: return max(await expression.evaluate(self.kol, self._hp), 1) if self.get_scale() is None: return -1 hp = min( self.get_cap(), max(self.get_floor(), self.kol.get_stat(Stat.Muscle, buffed=True)), ) return max(hp // (4 / 3), 1) @classmethod async def identify(cls, name: str, image: Optional[str] = None) -> "Monster": if image is not None: from .MonsterImage import MonsterImage monster_image = await MonsterImage.get( image=image).prefetch_related("monster") return monster_image.monster if name.startswith(("a ", "an")): name = name[name.find(" "):] return await cls.get(name=name)
class User(BaseModel): """User Model""" FIRST_NAME_LENGHT = 20 LOGIN_LENGHT = 20 LAST_NAME_LENGHT = 20 EMAIL_LENGHT = 60 login = CharField(LOGIN_LENGHT, unique=True, description="The login name of the user.") first_name = CharField(FIRST_NAME_LENGHT, null=True) last_name = CharField(LAST_NAME_LENGHT, null=True) email = CharField(EMAIL_LENGHT, null=True) is_active = BooleanField(null=True, default=False) is_verified = BooleanField(null=True, default=False) verified_at = DatetimeField(null=True) password_hash = CharField(256, null=True) last_login_at = DatetimeField(null=True) roles = ManyToManyField("model.Role", related_name="users", through="user_roles") class Meta: # pylint: disable=too-few-public-methods # pylint: disable=missing-docstring table = "user" ordering = ["login", "last_name", "first_name"] class PydanticMeta: include = ( "id", "login", "last_login_at", "first_name", "last_name", "verified_at", "created_at", ) def __str__(self): return "User" @property def password(self): """ Reading the password is forbidden. """ raise EnvironmentError() @property def is_password_set(self): """ Checks, if the password has been set. """ return self.password_hash is not None @password.setter def password(self, password): """Hash a password for storing.""" self.password_hash = generate_password_hash(password) def verify_password(self, password: str) -> None: """ Generate a hashed password and compere it with the stored password hash. """ if not self.is_password_set: return False return check_password_hash(self.password_hash, password)
class Bonus(Model): item: Optional["libkol.Item"] = ForeignKeyField("models.Item", related_name="bonuses", null=True) # type: ignore item_id: Optional[int] effect: Optional["libkol.Effect"] = ForeignKeyField( "models.Effect", related_name="bonuses", null=True) # type: ignore effect_id: Optional[int] outfit: Optional["libkol.Outfit"] = ForeignKeyField( "models.Outfit", related_name="bonuses", null=True) # type: ignore outfit_id: Optional[int] familiar: Optional["libkol.Familiar"] = ForeignKeyField( "models.Familiar", related_name="passive_bonuses", null=True) # type: ignore familiar_id: Optional[int] throne_familiar: Optional["libkol.Familiar"] = ForeignKeyField( "models.Familiar", related_name="throne_bonus", null=True) # type: ignore throne_familiar_id: Optional[int] modifier: Modifier = EnumField(enum_type=Modifier) # type: ignore numeric_value: Optional[int] = IntField(null=True) # type: ignore string_value: str = CharField(max_length=255, null=True) # type: ignore percentage: bool = BooleanField(default=False) # type: ignore expression_value: Optional[str] = PickleField(null=True) # type: ignore async def get_value( self, normalise: bool = False, smithsness: Optional[int] = None, familiar_weight: Optional[int] = None, hobo_power: Optional[int] = None, ): kol = self.kol if self.string_value: return 1 if self.numeric_value: if self.percentage is False: return self.numeric_value return self.modifier.apply_percentage(kol, self.numeric_value / 100) if self.expression_value: subs = {} if familiar_weight is not None: subs["W"] = familiar_weight if smithsness is not None: subs["K"] = smithsness if hobo_power is not None: subs["H"] = hobo_power return await expression.evaluate(kol, self.expression_value, subs) return 0
class Item(Model, metaclass=ItemMeta): id: int = IntField(pk=True, generated=False) # type: ignore name: str = CharField(max_length=255) # type: ignore desc_id: int = IntField() # type: ignore plural: str = CharField(max_length=255, null=True) # type: ignore image: str = CharField(max_length=255) # type: ignore autosell_value: int = IntField(default=0) # type: ignore level_required: int = IntField(default=0) # type: ignore # Consumables food: bool = BooleanField(default=False) # type: ignore fullness: int = IntField(default=0) # type: ignore booze: bool = BooleanField(default=False) # type: ignore inebriety: int = IntField(default=0) # type: ignore spleen: bool = BooleanField(default=False) # type: ignore spleenhit: int = IntField(default=0) # type: ignore quality: str = CharField(max_length=255, null=True) # type: ignore gained_adventures_min: int = IntField(default=0) # type: ignore gained_adventures_max: int = IntField(default=0) # type: ignore gained_muscle_min: int = IntField(default=0) # type: ignore gained_muscle_max: int = IntField(default=0) # type: ignore gained_mysticality_min: int = IntField(default=0) # type: ignore gained_mysticality_max: int = IntField(default=0) # type: ignore gained_moxie_min: int = IntField(default=0) # type: ignore gained_moxie_max: int = IntField(default=0) # type: ignore # Usability usable: bool = BooleanField(default=False) # type: ignore multiusable: bool = BooleanField(default=False) # type: ignore combat_usable: bool = BooleanField(default=False) # type: ignore reusable: bool = BooleanField(default=False) # type: ignore combat_reusable: bool = BooleanField(default=False) # type: ignore # Can be used on others curse: bool = BooleanField(default=False) # type: ignore # Equipment hat: bool = BooleanField(default=False) # type: ignore pants: bool = BooleanField(default=False) # type: ignore shirt: bool = BooleanField(default=False) # type: ignore weapon: bool = BooleanField(default=False) # type: ignore weapon_hands: Optional[int] = IntField(null=True) # type: ignore weapon_type: Optional[str] = CharField(max_length=255, null=True) # type: ignore offhand: bool = BooleanField(default=False) # type: ignore offhand_type: Optional[str] = CharField(max_length=255, null=True) # type: ignore accessory: bool = BooleanField(default=False) # type: ignore container: bool = BooleanField(default=False) # type: ignore sixgun: bool = BooleanField(default=False) # type: ignore familiar_equipment: bool = BooleanField(default=False) # type: ignore power: Optional[int] = IntField(null=True) # type: ignore required_muscle: int = IntField(default=0) # type: ignore required_mysticality: int = IntField(default=0) # type: ignore required_moxie: int = IntField(default=0) # type: ignore required_class: Optional[CharacterClass] = EnumField( enum_type=CharacterClass, null=True) # type: ignore notes: str = CharField(max_length=255, default="") # type: ignore # Collections foldgroup: Optional["libkol.FoldGroup"] = ForeignKeyField( "models.FoldGroup", related_name="items", null=True) # type: ignore foldgroup_id: Optional[int] zapgroup: Optional["libkol.ZapGroup"] = ForeignKeyField( "models.ZapGroup", related_name="items", null=True) # type: ignore zapgroup_id: Optional[int] outfit_variants = ManyToManyField("models.OutfitVariant", related_name="pieces", null=True) # NPC Store Info store_row: Optional[int] = IntField(null=True) # type: ignore store_price: Optional[int] = IntField(null=True) # type: ignore store: Optional["libkol.Store"] = ForeignKeyField( "models.Store", related_name="items", null=True) # type: ignore store_id: Optional[int] # Flags hatchling: bool = BooleanField(default=False) # type: ignore pokepill: bool = BooleanField(default=False) # type: ignore sticker: bool = BooleanField(default=False) # type: ignore card: bool = BooleanField(default=False) # type: ignore folder: bool = BooleanField(default=False) # type: ignore bootspur: bool = BooleanField(default=False) # type: ignore bootskin: bool = BooleanField(default=False) # type: ignore food_helper: bool = BooleanField(default=False) # type: ignore booze_helper: bool = BooleanField(default=False) # type: ignore guardian: bool = BooleanField(default=False) # type: ignore single_equip: bool = BooleanField(default=True) # type: ignore # Can appear as a bounty item bounty: bool = BooleanField(default=False) # type: ignore # 0: n/a, 1: simple, 2: complex candy: int = IntField() # type: ignore # What is this for? sphere: bool = BooleanField(default=False) # type: ignore # is a quest item quest: bool = BooleanField(default=False) # type: ignore # is a gift item gift: bool = BooleanField(default=False) # type: ignore # is tradeable tradeable: bool = BooleanField(default=False) # type: ignore # is discardable discardable: bool = BooleanField(default=False) # type: ignore # is considered salad when consumed salad: bool = BooleanField(default=False) # type: ignore # is considered beer when consumed beer: bool = BooleanField(default=False) # type: ignore # is considered wine when consumed wine: bool = BooleanField(default=False) # type: ignore # is considered martini when consumed martini: bool = BooleanField(default=False) # type: ignore # is considered saucy when consumed saucy: bool = BooleanField(default=False) # type: ignore # is considered lasagna when consumed lasagna: bool = BooleanField(default=False) # type: ignore # is considered pasta when consumed pasta: bool = BooleanField(default=False) # type: ignore @property def adventures(self): return (self.gained_adventures_min + self.gained_adventures_max) / 2 def pluralize(self): return "{}s".format(self.name) if self.plural is None else self.plural @property def space(self): s = (self.fullness if self.food else self.inebriety if self.booze else self.spleenhit if self.spleen else None) if s is None: raise WrongKindOfItemError("You cannot consume this item") return s async def autosell(self, quantity: int = 1): return await request.autosell_items(self.kol, [self]).parse() @property def cleans_organ(self) -> Optional[Tuple[int, str]]: m = re.match(r"-(\d+) spleen", self.notes) if m is None: return None return (int(m.group(1)), "spleen") async def consume(self, utensil: Optional["Item"] = None ) -> parsing.ResourceGain: if self.food: return await request.eat(self.kol, self, utensil=utensil).parse() elif self.booze: return await request.drink(self.kol, self, utensil=utensil).parse() elif self.spleen: return await request.chew(self.kol, self).parse() else: raise WrongKindOfItemError("You cannot consume this item") @classmethod async def get_or_discover(cls, *args, **kwargs) -> "Item": result = await cls.filter(*args, **kwargs).first() if result is None: id: int = kwargs.get("id", None) desc_id: int = kwargs.get("desc_id", None) return await cls.discover(id=id, desc_id=desc_id) return result @classmethod async def discover(cls, id: int = None, desc_id: int = None): """ Discover this item using its id or description id. The description id is preferred as it provides more information, so if only an id is provided, libkol will first determine the desc_id. Note that this Returns an Item object but it is not automatically committed to the database. :param id: Id of the item to discover :param desc_id: Description id of the item to discover """ if id is not None: desc_id = (await request.item_information(cls.kol, id).parse()).descid if desc_id is None: raise ItemNotFoundError( "Cannot discover an item without either an id or a desc_id") info = await request.item_description(cls.kol, desc_id).parse() return Item(**{k: v for k, v in info.items() if v is not None}) @property def type(self): types = [ "hat", "shirt", "weapon", "offhand", "pants", "familiar_equipment", "accessory", ] return next((t for t in types if getattr(self, t)), "other") @property def slot(self) -> Optional[Slot]: return Slot.from_db(self.type) async def get_description(self): return await request.item_description(self.kol, self.desc_id).parse() async def get_mall_price(self, limited: bool = False) -> Optional[int]: """ Get the lowest price for this item in the mall :param limited: Include limited sales in this search """ prices = await request.mall_price(self.kol, self).parse() if limited and len(prices.limited) > 0: return prices.limited[0].price if len(prices.unlimited) > 0: return prices.unlimited[0].price return None async def get_cf_price(self, days: int = 30) -> Optional[int]: """ Get the average transaction price from Coldfront logs :param days: Number of days of transactions to consider (default 30) """ ts = int(time.time()) params = { "itemid": self.id, "starttime": ts - 60 * 68 * 24 * days, "endtime": ts, } response = await self.kol.request( "http://kol.coldfront.net/newmarket/translist.php", "GET", params=params) result = await response.text() transactions = [ parsing.to_float(t[(t.rfind("@") + 1):]) for t in result[result.find("\n"):].split("\n") if t not in ["", "."] ] return int(mean(transactions)) if len(transactions) > 0 else None async def get_mall_listings(self, **kwargs) -> List["types.Listing"]: return await request.mall_search(self.kol, query=self, **kwargs).parse() async def buy_from_mall( self, listing: "types.Listing" = None, store_id: int = None, price: int = 0, quantity: int = 1, ): if listing is None and store_id is None: listings = await self.get_mall_listings(num_results=quantity, max_price=price) tasks = [] # type: List[Coroutine] for l in listings: q = min(quantity, (l.limit if l.limit > 0 else quantity), l.stock) tasks += [ request.mall_purchase(self.kol, item=self, listing=l, quantity=q).parse() ] quantity -= q return await asyncio.gather(*tasks) return await request.mall_purchase( self.kol, item=self, listing=listing, store_id=store_id, price=price, quantity=quantity, ).parse() async def acquire(self, quantity: int = 1): need = quantity - self.amount if need > 0: await self.buy_from_mall(quantity=need) return True @property def amount(self): return self.kol.inventory[self] + list( self.kol.equipment.values()).count(self) def equipped(self): return self in self.kol.equipment.values() async def equip(self, slot: Optional[Slot] = None) -> bool: actual_slot = self.slot if slot is None else slot if actual_slot is None: raise WrongKindOfItemError("This item cannot be equipped") curr = self.kol.equipment # If it's already there don't worry if curr[actual_slot] == self: return True # If user didn't specify an accessory and we have it in a slot, don't worry if (actual_slot is Slot.Acc1 and slot is None and (curr[Slot.Acc2] == self or curr[Slot.Acc3] == self)): return True return await request.equip(self.kol, self, actual_slot).parse() def have(self): return self.amount > 0 def meet_requirements(self): return (self.kol.level >= self.level_required and self.kol.get_stat(Stat.Muscle) >= self.required_muscle and self.kol.get_stat( Stat.Mysticality) >= self.required_mysticality and self.kol.get_stat(Stat.Moxie) >= self.required_moxie) async def use(self, quantity: int = 1, multi_use: bool = True): if self.usable is False: raise WrongKindOfItemError("This item cannot be used") if self.multiusable and multi_use and quantity > 1: await request.item_multi_use(self.kol, self, quantity).parse() return tasks = [ request.item_use(self.kol, self).parse() for _ in range(quantity) ] return await asyncio.gather(*tasks)