Beispiel #1
0
    class _CheatedOnHomework(HasTunableSingletonFactory, AutoFactoryInit):
        FACTORY_TUNABLES = {
            'course_career_slot':
            TunablePackSafeReference(
                description=
                '\n                The course career slot we will get the course from to update the \n                cheating status of.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.CAREER),
                class_restrictions='UniversityCourseCareerSlot'),
            'success_chance':
            SuccessChance.TunableFactory(
                description=
                '\n                Chance that the sim will be caught cheating.\n                '
            )
        }

        def perform(self, subject, target, resolver):
            if subject is None or not subject.is_sim:
                return
            if not subject.is_sim:
                return
            sim_info = subject.sim_info
            if self.course_career_slot is None:
                logger.error(
                    'Attempting to update the final project completion status for sim {}, but the specified course career slot is None.Possibly due to PackSafeness.',
                    sim_info)
                return
            degree_tracker = sim_info.degree_tracker
            if degree_tracker is None:
                logger.error(
                    'Attempting to mark that sim {} cheated on their homework but they have no degree tracker',
                    sim_info)
                return
            if random.random() > self.success_chance.get_chance(resolver):
                degree_tracker.update_homework_cheating_status(
                    self.course_career_slot,
                    HomeworkCheatingStatus.CHEATING_FAIL)
                return
            degree_tracker.update_homework_cheating_status(
                self.course_career_slot,
                HomeworkCheatingStatus.CHEATING_SUCCESS)
Beispiel #2
0
class CareerTone(AwayAction):
    INSTANCE_TUNABLES = {
        'dominant_tone_loot_actions':
        TunableList(
            description=
            '\n            Loot to apply at the end of a work period if this tone ran for the\n            most amount of time out of all tones.\n            ',
            tunable=TunableReference(
                manager=services.get_instance_manager(
                    sims4.resources.Types.ACTION),
                class_restrictions=('LootActions', 'RandomWeightedLoot'))),
        'performance_multiplier':
        Tunable(
            description=
            '\n            Performance multiplier applied to work performance gain.\n            ',
            tunable_type=float,
            default=1),
        'periodic_sim_filter_loot':
        TunableList(
            description=
            '\n            Loot to apply periodically to between the working Sim and other\n            Sims, specified via a Sim filter.\n            \n            Example Usages:\n            -Gain relationship with 2 coworkers every hour.\n            -Every hour, there is a 5% chance of meeting a new coworker.\n            ',
            tunable=TunableTuple(
                chance=SuccessChance.TunableFactory(
                    description=
                    '\n                    Chance per hour of loot being applied.\n                    '
                ),
                sim_filter=TunableSimFilter.TunableReference(
                    description=
                    '\n                    Filter for specifying who to set at target Sims for loot\n                    application.\n                    '
                ),
                max_sims=OptionalTunable(
                    description=
                    '\n                    If enabled and the Sim filter finds more than the specified\n                    number of Sims, the loot will only be applied a random\n                    selection of this many Sims.\n                    ',
                    tunable=TunableRange(tunable_type=int,
                                         default=1,
                                         minimum=1)),
                loot=LootActions.TunableReference(
                    description=
                    '\n                    Loot actions to apply to the two Sims. The Sim in the \n                    career is Actor, and if Targeted is enabled those Sims\n                    will be TargetSim.\n                    '
                )))
    }
    runtime_commodity = None

    @classmethod
    def _tuning_loaded_callback(cls):
        if cls.runtime_commodity is not None:
            return
        commodity = RuntimeCommodity.generate(cls.__name__)
        commodity.decay_rate = 0
        commodity.convergence_value = 0
        commodity.remove_on_convergence = True
        commodity.visible = False
        commodity.max_value_tuning = date_and_time.SECONDS_PER_WEEK
        commodity.min_value_tuning = 0
        commodity.initial_value = 0
        commodity._time_passage_fixup_type = CommodityTimePassageFixupType.DO_NOT_FIXUP
        cls.runtime_commodity = commodity

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._update_alarm_handle = None
        self._last_update_time = None

    def run(self, callback):
        super().run(callback)
        self._last_update_time = services.time_service().sim_now
        time_span = clock.interval_in_sim_minutes(
            Career.CAREER_PERFORMANCE_UPDATE_INTERVAL)
        self._update_alarm_handle = alarms.add_alarm(
            self,
            time_span,
            lambda alarm_handle: self._update(),
            repeating=True)

    def stop(self):
        if self._update_alarm_handle is not None:
            alarms.cancel_alarm(self._update_alarm_handle)
            self._update_alarm_handle = None
        self._update()
        super().stop()

    def _update(self):
        career = self.sim_info.career_tracker.get_at_work_career()
        if career is None:
            logger.error(
                'CareerTone {} trying to update performance when Sim {} not at work',
                self,
                self.sim_info,
                owner='tingyul')
            return
        if career._upcoming_gig is not None and career._upcoming_gig.odd_job_tuning is not None:
            return
        now = services.time_service().sim_now
        elapsed = now - self._last_update_time
        self._last_update_time = now
        career.apply_performance_change(elapsed, self.performance_multiplier)
        career.resend_career_data()
        resolver = SingleSimResolver(self.sim_info)
        for entry in self.periodic_sim_filter_loot:
            chance = entry.chance.get_chance(resolver) * elapsed.in_hours()
            if random.random() > chance:
                continue
            services.sim_filter_service().submit_filter(
                entry.sim_filter,
                self._sim_filter_loot_response,
                callback_event_data=entry,
                requesting_sim_info=self.sim_info,
                gsi_source_fn=self.get_sim_filter_gsi_name)

    def get_sim_filter_gsi_name(self):
        return str(self)

    def _sim_filter_loot_response(self, filter_results, callback_event_data):
        entry = callback_event_data
        if entry.max_sims is None:
            targets = tuple(result.sim_info for result in filter_results)
        else:
            sample_size = min(len(filter_results), entry.max_sims)
            targets = tuple(
                result.sim_info
                for result in random.sample(filter_results, sample_size))
        for target in targets:
            resolver = DoubleSimResolver(self.sim_info, target)
            entry.loot.apply_to_resolver(resolver)

    def apply_dominant_tone_loot(self):
        resolver = self.get_resolver()
        for loot in self.dominant_tone_loot_actions:
            loot.apply_to_resolver(resolver)
Beispiel #3
0
class BaseLootOperation(HasTunableSingletonFactory, HasDisplayTextMixin):
    FACTORY_TUNABLES = {
        'tests':
        TunableTestSet(
            description=
            '\n            The test to decide whether the loot action can be applied.\n            '
        ),
        'chance':
        SuccessChance.TunableFactory(
            description=
            '\n            Percent chance that the loot action will be considered. The\n            chance is evaluated just before running the tests.\n            '
        )
    }

    @staticmethod
    def get_participant_tunable(
            tunable_name,
            optional=False,
            description='',
            participant_type_enum=interactions.ParticipantType,
            default_participant=interactions.ParticipantType.Actor,
            invalid_participants=interactions.ParticipantType.Invalid):
        enum_tunable = TunableEnumEntry(description=description,
                                        tunable_type=participant_type_enum,
                                        default=default_participant,
                                        invalid_enums=invalid_participants)
        if optional:
            return {
                tunable_name:
                OptionalTunable(description=description, tunable=enum_tunable)
            }
        return {tunable_name: enum_tunable}

    @TunableFactory.factory_option
    def subject_participant_type_options(description=singletons.DEFAULT,
                                         **kwargs):
        if description is singletons.DEFAULT:
            description = 'The sim(s) the operation is applied to.'
        return BaseLootOperation.get_participant_tunable(
            *('subject', ), description=description, **kwargs)

    def __init__(self,
                 *args,
                 subject=interactions.ParticipantType.Actor,
                 target_participant_type=None,
                 advertise=False,
                 tests=None,
                 chance=SuccessChance.ONE,
                 exclusive_to_owning_si=None,
                 **kwargs):
        super().__init__(*args, **kwargs)
        self._advertise = advertise
        self._subject = subject
        self._target_participant_type = target_participant_type
        self._tests = tests
        self._chance = chance

    def __repr__(self):
        return '<{} {}>'.format(type(self).__name__, self.subject)

    @property
    def advertise(self):
        return self._advertise

    @property
    def tested(self):
        return self._tests is not None

    @property
    def stat(self):
        pass

    def get_stat(self, interaction):
        return self.stat

    @property
    def subject(self):
        return self._subject

    @property
    def target_participant_type(self):
        return self._target_participant_type

    @property
    def chance(self):
        return self._chance

    @property
    def loot_type(self):
        return interactions.utils.LootType.GENERIC

    def test_resolver(self, resolver, ignore_chance=False):
        if not ignore_chance and not self._chance.multipliers and random.random(
        ) > self._chance.get_chance(resolver):
            return False
        test_result = True
        if self._tests:
            test_result = self._tests.run_tests(resolver)
            if not test_result:
                return test_result
        if self._chance.multipliers:
            chance = self._chance.get_chance(resolver)
            if ignore_chance and not chance:
                return False
            elif random.random() > chance:
                return False
        return test_result

    def _get_display_text_tokens(self, resolver=None):
        if resolver is not None:
            subject = resolver.get_participant(self._subject)
            target = resolver.get_participant(self._target_participant_type)
            return (subject, target)
        return ()

    @staticmethod
    def resolve_participants(participant, resolver):
        if isinstance(participant, tag.Tag):
            return tuple(obj for obj in services.object_manager().values()
                         if obj.has_tag(participant))
        return resolver.get_participants(participant)

    def apply_to_resolver(self, resolver, skip_test=False):
        if not skip_test and not self.test_resolver(resolver):
            return (False, None)
        if self.subject is not None:
            for recipient in self.resolve_participants(self.subject, resolver):
                if self.target_participant_type is not None:
                    for target_recipient in self.resolve_participants(
                            self.target_participant_type, resolver):
                        self._apply_to_subject_and_target(
                            recipient, target_recipient, resolver)
                    else:
                        self._apply_to_subject_and_target(
                            recipient, None, resolver)
                else:
                    self._apply_to_subject_and_target(recipient, None,
                                                      resolver)
        elif self.target_participant_type is not None:
            for target_recipient in self.resolve_participants(
                    self.target_participant_type, resolver):
                self._apply_to_subject_and_target(None, target_recipient,
                                                  resolver)
        else:
            self._apply_to_subject_and_target(None, None, resolver)
        return (True, self._on_apply_completed())

    def _apply_to_subject_and_target(self, subject, target, resolver):
        raise NotImplemented

    def _get_object_from_recipient(self, recipient):
        if recipient is None:
            return
        elif recipient.is_sim:
            return recipient.get_sim_instance(
                allow_hidden_flags=ALL_HIDDEN_REASONS)
        return recipient

    def _on_apply_completed(self):
        pass

    def apply_to_interaction_statistic_change_element(self, resolver):
        self.apply_to_resolver(resolver, skip_test=True)
Beispiel #4
0
class _BasePhotoMode(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'zoom_capability':
        TunableEnumEntry(
            description=
            '\n            The zoom capability of the camera.\n            ',
            tunable_type=ZoomCapability,
            default=ZoomCapability.NO_ZOOM),
        'camera_quality':
        TunableEnumEntry(
            description=
            '\n            The quality of the camera.\n            ',
            tunable_type=CameraQuality,
            default=CameraQuality.CHEAP),
        'hide_photographer':
        Tunable(
            description=
            '\n            Whether or not to hide the photographer during the photo session.\n            ',
            tunable_type=bool,
            default=False),
        'success_chance':
        SuccessChance.TunableFactory(
            description=
            '\n            Percent chance that a photo will be successful.\n            '
        ),
        'camera_position_bone_name':
        Tunable(
            description=
            '\n            Which bone on the photographer to use for the camera position.\n            ',
            tunable_type=str,
            default='',
            allow_empty=True),
        'camera_position_bone_object':
        TunableEnumEntry(
            description=
            '\n            The object that has the bone from which the camera position is\n            obtained. This is usually the photographer sim.\n            ',
            tunable_type=ParticipantTypeSingle,
            default=ParticipantType.Actor),
        'objects_to_hide_tags':
        OptionalTunable(
            description=
            '\n            If enabled, objects that match any of these tags will be hidden in the photo\n            session.\n            ',
            tunable=TunableReference(manager=services.get_instance_manager(
                sims4.resources.Types.TAG_SET),
                                     class_restrictions=('TunableTagSet', ))),
        'number_of_photos_override':
        OptionalTunable(
            description=
            '\n            If tuned, the number of photos that the player can take in this photo session.\n            ',
            tunable=Tunable(tunable_type=int, default=5)),
        'filters_disabled':
        Tunable(
            description=
            '\n            Whether or not to disable photo filters.\n            ',
            tunable_type=bool,
            default=False),
        'single_shot_mode':
        Tunable(
            description=
            '\n            Whether or not to only allow the photographer to take one photo\n            per session.\n            ',
            tunable_type=bool,
            default=False),
        'photo_pose':
        ObjectPose.TunableReference(
            description=
            '\n            The pose the sims in the photo will use.\n            '
        ),
        'photographer_sim':
        TunableEnumEntry(
            description=
            '\n            The participant Sim that is the photographer.\n            ',
            tunable_type=ParticipantTypeSingle,
            default=ParticipantType.Actor),
        'order_photo_target_sims':
        Tunable(
            description=
            '\n            If checked, the targets of this TakePhoto will be assigned actors in\n            the asm based on tags on the interactions they are running. If\n            unchecked, they will be assigned in an arbitrary manner (which may\n            not be random).\n            ',
            tunable_type=bool,
            default=True),
        'photo_target_sims_participants':
        TunableList(
            description=
            '\n            The participants whose Sims are the target of the photograph.\n            ',
            tunable=TunableEnumEntry(tunable_type=ParticipantType,
                                     default=ParticipantType.TargetSim)),
        'photo_target_sims_from_situation':
        OptionalTunable(
            description=
            '\n            Tuning to add a group of situation sims as targets of this photo\n            session.\n            ',
            tunable=TunableTuple(
                situation=TunableReference(
                    description=
                    '\n                    The situation in which to look for sims.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.
                        SITUATION)),
                photographer_job=
                OptionalTunable(
                    description=
                    '\n                    If enabled, the job the photographer sim must have in the tuned\n                    situation in order for that situation to be used. Use this\n                    tuning to ensure we are using the correct situation instance.\n                    ',
                    tunable=TunableReference(
                        manager=services.get_instance_manager(
                            sims4.resources.Types.SITUATION_JOB))),
                target_jobs=TunableSet(
                    description=
                    '\n                    The chosen sims must have one of the following jobs.\n                    ',
                    tunable=TunableReference(
                        manager=services.get_instance_manager(
                            sims4.resources.Types.SITUATION_JOB)))))
    }

    def create_take_photo_op(self, sims, interaction):
        take_photo_proto = DistributorOps_pb2.TakePhoto()
        self._populate_take_photo_op(sims, interaction, take_photo_proto)
        take_photo_op = GenericProtocolBufferOp(
            DistributorOps_pb2.Operation.TAKE_PHOTO, take_photo_proto)
        return take_photo_op

    def _populate_take_photo_op(self, sims, interaction, take_photo_proto):
        take_photo_proto.camera_mode = self._get_camera_mode()
        take_photo_proto.zoom_capability = self.zoom_capability
        take_photo_proto.camera_quality = self.camera_quality
        take_photo_proto.hide_photographer = self.hide_photographer
        take_photo_proto.success_chance = self.success_chance.get_chance(
            interaction.get_resolver())
        take_photo_proto.camera_position_bone_name = self.camera_position_bone_name
        offset = self._get_offset(interaction)
        take_photo_proto.camera_position_offset.x = offset.x
        take_photo_proto.camera_position_offset.y = offset.y
        take_photo_proto.camera_position_offset.z = offset.z
        take_photo_proto.rotate_target = self.enable_rotate_target(interaction)
        take_photo_proto.filters_disabled = self.filters_disabled
        take_photo_proto.single_shot_mode = self.single_shot_mode
        take_photo_proto.painting_size = self._get_photo_size()
        take_photo_proto.num_photos_per_session = self.number_of_photos_override if self.number_of_photos_override is not None else Photography.NUM_PHOTOS_PER_SESSION
        take_photo_proto.sim_mood_asm_param_name = sims[
            0].get_mood_animation_param_name()
        if self.objects_to_hide_tags is not None:
            objects_to_hide = list(
                obj.id
                for obj in services.object_manager().get_objects_with_tags_gen(
                    *self.objects_to_hide_tags.tags))
            take_photo_proto.objects_to_hide.extend(objects_to_hide)
        bone_object = interaction.get_participant(
            self.camera_position_bone_object)
        if bone_object is not None:
            take_photo_proto.camera_position_bone_object = bone_object.id
        for (index, sim) in enumerate(sims):
            with ProtocolBufferRollback(
                    take_photo_proto.sim_photo_infos) as entry:
                entry.participant_sim_id = sim.sim_id
                entry.participant_sim_position.x = sim.position.x
                entry.participant_sim_position.y = sim.position.y
                entry.participant_sim_position.z = sim.position.z
                if self.photo_pose is not None:
                    if self.photo_pose.asm is not None:
                        entry.animation_pose.asm = get_protobuff_for_key(
                            self.photo_pose.asm)
                        entry.animation_pose.state_name = self.photo_pose.state_name
                        actor_name = self._get_actor_name(index)
                        if actor_name is not None:
                            entry.animation_pose.actor_name = actor_name

    def _get_camera_mode(self):
        raise NotImplementedError(
            'Attempting to call _get_camera_mode() on the base class, use sub-classes instead.'
        )

    def _get_actor_name(self, index):
        return 'x'

    def _get_photo_size(self):
        return PhotoSize.LARGE

    def _get_offset(self, interaction):
        return Vector3.ZERO()

    def enable_rotate_target(self, interaction):
        return True
Beispiel #5
0
class XevtTriggeredElement(elements.ParentElement, HasTunableFactory,
                           AutoFactoryInit):
    AT_BEGINNING = 'at_beginning'
    AT_END = 'at_end'
    ON_XEVT = 'on_xevt'
    TIMING_DESCRIPTION = '\n        Determines the exact timing of the behavior, either at the beginning\n        of an interaction, the end, or when an xevt occurs in an animation\n        played as part of the interaction.\n        '
    FakeTiming = collections.namedtuple(
        'FakeTiming', ('timing', 'offset_time', 'criticality', 'xevt_id'))
    LOCKED_AT_BEGINNING = FakeTiming(AT_BEGINNING, None, None, None)
    LOCKED_AT_END = FakeTiming(AT_END, None, None, None)
    LOCKED_ON_XEVT = FakeTiming(ON_XEVT, None, None, None)
    FACTORY_TUNABLES = {
        'timing':
        TunableVariant(
            description=TIMING_DESCRIPTION,
            default=AT_END,
            at_beginning=TunableTuple(
                description=
                "\n                The behavior should occur at the very beginning of the\n                interaction.  It will not be tightly synchronized visually with\n                animation.  This isn't a very common use case and would most\n                likely be used in an immediate interaction or to change hidden\n                state that is used for bookkeeping rather than visual\n                appearance.\n                ",
                offset_time=OptionalTunable(
                    description=
                    '\n                    If enabled, the interaction will wait this amount of time\n                    after the beginning before running the element.\n                    \n                    Only use this if absolutely necessary. Better alternatives\n                    include using xevts, time based conditional action with\n                    loot ops, and using outcomes.\n                    ',
                    tunable=TunableSimMinute(
                        description=
                        'The interaction will wait this amount of time after the beginning before running the element',
                        default=2),
                    deprecated=True),
                locked_args={
                    'timing': AT_BEGINNING,
                    'criticality': CleanupType.NotCritical,
                    'xevt_id': None
                }),
            at_end=TunableTuple(
                description=
                '\n                The behavior should occur at the end of the interaction.  It\n                will not be tightly synchronized visually with animation.  An\n                example might be an object that gets dirty every time a Sim uses\n                it (so using a commodity change is overkill) but no precise\n                synchronization with animation is desired, as might be the case\n                with vomiting in the toilet.\n                ',
                locked_args={
                    'timing': AT_END,
                    'xevt_id': None,
                    'offset_time': None
                },
                criticality=TunableEnumEntry(CleanupType,
                                             CleanupType.OnCancel)),
            on_xevt=TunableTuple(
                description=
                "\n                The behavior should occur synchronized visually with an xevt in\n                an animation played as part of the interaction.  If for some\n                reason such an event doesn't occur, the behavior will occur at\n                the end of the interaction.  This is by far the most common use\n                case, as when a Sim flushes a toilet and the water level should\n                change when the actual flush animation and effects fire.\n                ",
                locked_args={
                    'timing': ON_XEVT,
                    'offset_time': None
                },
                criticality=TunableEnumEntry(CleanupType,
                                             CleanupType.OnCancel),
                xevt_id=Tunable(int, 100))),
        'success_chance':
        SuccessChance.TunableFactory(
            description=
            '\n            The percentage chance that this action will be applied.\n            '
        )
    }

    def __init__(self, interaction, *, timing, sequence=(), **kwargs):
        super().__init__(timing=None, **kwargs)
        self.interaction = interaction
        self.sequence = sequence
        self.timing = timing.timing
        self.criticality = timing.criticality
        self.xevt_id = timing.xevt_id
        self.result = None
        self.triggered = False
        self.offset_time = timing.offset_time
        self.__event_handler_handle = None
        success_chance = self.success_chance.get_chance(
            interaction.get_resolver())
        self._should_do_behavior = random.random() <= success_chance

    def _register_event_handler(self, element):
        self.__event_handler_handle = self.interaction.animation_context.register_event_handler(
            self._behavior_event_handler, handler_id=self.xevt_id)

    def _release_event_handler(self, element):
        self.__event_handler_handle.release()
        self.__event_handler_handle = None

    def _behavior_element(self, timeline):
        if not self.triggered:
            self.triggered = True
            if self._should_do_behavior:
                self.result = self._do_behavior()
            else:
                self.result = None
        return self.result

    def _behavior_event_handler(self, *_, **__):
        if not self.triggered:
            self.triggered = True
            if self._should_do_behavior:
                self.result = self._do_behavior()
            else:
                self.result = None

    def _run(self, timeline):
        if not self._should_do_behavior:
            return timeline.run_child(build_element(self.sequence))
        if self.timing == self.AT_BEGINNING:
            if self.offset_time is None:
                sequence = [self._behavior_element, self.sequence]
            else:
                sequence = build_delayed_element(self.sequence,
                                                 clock.interval_in_sim_minutes(
                                                     self.offset_time),
                                                 self._behavior_element,
                                                 soft_sleep=True)
        elif self.timing == self.AT_END:
            sequence = [self.sequence, self._behavior_element]
        elif self.timing == self.ON_XEVT:
            sequence = [
                build_critical_section(self._register_event_handler,
                                       self.sequence,
                                       self._release_event_handler),
                self._behavior_element
            ]
        child_element = build_element(sequence, critical=self.criticality)
        child_element = self._build_outer_elements(child_element)
        return timeline.run_child(child_element)

    def _build_outer_elements(self, sequence):
        return sequence

    def _do_behavior(self):
        raise NotImplementedError

    @classmethod
    def validate_tuning_interaction(cls, interaction, basic_extra):
        if basic_extra._tuned_values.timing.timing != XevtTriggeredElement.ON_XEVT:
            return
        if interaction.one_shot and interaction.basic_content.animation_ref is None:
            logger.error(
                'The interaction ({}) has a tuned basic extra ({}) that occurs on an xevt but has no animation content.',
                interaction,
                basic_extra.factory,
                owner='shipark')
        elif interaction.staging:
            staging_content = interaction.basic_content.content.content_set._tuned_values
            if staging_content.affordance_links is None and staging_content.phase_tuning is None and interaction.basic_content.animation_ref is None:
                if interaction.provided_posture_type is None:
                    logger.error(
                        'The interaction ({}) has a tuned basic extra ({}) that occurs on an xevt tuned on a staging interaction without any staging content.',
                        interaction,
                        basic_extra.factory,
                        owner='shipark')
                elif interaction.provided_posture_type._animation_data is None:
                    logger.error(
                        'The posture-providing interaction ({}) has a tuned basic extra ({}) that occurs on an xevt but has no animation content in the posture.',
                        interaction,
                        basic_extra.factory,
                        owner='shipark')
        elif interaction.looping and interaction.basic_content.animation_ref is None:
            logger.error(
                'The interaction ({}) has a tuned basic extra ({}) that occurs on an xevt but has no animation content.',
                interaction,
                basic_extra.factory,
                owner='shipark')

    @classmethod
    def validate_tuning_outcome(cls, outcome, basic_extra, interaction_name):
        if outcome.animation_ref is None and outcome.response is None and outcome.social_animation is None:
            logger.error(
                'The interaction ({}) has an outcome with a tuned basic extra ({}) that occurs on an xevt, but has no animation content.',
                interaction_name,
                basic_extra,
                owner='shipark')
Beispiel #6
0
class TimeOfDayComponent(Component,
                         HasTunableFactory,
                         component_name=types.TIME_OF_DAY_COMPONENT):
    DAILY_REPEAT = date_and_time.create_time_span(hours=24)
    FACTORY_TUNABLES = {
        'state_changes':
        TunableMapping(
            description=
            '\n            A mapping from state to times of the day when the state should be \n            set to a tuned value.\n            ',
            key_type=TunableStateTypeReference(
                description=
                '\n                The state to be set.\n                '),
            value_type=TunableList(
                description=
                '\n                List of times to modify the state at.\n                ',
                tunable=TunableTuple(
                    start_time=TunableRange(
                        description=
                        '\n                        The start time (24 hour clock time) for the Day_Time state.\n                        ',
                        tunable_type=float,
                        default=0,
                        minimum=0,
                        maximum=24),
                    value=TunableStateValueReference(
                        description=
                        '\n                        New state value.\n                        '
                    ),
                    loot_list=TunableList(
                        description=
                        '\n                        A list of loot operations to apply when changing state.\n                        ',
                        tunable=TunableReference(
                            manager=services.get_instance_manager(
                                sims4.resources.Types.ACTION),
                            class_restrictions=('LootActions', ),
                            pack_safe=True)),
                    chance=SuccessChance.TunableFactory(
                        description=
                        '\n                        Percent chance that the state change will be considered. \n                        The chance is evaluated just before running the tests.\n                        '
                    ),
                    tests=TunableTestSet(
                        description=
                        '\n                        Test to decide whether the state change can be applied.\n                        '
                    ))))
    }

    def __init__(self, owner, *, state_changes):
        super().__init__(owner)
        self.state_changes = state_changes
        self.alarm_handles = []

    def _apply_state_change(self, state, change):
        resolver = SingleObjectResolver(self.owner)
        chance = change.chance.get_chance(resolver)
        if random.random() > chance:
            return
        if not change.tests.run_tests(resolver):
            return
        self.owner.set_state(state, change.value)
        for loot_action in change.loot_list:
            loot_action.apply_to_resolver(resolver)

    def _add_alarm(self, cur_state, state, change):
        now = services.time_service().sim_now
        time_to_day = clock.time_until_hour_of_day(now, change.start_time)

        def alarm_callback(_):
            self._apply_state_change(state, change)

        self.alarm_handles.append(
            alarms.add_alarm(self.owner,
                             time_to_day,
                             alarm_callback,
                             repeating=True,
                             repeating_time_span=self.DAILY_REPEAT))
        if cur_state is None or time_to_day > cur_state[0]:
            return (time_to_day, change)
        return cur_state

    def on_add(self):
        for (state, changes) in self.state_changes.items():
            current_state = None
            for change in changes:
                current_state = self._add_alarm(current_state, state, change)
            if current_state is not None:
                self._apply_state_change(state, current_state[1])

    def on_remove(self):
        for handle in self.alarm_handles:
            alarms.cancel_alarm(handle)
Beispiel #7
0
class RouteEvent(RouteEventBase,
                 HasTunableReference,
                 metaclass=TunedInstanceMetaclass,
                 manager=services.get_instance_manager(resources.Types.SNIPPET)
                 ):
    INSTANCE_TUNABLES = {
        'event_type':
        TunableVariant(
            description=
            '\n            Define what is the event that is played.\n            ',
            animation=RouteEventTypeAnimation.TunableFactory(),
            balloon=RouteEventTypeBalloon.TunableFactory(),
            create_carry=RouteEventTypeCreateCarry.TunableFactory(),
            exit_carry=RouteEventTypeExitCarry.TunableFactory(),
            empty=RouteEventTypeEmpty.TunableFactory(),
            set_posture=RouteEventTypeSetPosture.TunableFactory(),
            default='animation'),
        'priority':
        TunableEnumEntry(
            description=
            '\n            The priority at which we play this route event when it overlaps\n            with another of the same Type.\n            ',
            tunable_type=RouteEventPriority,
            default=RouteEventPriority.DEFAULT),
        'tests':
        TunableTestSet(
            description=
            '\n            Tests whether or not the animation will play during a route. The\n            participants for these tests are dependent on the instance that\n            references this Route Event.\n            '
        ),
        'chance':
        SuccessChance.TunableFactory(
            description=
            '\n            Percent Chance that the Route Event will play.\n            '
        ),
        'skippable':
        Tunable(
            description=
            "\n            If disabled, this route event will not be skippable on the Client.\n            They will attempt to play it no matter what. This should only be\n            used in cases where the route event would stop the Sim's locomotion\n            so they can animate at a particular point on the ground. If you\n            disable this on an animation that does not stop locomotion, it\n            could look quite irregular.\n                        \n            Use caution when disabling this. Consult your GPE partner.\n            ",
            tunable_type=bool,
            default=True),
        'scheduling_override':
        OptionalTunable(
            description=
            '\n            If enabled, we will override schedule preference for the route\n            event and schedule it accordingly.\n            ',
            tunable=TunableEnumEntry(
                description=
                '\n                The schedule preference we want this route event to obey.\n                ',
                tunable_type=RouteEventSchedulePreference,
                default=RouteEventSchedulePreference.BEGINNING)),
        'prefer_straight_paths':
        OptionalTunable(
            description=
            "\n            If enabled, we will try to center this animation on the longest \n            segment available.\n            \n            Note: We do not consider collinear segments to be a single segment,\n            and won't take that into account when finding the longest.\n            ",
            tunable=TunableTuple(
                description=
                '\n                Tuning for straight path requirements.\n                ',
                straight_path_offset=OptionalTunable(
                    description=
                    '\n                    If enabled, allows setting a percentage offset (in time\n                    from the beginning of the route event) at which to start\n                    requiring a straight path. If disabled, the straight path\n                    will portion will be the center of the route event.\n                    ',
                    tunable=TunablePercent(
                        description=
                        '\n                        The offset of the straight path portion\n                        ',
                        default=0)),
                straight_path_percentage=TunablePercent(
                    description=
                    '\n                    The percent of the duration that we require to be\n                    on a straight segment.\n                    ',
                    default=MINIMUM_ROUTE_EVENT_SEGMENT_DURATION_RATIO))),
        'loot_actions':
        TunableList(
            description=
            '\n            A list of loot actions that are processed when the route event\n            fires, not when the event is scheduled.\n            ',
            tunable=TunableReference(
                description=
                '\n                A loot action that fires when the route event is hit.\n                \n                Note: This will not fire when the route event is scheduled.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.ACTION),
                class_restrictions=('LootActions', ),
                pack_safe=True)),
        'broadcaster':
        OptionalTunable(
            description=
            '\n            If enabled, we will create a broadcaster and attach it to the Sim.\n            It will ping at least once, and will be disabled when we have\n            finished playing any content attached to this Route Event.\n            ',
            tunable=BroadcasterRequest.TunableFactory(
                description=
                '\n                A broadcaster that is created when the route event fires and is\n                destroyed at the end of the duration.\n                '
            )),
        'allowed_at_animated_portal':
        Tunable(
            description=
            '\n            If enabled, we allow this route event to play at portals that has\n            animation on them (e.g. stairs). \n            ',
            tunable_type=bool,
            default=False)
    }

    def __init__(self,
                 *args,
                 provider=None,
                 provider_required=False,
                 **kwargs):
        super().__init__(*args, **kwargs)
        self.tag = 0
        self.provider_ref = weakref.ref(
            provider) if provider is not None else None
        self.event_id = id_generator.generate_object_id()
        self.event_data = None
        self._processed = False
        self._provider_required = provider_required

    @property
    def id(self):
        return self.event_id

    @property
    def processed(self):
        return self._processed

    @property
    def route_event_parameters(self):
        return {
            'provider': self.provider,
            'provider_required': self._provider_required
        }

    @property
    def duration(self):
        duration_override = self.event_data.duration_override
        if duration_override is not None:
            return duration_override
        return super().duration

    @property
    def provider(self):
        if self.provider_ref is not None:
            return self.provider_ref()

    @classmethod
    def test(cls, resolver, from_update=False):
        result = cls.tests.run_tests(resolver)
        if not result:
            return result
        if not from_update:
            actor = resolver.get_participant(ParticipantType.Actor)
            actor = actor.sim_info.get_sim_instance(
            ) if actor is not None and actor.is_sim else actor
            event_type = cls.event_type
            result = event_type.factory.test(actor, event_type)
            if not result:
                return result
        return TestResult.TRUE

    def prepare_route_event(self, sim):
        self.event_data = self.event_type()
        self.event_data.prepare(sim)

    def on_executed(self, sim, path=None):
        provider = self.provider
        if provider is not None:
            provider.on_event_executed(self, sim)
        self.event_data.execute(sim, path=path)
        if self.broadcaster is not None:
            broadcaster_request = self.broadcaster(sim)
            broadcaster_request.start_one_shot()
        if self.loot_actions:
            resolver = SingleSimResolver(sim.sim_info)
            for loot_action in self.loot_actions:
                loot_action.apply_to_resolver(resolver)

    def process(self, actor, time):
        self.time = time
        if self._processed:
            return
        with BlockOnAnimationTag() as tag:
            self.tag = tag
            self.event_data.process(actor)
        self._processed = True

    def is_route_event_valid(self, route_event, time, sim, path):
        provider = self.provider
        if self.provider is None:
            return not self._provider_required
        return provider.is_route_event_valid(route_event, time, sim, path)

    def build_route_event_msg(self, route_msg, time):
        with ProtocolBufferRollback(route_msg.events) as event_msg:
            event_msg.id = self.id
            event_msg.time = time
            event_msg.skippable = self.skippable
            event_msg.type = routing_protocols.RouteEvent.BARRIER_EVENT
            event_msg.duration = self.duration
            if self.tag:
                event_msg.tag = self.tag
class XevtTriggeredElement(elements.ParentElement, HasTunableFactory, AutoFactoryInit):
    __qualname__ = 'XevtTriggeredElement'
    AT_BEGINNING = 'at_beginning'
    AT_END = 'at_end'
    ON_XEVT = 'on_xevt'
    TIMING_DESCRIPTION = '\n        Determines the exact timing of the behavior, either at the beginning\n        of an interaction, the end, or when an xevt occurs in an animation\n        played as part of the interaction.\n        '
    FakeTiming = collections.namedtuple('FakeTiming', ('timing', 'offset_time', 'criticality', 'xevt_id'))
    LOCKED_AT_BEGINNING = FakeTiming(AT_BEGINNING, None, None, None)
    LOCKED_AT_END = FakeTiming(AT_END, None, None, None)
    LOCKED_ON_XEVT = FakeTiming(ON_XEVT, None, None, None)
    FACTORY_TUNABLES = {'description': '\n            The author of this tunable neglected to provide documentation.\n            Shame!\n            ', 'timing': TunableVariant(description=TIMING_DESCRIPTION, default=AT_END, at_beginning=TunableTuple(description="\n                The behavior should occur at the very beginning of the\n                interaction.  It will not be tightly synchronized visually with\n                animation.  This isn't a very common use case and would most\n                likely be used in an immediate interaction or to change hidden\n                state that is used for bookkeeping rather than visual\n                appearance.\n                ", offset_time=OptionalTunable(description='\n                    If enabled, the interaction will wait this amount of time\n                    after the beginning before running the element\n                    ', tunable=TunableSimMinute(description='The interaction will wait this amount of time after the beginning before running the element', default=2)), locked_args={'timing': AT_BEGINNING, 'criticality': CleanupType.NotCritical, 'xevt_id': None}), at_end=TunableTuple(description='\n                The behavior should occur at the end of the interaction.  It\n                will not be tightly synchronized visually with animation.  An\n                example might be an object that gets dirty every time a Sim uses\n                it (so using a commodity change is overkill) but no precise\n                synchronization with animation is desired, as might be the case\n                with vomiting in the toilet.\n                ', locked_args={'timing': AT_END, 'xevt_id': None, 'offset_time': None}, criticality=TunableEnumEntry(CleanupType, CleanupType.OnCancel)), on_xevt=TunableTuple(description="\n                The behavior should occur synchronized visually with an xevt in\n                an animation played as part of the interaction.  If for some\n                reason such an event doesn't occur, the behavior will occur at\n                the end of the interaction.  This is by far the most common use\n                case, as when a Sim flushes a toilet and the water level should\n                change when the actual flush animation and effects fire.\n                ", locked_args={'timing': ON_XEVT, 'offset_time': None}, criticality=TunableEnumEntry(CleanupType, CleanupType.OnCancel), xevt_id=Tunable(int, 100))), 'success_chance': SuccessChance.TunableFactory(description='\n            The percentage chance that this action will be applied.\n            ')}

    def __init__(self, interaction, *, timing, sequence=(), **kwargs):
        super().__init__(timing=None, **kwargs)
        self.interaction = interaction
        self.sequence = sequence
        self.timing = timing.timing
        self.criticality = timing.criticality
        self.xevt_id = timing.xevt_id
        self.result = None
        self.triggered = False
        self.offset_time = timing.offset_time
        self._XevtTriggeredElement__event_handler_handle = None
        success_chance = self.success_chance.get_chance(interaction.get_resolver())
        self._should_do_behavior = random.random() <= success_chance

    def _register_event_handler(self, element):
        self._XevtTriggeredElement__event_handler_handle = self.interaction.animation_context.register_event_handler(self._behavior_event_handler, handler_id=self.xevt_id)

    def _release_event_handler(self, element):
        self._XevtTriggeredElement__event_handler_handle.release()
        self._XevtTriggeredElement__event_handler_handle = None

    def _behavior_element(self, timeline):
        if not self.triggered:
            self.triggered = True
            if self._should_do_behavior:
                self.result = self._do_behavior()
            else:
                self.result = None
        return self.result

    def _behavior_event_handler(self, *_, **__):
        if not self.triggered:
            self.triggered = True
            if self._should_do_behavior:
                self.result = self._do_behavior()
            else:
                self.result = None

    def _run(self, timeline):
        if self.timing == self.AT_BEGINNING:
            if self.offset_time is None:
                sequence = [self._behavior_element, self.sequence]
            else:
                delayed_sequence = build_element([elements.SleepElement(clock.interval_in_sim_minutes(self.offset_time)), self._behavior_element])
                if self.sequence:
                    sequence = elements.AllElement([delayed_sequence, self.sequence])
                else:
                    sequence = delayed_sequence
        elif self.timing == self.AT_END:
            sequence = [self.sequence, self._behavior_element]
        elif self.timing == self.ON_XEVT:
            sequence = [build_critical_section(self._register_event_handler, self.sequence, self._release_event_handler), self._behavior_element]
        child_element = build_element(sequence, critical=self.criticality)
        child_element = self._build_outer_elements(child_element)
        return timeline.run_child(child_element)

    def _build_outer_elements(self, sequence):
        return sequence

    def _do_behavior(self):
        raise NotImplementedError
Beispiel #9
0
 def __init__(self, allow_multi_si_cancel=None, allow_social_animation=None, locked_args=None, animation_callback=DEFAULT, **kwargs):
     super().__init__(description='\n                            There are one success and one failure outcome for\n                            this interaction.\n                            ', success_chance=SuccessChance.TunableFactory(description='\n                            The success chance of the interaction. This\n                            determines which dual outcome is run.\n                            '), success_actions=TunableOutcomeActions(allow_multi_si_cancel=allow_multi_si_cancel, allow_social_animation=allow_social_animation, locked_args=locked_args, animation_callback=animation_callback), failure_actions=TunableOutcomeActions(allow_multi_si_cancel=allow_multi_si_cancel, allow_social_animation=allow_social_animation, locked_args=locked_args, animation_callback=animation_callback), callback=InteractionOutcome.tuning_loaded_callback, **kwargs)
Beispiel #10
0
class RepoSituation(SituationComplexCommon):
    INSTANCE_TUNABLES = {
        'repo_person_job_and_role_state':
        TunableSituationJobAndRoleState(
            description=
            '\n            The job and role state for the repo-person.\n            ',
            tuning_group=GroupNames.ROLES),
        'debtor_sim_job_and_role_state':
        TunableSituationJobAndRoleState(
            description=
            '\n            The job and role state for the Sim from the active household whose\n            unpaid debt is being collected by the repo-person.\n            ',
            tuning_group=GroupNames.ROLES),
        'repo_amount':
        TunableTuple(
            description=
            '\n            Tuning that determines the simoleon amount the repo-person is\n            trying to collect.\n            ',
            target_amount=TunablePercent(
                description=
                '\n                The percentage of current debt which determines the base\n                amount the repo-person will try to collect.\n                ',
                default=10),
            min_and_max_collection_range=TunableInterval(
                description=
                '\n                Multipliers that define the range around the target amount\n                that determine which objects should be taken.\n                ',
                tunable_type=float,
                default_lower=1,
                default_upper=1),
            tuning_group=GroupNames.SITUATION),
        'save_lock_tooltip':
        TunableLocalizedString(
            description=
            '\n            The tooltip to show when the player tries to save the game while\n            this situation is running. The save is locked when the situation\n            starts.\n            ',
            tuning_group=GroupNames.SITUATION),
        'find_object_state':
        _FindObjectState.TunableFactory(
            description=
            '\n            The state that picks an object for the repo-person to take.\n            ',
            display_name='1. Find Object State',
            tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP),
        'nothing_to_take_state':
        _NothingToTakeState.TunableFactory(
            description=
            '\n            The state at which there is nothing for the repo-person to take.\n            ',
            display_name='2. Nothing To Take State',
            tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP),
        'idle_at_object_state':
        _IdleAtObjectState.TunableFactory(
            description=
            '\n            The state at which the repo-person waits near the picked object\n            and can be asked not to take the object.\n            ',
            display_name='3. Idle At Object State',
            tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP),
        'repossess_object_state':
        _RepossessObjectState.TunableFactory(
            description=
            '\n            The state at which the repo-person will repossess the picked object.\n            ',
            display_name='4. Repossess Object State',
            tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP),
        'leave_state':
        _LeaveState.TunableFactory(
            description=
            '\n            The state at which the repo-person leaves the lot.\n            ',
            display_name='5. Leave State',
            tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP),
        'valid_object_tests':
        TunableTestSet(
            description=
            '\n            Test set that determines if an object on the lot is valid for\n            repossession.\n            ',
            tuning_group=GroupNames.SITUATION),
        'ask_not_to_take_success_chances':
        TunableList(
            description=
            '\n            List of values that determine the chance of success of the ask\n            not to take interaction, with each chance being used once and then\n            moving to the next. After using all the tuned chances the next\n            ask not to take interaction will always fail.\n            ',
            tunable=SuccessChance.TunableFactory(
                description=
                '\n                Chance of success of the "Ask Not To Take" interaction.\n                '
            ),
            tuning_group=GroupNames.SITUATION),
        'bribe_interaction':
        TunableInteractionOfInterest(
            description=
            '\n            If this interaction completes successfully, the repo-person will\n            leave the lot without repossessing anything.\n            '
        ),
        'ask_not_to_take_interaction':
        TunableInteractionOfInterest(
            description=
            '\n            When this interaction completes, the situation will determine if\n            the repo-person should find another object to repossess or not\n            based on the tuned success chances.\n            '
        ),
        'ask_not_to_take_failure_notification':
        OptionalTunable(
            description=
            '\n            A TNS that displays when an ask-not-to-take interaction fails, if enabled.\n            ',
            tunable=UiDialogNotification.TunableFactory()),
        'ask_not_to_take_success_notification':
        OptionalTunable(
            description=
            '\n            A TNS that displays when an ask-not-to-take interaction succeeds, if enabled.\n            ',
            tunable=UiDialogNotification.TunableFactory()),
        'debt_source':
        TunableEnumEntry(
            description=
            "\n            The source of where the debt is coming from and where it'll be removed.\n            ",
            tunable_type=DebtSource,
            default=DebtSource.SCHOOL_LOAN),
        'maximum_object_to_repossess':
        OptionalTunable(
            description=
            '\n            The total maximum objects that the situation will take.\n            ',
            tunable=TunableRange(
                description=
                '\n                The total maximum objects that the situation will take.\n                If Use Debt Amount is specified then the situation will keep taking objects\n                until there are no more valid objects to take or we have removed all of the\n                debt.\n                ',
                tunable_type=int,
                default=1,
                minimum=1),
            enabled_by_default=True,
            enabled_name='has_maximum_value',
            disabled_name='use_debt_amount'),
        'auto_clear_debt_event':
        OptionalTunable(
            description=
            '\n            If enabled then we will have an even we listen to to cancel the debt.\n            ',
            tunable=TunableEnumEntry(
                description=
                '\n                The event that when triggered will cause all the debt to be cancelled and the\n                repo man to leave.\n                ',
                tunable_type=TestEvent,
                default=TestEvent.Invalid,
                invalid_enums=(TestEvent.Invalid, )))
    }
    REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.objects_to_take = []
        self.current_object = None
        self.ask_not_to_take_success_chances_list = list(
            self.ask_not_to_take_success_chances)
        self._reservation_handler = None
        self._objects_repossessed = 0

    @classmethod
    def _states(cls):
        return (SituationStateData(1, _WaitForRepoPersonState),
                SituationStateData(2,
                                   _FindObjectState,
                                   factory=cls.find_object_state),
                SituationStateData(3,
                                   _NothingToTakeState,
                                   factory=cls.nothing_to_take_state),
                SituationStateData(4,
                                   _IdleAtObjectState,
                                   factory=cls.idle_at_object_state),
                SituationStateData(5,
                                   _RepossessObjectState,
                                   factory=cls.repossess_object_state),
                SituationStateData(6, _LeaveState, factory=cls.leave_state))

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return [(cls.repo_person_job_and_role_state.job,
                 cls.repo_person_job_and_role_state.role_state),
                (cls.debtor_sim_job_and_role_state.job,
                 cls.debtor_sim_job_and_role_state.role_state)]

    @classmethod
    def default_job(cls):
        pass

    def repo_person(self):
        sim = next(
            self.all_sims_in_job_gen(self.repo_person_job_and_role_state.job),
            None)
        return sim

    def debtor_sim(self):
        sim = next(
            self.all_sims_in_job_gen(self.debtor_sim_job_and_role_state.job),
            None)
        return sim

    def _cache_valid_objects(self):
        debt_value = self.get_debt_value()
        if debt_value is None:
            self._self_destruct()
            return
        target_amount = debt_value * self.repo_amount.target_amount
        unsorted = []
        plex_service = services.get_plex_service()
        check_common_area = plex_service.is_active_zone_a_plex()
        debtor_household_id = self.debtor_sim().household_id
        for obj in services.object_manager().valid_objects():
            if not obj.get_household_owner_id() == debtor_household_id:
                continue
            if not obj.is_on_active_lot():
                continue
            if check_common_area and plex_service.get_plex_zone_at_position(
                    obj.position, obj.level) is None:
                continue
            if not obj.is_connected(self.repo_person()):
                continue
            if obj.children:
                continue
            resolver = SingleObjectResolver(obj)
            if self.valid_object_tests.run_tests(resolver):
                delta = abs(obj.depreciated_value - target_amount)
                unsorted.append((obj.id, delta))
        self.objects_to_take = sorted(unsorted, key=operator.itemgetter(1))

    def _on_add_sim_to_situation(self,
                                 sim,
                                 job_type,
                                 role_state_type_override=None):
        super()._on_add_sim_to_situation(
            sim, job_type, role_state_type_override=role_state_type_override)
        if self.debtor_sim() is not None and self.repo_person() is not None:
            self._cache_valid_objects()
            self._change_state(self.find_object_state())

    def _destroy(self):
        super()._destroy()
        self.clear_current_object()
        services.get_persistence_service().unlock_save(self)
        if self.auto_clear_debt_event is not None:
            services.get_event_manager().unregister_single_event(
                self, self.auto_clear_debt_event)

    def start_situation(self):
        services.get_persistence_service().lock_save(self)
        super().start_situation()
        self._change_state(_WaitForRepoPersonState())
        if self.auto_clear_debt_event is not None:
            services.get_event_manager().register_single_event(
                self, self.auto_clear_debt_event)

    def handle_event(self, sim_info, event, resolver):
        super().handle_event(sim_info, event, resolver)
        if self.auto_clear_debt_event is None:
            return
        if event != self.auto_clear_debt_event:
            return
        self.clear_debt()
        self._change_state(self.leave_state())

    def reduce_debt(self, amount):
        if self.debt_source == DebtSource.SCHOOL_LOAN:
            host_sim_info = services.sim_info_manager().get(
                self._guest_list.host_sim_id)
            statistic = host_sim_info.get_statistic(
                LoanTunables.DEBT_STATISTIC, add=False)
            if statistic is None:
                return
            else:
                statistic.add_value(-amount)
        elif self.debt_source == DebtSource.BILLS:
            services.active_household().bills_manager.reduce_amount_owed(
                amount)
        else:
            logger.error('Attempting to use a debt source that is not handled',
                         owner='jjacobson')
            return

    def clear_debt(self):
        if self.debt_source == DebtSource.SCHOOL_LOAN:
            host_sim_info = services.sim_info_manager().get(
                self._guest_list.host_sim_id)
            statistic = host_sim_info.get_statistic(
                LoanTunables.DEBT_STATISTIC, add=False)
            if statistic is None:
                return
            else:
                statistic.set_value(0)
        elif self.debt_source == DebtSource.BILLS:
            services.active_household().bills_manager.pay_bill(clear_bill=True)
        else:
            logger.error(
                'Attempting to use a debt source {} that is not handled',
                self.debt_source,
                owner='jjacobson')
            return

    def get_debt_value(self):
        if self.debt_source == DebtSource.SCHOOL_LOAN:
            host_sim_info = services.sim_info_manager().get(
                self._guest_list.host_sim_id)
            statistic = host_sim_info.get_statistic(
                LoanTunables.DEBT_STATISTIC, add=False)
            if statistic is None:
                return
            return statistic.get_value()
        if self.debt_source == DebtSource.BILLS:
            return services.active_household(
            ).bills_manager.current_payment_owed
        else:
            logger.error('Attempting to use a debt source that is not handled',
                         owner='jjacobson')
            return

    def on_object_repossessed(self):
        self._objects_repossessed += 1
        if self.maximum_object_to_repossess is None or self._objects_repossessed < self.maximum_object_to_repossess:
            debt_value = self.get_debt_value()
            if debt_value is not None and debt_value > 0:
                self._change_state(self.find_object_state())
                return
        self._change_state(self.leave_state())

    def get_target_object(self):
        return self.current_object

    def get_lock_save_reason(self):
        return self.save_lock_tooltip

    def set_current_object(self, obj):
        self.current_object = obj
        if self._reservation_handler is not None:
            logger.error(
                'Trying to reserve an object when an existing reservation already exists: {}',
                self._reservation_handler)
            self._reservation_handler.end_reservation()
        self._reservation_handler = self.current_object.get_reservation_handler(
            self.repo_person())
        self._reservation_handler.begin_reservation()

    def clear_current_object(self):
        self.current_object = None
        if self._reservation_handler is not None:
            self._reservation_handler.end_reservation()
            self._reservation_handler = None