import datetime from typing import Any from peewee import * import resources.Environment as Env from src.model.BaseModel import BaseModel, db_obj from src.model.enums.GameStatus import GameStatus from src.model.enums.crew.CrewChestSpendingReason import CrewChestSpendingReason from src.model.enums.crew.CrewLevelUpgradeType import CrewLevelUpgradeType from src.model.enums.crew.CrewRole import CrewRole from src.model.enums.devil_fruit.DevilFruitAbilityType import DevilFruitAbilityType class Crew(BaseModel): """ Crew class """ name: str | CharField = CharField(max_length=Env.CREW_NAME_MAX_LENGTH.get_int()) can_accept_new_members: bool | BooleanField = BooleanField(default=True) is_active: bool | BooleanField = BooleanField(default=True) disband_date: datetime.datetime | DateTimeField = DateTimeField(null=True) chest_amount: int | BigIntegerField = BigIntegerField(default=0) total_gained_chest_amount: int | BigIntegerField = BigIntegerField(default=0) from_reset_chest_amount: int | BigIntegerField = BigIntegerField(default=0) level: int | IntegerField = IntegerField(default=1) max_abilities: int | IntegerField = IntegerField(default=Env.CREW_MAX_ABILITIES.get_int()) can_promote_first_mate: bool = BooleanField(default=True) can_promote_captain: bool = BooleanField(default=True) max_members: int | IntegerField = IntegerField(default=Env.CREW_MAX_MEMBERS.get_int()) description: str | CharField = CharField( max_length=Env.CREW_DESCRIPTION_MAX_LENGTH.get_int(), null=True ) required_bounty: int | BigIntegerField = BigIntegerField(default=0) allow_view_in_search: bool | BooleanField = BooleanField(default=True) allow_join_from_search: bool | BooleanField = BooleanField(default=True) is_full: bool | BooleanField = BooleanField(default=False) allow_davy_back_fight_request: bool | BooleanField = BooleanField(default=True) auto_accept_davy_back_fight: bool | BooleanField = BooleanField(default=False) auto_accept_join: bool | BooleanField = BooleanField(default=True) # Backref crew_abilities = None crew_chest_spending_records = None crew_join_requests = None crew_member_chest_contributions = None davy_back_fight_participants = None davy_back_fights_challengers = None davy_back_fights_opponents = None crew_members = None class Meta: db_table = "crew" def get_average_bounty(self) -> int: """ Returns the average bounty of the crew :return: The average bounty """ from src.model.User import User return self.crew_members.select(fn.Avg(User.bounty)).scalar() def get_average_location_level(self) -> int: """ Returns the average location level of the crew :return: The average location level """ from src.model.User import User return self.crew_members.select(fn.Avg(User.location_level)).scalar() def get_members(self, limit: int = None, page: int = 1) -> list: """ Returns the crew members ordered by join date :param limit: The number of members to return. If None, returns all members :param page: The page to return. If None, returns the first page :return: The crew member """ from src.model.User import User if limit is not None: return ( self.crew_members.select() .order_by(User.crew_join_date.asc()) .paginate(page, limit) ) return self.crew_members.select().order_by(User.crew_join_date.asc()) def get_members_order_by_davy_back_fight_priority(self) -> list: """ Returns the crew members ordered by Davy Back Fight priority :return: The crew members """ return sorted( self.get_members(), key=lambda x: ( x.crew_davy_back_fight_priority is None, x.crew_davy_back_fight_priority, x.crew_join_date, ), ) @staticmethod def get_by_name_if_active(name: str) -> "Crew": """ Returns the crew by name, case-insensitive :param name: The name :return: The crew """ return Crew.get_or_none((Crew.name**name) & (Crew.is_active == True)) @staticmethod def logical_get(crew_id: int) -> "Crew": """ Returns the Crew if it exists and is active :param crew_id: The crew id :return: The crew """ return Crew.get_or_none((Crew.id == crew_id) & (Crew.is_active == True)) def get_captain(self) -> Any: """ Returns the crew captain :return: The crew captain """ from src.model.User import User return self.crew_members.select().where(User.crew_role == CrewRole.CAPTAIN).get() def get_first_mate(self) -> Any: """ Returns the crew first mate :return: The crew first mate """ from src.model.User import User return ( self.crew_members.select().where(User.crew_role == CrewRole.FIRST_MATE).get_or_none() ) def has_first_mate(self) -> bool: """ Returns True if the crew has a first mate :return: True if the crew has a first mate """ return self.get_first_mate() is not None def get_member_count(self) -> int: """ Returns the number of crew members :return: The number of crew members """ return self.crew_members.select().count() def get_chest_contributions(self) -> list: """ Returns the crew members ordered by contribution :return: The crew members """ from src.model.CrewMemberChestContribution import CrewMemberChestContribution return self.crew_member_chest_contributions.select().order_by( CrewMemberChestContribution.amount.desc() ) def get_powerup_price(self, reason: CrewChestSpendingReason) -> int: """ Returns the next powerup price :param reason: The reason :return: The next powerup price """ # Not level up, price is current level * BASE_PRICE if reason is not CrewChestSpendingReason.LEVEL_UP: return Env.CREW_POWERUP_BASE_PRICE.get_int() * self.level # Level up, price is double the previous return Env.CREW_POWERUP_BASE_PRICE.get_int() * (2 ** (self.level - 1)) def get_powerup_price_formatted(self, reason: CrewChestSpendingReason) -> str: """ Returns the next powerup price formatted :param reason: The reason :return: The next powerup price formatted """ from src.utils.string_utils import get_belly_formatted return get_belly_formatted(self.get_powerup_price(reason)) def get_crew_chest_formatted(self) -> str: """ Returns the crew chest formatted :return: The crew chest formatted """ from src.utils.string_utils import get_belly_formatted return get_belly_formatted(self.chest_amount) def get_active_abilities(self) -> list: """ Returns the crew active abilities :return: The crew active abilities """ from src.model.CrewAbility import CrewAbility return self.crew_abilities.select().where( CrewAbility.expiration_date > datetime.datetime.now() ) def get_active_abilities_types(self) -> list[DevilFruitAbilityType]: """ Returns the crew active abilities types :return: The crew active abilities types """ return list( set([crew_ability.get_ability_type() for crew_ability in self.get_active_abilities()]) ) def get_active_ability(self, ability_type: DevilFruitAbilityType) -> list: """ Returns the crew active ability of the given type :param ability_type: The ability type :return: The crew active ability """ from src.model.CrewAbility import CrewAbility return self.crew_abilities.select().where( (CrewAbility.expiration_date > datetime.datetime.now()) & (CrewAbility.ability_type == ability_type.value) ) def get_next_level_upgrade_type(self) -> CrewLevelUpgradeType: """ Returns the next level upgrade type :return: The next level upgrade type """ return CrewLevelUpgradeType.get_by_level(self.level + 1) def get_upgrade_type_count(self, upgrade_type: CrewLevelUpgradeType) -> int: """ Returns the number of upgrades of the given type :param upgrade_type: The upgrade type :return: The number of upgrades """ if upgrade_type is CrewLevelUpgradeType.MEMBER: return self.max_members return self.max_abilities def level_up(self): """ Level up the crew :return: None """ # Increase max members or abilities upgrade_type = self.get_next_level_upgrade_type() if upgrade_type is CrewLevelUpgradeType.MEMBER: self.max_members += 1 self.can_accept_new_members = True self.set_is_full() elif upgrade_type is CrewLevelUpgradeType.ABILITY: self.max_abilities += 1 self.level += 1 self.save() @staticmethod def reset_level() -> None: """ Reset the crew level :return: None """ # Reset level conditions: list[tuple[bool, int]] = [ # If level / 2 is higher than allowed after reset, set to max ( (Crew.level / 2) > Env.CREW_MAX_LEVEL_AFTER_RESET.get_int(), Env.CREW_MAX_LEVEL_AFTER_RESET.get_int(), ), # If level / 2 is lower than 1 after reset, set to 1 ((Crew.level / 2) < 1, 1), ] # Else, set to level / 2 case_stmt = Case(None, conditions, Crew.level / 2) Crew.update(level=case_stmt).execute() # Reset max allowed members and abilities Crew.update( max_members=(Env.CREW_MAX_MEMBERS.get_int() + fn.ROUND((Crew.level - 1) / 2)), max_abilities=(Env.CREW_MAX_ABILITIES.get_int() + fn.FLOOR((Crew.level - 1) / 2)), ).execute() # Reset is full attribute Crew.set_is_full_for_all_crews() def get_name_escaped(self) -> str: """ Returns the crew name escaped :return: The crew name escaped """ from src.service.message_service import escape_valid_markdown_chars return escape_valid_markdown_chars(self.name) def get_description_escaped(self) -> str: """ Returns the crew description escaped :return: The crew description escaped """ from src.service.message_service import escape_valid_markdown_chars return escape_valid_markdown_chars(self.description) def get_required_bounty_formatted(self) -> str: """ Returns the crew required bounty formatted :return: The crew required bounty formatted """ from src.utils.string_utils import get_belly_formatted return get_belly_formatted(self.required_bounty) def set_is_full(self) -> None: """ Set the crew as full :return: None """ if self.get_member_count() >= self.max_members: self.is_full = True else: self.is_full = False self.save() # Also set Davy Back Fight priority self.set_davy_back_fight_priority() @staticmethod def set_is_full_for_all_crews() -> None: """ Set the crew as full for all crews :return: None """ raw_query = ( "update crew" " set is_full = IF(((select count(*) from user where crew_id = crew.id) <" " crew.max_members), 0, 1)" " where true;" ) db_obj.get_db().execute_sql(raw_query) def get_active_davy_back_fight(self) -> Any: """ Returns the current Davy Back Fight :return: The current Davy Back Fight """ from src.model.DavyBackFight import DavyBackFight return ( DavyBackFight.select() .where( ((DavyBackFight.challenger_crew == self) | (DavyBackFight.opponent_crew == self)) & ( (DavyBackFight.status == GameStatus.COUNTDOWN_TO_START) | (DavyBackFight.status == GameStatus.IN_PROGRESS) ) ) .get_or_none() ) def get_in_progress_davy_back_fight(self) -> Any: """ Returns the in progress Davy Back Fight :return: The in progress Davy Back Fight """ from src.model.DavyBackFight import DavyBackFight return ( DavyBackFight.select() .where( ((DavyBackFight.challenger_crew == self) | (DavyBackFight.opponent_crew == self)) & (DavyBackFight.status == GameStatus.IN_PROGRESS) ) .get_or_none() ) def get_pending_davy_back_fight(self) -> Any: """ Returns the pending Davy Back Fight as challenger :return: The pending Davy Back Fight """ from src.model.DavyBackFight import DavyBackFight return ( self.davy_back_fights_challengers.select() .where(DavyBackFight.status == GameStatus.AWAITING_OPPONENT_CONFIRMATION) .get_or_none() ) def get_penalty_davy_back_fight(self) -> Any: """ Returns the penalty Davy Back Fight :return: The penalty Davy Back Fight """ from src.model.DavyBackFight import DavyBackFight return ( DavyBackFight.select() .where( ( ( (DavyBackFight.challenger_crew == self) & (DavyBackFight.status == GameStatus.LOST) ) | ( (DavyBackFight.opponent_crew == self) & (DavyBackFight.status == GameStatus.WON) ) ) & (DavyBackFight.penalty_end_date > datetime.datetime.now()) ) .get_or_none() ) def get_name_with_deeplink(self, add_level: bool = False): """ Returns the crew name with deeplink :param add_level: Whether to add the level to the name :return: The crew name with deeplink """ from src.service.crew_service import get_crew_name_with_deeplink return get_crew_name_with_deeplink(self, add_level=add_level) def has_active_davy_back_fight(self) -> bool: """ Returns True if the crew has an active Davy Back Fight :return: True if the crew has an active Davy Back Fight """ return self.get_active_davy_back_fight() is not None def has_penalty_davy_back_fight(self) -> bool: """ Returns True if the crew has a penalty Davy Back Fight :return: True if the crew has a penalty Davy Back Fight """ return self.get_penalty_davy_back_fight() is not None def set_davy_back_fight_priority(self, should_reset: bool = False): """ Set the crew members Davy Back Fight priority :param should_reset: Whether to reset the priority :return: None """ from src.model.User import User # Sorting only by join date members: list[User] = ( self.get_members() if should_reset else self.get_members_order_by_davy_back_fight_priority() ) for i, member in enumerate(members): member.crew_davy_back_fight_priority = i + 1 member.save() Crew.create_table()