code:
# Gitignore file | DO NOT REMOVE OR EDIT

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/

# Ren'py Projects
project.json
*.rpa
*.rpyc
*.jpeg
*.png
*.bak
/*.exe
/renpy/
/lib/
/game/cache/
/game/gui/
/game/images/
/game/renpy/
/game/saves/
/game/tl/

# Ren'py Log Files
traceback.txt
log.txt
errors.txt

# UnRen specific ignores
unren
*.bat

# VScode and Testing
.vscode/
*.code-workspace
/document-guide

# Project Specific
/game/_02testsuite/tests/
/game/logging.txt
/game/renpy_log.txt

repo_name: pervy/rogues

path: .gitignore

license: unknown


code:
## Summary

(Summarize what you want to happen.)

## Description

(Describe, in as much detail as you can, what the requested feature does. Include why this feature should be included.)

## Requirements

(List, with a checkbox if possible, the requirements for this issue to be considered closed.)

## Tasks

_This section is populated by the developer and reports the expected practical activities to meet the requirements. Useful for estimating and for day-by-day monitoring. Better if it reports checkboxes to track completion._

* [ ] Task 1
* [ ] Task 2
* ...

/labels ~New-Issue ~needs-review ~request


code:
## User story

(Example: As a Player, I should be able to ask a Character to follow me so I can take them to another location.)

## Description

(What should happen, what mechanic should be availible, or what data should be available. We want to say what we want, not how to do it.)

## Requirements

(List, with a checkbox if possible, the requirements for this issue to be considered closed.)

## Tasks

_This section is populated by the developer and reports the expected practical activities to meet the requirements. Useful for estimating and for day-by-day monitoring. Better if it reports checkboxes to track completion._

* [ ] Task 1
* [ ] Task 2
* ...

/labels ~New-Issue ~needs-review


code:
# INIT PRIORITY

Dependencies
    -1000

Common
    -900


code:
# Enable developer logging:
$ config.log = True


code:
python early in trainer:

    attr_max = 1000
    attr_min = 0
    max_modifier = 5
    attributes = {}
    sex_acts = []

    class Attribute(renpy.store.object):
        """
        Represents a character attribute with customizable constraints, modifiers, and bonuses.

        Attributes are numerical values that define various characteristics of characters,
        such as strength, intelligence, or charisma. They can be constrained within specified
        minimum and maximum bounds, modified by factors, and have additional bonuses.

        Attributes:
            value (float): The current value of the attribute.
            minimum (float): The minimum allowed value of the attribute.
            maximum (float): The maximum allowed value of the attribute.
            modifier (float): The modifier factor applied to the attribute.
            bonus (float): Additional bonus added to the attribute value.

        Methods:
            __init__: Initializes an Attribute object.
            __float__: Returns the value of the attribute as a float.
            __repr__: Returns a string representation of the attribute.
            __str__: Returns a string representation of the attribute.
            __add__: Adds a value to the attribute.
            __sub__: Subtracts a value from the attribute.
            __mul__: Multiplies the attribute by a value.
            __truediv__: Divides the attribute by a value.
            __mod__: Computes the modulus of the attribute.
            __pow__: Raises the attribute to a power.
            __iadd__: Adds a value to the attribute in place.
            __isub__: Subtracts a value from the attribute in place.
            __imul__: Multiplies the attribute by a value in place.
            __itruediv__: Divides the attribute by a value in place.
            minimum: Getter and setter for the minimum attribute value.
            maximum: Getter and setter for the maximum attribute value.
            modifier: Getter for the modifier factor applied to the attribute.
            bonus: Getter and setter for the additional bonus added to the attribute value.

        Example:
            # Define an attribute for character strength.
            strength = Attribute(value=10, minimum=0, maximum=100, modifier=1)
        """

        def __init__(self, value=0, **properties):
            """
            Initializes an Attribute object.

            :param value: The initial value of the attribute (default is 0).
            :type value: float
            :param properties: Additional properties for the attribute.
            :type properties: dict
            """
            self._value = value
            self._minimum = properties.pop('min', attr_min)
            self._maximum = properties.pop('max', attr_max)
            self._modifier = min(properties.get('mod', 1), max_modifier)
            self._modifiers = properties.pop('mods', [])
            self.bonus = properties.pop('bonus', 0)

        def constrain(self, value):
            """
            Bind a value between the minimum and maximum.

            :param value: The value to be constrained.
            :type value: float
            :return: The constrained value.
            :rtype: float
            """
            return max(self._minimum, min(value, self._maximum))

        def dual_constrain(self):
            """
            Bind a value using both local and global constraints.

            :param value: The value to be constrained.
            :type value: float
            :return: The constrained value.
            :rtype: float
            """
            return self.constrain(min(self._value, attr_max), attr_min)

        def __float__(self):
            """
            Convert the attribute value to a float.

            :return: The attribute value as a float.
            :rtype: float
            """
            return float(self._dual_constrain(self._value + self.bonus))

        def __repr__(self):
            return "<Character Attribute>"

        # Define all arithmetic operations

        def __add__(self, other):
            return float(self) + other

        def __radd__(self, other):
            return self + other

        def __sub__(self, other):
            return float(self) - other

        def __rsub__(self, other):
            return other - float(self)

        def __mul__(self, other):
            return float(self) * other

        def __rmul__(self, other):
            return self * other

        def __truediv__(self, other):
            return float(self) / other

        def __rtruediv__(self, other):
            return other / float(self)

        def __floordiv__(self, other):
            return float(self) // other

        def __rfloordiv__(self, other):
            return other // float(self)

        def __mod__(self, other):
            return float(self) % other

        def __rmod__(self, other):
            return other % float(self)

        def __pow__(self, other):
            return float(self) ** other

        def __rpow__(self, other):
            return other ** float(self)

        # Define all in-place operations

        def __iadd__(self, other):
            self._value += self._modifier * other
            self._value = self._dual_constrain(self._value)
            return self

        def __isub__(self, other):
            self._value -= self._modifier * other
            self._value = self._dual_constrain(self._value)
            return self

        def __imul__(self, other):
            self._value *= self._modifier * other
            self._value = self._dual_constrain(self._value)
            return self

        def __itruediv__(self, other):
            self._value /= self._modifier * other
            self._value = self._dual_constrain(self._value)
            return self

        # Define comparison operations

        def __eq__(self, other):
            return float(self) == other

        def __ne__(self, other):
            return float(self) != other

        def __lt__(self, other):
            return float(self) < other

        def __le__(self, other):
            return float(self) <= other

        def __gt__(self, other):
            return float(self) > other

        def __ge__(self, other):
            return float(self) >= other

        # Define property getters and setters

        @property
        def minimum(self):
            return self._minimum

        @minimum.setter
        def minimum(self, value):
            self._minimum = value

        @property
        def maximum(self):
            return self._maximum

        @maximum.setter
        def maximum(self, value):
            self._maximum = value

        @property
        def modifier(self):
            return self._modifier

        @modifier.setter
        def modifier(self, value):
            self._modifier = min(value, max_modifier)

        # Define methods for managing modifiers

        def add_modifier(self, modifier):
            """
            Add a modifier to the attribute.

            :param modifier: The modifier to add.
            :type modifier: float
            """
            self._modifiers.append(modifier)

        def remove_modifier(self, modifier):
            """
            Remove a modifier from the attribute.

            :param modifier: The modifier to remove.
            :type modifier: float
            """
            if modifier in self._modifiers:
                self._modifiers.remove(modifier)

    trainer = {}
    traits={}

    class Trait(renpy.store.object):
        """
        Represents a character trait that can modify attributes, apply periodic effects, and be conditionally applied.

        Traits are a way to customize characters by adding specific behaviors, abilities, or characteristics to them.
        They can modify attributes, such as minimum, maximum, modifiers, and bonuses, when applied to a character.
        Additionally, traits can apply periodic effects over time and may have conditions under which they are applied.

        Attributes:
            name (str): The name of the trait.
            description (str): A description of the trait.
            attributes (list of dict): A list of attribute specifications to be modified by the trait.
            application_function (function): A function to be called when the trait is applied to a character.
            remove_function (function): A function to be called when the trait is removed from a character.
            tick_attributes (list of dict): Attribute specifications for periodic effects applied by the trait.
            tick_function (function): A function to be called periodically to apply effects on a character.
            applicable_condition (str): A string evaluating to a boolean condition for trait applicability.

        Methods:
            __init__: Initializes a Trait object.
            __str__: Gets the string representation of the trait.
            is_applicable: Checks if the trait is applicable based on a condition.
            apply: Applies the trait to a character, modifying attributes and applying effects.
            remove: Removes the trait from a character, reverting attribute modifications and effects.
            tick: Applies periodic effects of the trait on a character.

        Example:
            # Define a trait that increases strength and applies a periodic effect.
            strong_trait = Trait(
                name="Strong",
                description="Increases strength and provides a strength boost over time.",
                attributes=[{"attr": "strength", "modifier": 1}],
                tick_attributes=[{"attr": "strength", "modifier": 0.1}],
                applicable_condition="character.level >= 5"
            )
        """

        def __init__(self, name, description=None, attributes=None, **properties):
            """
            Initialize a Trait object.

            :param name: The name of the trait.
            :type name: str
            :param description: A description of the trait (optional).
            :type description: str
            :param attributes: A list of attribute specifications to be modified by the trait (optional).
            :type attributes: list of dict
            :param properties: Additional properties for the trait.
            :type properties: dict
            """
            self.name = name
            self.description = description
            self.attributes = attributes or []
            self.application_function = properties.pop('application_function', None)
            self.remove_function = properties.pop('remove_function', None)
            self.tick_attributes = properties.pop('tick_attributes', [])
            self.tick_function = properties.pop('tick_function', None)
            self.applicable_condition = properties.pop('applicable_condition', None)
            traits[self.name] = self

        def __repr__(self):
            return "<Trait: {!r}>".format(self._name)

        def is_applicable(self):
            """
            Check if the trait is applicable based on the applicable condition.

            :return: True if applicable, False otherwise.
            :rtype: bool
            """
            if self.applicable_condition is not None:
                return renpy.python.py_eval(self.applicable_condition)
            return True

        def apply(self, obj):
            """
            Apply the trait to a character.

            :param obj: The character object to apply the trait to.
            :type obj: TRNCharacter
            """
            if self.is_applicable():
                if self.application_function:
                    self.application_function(obj)
                for attribute_spec in self.attributes:
                    obj.apply_attribute_spec(attribute_spec)

        def remove(self, obj):
            """
            Remove the trait from a character.

            :param obj: The character object to remove the trait from.
            :type obj: TRNCharacter
            """
            if self.remove_function:
                self.remove_function(obj)
            for attribute_spec in self.attributes:
                obj.deapply_attribute_spec(attribute_spec)

        def tick(self, obj):
            """
            Apply periodic effects of the trait on a character.

            :param obj: The character object to apply the effects to.
            :type obj: TRNCharacter
            """
            if self.tick_function:
                self.tick_function(obj)
            for attribute_spec in self.tick_attributes:
                obj.apply_attribute_spec(attribute_spec)

    def execute_trait(parsed_trait):
        '''
            Stores trait in trainer.traits
        '''
        print(parsed_trait)
        name, description, attributes, properties = parsed_trait
        if name not in traits: # check if name in traits
            traits[name] = Trait(name, description, attributes, **properties)
        else:
            print(config.log)
            renpy.log("(%t) is already parsed!" % parsed_trait['name'])
            #config.log(f"{parsed_trait[0]} is already parsed!")
        return

    def parse_trait_attribute(l):
        '''
        Parses an Attribute block of a character trait.
        '''
        attr = {}
        attr['attr'] = l.require(l.name)
        l.require(":")
        l.expect_block("attribute")

        ll = l.subblock_lexer()
        ll.advance()

        while not ll.eob:
            if ll.keyword("bonus"):
                attr['bonus'] = ll.float()
                ll.advance()

            elif ll.keyword("max"):
                attr['max'] = ll.integer()
                ll.advance()

            elif ll.keyword("min"):
                attr['min'] = ll.integer()
                ll.advance()

            elif ll.keyword("modifier"):
                attr['mod'] = ll.float()
                ll.advance()

            elif ll.keyword("decay_modifier"):
                attr['decay_mod'] = ll.float()
                ll.advance()

            else:
                break

        return attr

    def parse_trait(l):
        '''
        Parses a Trainer Character Trait.
        '''
        attributes = []
        tick_attributes = []
        properties = {'applicable_condition' : None, 'application_function':None, 'deapplication_function':None,
                        'tick_function':None}

        # Requires format: `trait foo:`
        name = l.require(l.name)
        l.require(":")
        l.expect_block("trait")

        ll = l.subblock_lexer()
        ll.advance()

        description = ll.string()
        if description is not None :
            ll.expect_eol()
            ll.advance()

        while not ll.eob:
        # Checks all attribute subblocks.
            if ll.keyword('attribute'):
                attributes.append(parse_trait_attribute(ll))
                ll.advance()
            elif ll.keyword('tick_attribute'):
                tick_attributes.append(parse_trait_attribute(ll))
                ll.advance()
            elif ll.keyword('applicable_condition'):
                properties['applicable_condition'] = ll.require(ll.string)
                ll.advance()
            elif ll.keyword('application_function'):
                properties['application_function'] = ll.require(ll.string)
                ll.advance()
            elif ll.keyword('deapplication_function'):
                properties['deapplication_function'] = ll.require(ll.string)
                ll.advance()
            elif ll.keyword('tick_function'):
                properties['tick_function'] = ll.require(ll.string)
                ll.advance()

        if tick_attributes:
            properties['tick_attributes'] = tick_attributes

        return (name, description, attributes, properties)

    renpy.register_statement("trait", parse = parse_trait, execute = execute_trait, block = True)

    renpy.store.Attr = Attribute
    renpy.store.Trait = Trait


code:
""" 01character.rpy
Trainer Character
"""

init -900 python in trainer:

    pure_attributes = [
        'who_prefix',
        'who_suffix',
        'what_prefix',
        'what_suffix',
        'show_function',
        'predict_function',
        'condition',
        'dynamic',
        'image',
        'interact',
        'slow',
        'slow_abortable',
        'afm',
        'ctc',
        'ctc_pause',
        'ctc_timedpause',
        'ctc_position',
        'all_at_once',
        'with_none',
        'callback',
        'type',
        'advance',
        'who_style',
        'what_style',
        'window_style',
        'screen',
        'mode',
        'voice_tag',
        'kind',
        'attributes',
        'traits'
    ]

    @renpy.pure
    class TRNCharacter(renpy.store.ADVCharacter):
        """
        Represents a character in the game, inheriting from ADVCharacter.

        TRNCharacter is a subclass of ADVCharacter specifically designed for characters
        in the game. It extends the basic functionality of ADVCharacter with additional
        attributes, methods, and behaviors.

        Attributes:
            _actual_attributes (set): A set to store actual attribute names.
            _inventory (list): A list to store character's inventory.
            _sex_counter (dict): A dictionary to store sex acts counter.
            _available_sex_acts (list): A list of available sex acts for the character.
            _active (bool): Flag indicating whether the character is active.
            _real_name (str): The real name of the character.
            _last_location (Location): The last location the character was in.
            _location (Location): The current location of the character.
            _nickname (str): The nickname of the character.
            _party (list): A list of characters in the character's party.
            _relationships (list): A list of character relationships.
            _traits (list): A list of character traits.
            _flags (list): A list of flags associated with the character.
            _schedule (dict): A dictionary to store the character's actions as time moves forward

        Methods:
            __setattr__: Overrides setattr to apply changes to attributes.
            __init__: Initializes a TRNCharacter object.
            _apply_trait: Applies a trait to the character.
            _remove_trait: Removes a trait from the character.
            _update_name: Updates the character's displayed name.
            inventory: Property to get the character's inventory.
            sex_counter: Property to get the character's sex counter.
            available_sex_acts: Property to get the available sex acts.
            apply_attribute_modifications: Modifies the character's attributes based on a specification.
            revert_attribute_modifications: Reverts modifications made to the character's attributes.
            on_tick: Runs periodic actions for the character.
            move: Moves the character to a new location.
            add_flag: Adds a flag to the character.
            remove_flag: Removes a flag from the character.
            has_flag: Checks if a flag is present.
            add_item: Adds an item to the character's inventory.
            remove_item: Removes an item from the character's inventory.
            has_item: Checks if the character has an item.
            set_schedule: Changes the schedule to allow a in actions taken over time
            update: Updates the character by applying the schedule's actions
            get_character: Class method to get character data.

        """

        _actual_attributes = set()
        characters = {}

        def __init__(self, name=renpy.character.NotSet, kind=None, **properties):
            """
            Initializes a TRNCharacter object.

            :param name: The name of the character.
            :type name: str
            :param kind: The kind of character.
            :type kind: str
            :param properties: Additional properties for the character.
            :type properties: dict
            """
            self._initializing = True

            self._real_name = name
            self._nickname = properties.pop('nickname', self._real_name)

            self._flags = properties.pop('flags', [])
            self._inventory = properties.pop('inventory', [])
            self._traits = properties.pop('traits', [])

            self._home = properties.pop('home', None)
            self._location = None
            self._last_location = None

            self._active = False
            self._layer = None

            self._following = None
            self._party = []
            self._relationships = properties.pop('relationships', [])

            self._sex_counter = properties.pop('sex_counter', {})
            self._available_sex_acts = properties.pop('available_sex_acts', [])

            self._schedule = properties.pop("schedule", {}) # TODOC

            if not self._sex_counter:
                self._sex_counter = {sex_act: 0 for sex_act in sex_acts}

            if not self._available_sex_acts:
                self._available_sex_acts = sex_acts.copy()

            if kind is None:
                kind = renpy.store.trn

            if "dynamic" not in properties:
                properties["dynamic"] = False

            if 'attributes' in properties:
                for k, v in properties.get('attributes').items():
                    if k.lower() in vars(TRNCharacter()) and k not in attributes and k.capitalize() not in attributes:
                        raise AttributeError("Attribute names should not overwrite non-Attribute TRNCharacter fields")
                    a = Attribute(v)
                    setattr(self, k.lower(), a)
                    self._actual_attributes.add(k.lower())

            for k, v in attributes.items():
                if not hasattr(self, k.lower()):
                    a = Attribute(v)
                    setattr(self, k.lower(), a)
                    self._actual_attributes.add(k.lower())
            TRNCharacter.characters[name] = self
            if self._traits:
                for trait in self._traits:
                    self._apply_trait(trait)

            renpy.store.ADVCharacter.__init__(
                self,
                name,
                kind=None,
                **properties
            )

            delattr(self, "_initializing")

        def __setattr__(self, name, value):
            """Overrides setattr to apply changes to attributes."""
            if name in pure_attributes and not hasattr(self, "_initializing"):
                raise AttributeError(f"Cannot modify inherited attribute '{name}' after initialization")
            elif name in self._actual_attributes and hasattr(self, name):
                a = getattr(self, name)
                a.set_value(value)
            else:
                super().__setattr__(name, value)

        def __repr__(self):
            return "<TRNCharacter: {!r}>".format(self.name)

        @property
        def available_sex_acts(self):
            """Property to get the available sex acts."""
            return self._available_sex_acts

        @property
        def inventory(self):
            """Property to get the character's inventory."""
            return self._inventory

        @property
        def sex_counter(self):
            """Property to get the character's sex counter."""
            return self._sex_counter

        @property
        def schedule(self):
            """Property to get the character's schedule"""
            return self._schedule

        @property
        def location(self):
            """Property to get the character's location"""
            return self._location

        def add_flag(self, flag):
            """Adds a flag to the character."""
            self._flags.append(flag)

        def add_item(self, item):
            """Adds an item to the character's inventory."""
            self._inventory.append(item)

        def apply_attribute_modifications(self, modifications):
            """Modifies the character's attributes based on a specification."""
            for mod in modifications:
                attr_name = mod['attribute']
                if not hasattr(self, attr_name):
                    raise AttributeError(attr_name)

                a = getattr(self, attr_name)

                if 'min' in mod :
                    a.minimum += mod['min']
                if 'max' in mod:
                    a.maximum += mod['max']
                if 'bonus' in mod:
                    a.bonus += mod['bonus']
                if 'modifier' in mod:
                    a.add_modifier(mod['modifier'])
            return

        def _apply_trait(self, trait):
            """Applies a trait to the character."""
            Trait.apply(trait, self)
            return

        def has_flag(self, flag):
            """Checks if a flag is present."""
            return flag in self._flags

        def has_item(self, item):
            """Checks if the character has an item."""
            return item in self._inventory

        def move(self, location):
            """Moves the character to a new location."""
            self._last_location = self._location
            self._location = location
            if (self._last_location is not None):
                self._last_location.remove_person(self) # BUG: Further down the road, will characters always start in a location? If not FIXME
            self._location.add_person(self)
            return

        def remove_flag(self, flag):
            """Removes a flag from the character."""
            self._flags.remove(flag)

        def remove_item(self, item):
            """Removes an item from the character's inventory."""
            self._inventory.remove(item)

        def _remove_trait(self, trait):
            """Removes a trait from the character."""
            Trait.remove(trait, self)
            return

        def _update_name(self):
            """Updates the character's displayed name."""
            return self._nickname if self._nickname != self._real_name else self._real_name

        def revert_attribute_modifications(self, modifications):
            """Reverts modifications made to the character's attributes."""
            for mod in modifications:
                attr_name = mod['attribute']
                if not hasattr(self, attr_name):
                    raise AttributeError(attr_name)

                a = getattr(self, attr_name)

                if 'min' in mod :
                    a.minimum -= mod['min']
                if 'max' in mod:
                    a.maximum -= mod['max']
                if 'bonus' in mod:
                    a.bonus -= mod['bonus']
                if 'modifier' in mod:
                    a.remove_modifier(mod['modifier'])
            return

        def on_tick(self):
            """Runs periodic actions for the character."""
            for trait in self._traits:
                Trait.tick(trait, self)

        def set_schedule(self, schedule):
            """Edits the schedule of the character"""
            self._schedule = schedule

        def update(self):
            """
            RVD: Currently updates the character's location based on the their schedule
            """
            self._schedule.update()

        @classmethod
        def get_character(cls, name):
            """Class method to get character data."""
            try:
                return cls.characters[name]
            except KeyError:
                print(f"The character '{name}' does not exist.")

init -899 python:
    # Default Trainer Character based on ADVCharacter
    trn = trainer.TRNCharacter(
            who_prefix='',
            who_suffix='',
            what_prefix='',
            what_suffix='',

            show_function=renpy.show_display_say,
            predict_function=renpy.predict_show_display_say,

            condition=None,
            dynamic=False,
            image=None,

            interact=True,
            slow=True,
            slow_abortable=True,
            afm=True,
            ctc=None,
            ctc_pause=None,
            ctc_timedpause=None,
            ctc_position="nestled",
            all_at_once=False,
            with_none=None,
            callback=None,
            type='say',
            advance=True,

            who_style='say_label',
            what_style='say_dialogue',
            window_style='say_window',
            screen='say', # say screen that we can change
            mode='say',
            voice_tag=None,

            kind=renpy.store.adv,
            attributes={},
            traits=[])

    renpy.store.TRNCharacter = trainer.TRNCharacter
    renpy.store.trn = trn


code:
"""
This file contains the clothing class that is used to represent a piece of clothing.
"""

init -900 python in clothing:
    from dataclasses import dataclass
    from store import trainer_item

    class InvalidStateError(ValueError):
        pass

    class MissingPartError(KeyError):
        pass

    @dataclass
    class BodyPart:
        name: str
        covered: bool = False
        restricted: bool = False
        value: int = 0

    def exposed_function(body_parts):
        """
        Calculates the total value of body parts that are exposed (not restricted).

        Args:
            body_parts (list[dict]): A list of body parts with their properties.

        Returns:
            int: The sum of the values of exposed body parts.
        """
        return sum([part.get('value') if not part.get('restricted') else 0 for part in body_parts])

    def seen_function(body_parts):
        """
        Calculates the total value of body parts that are visible (not hidden).

        Args:
            body_parts (list[dict]): A list of body parts with their properties.

        Returns:
            int: The sum of the values of visible body parts.
        """
        return sum([part.get('value') if not part.get('hidden') else 0 for part in body_parts])

    def shame_function(appropriate, base_shame, covered, exposed_value, seen_value, shame_modifier=1, covered_modifier=0.3, inappropriate_multiplier=2.0):
        """
        Standalone function to calculate shame based on passed parameters.

        Args:
            appropriate (bool): Whether the clothing is appropriate for the location.
            base_shame (int, float): The base shame value for the clothing.
            covered (bool): Whether the clothing covers the body parts.
            exposed_value (int, float): Value representing how exposed the body parts are.
            seen_value (int, float): Value representing how visible the body parts are.
            shame_modifier (int, float): Additional modifier for adjusting shame.
            covered_modifier (float): Multiplier applied when body parts are covered.
            inappropriate_multiplier (float): Multiplier when clothing is inappropriate for the location.

        Returns:
            float: Calculated shame value.
        """

        # Adjust modifier based on appropriateness.
        if not appropriate:
            shame_modifier *= inappropriate_multiplier

        # Return the calculated shame.
        return shame_modifier * (base_shame + seen_value + exposed_value) * (covered_modifier if covered else 1)

    class Clothing(renpy.store.TRNItem):
        """
        Represents a piece of clothing or accessory.

        Clothing manages clothing attributes and their properties, including hiding and covering body parts,
        appropriateness for specific locations, and calculating shame values.

        Attributes:
            name (str): The name of the clothing.
            category (str): The category this clothing belongs to.
            attributes (list): List of ClothingAttributes controlled by this clothing.
            kind (str): The type of clothing or accessory.
            cost (int): The price of the clothing.
            shame (int): The shame value associated with wearing this clothing.
            restricts (dict): Dictionary of body parts this clothing restricts.
            covers (dict): Dictionary of body parts this clothing covers.
            states (list): List of possible states this clothing can be in.
            poses (list): List of poses this clothing can be worn in.
            location_types (list): List of location types where this clothing is appropriate.

        Methods:
            __init__: Initializes a Clothing object.
            add_attribute: Adds a ClothingAttribute to the clothing.
            appropriate: Checks if the clothing is appropriate for a location.
            calculate_shame: Calculates the shame value based on location and clothing attributes.
            is_covering: Checks if a specific body part is covered by the clothing.
            is_hiding: Checks if a specific body part is hidden by the clothing.
            _update_covers_and_restricts: Updates restricts and covers based on the current state of the clothing.
        """

        def __init__(self, name, category, attributes=[], shame_calculation=shame_function, exposed_function=exposed_function, seen_function=seen_function, **properties):
            # Basic information
            self._name = name
            self._attributes = attributes
            self._layers = properties.get("layers", {})
            self._default_layers = properties.get("defaults", [])

            # Shame and calculation
            self._shame = properties.get("shame", 0)
            self._shame_calculation = shame_calculation
            self._exposed_function = exposed_function
            self._seen_function = seen_function

            # Body part coverage and restrictions
            self._body_parts = {}
            self._covered = False

            # States and poses
            self._states = properties.get("states", [])
            self._current_state = None
            self._poses = properties.get("poses", ["default"])

            # Location appropriateness
            self._location_types = properties.get("location_types", [])

            # Initialize restricts and covers
            self._update_covers_and_restricts()

            super().__init__(name, kind='clothing', **properties)

        # Overload of base class method
        def __repr__(self):
            return "<Clothing: {!r}>".format(self.__str__())

        @property
        def body_parts(self):
            """
            Gets the body parts this clothing covers.

            Returns:
                dict: Dictionary of body parts and their status.
            """
            return self._body_parts

        @property
        def covered(self):
            """
            Indicates whether the clothing item is covering the body parts.

            Returns:
                bool: True if the clothing is covering the body parts, False otherwise.
            """
            return self._covered

        @covered.setter
        def covered(self, state):
            """
            Sets the covered state of the clothing.

            Args:
                state (bool): The new covered state (True or False).
            """
            self._covered = state

        @property
        def current_state(self):
            """
            Gets the current state of the clothing.

            Returns:
                str: The current state of the clothing.
            """
            return self._current_state

        @current_state.setter
        def current_state(self, state):
            """
            Sets the state of the clothing.

            Args:
                state (str): The state to set.
            """
            if state in self.states:
                self._current_state = state
                self._update_covers_and_restricts()
            else:
                raise ValueError(f"State '{state}' not defined for {self._name}")

        @property
        def layers(self):
            """
            Return the layers the clothing is currently using.
            """
            layers = []

            for layer, attributes in self._layers:
                for attribute in attributes:
                    if attribute in self._current_state or attribute in self._default_layers:
                        layers.append(attribute)

            return layers

        @layers.setter
        def layers(self, layers):
            """
            Set the layers the clothing is on, provided by the wardrobe/paper doll system.
            """
            self._layers = layers

        @property
        def location_types(self):
            """
            Gets the location types where the clothing is appropriate.

            Returns:
                list[str]: A list of location types.
            """
            return self._location_types

        @location_types.setter
        def location_types(self, location_types):
            """
            Sets the location types where the clothing is appropriate.

            Args:
                location_types (list[str]): A list of location types.
            """
            self._location_types = location_types

        @property
        def restricts(self):
            """
            Gets the body parts this clothing restricts.

            Returns:
                list: List of body parts restricted by the clothing.
            """
            return [part for part in self._body_parts.values() if part.get('restricted')]

    def add_layer(self, layer):
        """
        Adds a layer to the clothing, if it does not already exist.

        Args:
            layer (str): The layer to be added.
        """
        if layer not in self._layers:
            self._layers.append(layer)

        def add_attribute(self, attribute):
            """
            Adds a ClothingAttribute to the clothing.

            Args:
                attribute (ClothingAttribute): The ClothingAttribute to add.
            """
            self._attributes.append(attribute)

        def appropriate(self, location):
            """
            Checks if the clothing is appropriate for a location.

            Args:
                location (Location): The location to check.

            Returns:
                bool: True if the clothing is appropriate, False otherwise.
            """
            return location.kind in self._location_types

        def assign_to_group(self, group_name, attribute):
            """
            Assigns an attribute to a specific group within the clothing's layers.

            Args:
                group_name (str): The name of the group to assign the attribute to.
                attribute (str): The attribute to assign to the group.
            """
            if group_name not in self._layers:
                self._layers[group_name] = []
            self._layers[group_name].append(attribute)

        def calculate_shame(self, location):
            """
            Calculates the shame value based on location and clothing attributes.

            Args:
                location (Location): The location where the clothing is worn.

            Returns:
                int: The shame value for wearing this clothing in the specified location.
            """
            if self._shame_calculation:
                return self._shame_calculation(
                    self.appropriate(location),
                    self._shame,
                    self._covered,
                    self.exposed_value,
                    self.seen_value
                )
            else:
                return self._shame

        def exposed_value(self):
            """
            Calculates the total value of exposed body parts for the clothing item.

            Returns:
                int: The sum of the values of exposed body parts.
            """
            return self._exposed_function(self._body_parts)

        def get_body_part(self, part_name):
            """
            Retrieves the body part with the given name, raises MissingPartError if not found.

            Args:
                part_name (str): Name of the body part to retrieve.

            Returns:
                BodyPart: The corresponding body part object.

            Raises:
                MissingPartError: If the body part is not found.
            """
            if part_name not in self._body_parts:
                raise MissingPartError(f"Body part '{part_name}' does not exist in clothing '{self._name}'.")
            return self._body_parts[part_name]

        def is_hidden(self, part):
            """
            Checks if a specific body part is hidden by the clothing.

            Args:
                part (str): The body part to check.

            Returns:
                bool: True if the body part is hidden, False otherwise.
            """
            return self._body_parts.get(part, {}).get('state', False) if not self._covered else self._body_parts.get(part, False)

        def is_restricting(self, part):
            """
            Checks if a specific body part is restricted by the clothing.

            Args:
                part (str): The body part to check.

            Returns:
                bool: True if the body part is restricted, False otherwise.
            """
            return self._body_parts.get(part, {}).get('state', False)

        def is_pose_supported(self, pose):
            """
            Check if a pose is supported by this clothing item.
            """
            return pose in self._poses

        def seen_value(self):
            """
            Calculates the total value of visible body parts for the clothing item.

            Returns:
                int: The sum of the values of visible body parts.
            """
            return seen_function(self._body_parts)

        def _update_body_parts(self):
            """
            Updates the body parts coverage and restriction status based on the current state of the clothing.
            This method will update each body part's 'covered' and 'restricted' attributes depending on the state
            of the clothing item.
            """
            # Ensure we have a valid current state
            if self._current_state is None:
                raise InvalidStateError(f"Clothing item '{self._name}' does not have a valid state set.")

            # Clear any previous state effects on body parts
            for part in self._body_parts.values():
                part['covered'] = False
                part['restricted'] = False

            # Update the body parts based on the current state of the clothing
            for part_name, part_data in self._body_parts.items():
                # If the current state dictates covering or restricting a body part, update the flags
                if part_name in self._states[self._current_state].get('covers', []):
                    part_data['covered'] = True
                if part_name in self._states[self._current_state].get('restricts', []):
                    part_data['restricted'] = True

            # Update the overall covered status based on the state of individual body parts
            self._covered = any(part['covered'] for part in self._body_parts.values())

    renpy.store.Clothing = Clothing


code:
"""
01dynamicentry.rpy
Adds a dynamic menu system to Ren'Py.
"""

python early in dynamicentry:

    menu_list = []

    class DynamicEntry(renpy.store.object):
        """
        DynamicEntry is a node for the dynamic menu system.
        When spawned, the DynamicEntry is added to its designated parent and tag lists.
        """

        def __init__(self, name=None, parents=None, caption=None, condition=None, actions=None, kind='entry', **properties):
            # Private variables
            self._actions = actions or []
            self._caption = caption or name
            self._condition = condition
            self._kind = kind
            self._name = name

            # Additional properties
            self._children = properties.get("children", [])
            self._hidden = properties.get('hidden', False)
            self._label = properties.get("label", None)
            self._locked = properties.get('locked', False)
            self._menu_list = properties.get("menu_list", menu_list)
            self._text = properties.get("text", name)

            # Assign parents
            if parents:
                for parent in parents:
                    if isinstance(parent, DynamicEntry):
                        parent.add_child(self)
                    else:
                        self._menu_list[parent].add_child(self)

            # Append to menu list
            self._menu_list.append(self)

        def __str__(self):
            """
            Returns the name of the item, evaluating dynamic names if necessary.

            Returns:
                str: The name of the item.
            """
            what = self._name

            if PY2:
                what = what.encode("utf-8")

            return what

        def __format__(self, spec):
            """
            Formats the item as a string according to the given format specification.

            Args:
                spec (str): The format specification.

            Returns:
                str: The formatted string.
            """
            return format(str(self), spec)

        def __repr__(self):
            """
            Provides a string representation of the item for debugging purposes.

            Returns:
                str: A string representation of the item.
            """
            return "<DynamicEntry: {!r}>".format(self.__str__())

        # Properties
        @property
        def actions(self):
            return self._actions

        @property
        def caption(self):
            return self._caption

        @property
        def condition(self):
            return self._condition

        @property
        def hidden(self):
            return self._hidden

        @property
        def kind(self):
            return self._kind

        @property
        def label(self):
            return self._label

        @property
        def locked(self):
            return self._locked

        @property
        def name(self):
            return self._name

        @property
        def text(self):
            return self._text

        # Public Methods
        def add_child(self, de):
            """
            Adds a child entry to the DynamicEntry.

            Args:
                de (DynamicEntry): The child entry to add.
            """
            if de not in self._children:
                self._children.append(de)

        def add_entry(self, name=None, parents=None, caption=None, condition=None, actions=None, kind='entry', **properties):
            """
            Adds a new entry to the DynamicEntry.

            Args:
                name (str): The name of the entry.
                parents (list): List of parent entries.
                caption (str): The caption of the entry.
                condition (callable): A function to determine if the entry should be visible.
                actions (list): List of actions associated with the entry.
                kind (str): The type of entry.
                **properties: Additional properties for the entry.
            """
            DynamicEntry(name, parents=[self] if parents is None else parents, caption=caption, condition=condition, actions=actions, kind=kind, **properties)

    renpy.store.menu_list = menu_list
    renpy.store.DynamicEntry = DynamicEntry

init 1 python in trainer:
    '''Removes any duplicated items a creator may have missed.'''
    renpy.store.trainer.menu_set = list(set(renpy.store.dynamicentry.menu_list))

label game_menu(dynamic_entry=None, style='trainer', entry_list=trainer.menu_set):
    # Displays the screen game_menu unless supplied a DynamicMenu.

    $ de = trainer.fetch_entry(dynamic_entry, entry_list)

    if de is not None and isinstance(de, DynamicEntry):
        call expression "dynamic_menu" pass (de)

    call screen game_menu(de)
    call expression "game_menu" pass (de)

label dynamic_menu(dynamic_entry=None, entry_list=trainer.menu_set):
    # Calls proper screen based on entry.

    $ de = trainer.fetch_entry(dynamic_entry, entry_list)

    if de is None or not isinstance(de, DynamicEntry):
        call expression "game_menu" pass (de)

    if de.kind == "menu":
        call screen dynamic_choice(de, entry_list)
    elif de.kind == "interaction":
        call screen dynamic_interaction(de, entry_list)
    elif de.kind == "location":
        call screen dynamic_location(de, entry_list)
    elif de.kind == "sex":
        call screen dynamic_sex(de, entry_list)
    else:
        call screen dynamic_choice(de, entry_list)

    jump game_menu


code:
init offset = -99

python early in emotion:

    class ExpressionController:
        def __init__(self, character, **properties):
            self._character = character
            self._expression_groups = properties.get("groups", [])  # List to hold references to ExpressionGroup objects
            self._max_count = properties.get("max_count", 5)  # Number of previous values to consider for averaging
            self._average_intensity = 0
            self._average_positivity = 0
            self._intensity_values = []
            self._positivity_values = []
            self._reaction_count = 0
            self._reaction_intensity = 0
            self._reaction_positivity = 0

        def add_expression_group(self, group):
            """
            Add an ExpressionGroup object to the controller.

            Args:
                group (ExpressionGroup): The ExpressionGroup object to add.
            """
            self._expression_groups.append(group)

        def add_reaction(self, intensity, positivity):
            """
            Add a reaction to the character's emotion.

            Args:
                intensity (float): The intensity of the reaction.
                positivity (float): The positivity of the reaction.
            """
            self._reaction_intensity += intensity
            self._reaction_positivity += positivity
            self._intensity_values.append(self._reaction_intensity)
            self._positivity_values.append(self._reaction_positivity)
            self._reaction_count += 1

        def update_expression(self):
            """
            Update the character's emotion based on the given positivity and intensity values.
            """
            # Calculate the averages
            self._average_intensity = sum(self._intensity_values) / len(self._intensity_values)
            self._average_positivity = sum(self._positivity_values) / len(self._positivity_values)

            # Update expression groups
            self._display_working_emotion()

        def _apply_expression(self, intensity, positivity):
            """
            Apply the selected expression to the character based on the current intensity and positivity values.
            """
            for group in self._expression_groups:
                group.update_expression(intensity, positivity)

        def _display_current_emotion(self):
            """
            Display the current emotion of the character.
            """
            self.apply_expression(self._reaction_intensity, self._reaction_positivity)

        def _display_working_emotion(self):
            """
            Display the working emotion of the character.
            """
            self.apply_expression(self._average_intensity, self._average_positivity)

        def _trim_reactions(self):
            if len(self._positivity_values) > self._max_count:
                self._positivity_values.pop(0)
            if len(self._intensity_values) > self._max_count:
                self._intensity_values.pop(0)


code:
"""
events.rpy

Houses the Event class and EventHandler class -- used for all forms of actions including dailies, repeatables, interactable events.
EventHandler has one instance and should keep track of ALL events regardless of where they are.
"""

init -1000 python in trainer:
    import json
    import re

    # TODO: Rewrite all execeptions into a seperate file #189
    class EventInvalidKeyError(Exception):
        """
        Exception raised when the key is not an instance of a string.
        """
        pass

    class EmptyConditionsExceptions(Exception):
        """
        Exception raised when there are no conditions in this event to meet requirements.
        """
        pass

    class SameEventName(Exception):
        """
        Exception raised when event names/labels must be different from one another.
        """
        pass

    class Event(renpy.store.object):
        """
        Event object that stores all the possible variables needed for all types of events and the required specifications.

        :param label: The label of the event.
        :type label: str
        :param conditions: The conditions for the event.
        :type conditions: list
        :param trigger: The trigger for the event.
        :type trigger: str
        :param kwargs: Additional keyword arguments for optional parameters.
        """

        def __init__(self, label, conditions, trigger, **kwargs):
            '''
            Event object that stores all the possible variables needed for all types of events and the required specifications
            '''
            self._label = label
            self._conditions = conditions
            self._cleared_conditions = [True] if (len(conditions) <= 0) else [False for i in range(len(conditions))]
            self._trigger = trigger
            self._id = None

            # Optional
            self._static = kwargs.get("static", False)
            self._repeatable = kwargs.get("repeatable", False)
            self._random = kwargs.get("random", False)
            self._weight = int(kwargs.get("weight", 0))
            self._weight_mod = 0
            self._special = kwargs.get("special", False)
            self._active = kwargs.get("active", False)
            self._complete = False
            self._interactable = kwargs.get("interactable", False)
            self._button_text = kwargs.get("button_text", None)
            self._incr_time = int(kwargs.get("incr_time", 0))
            if self._button_text is None:
                self._button_text = re.sub("_"," ", self._label).capitalize()
            self._tags = kwargs.get("event_tags", ['all'])

        def __str__(self):
            """
            Returns a string representation of the Event object.
            """
            return f"Event object {self._label}, event_tags: {self.tags}, conditions: {self.conditions}, trigger: {self.trigger}"

        @property
        def label(self):
            """
            :return: The label of the event.
            :rtype: str
            """
            return self._label

        @property
        def button_text(self):
            """
            :return: The button text of the event.
            :rtype: str
            """
            return self._button_text

        @property
        def weight(self):
            """
            :return: The weight of the event.
            :rtype: int
            """
            return self._weight + self._weight_mod

        @property
        def weight_mod(self):
            """
            :return: The weight modifier of the event.
            :rtype: int
            """
            return self._weight_mod

        @property
        def incr_time(self):
            """
            :return: The time increment of the event.
            :rtype: int
            """
            return self._incr_time

        @weight_mod.setter
        def weight_mod(self, mod):
            """
            Sets the weight modifier for the event.

            :param mod: The weight modifier.
            :type mod: int
            """
            self._weight_mod = mod

        @property
        def id(self):
            """
            :return: The ID of the event.
            :rtype: int
            """
            return self._id

        @id.setter
        def id(self, id):
            """
            Sets the ID for the event.

            :param id: The ID of the event.
            :type id: int
            """
            self._id = id

        @property
        def tags(self):
            """
            :return: The tags of the event.
            :rtype: list
            """
            return self._tags

        @tags.setter
        def tags(self, tags):
            """
            Sets the tags for the event.

            :param tags: The tags of the event.
            :type tags: list
            """
            self._tags = tags

        def is_static(self):
            """
            :return: Whether the event is static.
            :rtype: bool
            """
            return self._static

        def is_interactable(self):
            """
            :return: Whether the event is interactable.
            :rtype: bool
            """
            return self._interactable

        def is_repeatable(self):
            """
            :return: Whether the event is repeatable.
            :rtype: bool
            """
            return self._repeatable

        def is_random(self):
            """
            :return: Whether the event is random.
            :rtype: bool
            """
            return self._random

        def is_special(self):
            """
            :return: Whether the event is special.
            :rtype: bool
            """
            return self._special

        def is_active(self):
            """
            :return: Whether the event is active.
            :rtype: bool
            """
            return self._active

        def set_active(self, active):
            """
            Sets the active status for the event.

            :param active: The active status.
            :type active: bool
            """
            self._active = active

        def is_complete(self):
            """
            :return: Whether the event is complete.
            :rtype: bool
            """
            return self._complete

        def set_complete(self, com):
            """
            Sets the complete status for the event.

            :param com: The complete status.
            :type com: bool
            """
            self._complete = com

        def update(self):
            """
            Iterates through all event conditions and checks if they have been cleared.
            """
            for i, condition in enumerate(self._conditions):
                if self._cleared_conditions[i]: continue
                if check_condition(condition): self._cleared_conditions[i] = True

        def is_triggered(self):
            """
            Checks if the event has been triggered.

            :return: Whether the event is triggered.
            :rtype: bool
            """
            return all(self._conditions) and check_condition(self._trigger)

    def check_condition(condition):
        """
        Helper function that evaluates the conditions of the events if conditions are proper
        # TODO: Have a creator defined statement that helps creates conditions easier
        """
        try:
            return eval(condition)
        except SyntaxError as synErr:
            renpy_log.warning("You made a improper expression, look at the operators and variables to see if you structured properly")
            renpy_log.warning(str(synErr) + " in " + str(condition))
            return False
        except Exception as err:
            renpy_log.warning("Something went wrong updating event when checking condition")
            renpy_log.warning(str(err) + " in " + str(condition))
            return False

    class EventHandler(renpy.store.object):
        """
        Handles (updates) and collects all events created
        """

        DEFAULT_WEIGHT = 25

        def __init__(self):
            self.__events = {}
            self.__active_events = {}
            self.__event_names = {}
            self.__queue_events = []

        def get_interact_events(self,tag):
            """
            Returns a list of events based on a tag

            :return: A list of events
            :rtype: list
            """
            interact_events = []

            for evnt in self.__events[tag]:
                if evnt.is_interactable():
                    if evnt.is_triggered():
                        interact_events.append((evnt.button_text, (evnt, "event")))
            return interact_events

        def calculate_weighted_events(self, random_events):
            """
            Algorithem used to pick event based on event weights

            :param random_events:
            :type random_events:

            :return: An event random selected by weight
            :rtype: A
            """
            if len(random_events) <= 0: return None
            tiers = {self.DEFAULT_WEIGHT: None}
            for evnt in random_events:
                if evnt.weight in tiers.keys():
                    tiers[int(evnt.weight)].append(evnt)
                else:
                    tiers[int(evnt.weight)] = [evnt]
            total_weight = 0
            for weight in tiers.keys():
                total_weight = total_weight + weight
            roll = renpy.random.randint(0, total_weight - 1)
            running_total = 0
            tier_keys = list(tiers.keys())
            tier_keys.sort()
            for i in range(len(tier_keys)):
                running_total = running_total + tier_keys[i]
                if roll < running_total:
                    rdm_evnt = renpy.random.choice(tiers[tier_keys[i]])
                    return (rdm_evnt)

        def update_events(self, *tags):
            """
            updates certain events based on a tag

            :param tags: A list of "tags" for key arguments
            :type tags: str
            """
            # renpy_log.info(tags)
            # renpy_log.info( self.__events.keys())
            for event_tag in tags:
                if event_tag in self.__event_names:
                    self.__event_names[event_tag].update()
                    continue
                if event_tag not in self.__events.keys():
                    continue
                for evnt in self.__events[event_tag]:
                    evnt.update()
                    renpy_log.update
                    if evnt.is_triggered():
                        self.__queue_events.append(evnt)

        def play_triggered_events(self):
            """
            Plays the assorted events after there conditions have been met and the trigger is set
            """
            events_to_play = []
            random_events = []
            for evnt in self.__queue_events:
                if evnt.is_random():
                    random_events.append(evnt)

            rdm_evnt = self.calculate_weighted_events(random_events)

            if rdm_evnt is not None:
                random_events.remove(rdm_evnt)
                for evnt in random_events:
                    evnt.weight_mod = (evnt.weight_mod + 5)
                rdm_evnt.weight_mod = 0
                play_event(rdm_evnt)

            self.__queue_events.clear()

        def add_event(self,event):
            """
            Function for sorting and filtering a newly created even into the eventhandler

            :param event: The event object to be added.
            :type event: Event
            """
            #TODO look out for events with duplicate names
            self.__event_names[event.label] = event
            for event_tag in event.tags:
                if event_tag in self.__events.keys():
                    self.__events[event_tag].append(event)
                else:
                    self.__events[event_tag] = [event]

    def play_event(evnt):
        """
        Global function that will play the event.
        The event should be its own designated label to call to.

        :param evnt: Event object to be played.
        :type evnt: Event
        """
        if renpy.has_label(evnt.label):
            game_clock.advance_time(actions = evnt.incr_time)
            # MUST BE LAST IN THE CODE LINE! Terminates any othe code line because control will be given to next statement
            renpy.call(evnt.label)
        else:
            renpy.call("no_event_label", evnt.label)
            renpy_log.info("Please change you ALSO created a label with dialouge with the same name as the event!")

    #### INITIALIZE EVENT_HANDLER
    renpy.store.event_handler = EventHandler()

    renpy.store.TEvent = Event
    renpy.store.TEventHandler = EventHandler
    renpy.store.play_tevent = play_event

init python early:
    import re

    def check_event_params(l, event_params):
        """
        Sorts the event parameters into boolean, string, or integer based
        # TODO: Reworked into a high level wrapper attributes

        :param event_params: A hashmap to store all the parameters.
        :type event_params: dictionary
        """
        event_params["conditions"] = ["True"]
        event_params["trigger"] = "True"
        while l.advance():

            for param in ("interactable", "static", "active", "repeatable", "random"):
                # set_bool_param(l, event_params,param)
                pass

            for param in ("button_text","trigger"):
                # set_string_param(l, event_params,param)
                pass

            for param in ("weight", "incr_time"):
                # set_int_param(l, event_params, param)
                pass

            if l.keyword("event_tags"):
                tags = l.rest()
                tag_list = re.split(" ", tags)
                event_params["event_tags"] = tag_list

            if l.keyword('conditions'):
                ll = l.subblock_lexer()
                # Not really satisfied with nested while loop; will try to think of an alternative
                while(ll.advance()):
                    # description
                    # if ll.keyword("description"):

                    event_params['conditions'].append(ll.simple_expression())

        return event_params

    def parse_event(l):
        """
        Interprets the event parameters from the CDS

        :param l: The lexer object that contains interpretable data.
        :type l: lexer
        """
        name = l.require(l.name)
        l.require(":")
        l.expect_eol()
        event_params = {}
        _label = re.sub('[ ]','_',re.sub('[()[\]{}+!@#$%^&*-]','',name)).lower()
        event_params["label"] = _label
        l.expect_block(f"Event block {_label} requires properties")
        return check_event_params(l.subblock_lexer(), event_params)

    def execute_event(event_params):
        """
        Generates the event objects and places it into the Event Handler

        :param event_params: A hashmap to store all the parameters.
        :type event_params: dictionary
        """
        try:
            new_event = trainer.Event(**event_params)
            renpy.store.event_handler.add_event(new_event)
        except Exception as err:
            renpy_log.warning("********************************* ERROR EXCEPTION HANDLING 'execute_update_event *********************************")
            renpy_log.warning(err)
            renpy_log.warning(f"event_parameters -- {event_params}")
            renpy_log.warning("********************************* ERROR EXCEPTION HANDLING 'execute_update_event *********************************")

    def lint_event(event_params):
        """
        Error checking for events
        # TODO: Improve error checking on event addition

        :param event_params: A hashmap to store all the parameters.
        :type event_params: dictionary
        """
        # try:
            # event_params["label"]
            # if len(event_params["conditions"]) <= 0:
            #     raise EmptyConditionsExceptions
            # checks for conditions
            # for condition in event_params["conditions"]:
            #     renpy.try_eval("Creating An Event", conditions)
            #     eval(condition)
            # checks for trigger
            # eval(event_params["trigger"])

        # except EmptyConditionsExceptions as err:
        #     renpy.error("Please add conditions to the event!")
        #     renpy_log.warning("Please add conditions to the event!")
        #     event_params["conditions"].append(True)
        # except Exception as err:
        #     renpy.error("Please fix the conditions -- non-empty string or proper expressions")
        #     renpy_log.warning("Please fix the conditions -- non-empty string or proper expressions")
        #     renpy_log.warning(err)
        return

    renpy.register_statement("add event", parse = parse_event, lint = lint_event, execute = execute_event, block = True, )

    def parse_condition(l):
        """
        Will interprete whether the conditions of the events are logical

        :param l: The lexer object that contains interpretable data.
        :type l: lexer
        """
        sl = l.subblock_lexer()
        py_expressions = []
        while sl.advance():
            py_expressions.append(sl.simple_expression())
            # py_expressions.append(sl.string())

        return py_expressions

    def execute_condition(o):
        """
        Generates in the conditions inside the event objects

        :param o: The condition object that contains interpretable data.
        :type o: python expression
        """
        renpy_log.info(f"Renpy conditions: {o}")
        string_manipe = []
        for exp in o:
            # new_exp = re.sub("\W+| in | or | not | is | is not | not in | and | == | != | < | > | <= | >= ",',',exp)
            new_exp = re.sub("( and )|( in )|( not )|( or )|( is )|\W+",' ',exp)
            renpy_log.info(f"Object type: {exp}")
            renpy_log.info(f"FIXED Object type: {new_exp}")
            string_manipe.append(new_exp.split())

        renpy_log.info(f"String Manip: {string_manipe}")
    def l_cond(o):
        """
        Linter for conditions
        """
        pass

    renpy.register_statement("py_cond", parse = parse_condition, lint = l_cond, execute = execute_condition, block = True)

    # EVENT UPDATE CCS (specific)

    def parse_event_update(l):
        """
        Event tag formater

        :param l: Lexer object that contains event update commands
        :type l: lexer
        """
        event_tags = re.split(" ", l.rest())
        return event_tags

    def execute_update_event(tags):
        """
        Updates certain events based on the tags

        :param tags: Tag for key arguments
            :type tags: str
        """
        global event_handler
        event_handler.update_events(tags)

    def lint_update_event(tags):
        """
        Linter for upddating events based on tags

        :param tags: Tag for key arguments
        :type tags: str
        """
        pass

    renpy.register_statement("update events", parse = parse_event_update, execute = execute_update_event, lint = lint_update_event)


code:
"""
    01helpers.rpy

    This file contains globally accessible helper functions.
"""

init -2 python in trainer:

    ######################################################
    # Sex acts functions:
    ######################################################

    def has_available_sex_act(who, sex_act):
        return sex_act in who.available_sex_acts

    def add_available_sex_act(who, sex_act):
        if sex_act not in who.available_sex_acts:
            who.available_sex_acts.append(sex_act)

    def remove_available_sex_act(who, sex_act):
        if sex_act in who.available_sex_acts:
            who.available_sex_acts.remove(sex_act)

    ######################################################
    # Sex counter functions:
    ######################################################

    # ???: those "*_sex_act" looks kinda bad, but i don't know how it's better to shorten it.
    def can_perform_sex_act(who, sex_act):
        return sex_act in who.sex_counter

    def get_count_per_sex_act(who, sex_act):
        return who.sex_counter[sex_act]

    def add_sex_act_count(who, sex_act, increment=1):
        who.sex_counter[sex_act] += increment

    ######################################################
    # Inventory functions:
    ######################################################

    def add_items(entity, items):
        if not isinstance(items, list):
            items = [items]

        for item in items:
            entity.add_item(item)

    def remove_items(entity, items):
        if not isinstance(items, list):
            items = [items]

        for item in items:
            try:
                entity.remove_item(item)
            except:
                renpy.log(f"{entity} doesn't have an {item}")

    def has_items(entity, items):
        if not isinstance(items, list):
            items = [items]

        # ???: Is this logic correct or should it return status of each item separated?
        for item in items:
            if not entity.has_item(item):
                return False

        return True

    def get_items(entity):
        return entity.inventory

    def move_items(entity_from, entity_to, items):
        if has_items(entity_from, items):
            add_items(entity_to, items)
            remove_items(entity_from, items)
        else:
            # Would do something later (probably?).
            pass

    ######################################################
    # Flags functions:
    ######################################################

    def add_flag(entities, flag):
        if not isinstance(entities, list):
            entities = [entities]

        for entity in entities:
            if not is_flag_present(entity, flag):
                entity.add_flag(flag)

    def remove_flag(entities, flag):
        if not isinstance(entities, list):
            entities = [entities]

        for entity in entities:
            if is_flag_present(entity, flag):
                entity.remove_flag(flag)

    def is_flag_present(entities, flag):
        if not isinstance(entities, list):
            entities = [entities]

        for entity in entities:
            if not entity.is_flag_present(flag):
                return False

        return True

    def is_flag_present(entities, flag):
        if not isinstance(entities, list):
            entities = [entities]

        for entity in entities:
            if not entity.is_flag_present(flag):
                return False

        return True

    ######################################################
    # Character functions:
    ######################################################

    def trait_comparison(who, trait_1, trait_2):
        if getattr(who, trait_1) >= trait_2:
            return True
        else:
            return False

    def petname_check(who, petname):
        petname_info = store.self_petnames[petname]
        if trait_comparison(who, petname_info[0], petname_info[1]):
            return True
        else:
            return False

    def relationship_check(who, relationship):
        relationship_info = store.relationship_types[relationship]
        if trait_comparison(who, relationship_info[0], relationship_info[1]):
            return True
        else:
            return False

    def party_check(who, following, trait, requirement):
        """
        Function stub to check if character actually wants to follow someone else
        other characters may form their parties without any trait requirements (because they simply dont exist between other characters)
        """
        if trait == "no trait provided":
            # check in case if party is being created between other characters
            #TODO: change "mc" to actual main character
            #TODO: maybe add relationship requirement for this case
            if following is not mc:
                return True
            else:
                return False
        else:
            if trait_comparison(who, trait, requirement):
                return True
            else:
                return False

    ######################################################
    # Menu functions:
    ######################################################

    menu_set = list(set(renpy.store.dynamicentry.menu_list))

    def fetch_entry(dynamic_entry, entry_list = menu_set):
        """ This function finds a dynamic_entry and returns it """

        de = dynamic_entry
        try:
            if not isinstance(de, DynamicEntry):
                de = [entry for entry in menu_list if entry.text == dynamic_entry]
            return de
        except ValueError:
            return False

    def build_menu(dynamic_entry):
        """ Builds a menu from the supplied Dynamic Entry node.

        Args:
            dynamic_entry: An object of DynamicEntry.

        Returns:
            list: A list of Dynamic_Entry objects.
        """
        menu_list = []

        for child in dynamic_entry.children:
            # Checks to condition to show menu entry.
            if child.condition is None or (child.condition is not None and renpy.python.py_eval(child.condition)):
                menu_list.append(child)

        return menu_list

    ######################################################
    # Party / Follower functions:
    ######################################################

    def follow(who, following, trait="no trait provided", requirement=-1):
        """ Causes a given character to follow another.

            Returns:
                bool: True/False on success
        """

        if party_check(who, following, trait, requirement) and (who.follow is not following):
            who.follow = following
            following.party.append(who)
            return True
        else:
            return False

    def is_following(who, follower):
        """ Checks if a given character is following another character.

            Returns:
                bool: is following
        """
        if who in following.party:
            return True
        else:
            return False

    def stop_following(who):
        """ Causes a given character to stop following another. """
        #TODO: Resolve if this will work.
        who.following.party.remove(who)
        who.following = None
        return

    def move_party_to_location(party, location):
        """ Moves a given list of characters to a specified location. """
        for who in party:
            if who.location is not location:
                who.move(location)
        return

    def remove_location_check(who, relationship):
        """ Checks if the given character will accept a command to move to a different location.

            Returns:
                bool: if relationship is high enough
        """
        if relationship_check(who, relationship):
            return True
        else:
            return False

    def remove_from_location(who, destination):
        """ Removes a character from a location if a relationship check is passed.

            Returns:
                bool: if character is removed
        """
        if remove_location_check(who):
            stop_following(who)
            who.move(who, destination)
            return True
        else:
            return False

    ######################################################
    # Test functions to treat nested lists like dicts
    ######################################################

    def give_contains(list, searchable):
        for x in list:
            if x[0] == searchable:
                return x[1]

    def give_index(list, searchable):
        for idx, x in enumerate(list):
            if x[0] == searchable:
                return idx


code:
"""
This file contains the Inventory class, which manages a collection of items
and tracks in-game currency (money). The Inventory class extends the Python list-like behavior
and provides additional methods for managing item categories, status, and money.
"""

python early in trainer_item:

    class Inventory(renpy.store.object):
        """
        A class to manage an inventory of items and associated money in the game.

        Implements many list-like behaviors for handling items, while also managing currency (money).
        """

        def __init__(self, inventory = []):
            """
            Initializes a new instance of the Inventory class.
            The inventory is represented as a list, and money is tracked separately as an integer.
            """
            super().__init__()
            self._inventory = []  # List to store inventory items.
            self._money = 0  # Variable to store money.

            for item in inventory:
                self.append(item)

        # Property to get and set the money value.
        @property
        def money(self):
            return self._money

        @money.setter
        def money(self, value):
            """
            Sets the money value. This allows money to be updated directly.

            Args:
                value (int, float): The new money value.
            """
            self._money = value

        #region Public Methods
        def append(self, value):
            """
            Adds an item or money to the inventory. If the value is numerical (int or float),
            it is added to the money. Otherwise, it's treated as an item and added to the inventory.

            Args:
                value (Item or int/float): The item or money to add.
            """
            if isinstance(value, (int, float)):
                self._money += value
            self._inventory.append(value)

        def clear(self):
            """
            Clears all items from the inventory.
            """
            self._inventory.clear()

        def copy(self):
            """
            Returns a copy of the Inventory instance.

            Returns:
                Inventory: A copy of the current inventory.
            """
            return self.__class__(self)

        def count(self, value):
            """
            Counts the occurrences of an item in the inventory.

            Args:
                value: The item to count.

            Returns:
                int: The number of occurrences of the item.
            """
            return self._inventory.count(value)

        def extend(self, other):
            """
            Extends the inventory with another inventory, list, or money.
            If `other` is another `Inventory` instance, their items are combined.
            If `other` is money (int or float), it's added to the current money.
            If it's a list, the items are appended to the current inventory.

            Args:
                other (Inventory, list, int, or float): The data to extend the inventory with.
            """
            if isinstance(other, Inventory):
                self._inventory.extend(other._inventory)
            elif isinstance(other, (int, float)):
                self._money += other
            else:
                self._inventory.extend(other)

        def get_category(self, category):
            """
            Gets a list of items that belong to a specific category.

            Args:
                category (str): The category to filter by.

            Returns:
                list: A list of items that match the category.
            """
            rv = []

            for item in self._inventory:
                if category in item.category:
                    rv.append(item)
            return rv

        def get_item(self, name):
            """
            Gets an item by name.

            Args:
                name (str): The name of the item to retrieve.

            Returns:
                Item: The item with the given name, or None if not found.
            """
            for item in self._inventory:
                if item.name == name or item == name:
                    return item

        def get_simple_items(self):
            """
            Gets a list of non-Item objects in the inventory.

            Returns:
                list: A list of non-Item objects.
            """
            return [item for item in self._inventory if not isinstance(item, Item)]

        def get_status(self, status):
            """
            Gets a list of items that have a specific status.

            Args:
                status (str): The status to filter by.

            Returns:
                list: A list of items that match the status.
            """
            return [item for item in self._inventory if item.status == status]

        def index(self, value):
            """
            Gets the index of an item in the inventory.

            Args:
                value: The item to find.

            Returns:
                int: The index of the item.
            """
            return self._inventory.index(value)

        def insert(self, index, value):
            """
            Inserts an item at a specific index in the inventory.

            Args:
                index (int): The index where the item will be inserted.
                value (Item): The item to insert.

            Raises:
                ValueError: If the value is a numerical type (int or float), as money can't be inserted this way.
            """
            if isinstance(value, (int, float)):
                raise ValueError("Numerical values cannot be inserted into Inventory.")
            else:
                self._inventory.insert(index, value)

        def pop(self, index=-1):
            """
            Removes and returns the item at the given index (default is the last item).

            Args:
                index (int, optional): The index of the item to pop.

            Returns:
                Item: The popped item.
            """
            return self._inventory.pop(index)

        def remove(self, value):
            """
            Removes the first occurrence of a specific item from the inventory.

            Args:
                value: The item to remove.
            """
            self._inventory.remove(value)

        def reverse(self):
            """
            Reverses the order of items in the inventory.
            """
            self._inventory.reverse()

        def sort(self, *args, **kwargs):
            """
            Sorts the inventory items based on the item names.

            Args:
                args: Additional arguments for sorting.
                kwargs: Additional keyword arguments for sorting.
            """
            self._inventory.sort(key=self.__sort, *args, **kwargs)

        #endregion

        #region Private Methods
        def __sort(self, value):
            """
            Helper function to sort items by their name.

            Args:
                value: The item to sort by.

            Returns:
                str: The item's name if it is an `Item`, otherwise the item itself.
            """
            if isinstance(value, Item):
                return value.name
            return value

        def __cast(self, other):
            """
            Helper function to cast another inventory or list to the appropriate type.

            Args:
                other (Inventory or list): The object to cast.

            Returns:
                list: The inventory or list.
            """
            return other._inventory if isinstance(other, Inventory) else other

        #endregion

        #region Built-ins
        def __repr__(self):
            """
            Returns a string representation of the inventory.

            Returns:
                str: A string representation of the inventory.
            """
            return "<{}: {}> ".format(self.__class__.__name__, repr(self._inventory))

        def __len__(self):
            """
            Returns the length of the inventory (number of items).

            Returns:
                int: The number of items in the inventory.
            """
            return len(self._inventory)

        def __contains__(self, value):
            """
            Checks if an item is in the inventory.

            Args:
                value: The item to check.

            Returns:
                bool: True if the item is in the inventory, False otherwise.
            """
            return value in self._inventory

        def __getitem__(self, index):
            """
            Gets an item by index.

            Args:
                index (int or slice): The index or slice of the item(s).

            Returns:
                Item or list: The item(s) at the specified index.
            """
            if isinstance(index, slice):
                return self.__class__(self._inventory[index])
            return self._inventory[index]

        def __setitem__(self, index, value):
            """
            Sets the item at a specific index.

            Args:
                index (int): The index of the item to set.
                value (Item): The new item to set.
            """
            self._inventory[index] = value

        def __delitem__(self, index):
            """
            Deletes the item at a specific index.

            Args:
                index (int): The index of the item to delete.
            """
            del self._inventory[index]

        def __iter__(self):
            """
            Iterates over the inventory items.

            Returns:
                Iterator: An iterator for the inventory items.
            """
            return iter(self._inventory)

        def __add__(self, other):
            """
            Adds another inventory, list, or money to this inventory.

            Args:
                other (Inventory, list, or int/float): The object to add.

            Returns:
                Inventory: A new inventory with the combined items or money.
            """
            if isinstance(other, Inventory):
                return self.__class__(self._inventory + other._inventory)
            elif isinstance(other, (int, float)):
                return self.__class__(self._money + other)
            return self.__class__(self._inventory + list(other))

        def __radd__(self, other):
            """
            Reverse addition for Inventory.

            Args:
                other (Inventory, list, or int/float): The object to add.

            Returns:
                Inventory: A new inventory with the combined items or money.
            """
            return self.__add__(other)

        def __iadd__(self, other):
            """
            In-place addition for Inventory.

            Args:
                other (Inventory, list, or int/float): The object to add.

            Returns:
                Inventory: The current inventory with the added items or money.
            """
            if isinstance(other, Inventory):
                self._inventory += other._inventory
            elif isinstance(other, (int, float)):
                self._money += other
            else:
                self._inventory += other
            return self

        def __mul__(self, value):
            """
            Multiplies the inventory by a given value (repeating items).

            Args:
                value (int): The multiplier.

            Returns:
                Inventory: A new inventory with multiplied items.
            """
            return self.__class__(self._inventory * value)

        def __imul__(self, value):
            """
            In-place multiplication for Inventory.

            Args:
                value (int): The multiplier.

            Returns:
                Inventory: The current inventory with multiplied items.
            """
            self._inventory *= value
            return self

        def __copy__(self):
            """
            Creates a shallow copy of the inventory.

            Returns:
                Inventory: A copy of the current inventory.
            """
            instance = self.__class__.__new__(self.__class__)
            instance.__dict__.update(self.__dict__)
            instance._inventory = self._inventory[:]
            instance._money = self._money
            return instance

        #endregion

    renpy.store.Inventory = Inventory