from __future__ import annotations
import builtins
import inspect
from functools import cached_property
from typing import Iterable, Literal
import renpy
from renpy import persistent
from game.bugfix_additions.debug_info_ren import write_log
from game.bugfix_additions.mapped_list_ren import MappedList
from game.helper_functions.list_functions_ren import all_locations_in_the_game
from game.helper_functions.play_sounds_ren import play_notification_sound
from game.main_character.goals_ren import create_initial_sex_goal, create_initial_stat_goal, create_initial_work_goal, create_new_sex_goal, create_new_stat_goal, create_new_work_goal
from game.main_character.perks.Perks_ren import perk_system
from game.major_game_classes.clothing_related.Clothing_ren import Clothing
from game.major_game_classes.game_logic.ActionList_ren import ActionList
from game.major_game_classes.game_logic.Goal_ren import Goal
from game.major_game_classes.game_logic.StatTracker_ren import StatTracker
from game.map.map_code_ren import HomeHub, MapHub, list_of_hubs
from game.map.MapHub_ren import home_hub, office_hub, strip_club_hub
from game.major_game_classes.character_related.Person_ren import Person, list_of_people
from game.major_game_classes.game_logic.ListenerManagementSystem_ren import ListenerManagementSystem
from game.major_game_classes.game_logic.Room_ren import Room, list_of_places, bedroom
from game.major_game_classes.game_logic.TextMessageManager_ren import TextMessageManager
from game.major_game_classes.serum_related.SerumInventory_ren import SerumInventory
from game.major_game_classes.business_related.Business_ren import Business, Wardrobe
from game.game_screens.subscreens.floating_notification_handler_ren import add_notification

mc: MainCharacter
day = 0
time_of_day = 0
"""renpy
IF FLAG_OPT_IN_ANNOTATIONS:
    rpy python annotations
init -2 python:
"""
import time

class MainCharacter():
    def __init__(self, location, name, last_name, stat_array: list[int], skill_array: list[int], sex_array: list[int]):
        self.name = name
        self.last_name = last_name
        self.energy = 50
        self.designed_wardrobe = Wardrobe("Designed Wardrobe")
        self.business: Business
        self.inventory = SerumInventory()
        self._stats = StatTracker()

        ##Mental stats##
        #Mental stats are generally fixed and cannot be changed permanently.
        self.charisma: int = stat_array[0]#How likeable the person is. Mainly influences marketing, also determines how well interactions with other characters go. Main stat for HR and sales
        self.int: int = stat_array[1] #How smart the person is. Mainly influences research, small bonuses to most tasks. #Main stat for research and production.
        self.focus: int = stat_array[2]#How on task the person stays. Influences most tasks slightly. #Main stat for supplies

        ##Work Skills##
        #Skills can be trained up over time, but are limited by your raw stats.
        self.hr_skill: int = skill_array[0]
        self.market_skill: int = skill_array[1]
        self.research_skill: int = skill_array[2]
        self.production_skill: int = skill_array[3]
        self.supply_skill: int = skill_array[4]

        ##Sex Stats##
        # These are physical stats about the character that impact how they behave in a sex scene. Future values might include penis size or sensitivity.
        self.arousal = 0 #How close to an orgasm you are. You cum when you reach your max_arousal, default 100.
        self.max_arousal = 100

        self.masturbation_novelty = 100 #How novel masturbation is. As something becomes less Novel you convert clarity less efficiently.
        self.locked_clarity = 50 #Clarity generated by events, but not yet released by cumming.
        self.free_clarity = 25 #Clarity unlocked by cumming, available for use unlocking serum traits.
        self.clarity_multiplier = 1.0 # Default clarity multiplier

        ##Sex Skills##
        # These skill represent your knowledge and experience with different types of intimacy. Useful for raising a girls arousal faster than your own.
        self.sex_skills: dict[str, int] = {}
        self.sex_skills["Foreplay"] = sex_array[0] # A catch all for everything that goes on before blowjobs, sex, etc. Includes things like kissing, massages, etc.
        self.sex_skills["Oral"] = sex_array[1] # Your skill at eating a girl out.
        self.sex_skills["Vaginal"] = sex_array[2] # Your skill at different positions that involve vaginal sex.
        self.sex_skills["Anal"] = sex_array[3] # Your skill skill at different positions that involve anal sex.
        #
        # self.max_stamina = 2 # How many times you can seduce someone each day
        # self.current_stamina = 2 # Current stamina.

        self.max_energy = 100 #Your physical energy. Mainly consumed by having sex, slowly comes back during the day (with some actions speeding this up), and a lot of it returns at the end of the day
        self.energy = self.max_energy

        self.main_character_actions: ActionList = ActionList() # A list of actions enabled for the main character when they talk to people. Kind of like a "role" for the MC.

        self._condom = "None"
        self.recently_orgasmed = False #If True you recently orgasmed and aren't hard until your arousal rises to 10 or the encounter ends.

        self.known_home_locations = MappedList(Room, all_locations_in_the_game) #When the MC learns a character's home location the room reference should be added here. They can then get to it from the map.

        self._having_text_conversation: int = None #Holds the person identifier for the current phone conversation
        self.text_conversation_paused = False #Shows the say window as normal for all dialogue with the phone display underneath if having_text_conversation is set to a Person

        self.phone = TextMessageManager()

        self.listener_system = ListenerManagementSystem() #A listener manager to let us enrol to events and update goals when they are triggered.

        #How many free points does the main character have to spend on their skills/abilities
        self.free_stat_points = 0
        self.free_work_points = 0
        self.free_sex_points = 0

        #The maximum score you can have in each of the major skill categories
        #Can be enhanced by Perks (permanent or temporary)
        self.max_stats = 8
        self.max_work_skills = 8
        self.max_sex_skills = 8
        self.max_energy_cap = 200

        #The current goals set for the player to achieve. On completion they gain 1 point towards that class of skills
        self.stat_goal = None
        self.work_goal = None
        self.sex_goal = None

        #The difficulty of goals. Some goals will be removed once the difficulty is high enough, others will be added, and some will have completion requirements based on the difficulty.
        self.stat_goal_difficulty = 0
        self.work_goal_difficulty = 0
        self.sex_goal_difficulty = 0

        self.clarity_purchase_level = 1 #Increased by 1 every time you buy a stat with Clarity.
        # Each level costs 50*(2^level)

        self.log_items = [] #A list of items to display as a log. is a tuple of: [string_to_display, text_style, unix_time]
        self.log_max_size = 30

        self.scrap_goal_available = True

        self.can_skip_time = False #A flag used to determine when it is safe to skip time and when it is not. Left in as of v0.19.0 to ensure missed references do not cause a crash; has no function.

        self.stolen_underwear: dict[str, list[Clothing]] = {} #Person should be a key, and should hold a list of clothing that has been taken by the MC.
        self.event_triggers_dict = {} #General purpose dict for storing event flags related to the MC.
        self._location = None #Location tracking
        self.change_location(location)

    def __getstate__(self): # excludes decorators from serialization
        state = self.__dict__.copy()
        for x in MainCharacter._location_clear_keys:
            state.pop(x, None)
        return state

    @property
    def condom(self) -> bool:
        '''
        Returns True when the Main Character is using a condom or he has slipped it off secretly or it broke.
        All dialogues will assume a condom is in use when this is True.
        '''
        if not hasattr(self, "_condom"):
            self._condom = "None"
        return self._condom in ("Using", "Removed", "Broken")

    @condom.setter
    def condom(self, value: bool):
        if value:
            self._condom = "Using"
        else:
            self._condom = "None"

    @property
    def has_removed_condom(self) -> bool:
        return self._condom == "Removed"

    def remove_condom_stealth(self):
        '''
        Main Character has slipped the condom off without partner noticing.
        '''
        self._condom = "Removed"

    @property
    def has_condom_failed(self) -> bool:
        self._condom == "Broken"

    def condom_breaks(self):
        '''
        The used condom breaks without either participant noticing.
        '''
        self._condom = "Broken"

    @cached_property
    def location(self) -> Room:
        return next((x for x in list_of_places if x.identifier == self._location), bedroom) # fallback location is MC bedroom

    def _set_location(self, value: Room):
        if not isinstance(value, Room):
            write_log("location.setter(): Error new location parameter is not a room.")
            return NotImplemented
        self._location = value.identifier
        self._clear_location_cache()

    _location_clear_keys = ("location", "current_location_hub", "nearby_people", "is_home", "is_at_office", "is_at_work", "is_at_stripclub", "is_in_bed")

    def _clear_location_cache(self):
        for x in MainCharacter._location_clear_keys:
            self.__dict__.pop(x, None)

    @cached_property
    def current_location_hub(self) -> MapHub:
        return next((x for x in list_of_hubs if self.location in x), MapHub("Current", "Current", locations = [self.location], position = home_hub.position))

    @cached_property
    def nearby_people(self) -> tuple[Person]:
        '''
        List of people in same hub or location as MC
        '''
        return tuple(x for x in list_of_people if x.is_at(self.current_location_hub))

    @property
    def stats(self) -> StatTracker:
        return self._stats

    def change_location(self, destination: Room, show_background = True) -> bool:
        '''
        Returns True when MC changes location
        '''
        self.recently_orgasmed = False # reset on location change
        if self.is_at(destination):
            return False
        self._set_location(destination)
        for person in (x for x in list_of_people if x.follow_mc):
            person.change_location(destination)
        if show_background:
            self.location.show_background()
        return True

    def change_stats(self, arousal = None, locked_clarity = None, energy = None, add_to_log = True):
        message = []
        if arousal is not None:
            amount = self.change_arousal(arousal)
            if amount != 0:
                message.append(f"{amount:+} {{image=arousal_token_small}}")
        if locked_clarity is not None:
            amount = self.change_locked_clarity(locked_clarity, add_to_log = False)
            if amount != 0:
                message.append(f"{amount:+} {{image=lust_eye_token_small}}")
        if energy is not None:
            amount = self.change_energy(energy, add_to_log = False)
            if amount != 0:
                message.append(f"{amount:+} {{image=energy_token_small}}")
        if add_to_log and message:
            self.log_event(f"You: {' '.join(message)}", "float_text_yellow")
            if locked_clarity is not None and persistent.clarity_messages:
                effect_strength = builtins.min((amount / 80.0) + 0.4, 1.0)
                renpy.show_screen("border_pulse", effect_strength, _transient = True)

    def change_arousal(self, amount: int):
        amount = builtins.int(amount)
        if self.arousal + amount < 0:
            amount = -self.arousal
        self.arousal += amount
        return amount

    def reset_arousal(self):
        self.arousal = 0

    @property
    def arousal_perc(self) -> float:
        return ((self.arousal * 1.0) / max(self.max_arousal, 1)) * 100

    @property
    def lust_tier(self) -> int:
        if self.locked_clarity > 900:
            return 4
        if self.locked_clarity > 500:
            return 3
        if self.locked_clarity > 250:
            return 2
        if self.locked_clarity > 100:
            return 1
        return 0

    def change_energy(self, amount: int, add_to_log = True) -> int:
        amount = builtins.int(builtins.round(amount))
        if amount + self.energy > self.max_energy:
            amount = self.max_energy - self.energy
        elif amount + self.energy < 0:
            amount = -self.energy

        self.energy += amount

        if add_to_log and amount != 0:
            # self.log_event(f"You: {amount:+} Energy", "float_text_yellow")
            self.log_event(f"You: {{energy=+{amount}}}", "float_text_yellow")
        return amount

    def change_max_energy(self, amount: int, add_to_log = True):
        amount = builtins.int(builtins.round(amount))

        # allow for pushing the mc to higher max_energy_cap
        old_max = self.max_energy
        if self.max_energy + amount > self.max_energy_cap:
            if self.max_energy + amount > 300:  # cap max_energy at 300
                amount = 300 - self.max_energy
            self.max_energy_cap = self.max_energy + amount

        self.max_energy += amount
        self.max_energy = max(self.max_energy, 1)
        
        self.energy = round(self.energy * self.max_energy / old_max) 

        if self.energy > self.max_energy: #No having more energy than max in case we lower max
            self.energy = self.max_energy

        if add_to_log and amount != 0:
            self.log_event(f"You: {amount:+} Max Energy", "float_text_yellow")

    def change_masturbation_novelty(self, amount: int, add_to_log = True):
        amount = builtins.int(builtins.round(amount))
        if amount + self.masturbation_novelty > 100:
            amount = 100 - self.masturbation_novelty

        elif amount + self.masturbation_novelty < 50:
            amount = -(self.masturbation_novelty - 50)

        self.masturbation_novelty += amount

        if add_to_log and amount != 0:
            self.log_event(f"You: {amount:+} Masturbation Novelty", "float_text_yellow")

    def change_locked_clarity(self, amount: int, add_to_log = True) -> int:
        amount = builtins.int(amount * perk_system.clarity_multiplier)
        if perk_system.get_ability_active("Lustful Priorities"):
            amount += 5

        amount = builtins.int(amount * self.clarity_multiplier) # increase clarity by multiplier

        if amount + self.locked_clarity > 1000: # locked clarity maxes out at 1000
            amount = 1000 - self.locked_clarity

        self.locked_clarity += amount

        arousal = builtins.int(amount * .2)
        arousal = min(arousal, 5)   # max change value is 5
        self.change_arousal(arousal)

        if add_to_log and amount != 0:
            self.log_event(f"You: {amount:+} {{image=lust_eye_token_small}} {arousal:+} {{image=arousal_token_small}}", "float_text_blue")

            effect_strength = builtins.min((amount / 80.0) + 0.4, 1.0)
            renpy.show_screen("border_pulse", effect_strength, _transient = True)
        return amount

    def convert_locked_clarity(self, conversion_multiplier = 1.0, with_novelty: int | None = None, add_to_log = True): #TODO: Decide if clarity should decay over time.
        amount = self.locked_clarity * conversion_multiplier
        if with_novelty:
            amount = amount * (with_novelty / 100.0) #NOTE: Novelty is a score from 50 to 100, but is often treated as a percent.
        amount = builtins.int(amount)
        self.locked_clarity = 0
        self.free_clarity += amount

        if add_to_log and amount != 0:
            log_string = f"You: {amount} Clarity Released!"
            if with_novelty and with_novelty < 100:
                log_string += f"\n{100 - with_novelty}% lost due to low Novelty."
            self.log_event(log_string, "float_text_blue")
            renpy.show_screen("cum_screen", _transient = True)

    def spend_clarity(self, amount: int, add_to_log = False):
        amount = min(self.free_clarity, max(0, builtins.int(amount)))
        self.free_clarity -= amount

        if add_to_log and amount != 0:
            self.log_event(f"You: Spent {amount} Clarity", "float_text_blue")

    def add_clarity(self, amount, add_to_log = True): #Adds a flat amount of Clarity, without interacting with Locked Clarity. Used when an outside influence generates Clarity.
        amount = max(0, builtins.int(amount))
        self.free_clarity += amount

        if add_to_log and amount != 0:
            self.log_event(f"You: received {amount} Clarity", "float_text_blue")

    @cached_property
    def is_home(self) -> bool:
        return self.is_at(home_hub)

    @cached_property
    def is_at_office(self) -> bool:
        '''
        Returns True when the Main Character is at the business.
        '''
        return self.is_at(office_hub)

    @cached_property
    def is_at_work(self) -> bool:
        '''
        This property is [DEPRECATED] and only preserved for backwards compatibility.
        USE: mc.is_at_office
        '''
        return self.is_at_office

    @cached_property
    def is_at_stripclub(self) -> bool:
        '''
        Returns True when the Main Character is at the strip club.
        '''
        return self.is_at(strip_club_hub)

    @cached_property
    def is_in_bed(self) -> bool:
        return time_of_day == 4 and self.is_at(bedroom)

    def is_at(self, location: Iterable[Room | MapHub | HomeHub] | Room | MapHub | HomeHub) -> bool:
        '''
        Return True when person is in passed Room, MapHub or HomeHub
        '''
        if isinstance(location, Room):
            return self.location == location
        if isinstance(location, (HomeHub, MapHub)):
            return self.current_location_hub == location
        if isinstance(location, Iterable) or inspect.isgenerator(location):
            return any(x for x in location if self.is_at(x))
        return NotImplemented

    @property
    def has_dungeon(self) -> bool:
        return self.event_triggers_dict.get("dungeon_owned", False)

    @property
    def has_harem_mansion(self) -> bool:
        return self.event_triggers_dict.get("harem_mansion_build", False)

    def run_turn(self):
        self.listener_system.fire_event("time_advance")
        amount = self.change_arousal(-20)
        if amount < 0:  # bleeding arousal increases lust
            self.change_locked_clarity(-amount)
        self.change_energy(builtins.int(self.max_energy * .2), add_to_log = False)

    def run_day(self):
        self.listener_system.fire_event("end_of_day")
        if self.max_energy > .9 * self.max_energy_cap:   # daily max energy bleed, depends on how far the MC has pushed his max energy by workouts
            self.change_max_energy(-1, add_to_log = False)
        self.change_energy(builtins.int(self.max_energy * .6), add_to_log = False)
        self.change_masturbation_novelty(1, add_to_log = False)
        self.change_locked_clarity(self.arousal)    # reset arousal at night creates lust
        self.reset_arousal()
        self.scrap_goal_available = True

    def complete_goal(self, goal: Goal):
        if goal == self.stat_goal:
            self.free_stat_points += 1 #The player gets some new points to spend
            self.stat_goal_difficulty += 1 #Future goals become more difficult
            self.stat_goal = create_new_stat_goal(self.stat_goal_difficulty) #Generate a new goal

        elif goal == self.work_goal:
            self.free_work_points += 1
            self.work_goal_difficulty += 1
            self.work_goal = create_new_work_goal(self.work_goal_difficulty)

        elif goal == self.sex_goal:
            self.free_sex_points += 1
            self.sex_goal_difficulty += 1
            self.sex_goal = create_new_sex_goal(self.sex_goal_difficulty)

    def scrap_goal(self, goal: Goal):
        if goal == self.stat_goal:
            self.stat_goal = create_new_stat_goal(self.stat_goal_difficulty) #Generate a new goal

        elif goal == self.work_goal:
            self.work_goal = create_new_work_goal(self.work_goal_difficulty)

        elif goal == self.sex_goal:
            self.sex_goal = create_new_sex_goal(self.sex_goal_difficulty)

        self.scrap_goal_available = False

    def generate_goals(self):
        self.stat_goal = create_initial_stat_goal(self.stat_goal_difficulty)
        self.work_goal = create_initial_work_goal(self.work_goal_difficulty)
        self.sex_goal = create_initial_sex_goal(self.sex_goal_difficulty)

    def buy_point(self, goal_type: Literal["stat", "work", "sex"], clarity_cost: int = -1):
        if goal_type not in ("stat", "work", "sex"):
            return TypeError

        if goal_type == "stat":
            self.free_stat_points += 1

        elif goal_type == "work":
            self.free_work_points += 1

        elif goal_type == "sex":
            self.free_sex_points += 1

        if clarity_cost == -1:
            clarity_cost = self.buy_point_cost

        self.clarity_purchase_level += 1
        self.spend_clarity(clarity_cost)

    @property
    def buy_point_cost(self):
        point_cost = builtins.int(100 * (2 ** (self.clarity_purchase_level / 2)))
        return builtins.int(point_cost)

    def improve_stat(self, stat_name: Literal["int", "cha", "foc"], amount: int = 1):
        if stat_name not in ("int", "cha", "foc"):
            return TypeError

        if amount > self.free_stat_points:
            amount = self.free_stat_points
        if stat_name == "int":
            self.int += amount
        elif stat_name == "cha":
            self.charisma += amount
        elif stat_name == "foc":
            self.focus += amount

        self.free_stat_points += -amount

    def improve_work_skill(self, skill_name: Literal["hr", "market", "research", "production", "supply"], amount: int = 1):
        if skill_name not in ("hr", "market", "research", "production", "supply"):
            return TypeError

        if amount > self.free_work_points:
            amount = self.free_work_points

        if skill_name == "hr":
            self.hr_skill += amount
        elif skill_name == "market":
            self.market_skill += amount
        elif skill_name == "research":
            self.research_skill += amount
        elif skill_name == "production":
            self.production_skill += amount
        elif skill_name == "supply":
            self.supply_skill += amount

        self.free_work_points += -amount

    def improve_sex_skill(self, sex_string: str, amount: int = 1):
        if amount > self.free_sex_points:
            amount = self.free_sex_points

        if sex_string in self.sex_skills:
            self.sex_skills[sex_string] += amount
        elif sex_string == "stam":
            if amount <= 0:
                return
            tmp = min((self.max_energy_cap-self.max_energy)//20,amount)
            self.max_energy += tmp*20
            self.energy += tmp*20
            amount -= tmp
            self.free_sex_points -= tmp
            if amount<=0 or self.foreplay_sex_skill<self.max_sex_skills or self.oral_sex_skill<self.max_sex_skills or self.vaginal_sex_skill<self.max_sex_skills or self.anal_sex_skill<self.max_sex_skills:
                return
            amount = min(((300-self.max_energy_cap)*5+self.max_energy_cap-self.max_energy) // 20, amount)
            if amount == 0:
                return
            tmp = (self.max_energy_cap-self.max_energy)//5
            self.max_energy_cap += amount * 4 - tmp
            self.max_energy += (amount+tmp)*4
            self.energy += (amount+tmp)*4

        self.free_sex_points += -amount

    def log_event(self, the_text: str = "???", the_text_style: str = "float_text_grey"):
        event_tuple = (the_text, the_text_style, time.time()) #Stores the unix time the event was added so we can run a little animation.
        self.log_items.insert(0, event_tuple)
        while len(self.log_items) > self.log_max_size:
            self.log_items.pop() #Pop off extra items until we are down to size.

    def log_notification(self, the_text: str = "???", the_text_style: str = "float_text_grey", duration = 5):
        add_notification(the_text, the_text_style, duration)

    @property
    def having_text_conversation(self) -> Person:
        return Person.get_person_by_identifier(self._having_text_conversation)

    @having_text_conversation.setter
    def having_text_conversation(self, person: Person = None):
        self._having_text_conversation = None
        if isinstance(person, Person):
            self._having_text_conversation = person.identifier

    def start_text_convo(self, person: Person): #Triggers all the appropriate variables so say entries will go into the phone text log.
        self.phone.register_number(person)
        self.having_text_conversation = person
        self.text_conversation_paused = False
        play_notification_sound()

    def end_text_convo(self): #Resets all triggers from texting someone, so say messages are displayed properly again, ect.
        self.having_text_conversation = None
        self.text_conversation_paused = False

    def pause_text_convo(self): #Keeps the phone UI and display up, but your dialogue and dialogue from any girl other than the one you're texting will display as normal and not be logged.
        self.text_conversation_paused = True

    def resume_text_convo(self): #Start hiding the phone UI again. Use after you have paused a text convo
        self.text_conversation_paused = False

    # def log_text_message(self, the_person, the_message):
    #     #TODO: Allow you to insert arbitrary messages by building history entries here! Use this for a narrator style "[Sent a picture]"!
    #     return

    def steal_underwear(self, person: Person, cloth: Clothing):
        if person.identifier not in self.stolen_underwear:
            self.stolen_underwear[person.identifier] = []

        self.stolen_underwear[person.identifier].append(cloth)

    def get_underwear_list(self) -> list[tuple[str, Clothing]]:
        return_list = []
        for person_identifier, clothing_items in self.stolen_underwear.items():
            person = Person.get_person_by_identifier(person_identifier)
            for item in clothing_items:
                return_list.append((f"{person.display_name}'s {item.display_name}", item))

        return return_list

##########################################
# event day functions                    #
##########################################
    def has_event_day(self, dict_key: str) -> bool:
        '''
        Return True when event day is set
        '''
        return dict_key in self.event_triggers_dict

    def has_event_delay(self, dict_key: str, delay: int = 7) -> bool:
        '''
        Retruns True when dict_key is not set or delay for dict_key has passed
        delay: number of days passed since dict_key was set
        '''
        return not self.has_event_day(dict_key) or self.days_since_event(dict_key) > delay

    def set_event_day(self, dict_key: str, set_day = None):
        '''
        Set event day with passed key, when no set_day is passed, current day is set
        '''
        self.event_triggers_dict[dict_key] = day if set_day is None else set_day

    def get_event_day(self, dict_key: str) -> int:
        '''
        Returns the day value set for key
        When key doesn't exist returns 0
        '''
        return self.event_triggers_dict.get(dict_key, 0)

    def days_since_event(self, dict_key: str) -> int:
        '''
        Number of days passed since value set for key
        When key does not exist returns 0
        '''
        return day - self.event_triggers_dict.get(dict_key, day)

######################
# Sex skill wrappers #
######################

    @property
    def foreplay_sex_skill(self) -> int:
        return self.sex_skills.get("Foreplay", 0)

    @property
    def oral_sex_skill(self) -> int:
        return self.sex_skills.get("Oral", 0)

    @property
    def vaginal_sex_skill(self) -> int:
        return self.sex_skills.get("Vaginal", 0)

    @property
    def anal_sex_skill(self) -> int:
        return self.sex_skills.get("Anal", 0)

##################
# Other wrappers #
##################

    @property
    def owns_strip_club(self) -> bool:
        return self.business.event_triggers_dict.get("foreclosed_stage", 0) >= 5

    @property
    def offspring_count(self) -> int:
        '''
        Total number of children of MC (born)
        '''
        return sum(x.number_of_children_with_mc for x in list_of_people)

    @property
    def girls_knocked_up(self) -> tuple[Person]:
        '''
        All girls currently pregnant by MC
        '''
        return tuple(x for x in list_of_people if (x.knows_pregnant and x.is_mc_father))
