示例#1
0
class _LeaveState(_SingleJobComplexSituationState):
    FACTORY_TUNABLES = {
        '_leave_interaction':
        TunableInteractionOfInterest(
            description=
            '\n             The interaction that, once completed, can end the situation.\n             Typically the interaction that the Sim uses to route back\n             to the starting object.\n             \n             As a fallback, setting a timeout will also end the situation.\n             If for some reason this interaction fails to run or complete.\n             '
        )
    }

    def __init__(self, *args, _leave_interaction, **kwargs):
        super().__init__(*args, **kwargs)
        self._leave_interaction = _leave_interaction

    def on_activate(self, reader=None):
        super().on_activate(reader)
        for custom_key in self._leave_interaction.custom_keys_gen():
            self._test_event_register(TestEvent.InteractionComplete,
                                      custom_key)

    def handle_event(self, sim_info, event, resolver):
        if event == TestEvent.InteractionComplete and self.owner.is_sim_info_in_situation(
                sim_info) and resolver(self._leave_interaction):
            self.owner._self_destruct()

    def timer_expired(self):
        for sim in self.owner.all_sims_in_situation_gen():
            services.get_zone_situation_manager().make_sim_leave_now_must_run(
                sim)
        self.owner._self_destruct()
class _GreetedPlayerVisitingNPCState(CommonSituationState):
    FACTORY_TUNABLES = {'scolding_interactions': TunableInteractionOfInterest(description='\n                 The interaction, when run increases your scold count.\n                 '), 'scolding_notification': UiDialogNotification.TunableFactory(description='\n            The notification to display after scolding a greeted player.\n            '), 'inappropriate_behavior_threshold': TunableThreshold(description='\n            Threshold for times a Sim may be scolded for inappropriate behavior.\n            When leaving this threshold, they will be sent away. \n            '), 'send_away_notification': UiDialogNotification.TunableFactory(description='\n            Notification to be triggered when sending away the sim.\n            '), 'send_away_inappropriate_sim_interaction': TunableReference(description='\n            The affordance that the reacting NPC will run to tell \n            the inappropriate Sim to leave. \n            ', manager=services.get_instance_manager(sims4.resources.Types.INTERACTION))}

    def __init__(self, scolding_interactions, scolding_notification, inappropriate_behavior_threshold, send_away_notification, send_away_inappropriate_sim_interaction, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.scolding_interactions = scolding_interactions
        self.scolding_notification = scolding_notification
        self.inappropriate_behavior_threshold = inappropriate_behavior_threshold
        self.send_away_notification = send_away_notification
        self.send_away_inappropriate_sim_interaction = send_away_inappropriate_sim_interaction
        self._scold_count = 0

    def on_activate(self, reader=None):
        super().on_activate(reader)
        if reader is None:
            self._scold_count = 0
        else:
            self._scold_count = reader.read_uint32(SCOLD_COUNT_TOKEN, 0)
        for custom_key in self.scolding_interactions.custom_keys_gen():
            self._test_event_register(TestEvent.InteractionComplete, custom_key)

    def save_state(self, writer):
        super().save_state(writer)
        writer.write_uint32(SCOLD_COUNT_TOKEN, self._scold_count)

    def handle_event(self, sim_info, event, resolver):
        if event == TestEvent.InteractionComplete and resolver(self.scolding_interactions):
            self._handle_scolding_interaction(sim_info, event, resolver)

    def _handle_scolding_interaction(self, sim_info, event, resolver):
        target = resolver.interaction.target
        if resolver.interaction.sim.sim_info is not sim_info:
            return
        if not self.owner.is_sim_in_situation(target):
            return
        self._scold_count += 1
        if self.inappropriate_behavior_threshold.compare(self._scold_count):
            dialog = self.scolding_notification(sim_info)
            dialog.show_dialog(secondary_icon_override=IconInfoData(obj_instance=sim_info))
        else:
            dialog = self.send_away_notification(sim_info)
            dialog.show_dialog(secondary_icon_override=IconInfoData(obj_instance=sim_info))
            sim = sim_info.get_sim_instance()
            context = InteractionContext(sim, InteractionContext.SOURCE_SCRIPT, Priority.Critical)
            execute_result = sim.push_super_affordance(self.send_away_inappropriate_sim_interaction, target, context)
            if execute_result:
                execute_result.interaction.register_on_finishing_callback(self._sent_away_finished_callback)

    def _sent_away_finished_callback(self, interaction):
        if not interaction.is_finishing_naturally:
            return
        self.owner._change_state(self.owner.leave_npc_house_state())
示例#3
0
class _ShowToTableState(CommonSituationState, HostSituationStateMixin):
    FACTORY_TUNABLES = {
        'interaction_of_interest':
        TunableInteractionOfInterest(
            description=
            '\n                 The interaction that when it exits the interaction \n                 pipeline causes this particular state to change back to the\n                 arrival state.\n                 '
        )
    }

    def __init__(self, *args, interaction_of_interest, **kwargs):
        super().__init__(*args, **kwargs)
        self._group = None
        self._interaction_of_interest = interaction_of_interest

    def handle_event(self, sim_info, event, resolver):
        if event != TestEvent.InteractionExitedPipeline:
            return
        if self.owner is None or not self.owner.sim_of_interest(sim_info):
            return
        if not resolver(self._interaction_of_interest):
            return
        if self._group is not None:
            for sim in self._group.all_sims_in_situation_gen():
                services.get_event_manager().process_event(
                    test_events.TestEvent.RestaurantTableClaimed,
                    sim_info=sim.sim_info)
        log_host_action('Leaving Show To Table State', 'Success')
        self._change_state(self.owner._arriving_situation_state())

    def on_activate(self, reader=None):
        super().on_activate(reader)
        log_host_action('Starting Show To Table State', 'Success')
        self._group = self.get_valid_group_to_seat()
        if self._group is None:
            self._change_state(self.owner._arriving_situation_state())
            return
        if not self.owner.push_show_table_interactions(self._group):
            log_host_action(
                'Leaving Show To Table State',
                'Failed - Leaving without performing interactions.')
            self._change_state(self.owner._arriving_situation_state())
        for custom_key in self._interaction_of_interest.custom_keys_gen():
            self._test_event_register(TestEvent.InteractionExitedPipeline,
                                      custom_key)

    def timer_expired(self):
        log_host_action('During Show To Table State',
                        'Failed - Timeout occured.')
        self.owner._change_state(self.owner._arriving_situation_state())
示例#4
0
class _OrderAndWaitForCoffeeState(CommonSituationState):
    FACTORY_TUNABLES = {
        'interaction_of_interest':
        TunableInteractionOfInterest(
            description=
            '\n             The interaction that needs to run to \n             '
        ),
        'order_coffee_timeout':
        OptionalTunable(
            description=
            "\n            Optional tunable for how long to wait before progressing to the\n            next state. This is basically here if you don't care if they order\n            coffee all of the time.\n            ",
            tunable=TunableSimMinute(
                description=
                '\n                The length of time before moving onto the next state.\n                ',
                default=60))
    }

    def __init__(self, interaction_of_interest, order_coffee_timeout,
                 **kwargs):
        super().__init__(**kwargs)
        self._interaction_of_interest = interaction_of_interest
        self._order_coffee_timeout = order_coffee_timeout

    def on_activate(self, reader=None):
        super().on_activate(reader)
        for custom_key in self._interaction_of_interest.custom_keys_gen():
            self._test_event_register(TestEvent.InteractionStart, custom_key)
        if self._order_coffee_timeout is not None:
            self._create_or_load_alarm(ORDER_COFFEE_TIMEOUT,
                                       self._order_coffee_timeout,
                                       lambda _: self.timer_expired(),
                                       should_persist=True,
                                       reader=reader)

    def handle_event(self, sim_info, event, resolver):
        if event != TestEvent.InteractionStart:
            return
        if not resolver(self._interaction_of_interest):
            return
        if not self.owner.sim_of_interest(sim_info):
            return
        self.owner._change_state(self.owner.get_post_coffee_state())

    def timer_expired(self):
        self.owner._change_state(self.owner.get_post_coffee_state())
示例#5
0
    class _EmployeeSituationState(HasTunableFactory, AutoFactoryInit,
                                  SituationState):
        FACTORY_TUNABLES = {
            'role_state':
            RoleState.TunableReference(
                description=
                '\n                The role state that is active on the employee for the duration\n                of this state.\n                '
            ),
            'timeout_min':
            TunableSimMinute(
                description=
                '\n                The minimum amount of time, in Sim minutes, the employee will be\n                in this state before moving on to a new state.\n                ',
                default=10),
            'timeout_max':
            TunableSimMinute(
                description=
                '\n                The maximum amount of time, in Sim minutes, the employee will be\n                in this state before moving on to a new state.\n                ',
                default=30),
            'push_interaction':
            TunableInteractionOfInterest(
                description=
                '\n                If an interaction of this type is run by the employee, this\n                state will activate.\n                '
            )
        }

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

        def on_activate(self, reader=None):
            super().on_activate(reader)
            self.owner._set_job_role_state(self.owner.employee_job,
                                           self.role_state)
            timeout = random.randint(self.timeout_min, self.timeout_max)
            self._create_or_load_alarm(self.state_name,
                                       timeout,
                                       self._timeout_expired,
                                       reader=reader)

        def _timeout_expired(self, *_, **__):
            self._change_state(self.owner._choose_next_state())
示例#6
0
class RetailEmployeeSituation(BusinessEmployeeSituationMixin,
                              SituationComplexCommon):
    class _EmployeeSituationState(HasTunableFactory, AutoFactoryInit,
                                  SituationState):
        FACTORY_TUNABLES = {
            'role_state':
            RoleState.TunableReference(
                description=
                '\n                The role state that is active on the employee for the duration\n                of this state.\n                '
            ),
            'timeout_min':
            TunableSimMinute(
                description=
                '\n                The minimum amount of time, in Sim minutes, the employee will be\n                in this state before moving on to a new state.\n                ',
                default=10),
            'timeout_max':
            TunableSimMinute(
                description=
                '\n                The maximum amount of time, in Sim minutes, the employee will be\n                in this state before moving on to a new state.\n                ',
                default=30),
            'push_interaction':
            TunableInteractionOfInterest(
                description=
                '\n                If an interaction of this type is run by the employee, this\n                state will activate.\n                '
            )
        }

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

        def on_activate(self, reader=None):
            super().on_activate(reader)
            self.owner._set_job_role_state(self.owner.employee_job,
                                           self.role_state)
            timeout = random.randint(self.timeout_min, self.timeout_max)
            self._create_or_load_alarm(self.state_name,
                                       timeout,
                                       self._timeout_expired,
                                       reader=reader)

        def _timeout_expired(self, *_, **__):
            self._change_state(self.owner._choose_next_state())

    INSTANCE_TUNABLES = {
        'employee_job':
        SituationJob.TunableReference(
            description=
            '\n            The situation job for the employee.\n            '),
        'role_state_go_to_store':
        RoleState.TunableReference(
            description=
            '\n            The role state for getting the employee inside the store. This is\n            the default role state and will be run first before any other role\n            state can start.\n            '
        ),
        'role_state_go_to_store_timeout':
        TunableSimMinute(
            description=
            "\n            Automatically advance out of the role state after waiting for this\n            duration. There's a number of reasons the employee can fail to exit\n            the role state in a timely fashion, such as the register is blocked\n            (by another employee clocking, even) and hijacked by a social.\n            ",
            default=60),
        'state_socialize':
        _EmployeeSituationState.TunableFactory(
            description=
            '\n            The state during which employees socialize with customers.\n            ',
            locked_args={'state_name': 'socialize'}),
        'state_restock':
        _EmployeeSituationState.TunableFactory(
            description=
            '\n            The state during which employees restock items.\n            ',
            locked_args={'state_name': 'restock'}),
        'state_clean':
        _EmployeeSituationState.TunableFactory(
            description=
            '\n            The state during which employees clean the store.\n            ',
            locked_args={'state_name': 'clean'}),
        'state_slack_off':
        _EmployeeSituationState.TunableFactory(
            description=
            '\n            The state during which employees slack off.\n            ',
            locked_args={'state_name': 'slack_off'}),
        'state_ring_up_customers':
        _EmployeeSituationState.TunableFactory(
            description=
            '\n            The state during which employees will ring up customers.\n            ',
            locked_args={'state_name': 'ring_up_customers'}),
        'go_to_store_interaction':
        TunableInteractionOfInterest(
            description=
            '\n            The interaction that, when run by an employee, will switch the\n            situation state to start cleaning, upselling, restocking, etc.\n            '
        ),
        'go_home_interaction':
        TunableInteractionOfInterest(
            description=
            '\n            The interaction that, when run on an employee, will have them end\n            this situation and go home.\n            '
        )
    }
    REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._employee_sim_info = None
        self._register_test_event_for_keys(
            TestEvent.InteractionComplete,
            self.state_socialize.push_interaction.custom_keys_gen())
        self._register_test_event_for_keys(
            TestEvent.InteractionComplete,
            self.state_restock.push_interaction.custom_keys_gen())
        self._register_test_event_for_keys(
            TestEvent.InteractionComplete,
            self.state_clean.push_interaction.custom_keys_gen())
        self._register_test_event_for_keys(
            TestEvent.InteractionComplete,
            self.state_slack_off.push_interaction.custom_keys_gen())
        self._register_test_event_for_keys(
            TestEvent.InteractionComplete,
            self.state_ring_up_customers.push_interaction.custom_keys_gen())
        self._register_test_event_for_keys(
            TestEvent.InteractionComplete,
            self.go_home_interaction.custom_keys_gen())

    @classmethod
    def _states(cls):
        return (SituationStateData(1, _GoToStoreState),
                SituationStateData(2, cls.state_socialize),
                SituationStateData(5, cls.state_restock),
                SituationStateData(6, cls.state_clean),
                SituationStateData(7, cls.state_slack_off),
                SituationStateData(8, cls.state_ring_up_customers))

    @classmethod
    def _state_to_uid(cls, state_to_find):
        state_type_to_find = type(state_to_find)
        if state_type_to_find is _GoToStoreState:
            return 1
        state_name = getattr(state_to_find, 'state_name', None)
        if state_name is None:
            return cls.INVALID_STATE_UID
        for state_data in cls._states():
            if getattr(state_data.state_type, 'state_name',
                       None) == state_name:
                return state_data.uid
        return cls.INVALID_STATE_UID

    def _save_custom_situation(self, writer):
        super()._save_custom_situation(writer)
        writer.write_uint64('original_duration', self._original_duration)

    def handle_event(self, sim_info, event, resolver):
        if event == TestEvent.InteractionComplete:
            target_sim = resolver.interaction.get_participant(
                ParticipantType.TargetSim)
            if target_sim is None:
                target_sim = resolver.interaction.get_participant(
                    ParticipantType.Actor)
            target_sim = getattr(target_sim, 'sim_info', target_sim)
            if target_sim is self._employee_sim_info:
                if resolver(self.state_socialize.push_interaction):
                    self._change_state(self.state_socialize())
                elif resolver(self.state_restock.push_interaction):
                    self._change_state(self.state_restock())
                elif resolver(self.state_clean.push_interaction):
                    self._change_state(self.state_clean())
                elif resolver(self.state_slack_off.push_interaction):
                    self._change_state(self.state_slack_off())
                elif resolver(self.state_ring_up_customers.push_interaction):
                    self._change_state(self.state_ring_up_customers())
                elif resolver(self.go_home_interaction):
                    self._on_business_closed()
        super().handle_event(sim_info, event, resolver)

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return [(cls.employee_job, cls.role_state_go_to_store)]

    @classmethod
    def default_job(cls):
        return cls.employee_job

    def start_situation(self):
        super().start_situation()
        self._change_state(_GoToStoreState())

    @classmethod
    def get_sims_expected_to_be_in_situation(cls):
        return 1

    @classproperty
    def situation_serialization_option(cls):
        return situations.situation_types.SituationSerializationOption.LOT

    def get_employee_sim_info(self):
        if self._employee_sim_info is not None:
            return self._employee_sim_info
        return next(self._guest_list.invited_sim_infos_gen(), None)

    def _on_set_sim_job(self, sim, job_type):
        super()._on_set_sim_job(sim, job_type)
        self._employee_sim_info = sim.sim_info
        self._update_work_buffs(from_load=True)

    @property
    def _is_clocked_in(self):
        business_manager = services.business_service(
        ).get_business_manager_for_zone()
        if business_manager is None:
            return False
        return business_manager.is_employee_clocked_in(self._employee_sim_info)

    def _choose_next_state(self):
        valid_states = [
            self.state_socialize, self.state_restock, self.state_clean,
            self.state_ring_up_customers
        ]
        random_state = random.choice(valid_states)
        return random_state()
示例#7
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
class VetEmployeeSituation(BusinessEmployeeSituationMixin,
                           SituationComplexCommon):
    INSTANCE_TUNABLES = {
        '_default_state':
        VetManagedEmployeeSituationState.TunableFactory(
            description=
            '\n                Default state for the vet employee, which can never be disabled.\n                ',
            locked_args={'state_type': VetEmployeeSituationStates.DEFAULT},
            tuning_group=GroupNames.SITUATION),
        '_managed_states':
        TunableMapping(
            description=
            '\n            A mapping of state types to states.\n            ',
            key_type=TunableEnumEntry(
                VetEmployeeSituationStates,
                default=VetEmployeeSituationStates.DEFAULT,
                invalid_enums=(VetEmployeeSituationStates.DEFAULT, )),
            value_type=TunableTuple(
                state=VetManagedEmployeeSituationState.TunableFactory(),
                enable_disable=OptionalTunable(
                    display_name='Enable/Disable Support',
                    tunable=TunableTuple(
                        enable_interaction=TunableInteractionOfInterest(
                            description=
                            '\n                            Interaction of interest which will cause this state to be enabled.\n                            '
                        ),
                        disable_interaction=TunableInteractionOfInterest(
                            description=
                            '\n                            Interaction of interest which will cause this state to be disabled.\n                            '
                        ),
                        disabling_buff=TunableReference(
                            description=
                            '\n                            The Buff that disables the state, used to set\n                            the state from the load.\n                            ',
                            manager=services.get_instance_manager(
                                Types.BUFF)))),
                weight=TunableRange(
                    description=
                    '\n                    A weight to use to choose to run this state in a random lottery.\n                    ',
                    tunable_type=int,
                    minimum=0,
                    default=1)),
            tuning_group=GroupNames.SITUATION),
        '_default_state_weight':
        TunableRange(
            description=
            '\n            A weight to use to choose to for the default state in a random\n            lottery of which state to run.\n            ',
            tunable_type=int,
            minimum=1,
            default=1)
    }
    REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES

    @classmethod
    def _states(cls):
        state_data = []
        state_data.append(
            SituationStateData(VetEmployeeSituationStates.DEFAULT.value,
                               VetManagedEmployeeSituationState,
                               factory=cls._default_state))
        for (state_type, state_tuning) in cls._managed_states.items():
            state_data.append(
                SituationStateData(state_type.value,
                                   VetManagedEmployeeSituationState,
                                   factory=functools.partial(
                                       state_tuning.state, state_type)))
        return state_data

    @classmethod
    def default_job(cls):
        pass

    @classmethod
    def _state_to_uid(cls, state_to_find):
        return state_to_find.state_type.value

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return list(
            cls._default_state._tuned_values.job_and_role_changes.items())

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._locked_states = set()
        self._type_to_disable_interaction = {}
        self._type_to_enable_interaction = {}
        self._state_disabling_buffs = set()
        for (state_type, state_tuning) in self._managed_states.items():
            enable_disable = state_tuning.enable_disable
            if enable_disable is None:
                continue
            self._register_test_event_for_keys(
                TestEvent.InteractionComplete,
                enable_disable.disable_interaction.custom_keys_gen())
            self._type_to_disable_interaction[
                state_type] = enable_disable.disable_interaction
            self._register_test_event_for_keys(
                TestEvent.InteractionComplete,
                enable_disable.enable_interaction.custom_keys_gen())
            self._type_to_enable_interaction[
                state_type] = enable_disable.enable_interaction
            self._state_disabling_buffs.add(enable_disable.disabling_buff)

    def start_situation(self):
        super().start_situation()
        self._change_state(self._default_state())

    def _on_set_sim_job(self, sim, job_type):
        super()._on_set_sim_job(sim, job_type)
        self._start_work_duration()

    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)
        sim.Buffs.on_buff_added.append(self._updated_disabled_states)

    def _on_remove_sim_from_situation(self, sim):
        super()._on_remove_sim_from_situation(sim)
        sim.Buffs.on_buff_added.remove(self._updated_disabled_states)

    def _updated_disabled_states(self, buff_type, sim_id):
        if buff_type not in self._state_disabling_buffs:
            return
        for state_type in self._managed_states:
            state_tuning = self._managed_states[state_type]
            if state_tuning.enable_disable is None:
                continue
            if state_tuning.enable_disable.disabling_buff == buff_type:
                self._disable_state(state_type)

    def get_employee(self):
        return next(iter(self.all_sims_in_situation_gen()), None)

    def get_employee_sim_info(self):
        employee = self.get_employee()
        if employee is None:
            return
        return employee.sim_info

    def handle_event(self, sim_info, event, resolver):
        super().handle_event(sim_info, event, resolver)
        target_sim_info = resolver.get_participant(ParticipantType.TargetSim)
        if not (target_sim_info is sim_info
                and self.is_sim_info_in_situation(sim_info)):
            return
        for (state_type,
             interaction_test) in self._type_to_disable_interaction.items():
            if resolver(interaction_test):
                self._disable_state(state_type)
        for (state_type,
             interaction_test) in self._type_to_enable_interaction.items():
            if resolver(interaction_test):
                self._enable_state(state_type)

    def try_set_next_state(self, next_state_type=None):
        if next_state_type is None or next_state_type in self._locked_states:
            next_state_type = self._choose_next_state(
                invalid_states=(next_state_type, ))
        self._change_to_state_type(next_state_type)

    def _change_to_state_type(self, state_type):
        self.log_flow_entry('Changing to state {}'.format(state_type.name))
        if state_type == VetEmployeeSituationStates.DEFAULT:
            self._change_state(self._default_state())
        else:
            self._change_state(
                self._managed_states[state_type].state(state_type))

    def _choose_next_state(self, invalid_states=None):
        available_states = set(
            self._managed_states.keys()) - self._locked_states
        if invalid_states is not None:
            available_states = available_states - set(invalid_states)
        if not available_states:
            return VetEmployeeSituationStates.DEFAULT
        weighted = [(self._managed_states[key].weight, key)
                    for key in available_states]
        weighted.append(
            (self._default_state_weight, VetEmployeeSituationStates.DEFAULT))
        return random.weighted_random_item(weighted)

    def _enable_state(self, state_type):
        if state_type in self._locked_states:
            self._locked_states.remove(state_type)

    def _disable_state(self, state_type):
        self._locked_states.add(state_type)
        if self._cur_state.state_type == state_type:
            self.try_set_next_state()

    def get_phase_state_name_for_gsi(self):
        if self._cur_state is None:
            return 'None'
        else:
            return self._cur_state.state_type.name

    def _gsi_additional_data_gen(self):
        yield ('Locked States', str(self._locked_states))

    def log_flow_entry(self, message):
        log_vet_flow_entry(repr(self.get_employee()),
                           type(self).__name__, message)
class CivicInspectorSituation(SituationComplexCommon):
    INSTANCE_TUNABLES = {'inspector_job_and_role_state': TunableSituationJobAndRoleState(description='\n            The job and role state for the eco-inspector.\n            ', tuning_group=GroupNames.ROLES), 'inspector_entry': _InspectorEntryState.TunableFactory(description='\n           Inspector Entry State. Listens for portal allowance.\n            ', display_name='1. Inspector Entry State', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP), 'civic_inspect_outside': _InspectorOutsideState.TunableFactory(description='\n           Inspecting from outside, not allowed inside.\n            ', display_name='2. Civic Inspect Outside', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP), 'civic_inspect_inside': _InspectorInsideState.TunableFactory(description='\n            Allowed inside, but may inspect outside if not objects.\n            ', display_name='3. Civic Inspect Inside', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP), 'final_checks': _FinalChecksState.TunableFactory(description='\n            Checks if civic policy is being followed using the conditions\n            described in tuning. \n            ', display_name='4. Final Checks', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP), 'leave': _LeaveState.TunableFactory(description='\n            Final checks before leaving.\n            ', display_name='5. Leaving state.', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP), 'inspection_successful_notification': UiDialogNotification.TunableFactory(description='\n            A TNS that is displayed to inform of successful inspection.\n            '), 'inspection_failure_notification': UiDialogNotification.TunableFactory(description='\n            A TNS that is displayed to inform of failed inspection.\n            '), 'inspection_partial_success_notification': UiDialogNotification.TunableFactory(description='\n            A TNS that is displayed to inform of failed inspection.\n            '), 'commodity_notfollow': Commodity.TunableReference(description='\n            lot commodity that we set when we want a multiplier for bill modifier.\n                '), 'commodity_follow': Commodity.TunableReference(description='\n            lot commodity that we set when we want a multiplier for bill modifier.\n                '), 'overlook_issues_success_interaction': TunableInteractionOfInterest(description='\n            Interaction pushed to indicate success of overlooking issues.\n            ')}

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.follow_status = PolicyFollowing.FULL_FOLLOWED
        self.policies_not_followed = []

    @classmethod
    def _states(cls):
        return (SituationStateData(1, _WaitState), SituationStateData(2, _InspectorEntryState, factory=cls.inspector_entry), SituationStateData(3, _InspectorInsideState, factory=cls.civic_inspect_inside), SituationStateData(4, _InspectorOutsideState, factory=cls.civic_inspect_outside), SituationStateData(5, _FinalChecksState, factory=cls.final_checks), SituationStateData(6, _LeaveState, factory=cls.leave))

    @classmethod
    def default_job(cls):
        pass

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

    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.inspector_person() is not None:
            self._change_state(self.inspector_entry())

    def _show_inspection_notification(self, inspection_notification, **kwargs):
        inspector = self.inspector_person()
        if inspector is not None:
            dialog = inspection_notification(inspector, resolver=SingleSimResolver(inspector.sim_info))
            dialog.show_dialog(**kwargs)

    def notify_result_and_push_bill_modifier(self):
        lot = services.active_lot()
        additional_tokens = ()
        if self.policies_not_followed:
            additional_tokens = (LocalizationHelperTuning.get_bulleted_list(None, *self.policies_not_followed),)
        if self.follow_status == PolicyFollowing.NOT_FOLLOWED:
            lot.set_stat_value(self.commodity_notfollow, self.commodity_notfollow.max_value_tuning)
            self._show_inspection_notification(self.inspection_failure_notification, additional_tokens=additional_tokens)
        elif self.follow_status == PolicyFollowing.PARTIAL_FOLLOWED:
            self._show_inspection_notification(self.inspection_partial_success_notification, additional_tokens=additional_tokens)
        else:
            lot.set_stat_value(self.commodity_follow, self.commodity_follow.max_value_tuning)
            self._show_inspection_notification(self.inspection_successful_notification)

    def inspector_person(self):
        sim = next(self.all_sims_in_job_gen(self.inspector_job_and_role_state.job), None)
        return sim

    def start_situation(self):
        super().start_situation()
        self._change_state(_WaitState())
示例#10
0
class HospitalPatientSituation(PatientSituationBase):
    INSTANCE_TUNABLES = {
        'admitted_role_state':
        RoleState.TunableReference(
            description=
            '\n            A reference to the hospital patients admitted\n            role state while in the situation. This is the\n            state where the patient is assigned to a bed \n            and the doctor is actively trying to diagnose\n            the issue.\n            ',
            tuning_group=PatientSituationBase.JOB_AND_STATE_GROUP,
            display_name='03_admitted_role_state'),
        'diagnosed_role_state':
        RoleState.TunableReference(
            description=
            '\n            A reference to the hospital patients diagnosed\n            role state while in the situation. This is\n            the state where the patient has been diagnosed \n            but it still waiting for the doctor to treat\n            them.\n            ',
            tuning_group=PatientSituationBase.JOB_AND_STATE_GROUP,
            display_name='04_diagnosed_role_state'),
        'go_to_diagnosed_interactions':
        TunableInteractionOfInterest(
            description=
            '\n            The interactions to look for when a Sim has been diagnosed by a \n            doctor and is now waiting for treatment.\n            ',
            tuning_group=PatientSituationBase.STATE_ADVANCEMENT_GROUP),
        'admitted_duration_for_time_jump':
        TunableSimMinute(
            description=
            '\n            The amount of time allowed to pass before a Sim in the admitted\n            state will be ignored on load with a time jump.\n            ',
            default=180,
            tuning_group=PatientSituationBase.TIMEOUT_GROUP),
        'diagnosed_duration_for_time_jump':
        TunableSimMinute(
            description=
            '\n            The amount of time allowed to pass before a Sim in the diagnosed\n            state will be ignored on load with a time jump.\n            ',
            default=180,
            tuning_group=PatientSituationBase.TIMEOUT_GROUP),
        'pre_diagnosed':
        Tunable(
            description=
            '\n            If this is true then when the Sim is pre-rolled it will skip to the\n            _DiagnosedState(). \n            \n            If it is False then it will default to pre-rolling\n            the Sim to _AdmittedState().\n            ',
            tunable_type=bool,
            default=False)
    }

    @classmethod
    def _states(cls):
        return (SituationStateData(1, ArrivingState),
                SituationStateData(2, WaitingState),
                SituationStateData(3, _AdmittedState),
                SituationStateData(4, _DiagnosedState),
                SituationStateData(5, TreatedState))

    def _skip_ahead_for_preroll(self):
        if self.pre_diagnosed:
            self._change_state(_DiagnosedState())
        else:
            self._change_state(_AdmittedState())

    def _on_done_waiting(self):
        self._change_state(_AdmittedState())

    @classmethod
    def should_state_type_load_after_time_jump(cls, state_type):
        if not super().should_state_type_load_after_time_jump(state_type):
            return False
        else:
            elapsed_time = services.current_zone(
            ).time_elapsed_since_last_save().in_minutes()
            if state_type is _AdmittedState:
                timeout = cls.admitted_duration_for_time_jump
            elif state_type is _DiagnosedState:
                timeout = cls.diagnosed_duration_for_time_jump
            else:
                timeout = None
            if timeout is not None and elapsed_time >= timeout:
                return False
        return True
示例#11
0
class TempleTuning:
    TEMPLES = TunableMapping(
        description=
        '\n        A Mapping of Temple Templates (house descriptions) and the data\n        associated with each temple.\n        ',
        key_name='Template House Description',
        key_type=TunableHouseDescription(pack_safe=True),
        value_name='Temple Data',
        value_type=TunableTuple(
            description=
            '\n            The data associated with the mapped temple template.\n            ',
            rooms=TunableMapping(
                description=
                '\n                A mapping of room number to the room data. Room number 0 will be\n                the entrance room to the temple, room 1 will be the first room\n                that needs to be unlocked, and so on.\n                ',
                key_name='Room Number',
                key_type=int,
                value_name='Room Data',
                value_type=TunableTempleRoomData(pack_safe=True)),
            enter_lot_loot=TunableSet(
                description=
                '\n                Loot applied to Sims when they enter or spawn in to this Temple.\n                \n                NOTE: Exit Lot Loot is not guaranteed to be given. For example,\n                if the Sim walks onto the lot, player switches to a different\n                zone, then summons that Sim, that Sim will bypass getting the\n                exit loot.\n                ',
                tunable=LootActions.TunableReference(pack_safe=True)),
            exit_lot_loot=TunableSet(
                description=
                '\n                Loot applied to Sims when they exit or spawn out of this Temple.\n                \n                NOTE: This loot is not guaranteed to be given after Enter Lot\n                Loot. For example, if the Sim walks onto the lot, player\n                switches to a different zone, then summons that Sim, that Sim\n                will bypass getting the exit loot.\n                ',
                tunable=LootActions.TunableReference(pack_safe=True))))
    GATE_TAG = TunableTag(
        description=
        '\n        The tag used to find the gate objects inside Temples.\n        ',
        filter_prefixes=('func_temple', ))
    TRAP_TAG = TunableTag(
        description=
        '\n        The tag used to identify traps inside temples. This will be used to find\n        placeholder traps as well.\n        ',
        filter_prefixes=('func_temple', ))
    CHEST_TAG = TunableTag(
        description=
        "\n        The tag used to identify the final chest of a temple. If it's in the\n        open state, the temple will be considered solved.\n        ",
        filter_prefixes=('func_temple', ))
    CHEST_OPEN_STATE = TunablePackSafeReference(
        description=
        '\n        The state that indicates the chest is open.\n        ',
        manager=services.get_instance_manager(
            sims4.resources.Types.OBJECT_STATE),
        class_restrictions='ObjectStateValue')
    GATE_STATE = TunablePackSafeReference(
        description=
        '\n        The state for temple gates. Used for easy look up.\n        ',
        manager=services.get_instance_manager(
            sims4.resources.Types.OBJECT_STATE),
        class_restrictions='ObjectState')
    GATE_UNLOCK_STATE = TunablePackSafeReference(
        description='\n        The unlock state for temple gates.\n        ',
        manager=services.get_instance_manager(
            sims4.resources.Types.OBJECT_STATE),
        class_restrictions='ObjectStateValue')
    TEMPLE_LOT_DESCRIPTION = TunablePackSafeLotDescription(
        description=
        '\n        A reference to the lot description file for the temple lot. This is used\n        for easier zone ID lookups.\n        '
    )
    GATE_LOCK_LOOT = LockDoor.TunableFactory(
        description=
        '\n        The LockDoor loot to run on the gates in the temple on load when they\n        should be locked.\n        '
    )
    GATE_UNLOCK_LOOT = UnlockDoor.TunableFactory(
        description=
        '\n        The UnlockDoor loot to run on the gates when they should be unlocked.\n        '
    )
    CHEST_OPEN_INTEARCTION = TunableInteractionOfInterest(
        description=
        '\n        A reference to the open interaction for chests. This interaction will be\n        listened for to determine temple completion.\n        '
    )
class JungleOpenStreetDirector(OpenStreetDirectorBase):
    DEFAULT_LOCK = LockAllWithSimIdExceptionData(lock_priority=LockPriority.PLAYER_LOCK, lock_sides=LockSide.LOCK_FRONT, should_persist=True, except_actor=False, except_household=False)
    PATH_LOCKED = 0
    PATH_UNAVAILABLE = 1
    PATH_UNLOCKED = 2
    MIN_CLEAR_COMMODITY = 0
    TEMPLE_STATE_NEEDS_RESET = 0
    TEMPLE_STATE_RESET = 1
    TEMPLE_STATE_IN_PROGRESS = 2
    TEMPLE_STATE_COMPLETE = 3
    TREASURE_CHEST_CLOSED = 0
    TREASURE_CHEST_OPEN = 1
    TEMPLE_PATH_OBSTACLE = TunableTag(description='\n        The tag for the path obstacle that leads to the Temple. This will be\n        used to gain a reference to it when the temple resets.\n        ', filter_prefixes=('Func',))
    TEMPLE_PATH_OBSTACLE_LOCK_STATE = TunableStateValueReference(description='\n        Indicates the temple is locked. This will be used to lock the\n        Path Obstacle.\n        ', pack_safe=True)
    TEMPLE_PATH_OBSTACLE_UNLOCK_STATE = TunableStateValueReference(description='\n        The unlock state for the path obstacles. Set when we load a brand new\n        vacation in the jungle.\n        ', pack_safe=True)
    TEMPLE_VENUE_TUNING = TunablePackSafeReference(description='\n        The venue for the temple zone.\n        ', manager=services.get_instance_manager(sims4.resources.Types.VENUE))
    TEMPLE_LOCK_COMMODITY = TunablePackSafeReference(description='\n        The commodity that controls the temple lock.\n        ', manager=services.get_instance_manager(sims4.resources.Types.STATISTIC), class_restrictions='Commodity')
    INSTANCE_TUNABLES = {'path_obstacle_data': TunableMapping(description='\n            Tuned data for the path obstacles in the open street. \n            \n            This includes which conditional layer the path obstacle is attached\n            to and what state that layer is in when the obstacle is locked.\n            ', key_name='obstacle_tag_id', key_type=TunableTag(description='\n                A tag for a specific path obstacle object that we might want\n                to mark blocked or access_PermanentlyBlocked. \n                ', filter_prefixes=('Func',)), value_name='obstacle_data', value_type=TunableTuple(description='\n                All of the data associated with the path obstacle.\n                ', always_available=Tunable(description='\n                    If True then this particular path obstacle is always \n                    available to be cleared and traveled through.\n                    \n                    If False then this path obstacle is subject to randomly\n                    being available or unavailable depending on the travel\n                    group.\n                    ', tunable_type=bool, default=False), layers=TunableList(description='\n                    A list of conditional layers and the status the layer starts\n                    in (visible/hidden) that are associated with this path\n                    obstacle.\n                    ', tunable=TunableTuple(description='\n                        Data about which conditional layer the obstacle is associated\n                        with and what state it is in.\n                        ', conditional_layer=TunableReference(description='\n                            A reference to the Conditional Layer found in the open streets.\n                            ', manager=services.get_instance_manager(sims4.resources.Types.CONDITIONAL_LAYER)), visible=Tunable(description='\n                            Whether or not the conditional layer is show/hidden when\n                            the corresponding path obstacle is locked.\n                            \n                            Checked signifies that the layer is visible when the\n                            obstacle is locked.\n                            \n                            Unchecked signifies that the layer is hidden when the \n                            obstacle is locked.\n                            ', tunable_type=bool, default=True), immediate=Tunable(description='\n                            If checked then the layer will load immediately. If\n                            not checked then the layer will load over time.\n                            ', tunable_type=bool, default=False))))), 'num_of_paths_available': TunableRange(description='\n            The number of paths that are available when a vacation group \n            arrives in the jungle for the first time.\n            ', tunable_type=int, minimum=0, default=1), 'clear_path_interaction': TunableInteractionOfInterest(description='\n            A reference to the interaction that a Sim runs in order to clear\n            the path obstacle so they can use the portal.\n            '), 'permanently_blocked_state': TunableStateValueReference(description='\n            The state the blocked path obstacles should be set to if they \n            cannot be cleared.\n            '), 'path_unlocked_state': TunableStateValueReference(description='\n            The state the blocked path obstacles should be set to if they are \n            unlocked.\n            '), 'path_clearing_commodity': TunableReference(description='\n            The commodity that has to reach 100 in order for a path to be\n            considered clear.\n            ', manager=services.get_instance_manager(sims4.resources.Types.STATISTIC)), 'treasure_chest_tag': TunableTag(description='\n            The tag used to identify a treasure chest.\n            '), 'treasure_chest_open_state': TunableStateValueReference(description='\n            The state that a treasure chest is in when it has already been \n            opened.\n            '), 'treasure_chest_closed_state': TunableStateValueReference(description='\n            The state that a treasure chest is in when it is still closed.\n            ')}

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._obstacle_status = {}
        self._path_obstacles = {}
        self._treasure_chest_status = {}
        travel_group_manager = services.travel_group_manager()
        household = services.active_household()
        travel_group = travel_group_manager.get_travel_group_by_household(household)
        if travel_group is None:
            logger.error("Trying to initialize the Jungle Open Street Director but there doesn't appear to be a travel group for the current household.")
            self._current_travel_group_id = None
        else:
            self._current_travel_group_id = travel_group.id
        services.get_event_manager().register_single_event(self, TestEvent.InteractionComplete)
        self._current_temple_id = None
        self._temple_state = self.TEMPLE_STATE_NEEDS_RESET
        self._last_time_saved = None

    def on_startup(self):
        super().on_startup()
        object_manager = services.object_manager()
        self._path_obstacles = self._get_path_obstacles()
        if self._current_travel_group_id in self._obstacle_status:
            path_obstacle_data = self._obstacle_status[self._current_travel_group_id]
            for (tag, status, progress) in path_obstacle_data:
                obstacles = object_manager.get_objects_matching_tags((tag,))
                for obstacle in obstacles:
                    if tag == self.TEMPLE_PATH_OBSTACLE:
                        if not obstacle.state_value_active(self.TEMPLE_PATH_OBSTACLE_UNLOCK_STATE):
                            self._update_temple_lock_commodity()
                            if obstacle.state_value_active(self.TEMPLE_PATH_OBSTACLE_UNLOCK_STATE):
                                status = JungleOpenStreetDirector.PATH_LOCKED
                            else:
                                status = JungleOpenStreetDirector.PATH_UNAVAILABLE
                            progress = 0
                    if status == JungleOpenStreetDirector.PATH_LOCKED:
                        self._lock_path_obstacle(obstacle, tag)
                    elif status == JungleOpenStreetDirector.PATH_UNAVAILABLE:
                        self._permanently_lock_path_obstacle(obstacle, tag)
                    elif status == JungleOpenStreetDirector.PATH_UNLOCKED:
                        self._unlock_path_obstacle(obstacle, tag)
                    else:
                        logger.error('Trying to setup an object that has a tag status that is not known. {}', status)
                    obstacle.set_stat_value(self.path_clearing_commodity, progress)
            if self._current_travel_group_id in self._treasure_chest_status:
                treasure_chest_data = self._treasure_chest_status[self._current_travel_group_id]
                for (obj_id, status) in treasure_chest_data:
                    chest = object_manager.get(obj_id)
                    if chest is None:
                        continue
                    if status == JungleOpenStreetDirector.TREASURE_CHEST_OPEN:
                        chest.set_state(self.treasure_chest_open_state.state, self.treasure_chest_open_state)
                    else:
                        chest.set_state(self.treasure_chest_closed_state.state, self.treasure_chest_closed_state)
        else:
            always_available_paths = []
            possible_paths = []
            for (obj, tag) in self._path_obstacles.items():
                if self.path_obstacle_data[tag].always_available:
                    always_available_paths.append((obj, tag))
                else:
                    possible_paths.append((obj, tag))
            available_paths = random.sample(possible_paths, min(len(possible_paths), self.num_of_paths_available))
            unavailable_paths = [path for path in possible_paths if path not in available_paths]
            for (path_obstacle, tag) in itertools.chain(always_available_paths, available_paths):
                self._lock_path_obstacle(path_obstacle, tag, reset_commodity=True)
            for (path_obstacle, tag) in unavailable_paths:
                self._permanently_lock_path_obstacle(path_obstacle, tag)
            for chest in object_manager.get_objects_matching_tags((self.treasure_chest_tag,)):
                chest.set_state(self.treasure_chest_closed_state.state, self.treasure_chest_closed_state)
            travel_group_manager = services.travel_group_manager()
            travel_groups = travel_group_manager.get_travel_group_ids_in_region()
            for group_id in travel_groups:
                if group_id == self._current_travel_group_id:
                    continue
                group = travel_group_manager.get(group_id)
                if group.played:
                    break
            else:
                self._setup_for_first_travel_group()
        if self._temple_needs_reset():
            self.reset_temple()

    def _update_temple_lock_commodity(self):
        obstacle = self._get_temple_entrance_obstacle()
        lock_tracker = obstacle.get_tracker(JungleOpenStreetDirector.TEMPLE_LOCK_COMMODITY)
        lock_stat = lock_tracker.get_statistic(JungleOpenStreetDirector.TEMPLE_LOCK_COMMODITY)
        lock_stat.update_commodity_to_time(self._last_time_saved)
        lock_state = JungleOpenStreetDirector.TEMPLE_PATH_OBSTACLE_UNLOCK_STATE.state
        obstacle.state_component.set_state_from_stat(lock_state, lock_stat)

    def _temple_needs_reset(self):
        if self._temple_state == self.TEMPLE_STATE_NEEDS_RESET:
            return True
        elif self._temple_state == self.TEMPLE_STATE_COMPLETE:
            sim_info_manager = services.sim_info_manager()
            temple_zones = tuple(services.venue_service().get_zones_for_venue_type_gen(self.TEMPLE_VENUE_TUNING))
            if len(temple_zones) != 1:
                logger.error('Found either 0 or more than 1 zone that is set as a temple venue. There can be only one!')
            temple_zone_id = next(iter(temple_zones))
            if not any(sim.zone_id == temple_zone_id and sim.is_played_sim for sim in sim_info_manager.get_all()):
                return True
        return False

    def _setup_for_first_travel_group(self):
        self._current_temple_id = None
        self._temple_state = self.TEMPLE_STATE_NEEDS_RESET
        self._unlock_temple_obstacle()

    def on_shutdown(self):
        super().on_shutdown()
        services.get_event_manager().unregister_single_event(self, TestEvent.InteractionComplete)
        self._path_obstacles.clear()

    def _get_path_obstacles(self):
        object_manager = services.object_manager()
        path_obstacles = {}
        for obstacle_tag in self.path_obstacle_data:
            obstacles = object_manager.get_objects_matching_tags((obstacle_tag,))
            for obstacle in obstacles:
                path_obstacles[obstacle] = obstacle_tag
        return path_obstacles

    def _lock_path_obstacle(self, path_obstacle, obstacle_tag, reset_commodity=False):
        path_obstacle.add_lock_data(JungleOpenStreetDirector.DEFAULT_LOCK)
        self._setup_corresponding_layers(obstacle_tag)
        if reset_commodity:
            path_obstacle.set_stat_value(self.path_clearing_commodity, JungleOpenStreetDirector.MIN_CLEAR_COMMODITY)

    def _permanently_lock_path_obstacle(self, path_obstacle, obstacle_tag):
        path_obstacle.add_lock_data(JungleOpenStreetDirector.DEFAULT_LOCK)
        path_obstacle.set_state(self.permanently_blocked_state.state, self.permanently_blocked_state)
        self._setup_corresponding_layers(obstacle_tag)

    def _unlock_path_obstacle(self, path_obstacle, obstacle_tag):
        path_obstacle.set_state(self.path_unlocked_state.state, self.path_unlocked_state)
        self._setup_corresponding_layers(obstacle_tag, unlock=True)

    def _setup_corresponding_layers(self, path_obstacle_tag, unlock=False):
        obstacle_datas = self.path_obstacle_data[path_obstacle_tag]
        for obstacle_data in obstacle_datas.layers:
            if unlock == obstacle_data.visible:
                self.remove_layer_objects(obstacle_data.conditional_layer)
            elif obstacle_data.immediate:
                self.load_layer_immediately(obstacle_data.conditional_layer)
            else:
                self.load_layer_gradually(obstacle_data.conditional_layer)

    @classproperty
    def priority(cls):
        return OpenStreetDirectorPriority.DEFAULT

    def handle_event(self, sim_info, event, resolver, **kwargs):
        if resolver(self.clear_path_interaction):
            obstacle = resolver.interaction.target
            statistic = obstacle.get_stat_instance(self.path_clearing_commodity)
            if statistic is not None:
                statistic_value = statistic.get_value()
                if statistic_value >= statistic.max_value:
                    obstacle.set_state(self.path_unlocked_state.state, self.path_unlocked_state)
                    self._setup_corresponding_layers(self._path_obstacles[obstacle], unlock=True)

    def _save_custom_open_street_director(self, street_director_proto, writer):
        group_ids = []
        tags = []
        tag_status = []
        clear_progress = []
        for (group_id, path_obstacle_data) in self._obstacle_status.items():
            if group_id == self._current_travel_group_id:
                continue
            for (tag, status, progress) in path_obstacle_data:
                group_ids.append(group_id)
                tags.append(tag)
                tag_status.append(status)
                clear_progress.append(progress)
        if self._current_travel_group_id is None:
            return
        for (path_obstacle, tag) in self._path_obstacles.items():
            group_ids.append(self._current_travel_group_id)
            tags.append(tag)
            tag_status.append(self._get_tag_status(path_obstacle))
            clear_progress.append(path_obstacle.get_stat_value(self.path_clearing_commodity))
        writer.write_uint64s(GROUP_TOKEN, group_ids)
        writer.write_uint64s(TAG_TOKEN, tags)
        writer.write_uint64s(TAG_STATUS_TOKEN, tag_status)
        writer.write_floats(CLEAR_PROGRESS_TOKEN, clear_progress)
        writer.write_uint64(CURRENT_TEMPLE_ID, self._current_temple_id)
        writer.write_uint32(TEMPLE_STATE, self._temple_state)
        writer.write_uint64(LAST_TIME_SAVED, services.time_service().sim_now.absolute_ticks())
        self._save_treasure_chest_data(writer)

    def _get_tag_status(self, path_obstacle):
        if path_obstacle.state_value_active(self.permanently_blocked_state):
            return JungleOpenStreetDirector.PATH_UNAVAILABLE
        if path_obstacle.state_value_active(self.path_unlocked_state):
            return JungleOpenStreetDirector.PATH_UNLOCKED
        return JungleOpenStreetDirector.PATH_LOCKED

    def _load_custom_open_street_director(self, street_director_proto, reader):
        if reader is None:
            return
        travel_group_manager = services.travel_group_manager()
        group_ids = reader.read_uint64s(GROUP_TOKEN, [])
        tags = reader.read_uint64s(TAG_TOKEN, [])
        tag_status = reader.read_uint64s(TAG_STATUS_TOKEN, [])
        clear_progress = reader.read_floats(CLEAR_PROGRESS_TOKEN, 0)
        self._current_temple_id = reader.read_uint64(CURRENT_TEMPLE_ID, 0)
        self._temple_state = reader.read_uint32(TEMPLE_STATE, self.TEMPLE_STATE_NEEDS_RESET)
        last_time_saved = reader.read_uint64(LAST_TIME_SAVED, 0)
        for (index, group_id) in enumerate(group_ids):
            if not travel_group_manager.get(group_id):
                continue
            if group_id not in self._obstacle_status:
                self._obstacle_status[group_id] = []
            path_obstacles = self._obstacle_status[group_id]
            path_obstacles.append((tags[index], tag_status[index], clear_progress[index]))
        self._last_time_saved = DateAndTime(last_time_saved)
        self._load_treasure_chest_data(reader)

    @property
    def current_temple_id(self):
        return self._current_temple_id

    def get_next_temple_id(self):
        if self._temple_state == self.TEMPLE_STATE_RESET:
            return self._current_temple_id

    def reset_temple(self, new_id=None, force=False):
        if self._temple_state == self.TEMPLE_STATE_COMPLETE and not force:
            self._lock_temple_obstacle()
            self._update_temple_lock_commodity()
        self._current_temple_id = self._get_new_temple_id(new_id=new_id)
        self._temple_state = self.TEMPLE_STATE_RESET
        self._update_temple_id_for_client()

    def set_temple_in_progress(self):
        self._temple_state = self.TEMPLE_STATE_IN_PROGRESS

    def set_temple_complete(self):
        self._temple_state = self.TEMPLE_STATE_COMPLETE

    def _set_temple_obstacle_state(self, state_value):
        obstacle = self._get_temple_entrance_obstacle()
        if obstacle is not None:
            obstacle.set_state(state_value.state, state_value)

    def _get_temple_entrance_obstacle(self):
        obstacle = services.object_manager().get_objects_matching_tags((self.TEMPLE_PATH_OBSTACLE,))
        if len(obstacle) != 1:
            logger.error('There should only be one Temple Entrance Path Obstacle. Found {} instead.', len(obstacle), owner='trevor')
            return
        return next(iter(obstacle))

    def _lock_temple_obstacle(self):
        self._set_temple_obstacle_state(self.TEMPLE_PATH_OBSTACLE_LOCK_STATE)

    def _unlock_temple_obstacle(self):
        self._set_temple_obstacle_state(self.TEMPLE_PATH_OBSTACLE_UNLOCK_STATE)

    def _get_new_temple_id(self, new_id=None):
        temples = list(TempleTuning.TEMPLES.keys())
        if new_id is not None and new_id in temples and new_id != self._current_temple_id:
            return new_id
        if self._current_temple_id is not None:
            temples.remove(self._current_temple_id)
        return random.choice(temples)

    def _update_temple_id_for_client(self):
        for proto in services.get_persistence_service().zone_proto_buffs_gen():
            if proto.lot_description_id == TempleTuning.TEMPLE_LOT_DESCRIPTION:
                proto.pending_house_desc_id = self._current_temple_id

    def _save_treasure_chest_data(self, writer):
        group_ids = []
        obj_ids = []
        status_ids = []
        for (group_id, treasure_chest_data) in self._treasure_chest_status.items():
            if group_id == self._current_travel_group_id:
                continue
            for (obj_id, curr_status) in treasure_chest_data:
                group_ids.append(group_id)
                obj_ids.append(obj_id)
                status_ids.append(curr_status)
        if self._current_travel_group_id is None:
            return
        for chest in services.object_manager().get_objects_matching_tags((self.treasure_chest_tag,)):
            if chest.is_on_active_lot():
                continue
            group_ids.append(self._current_travel_group_id)
            obj_ids.append(chest.id)
            status_ids.append(self._get_treasure_chest_status(chest))
        writer.write_uint64s(TREASURE_CHEST_GROUP, group_ids)
        writer.write_uint64s(TREASURE_CHEST_ID, obj_ids)
        writer.write_uint64s(TREASURE_CHEST_STATUS, status_ids)

    def _get_treasure_chest_status(self, chest):
        if chest.state_value_active(self.treasure_chest_open_state):
            return JungleOpenStreetDirector.TREASURE_CHEST_OPEN
        return JungleOpenStreetDirector.TREASURE_CHEST_CLOSED

    def _load_treasure_chest_data(self, reader):
        if reader is None:
            return
        travel_group_manager = services.travel_group_manager()
        group_ids = reader.read_uint64s(TREASURE_CHEST_GROUP, [])
        obj_ids = reader.read_uint64s(TREASURE_CHEST_ID, [])
        status = reader.read_uint64s(TREASURE_CHEST_STATUS, [])
        for (index, group_id) in enumerate(group_ids):
            if not travel_group_manager.get(group_id):
                continue
            if group_id not in self._treasure_chest_status:
                self._treasure_chest_status[group_id] = []
            treasure_chest = self._treasure_chest_status[group_id]
            treasure_chest.append((obj_ids[index], status[index]))
class FamilyMealSituation(SituationComplexCommon):
    __qualname__ = 'FamilyMealSituation'
    INSTANCE_TUNABLES = {
        'chef':
        sims4.tuning.tunable.TunableTuple(
            situation_job=SituationJob.TunableReference(
                description=
                '\n                    The SituationJob for the sim making the meal.'
            ),
            chef_cooking_role_state=RoleState.TunableReference(
                description=
                "\n                    Chef's role state while making food."),
            chef_eating_role_state=RoleState.TunableReference(
                description=
                "\n                    Chef's role state when eating."),
            tuning_group=GroupNames.ROLES),
        'household_eater':
        sims4.tuning.tunable.TunableTuple(
            situation_job=SituationJob.TunableReference(
                description=
                '\n                    The SituationJob for an eater (a non-chef) sim.'
            ),
            household_eater_cooking_role_state=RoleState.TunableReference(
                description=
                "\n                    Eater's role state while food is being prepared."
            ),
            household_eater_eating_role_state=RoleState.TunableReference(
                description=
                "\n                    Eater's role state when eating."),
            tuning_group=GroupNames.ROLES),
        'guest_eater':
        sims4.tuning.tunable.TunableTuple(
            situation_job=SituationJob.TunableReference(
                description=
                "\n                    The SituationJob for an eater (a non-chef) sim who doesn't live here."
            ),
            guest_eater_cooking_role_state=RoleState.TunableReference(
                description=
                "\n                    Guest eater's role state while food is being prepared."
            ),
            guest_eater_eating_role_state=RoleState.TunableReference(
                description=
                "\n                    Guest eater's role state when eating."),
            tuning_group=GroupNames.ROLES),
        'cook_group_meal_interaction':
        TunableInteractionOfInterest(
            description=
            '\n                                            When this interaction is started, the chef has successfully\n                                            begun preparing the meal.',
            tuning_group=GroupNames.TRIGGERS),
        'meal_is_done_interaction':
        TunableInteractionOfInterest(
            description=
            '\n                                            When this interaction has been completed by the chef, it will\n                                            signal the end of the eating phase.',
            tuning_group=GroupNames.TRIGGERS),
        'trying_to_cook_timeout':
        TunableSimMinute(
            description=
            '\n                        The amount of time the sim will attempt to try to start cooking.',
            default=30,
            tuning_group=GroupNames.TRIGGERS),
        'cooking_timeout':
        TunableSimMinute(
            description=
            '\n                        The amount of time the sim will attempt to actually cook.',
            default=60,
            tuning_group=GroupNames.TRIGGERS),
        'meal_timeout':
        TunableSimMinute(
            description=
            '\n                        The amount of time the meal will last, assuming not all sims made it to the meal.',
            default=30,
            tuning_group=GroupNames.TRIGGERS)
    }
    REMOVE_INSTANCE_TUNABLES = (
        '_cost', '_level_data', '_display_name', 'entitlement',
        'job_display_ordering', 'situation_description', 'minor_goal_chains',
        '_NPC_host_filter', '_NPC_hosted_player_tests',
        'NPC_hosted_situation_start_message',
        'NPC_hosted_situation_use_player_sim_as_filter_requester',
        'NPC_hosted_situation_player_job', 'venue_types',
        'venue_invitation_message', 'venue_situation_player_job', 'category',
        'main_goal', 'max_participants', '_initiating_sim_tests',
        'targeted_situation', '_icon')

    @staticmethod
    def _states():
        return [(1, TryingToCookState), (2, CookState), (3, EatState)]

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return [(cls.chef.situation_job, cls.chef.chef_cooking_role_state),
                (cls.household_eater.situation_job,
                 cls.household_eater.household_eater_cooking_role_state),
                (cls.guest_eater.situation_job,
                 cls.guest_eater.guest_eater_cooking_role_state)]

    @classmethod
    def default_job(cls):
        return cls.guest_eater.situation_job

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

    @property
    def chef_id(self):
        return self._chef_id

    def start_situation(self):
        super().start_situation()
        self._change_state(TryingToCookState())

    def _on_set_sim_job(self, sim, job_type):
        super()._on_set_sim_job(sim, job_type)
        if job_type is self.chef.situation_job:
            self._chef_id = sim.sim_id

    def _has_chef_started_cooking(self, event, resolver):
        if event == TestEvent.InteractionStart and resolver(
                self.cook_group_meal_interaction):
            participants = resolver.get_participants(ParticipantType.Actor)
            while True:
                for sim_info in participants:
                    while sim_info.id == self.chef_id:
                        return True
        return False

    def _is_chef_finished_eating(self, event, resolver):
        if event == TestEvent.InteractionComplete and resolver(
                self.meal_is_done_interaction):
            participants = resolver.get_participants(ParticipantType.Actor)
            while True:
                for sim_info in participants:
                    while sim_info.id == self.chef_id:
                        return True
        return False

    def _was_cooking_interaction_canceled(self, event, resolver):
        if event == TestEvent.InteractionComplete and resolver(
                self.cook_group_meal_interaction):
            if resolver.interaction is not None and resolver.interaction.is_finishing and resolver.interaction.has_been_canceled:
                participants = resolver.get_participants(ParticipantType.Actor)
                while True:
                    for sim_info in participants:
                        while sim_info.id == self.chef_id:
                            return True
        return False
class _WaitstaffDeliverOrderToTableState(_WaitstaffSituationStateBase):
    FACTORY_TUNABLES = {'must_exit_naturally_interactions': TunableInteractionOfInterest(description='\n                 The interaction(s) that will cause this state to be exited if\n                 they are removed from pipeline for any reason other than\n                 exiting naturally.\n                 '), 'resubmit_order_interactions': TunableInteractionOfInterest(description='\n                 The interaction(s) that will require the situation to go back\n                 to resubmit the order to the chef state.\n                 ')}

    def __init__(self, *args, must_exit_naturally_interactions=None, resubmit_order_interactions=None, **kwargs):
        super().__init__(*args, **kwargs)
        self._must_exit_naturally_interactions = must_exit_naturally_interactions
        self._resubmit_order_interactions = resubmit_order_interactions

    @property
    def resulting_order_status(self):
        return OrderStatus.ORDER_DELIVERED

    @property
    def has_target_override(self):
        return True

    def _get_role_state_overrides(self, sim, job_type, role_state_type, role_affordance_target):
        return (role_state_type, self.owner._current_order.get_first_table())

    def handle_event(self, sim_info, event, resolver):
        if event == TestEvent.InteractionExitedPipeline:
            if resolver(self._interaction_of_interest):
                if self.owner._current_order is not None:
                    self.owner._try_change_order_status(OrderStatus.ORDER_DELIVERY_FAILED)
                self.owner.advance_to_idle_state()
                return
            if resolver(self._resubmit_order_interactions):
                if self.owner._current_order is not None:
                    self.owner._current_order.expedited = True
                    self.owner._try_change_order_status(OrderStatus.ORDER_GIVEN_TO_CHEF)
                    self._change_state(self.owner.deliver_order_to_chef_state())
                return
            if resolver(self._must_exit_naturally_interactions):
                if not resolver.interaction.is_finishing_naturally:
                    if self.owner._current_order is not None:
                        self.owner._try_change_order_status(OrderStatus.ORDER_DELIVERY_FAILED)
                    self.owner.advance_to_idle_state()
                return
        super().handle_event(sim_info, event, resolver)

    def _on_interaction_of_interest_complete(self, **kwargs):
        self.owner._current_order.clear_serving_from_chef()
        super()._on_interaction_of_interest_complete()

    def on_activate(self, reader=None):
        order = self.owner._current_order
        skip_state = False
        if order is not None:
            if order.process_table_for_unfinished_food_drink():
                if self.owner._current_order.is_player_group_order():
                    waitstaff = self.owner.get_staff_member()
                    resolver = SingleSimResolver(waitstaff)
                    dialog = RestaurantTuning.FOOD_STILL_ON_TABLE_NOTIFICATION(waitstaff, resolver)
                    dialog.show_dialog()
                self.owner._current_order = None
                skip_state = True
        else:
            logger.error('Waitstaff {} entered the Deliver Order To Table state but has no current order.', self.owner.get_staff_member())
            skip_state = True
        if skip_state:
            next_state = self._get_next_state()
            self.owner._change_state(next_state())
        else:
            for custom_key in self._resubmit_order_interactions.custom_keys_gen():
                self._test_event_register(TestEvent.InteractionExitedPipeline, custom_key)
            for custom_key in self._must_exit_naturally_interactions.custom_keys_gen():
                self._test_event_register(TestEvent.InteractionExitedPipeline, custom_key)
            super().on_activate(reader)
示例#15
0
class PatientSituationBase(SituationComplexCommon):
    JOB_AND_STATE_GROUP = 'Job and State'
    STATE_ADVANCEMENT_GROUP = 'State Advancement'
    TIMEOUT_GROUP = 'Timeout And Time Jump'
    INSTANCE_TUNABLES = {
        'situation_job':
        SituationJob.TunableReference(
            description=
            '\n            A reference to the doctors Job while in the\n            situation.\n            ',
            tuning_group=JOB_AND_STATE_GROUP),
        'waiting_role_state':
        RoleState.TunableReference(
            description=
            '\n            A reference to the hospital patients waiting \n            role state while in the situation. At this \n            point the patient is just chilling in the \n            waiting room till the doctor (or nurse) takes\n            them back to a room.\n            ',
            tuning_group=JOB_AND_STATE_GROUP,
            display_name='02_waiting_role_state'),
        'treated_role_state':
        RoleState.TunableReference(
            description=
            '\n            A reference to the hospital patients treated\n            role state while in the situation. This is\n            the state where the patient has finished their \n            visit to the doctor and most likely only goes\n            home.\n            ',
            tuning_group=JOB_AND_STATE_GROUP,
            display_name='05_treated_role_state'),
        'arriving_state':
        OptionalTunable(
            description=
            '\n            If this is enabled then the situation will start out in the\n            arriving state and will use the go_to_waiting_interactions to move\n            from arriving to waiting.\n            \n            If this is disabled then the situation will start in the waiting\n            state.\n            ',
            tunable=TunableTuple(
                go_to_waiting_interactions=TunableInteractionOfInterest(
                    description=
                    '\n                    The interactions to look for when a Sim has checked in with \n                    admitting and is now waiting for the doctor to take them to a bed.\n                    '
                ),
                arriving_role_state=RoleState.TunableReference(
                    description=
                    "\n                    A reference to the hospital patient's basic \n                    arriving role state while in the situation.\n                    \n                    e.g. This is when the patient walks up to the \n                    admitting desk and checks in and then changes\n                    to the waiting state.\n                    "
                )),
            enabled_by_default=True,
            tuning_group=JOB_AND_STATE_GROUP,
            display_name='01_arriving_state'),
        'go_to_admitted_interactions':
        TunableInteractionOfInterest(
            description=
            '\n            The interactions to look for when a Sim has completed waiting \n            successfully and will now be admitted.\n            ',
            tuning_group=STATE_ADVANCEMENT_GROUP),
        'go_to_treated_interactions':
        TunableInteractionOfInterest(
            description=
            '\n            The interactions to look for when a Sim has been treated for illness\n            and their visit to the doctor is now over.\n            ',
            tuning_group=STATE_ADVANCEMENT_GROUP),
        'patient_type_buff':
        TunableReference(
            description=
            '\n            A buff used to mark the type of patient the Sim in this situation\n            will be. \n            \n            This buff is where you can tune weights and types of\n            diseases the Sim could get as part of the situation.\n            ',
            manager=services.get_instance_manager(sims4.resources.Types.BUFF)),
        'trigger_symptom_loot':
        TunableReference(
            description=
            '\n            The loot to apply to the Sim that triggers them to get a symptom.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.ACTION),
            allow_none=True,
            class_restrictions=('LootActions', )),
        'waiting_timeout':
        TunableSimMinute(
            description=
            '\n            The amount of time the sim will wait before leaving.\n            ',
            default=180,
            tuning_group=TIMEOUT_GROUP),
        'waiting_timedout_notification':
        OptionalTunable(
            description=
            '\n            When enabled, if the Sim in the situation times out a notification\n            will be displayed letting the player know that they are leaving.\n            ',
            tunable=TunableUiDialogNotificationSnippet(
                description=
                '\n                The notification that is displayed whenever a Sim times out while\n                waiting and leaves the lot.\n                '
            ),
            enabled_by_default=True,
            tuning_group=TIMEOUT_GROUP),
        'waiting_timedout_performance_penalty':
        Tunable(
            description=
            '\n            This is the amount of perfomance to add to the Sims work\n            performance when this situation times out while the Sim is waiting.\n            \n            To have this negatively affect the peformance you would use a \n            negative number like -10. Using a positive number will result in \n            it being added to the Sims performance.\n            ',
            tunable_type=int,
            default=0,
            tuning_group=TIMEOUT_GROUP),
        'force_sim_to_leave_lot_on_completion':
        Tunable(
            description=
            '\n            If set to True then when a Sim completes the situation, whether\n            by timeout or successful completion, the Sim will be forced to \n            leave the lot immediately.\n            \n            If this is set to False then when the situation is completed it\n            will be destroyed without the sim being forced off lot.\n            ',
            tunable_type=bool,
            default=True,
            tuning_group=STATE_ADVANCEMENT_GROUP)
    }
    REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES

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

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        if cls.arriving_state is not None:
            return [(cls.situation_job, cls.arriving_state.arriving_role_state)
                    ]
        return [(cls.situation_job, cls.waiting_role_state)]

    @classmethod
    def default_job(cls):
        return cls.situation_job

    @classmethod
    def get_tuned_jobs(cls):
        return {cls.situation_job}

    def start_situation(self):
        super().start_situation()
        reader = self._seed.custom_init_params_reader
        if reader is None and not services.current_zone().is_zone_running:
            self._skip_ahead_for_preroll()
        elif self.arriving_state:
            self._change_state(ArrivingState())
        else:
            self._change_state(WaitingState())

    def add_patient_type_buff(self, sim):
        if self.patient_type_buff is not None:
            sim.add_buff(self.patient_type_buff)
        if self.trigger_symptom_loot is not None:
            resolver = SingleSimResolver(sim.sim_info)
            self.trigger_symptom_loot.apply_to_resolver(resolver)

    def _on_set_sim_job(self, sim, job_type):
        super()._on_set_sim_job(sim, job_type)
        self._patient = sim

    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)
        self.add_patient_type_buff(sim)

    def get_patient(self):
        return self._patient

    def _on_done_waiting(self):
        self._change_state(TreatedState())

    @classmethod
    def should_load_after_time_jump(cls, seed):
        state_type = cls.get_current_state_type(seed)
        return cls.should_state_type_load_after_time_jump(state_type)

    @classmethod
    def should_state_type_load_after_time_jump(cls, state_type):
        if state_type is None or state_type is ArrivingState or state_type is TreatedState:
            return False
        elif state_type is WaitingState:
            elapsed_time = services.current_zone(
            ).time_elapsed_since_last_save().in_minutes()
            if elapsed_time >= cls.waiting_timeout:
                return False
        return True

    def _skip_ahead_for_preroll(self):
        self._change_state(WaitingState())

    def waiting_expired(self):
        pass

    def _on_remove_sim_from_situation(self, sim):
        sim_job = self.get_current_job_for_sim(sim)
        super()._on_remove_sim_from_situation(sim)
        self.manager.add_sim_to_auto_fill_blacklist(sim.id, sim_job)
示例#16
0
class SecretLabZoneDirector(RegisterTestEventMixin,
                            SchedulingZoneDirectorMixin, ZoneDirectorBase):
    INSTANCE_TUNABLES = {
        'section_doors':
        TunableList(
            description=
            '\n            An ordered set of doors, each of which unlocks a section of the lab\n            to explore.\n            ',
            tunable=TunableReference(
                manager=services.get_instance_manager(Types.OBJECT)),
            unique_entries=True),
        'door_lock_operations':
        TunableTuple(
            description=
            '\n            These operations are applied to doors that should be locked\n            based on the progress into the zone.\n            ',
            object_state=ObjectStateValue.TunableReference(
                description=
                '\n                An object state that should be set on the door when locked.\n                '
            ),
            lock_data=LockDoor.TunableFactory(
                description=
                '\n                The LockDoor loot to run on the doors in the lab to lock them.\n                '
            )),
        'door_unlock_operations':
        TunableTuple(
            description=
            '\n            These operations are applied to doors that should be unlocked\n            based on the progress into the zone.\n            ',
            object_state=ObjectStateValue.TunableReference(
                description=
                '\n                An object state that should be set on the door when unlocked.\n                '
            ),
            lock_data=UnlockDoor.TunableFactory(
                description=
                '\n                The UnlockDoor loot to run on the doors when they should be unlocked.\n                '
            )),
        'reveal_interactions':
        TunableInteractionOfInterest(
            description=
            '\n            Interactions that, when run on a door, reveal the plex associated \n            to the interacted door.\n            '
        ),
        'object_commodities_to_fixup_on_load':
        TunableList(
            description=
            '\n            Normally object commodities retain their previously saved value on \n            load and do not simulate the decay up to the current time.\n            This list allows specific objects to update commodities based off\n            passage of time if time had elapsed between load and the last time\n            the zone was saved.\n            ',
            tunable=TunableTuple(
                commodity=TunableReference(
                    description=
                    '\n                    The commodity to fix up if time elapsed since zone was last saved.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.
                        STATISTIC),
                    class_restrictions='Commodity'),
                object_test=TunableObjectMatchesDefinitionOrTagTest(
                    description=
                    '\n                    Test whether or not an object applies for this fixup.\n                    '
                )))
    }

    def __init__(self):
        super().__init__()
        self._revealed_plex = 0
        self._plex_door_map = self._generate_plex_door_map()
        self._reset_lab_data()
        self._command_handlers = {
            SecretLabCommand.RevealNextSection: self._reveal_next_section,
            SecretLabCommand.RevealAllSections: self._reveal_all_sections,
            SecretLabCommand.ResetLab: self._reset_all_sections
        }

    def on_startup(self):
        super().on_startup()
        self._register_test_event_for_keys(
            TestEvent.InteractionStart,
            self.reveal_interactions.custom_keys_gen())
        self._register_test_event_for_keys(
            TestEvent.InteractionComplete,
            self.reveal_interactions.custom_keys_gen())

    def on_shutdown(self):
        self._unregister_for_all_test_events()
        super().on_shutdown()

    def on_loading_screen_animation_finished(self):
        super().on_loading_screen_animation_finished()
        active_sim = services.get_active_sim()
        if active_sim is None:
            return
        if active_sim.is_on_active_lot():
            return
        camera.focus_on_sim(services.get_active_sim())

    def on_cleanup_zone_objects(self):
        super().on_cleanup_zone_objects()
        current_zone = services.current_zone()
        if current_zone.time_has_passed_in_world_since_zone_save():
            if self.object_commodities_to_fixup_on_load:
                time_of_last_zone_save = current_zone.time_of_last_save()
                for obj in list(services.object_manager().values()):
                    if not obj.is_on_active_lot():
                        continue
                    for commodity_fixup in self.object_commodities_to_fixup_on_load:
                        if not commodity_fixup.object_test(objects=(obj, )):
                            continue
                        fixup_commodity = obj.get_stat_instance(
                            commodity_fixup.commodity)
                        if fixup_commodity is not None:
                            fixup_commodity.update_commodity_to_time(
                                time_of_last_zone_save, update_callbacks=True)
        if self._should_reset_progress_on_load():
            self._revealed_plex = 0
        self._update_locks_and_visibility()

    def _determine_zone_saved_sim_op(self):
        if self._should_reset_progress_on_load():
            return _ZoneSavedSimOp.CLEAR
        return _ZoneSavedSimOp.MAINTAIN

    def _on_clear_zone_saved_sim(self, sim_info):
        if sim_info.is_selectable:
            self._request_spawning_of_sim_at_spawn_point(
                sim_info, SimSpawnReason.ACTIVE_HOUSEHOLD)
            return
        self._send_sim_home(sim_info)

    def _save_custom_zone_director(self, zone_director_proto, writer):
        writer.write_uint32(SAVE_LAST_REVEALED_PLEX, self._revealed_plex)
        super()._save_custom_zone_director(zone_director_proto, writer)

    def _load_custom_zone_director(self, zone_director_proto, reader):
        if reader is not None:
            self._revealed_plex = reader.read_uint32(SAVE_LAST_REVEALED_PLEX,
                                                     None)
        super()._load_custom_zone_director(zone_director_proto, reader)

    def handle_event(self, sim_info, event, resolver):
        interaction_start = event == TestEvent.InteractionStart
        interaction_complete = event == TestEvent.InteractionComplete
        if interaction_start or interaction_complete:
            if resolver(self.reveal_interactions):
                door = resolver.get_participant(ParticipantType.Object)
                try:
                    plex_to_unlock = self.section_doors.index(
                        door.definition) + 1
                except ValueError:
                    logger.error('Ran interaction {} on unexpected door {}',
                                 resolver.interaction, door)
                    plex_to_unlock = 0
                if interaction_complete:
                    self._handle_door_state(sim_info, door, True)
                else:
                    build_buy.set_plex_visibility(plex_to_unlock, True)
                    self._revealed_plex = max(plex_to_unlock,
                                              self._revealed_plex)

    def handle_command(self, command: SecretLabCommand, **kwargs):
        if command in self._command_handlers:
            self._command_handlers[command](**kwargs)

    def _should_reset_progress_on_load(self):
        current_zone = services.current_zone()
        return current_zone.active_household_changed_between_save_and_load(
        ) or current_zone.time_has_passed_in_world_since_zone_save()

    def _generate_plex_door_map(self):
        plex_door_map = {}
        obj_mgr = services.object_manager()
        for (i, door_def) in enumerate(self.section_doors, 1):
            door = next(iter(obj_mgr.get_objects_of_type_gen(door_def)), None)
            if door is None:
                logger.error(
                    'Unable to find the door {} on lot to unlock plex {}',
                    door_def, i)
            else:
                plex_door_map[i] = door
        return plex_door_map

    def _handle_door_state(self, sim_info, door, set_open):
        operations = self.door_unlock_operations if set_open else self.door_lock_operations
        resolver = SingleActorAndObjectResolver(sim_info, door, self)
        operations.lock_data.apply_to_resolver(resolver)
        state_value = operations.object_state
        door.set_state(state_value.state, state_value, force_update=True)

    def _update_locks_and_visibility(self):
        active_sim_info = services.active_sim_info()
        for i in range(1, len(self.section_doors) + 1):
            reveal = i <= self._revealed_plex
            build_buy.set_plex_visibility(i, reveal)
            door = self._plex_door_map.get(i, None)
            if door is None:
                continue
            self._handle_door_state(active_sim_info, door, reveal)

    def _reset_lab_data(self):
        self._revealed_plex = 0

    def _reveal_next_section(self):
        self._revealed_plex = min(len(self.section_doors),
                                  self._revealed_plex + 1)
        self._update_locks_and_visibility()

    def _reveal_all_sections(self):
        self._revealed_plex = len(self.section_doors)
        self._update_locks_and_visibility()

    def _reset_all_sections(self):
        self._reset_lab_data()
        self._update_locks_and_visibility()
示例#17
0
class ScarecrowSituation(ObjectBoundSituationMixin, SituationComplexCommon):
    INSTANCE_TUNABLES = {
        '_situation_job':
        SituationJob.TunableReference(
            description=
            '\n            The situation job for the Sim.\n            \n            This job should define a spawn affordance that will trigger\n            a continuation targeting the object the Sim spawns at.\n            ',
            tuning_group=GroupNames.SITUATION),
        '_do_stuff_state':
        _DoStuffState.TunableFactory(
            description=
            '\n            The state for the Sim doing stuff.\n            \n            This is the initial state after the Sim spawns onto the lot.\n\n            Any on-activate affordances run in this role will target\n            the object the Sim spawned near.\n            ',
            display_name='1. Do Stuff',
            tuning_group=GroupNames.STATE),
        '_leave_state':
        _LeaveState.TunableFactory(
            description=
            '\n            The state for the Sim leaving.\n            \n            Any on-activate affordances run in this role will target\n            the object the Sim spawned near.\n            ',
            display_name='2. Leave',
            tuning_group=GroupNames.STATE),
        '_spawn_object_targeting_affordance':
        TunableInteractionOfInterest(
            description=
            "\n            Affordance that runs targeting the object that the object that the\n            Sim had spawned at. This allows the situation to 'remember' that\n            object and when that object is destroyed, the situation will\n            be destroyed as well. \n            ",
            tuning_group=GroupNames.SITUATION),
        '_spawn_object_reset_loots':
        LootActions.TunableReference(
            description=
            '\n            Loots used to reset the object from which the scarecrow spawned from,\n            to handle cases for when the scarecrow Sim is not on lot during load.\n            ',
            tuning_group=GroupNames.SITUATION)
    }
    REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES

    @classmethod
    def _states(cls):
        return (SituationStateData(
            1, _DoStuffState,
            partial(cls._do_stuff_state, situation_job=cls._situation_job)),
                SituationStateData(
                    2, _LeaveState,
                    partial(cls._leave_state,
                            situation_job=cls._situation_job)))

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return [(cls._situation_job, None)]

    @classmethod
    def default_job(cls):
        return cls._situation_job

    @classmethod
    def get_sims_expected_to_be_in_situation(cls):
        return 1

    def _get_role_state_overrides(self, sim, job_type, role_state_type,
                                  role_affordance_target):
        return (role_state_type,
                services.object_manager().get(self.bound_object_id))

    def start_situation(self):
        super().start_situation()
        for custom_key in self._spawn_object_targeting_affordance.custom_keys_gen(
        ):
            self._register_test_event(TestEvent.InteractionStart, custom_key)
        self._change_state(
            self._do_stuff_state(situation_job=self._situation_job))

    def load_situation(self):
        scarecrow_guest_info = next(
            iter(self._guest_list.get_persisted_sim_guest_infos()))
        if scarecrow_guest_info is None:
            self._reset_scarecrow_object()
            return False
        scarecrow_sim_info = services.sim_info_manager().get(
            scarecrow_guest_info.sim_id)
        if scarecrow_sim_info is None or scarecrow_sim_info.zone_id != services.current_zone_id(
        ):
            self._reset_scarecrow_object()
            return False
        return super().load_situation()

    def _reset_scarecrow_object(self):
        scarecrow_object = services.object_manager().get(self._bound_object_id)
        resolver = SingleObjectResolver(scarecrow_object)
        self._spawn_object_reset_loots.apply_to_resolver(resolver)

    def handle_event(self, sim_info, event, resolver):
        if event == TestEvent.InteractionStart:
            if self.is_sim_info_in_situation(sim_info) and resolver(
                    self._spawn_object_targeting_affordance):
                target = resolver.get_participant(ParticipantType.Object)
                if target is None:
                    logger.error(
                        '{}: {} target is None, cannot find the object for this situation to bind to!',
                        self, resolver)
                    self._self_destruct()
                    return
                self.bind_object(target)
        else:
            super().handle_event(sim_info, event, resolver)

    def go_to_leave_state(self):
        self._change_state(
            self._leave_state(situation_job=self._situation_job))

    def _gsi_additional_data_gen(self):
        if isinstance(self._cur_state, _DoStuffState):
            yield ('Time till Leave State',
                   str(self._cur_state.get_time_remaining()))
class EcoInspectorSituation(SituationComplexCommon):
    INSTANCE_TUNABLES = {'inspector_job_and_role_state': TunableSituationJobAndRoleState(description='\n            The job and role state for the eco-inspector.\n            ', tuning_group=GroupNames.ROLES), 'eco_inspect_state': _EcoInspectState.TunableFactory(description='\n           Inspection + Vacuuming state\n            ', display_name='1. Eco Inspect', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP), 'expelling_smog_state': _ExpellingSmogState.TunableFactory(description='\n           Expelling state\n            ', display_name='2. Expelling Smog', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP), 'leaving_state': _LeavingState.TunableFactory(description='\n            Inspector is leaving after Vacuuming.\n            ', display_name='3. Leaving State', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP), 'expel_smog': TunableInteractionOfInterest(description='\n            Social: listen to move to expelling_smog state\n            '), 'delta_expel': Tunable(description='\n            How much to increase the pollution level by?  \n            ', tunable_type=float, default=0.0), 'leave': TunableInteractionOfInterest(description='\n            Listen for this enter the leave state.\n            '), 'expel_smog_notification': OptionalTunable(description='\n            A TNS that is displayed after expelling smog.\n            ', tunable=UiDialogNotification.TunableFactory()), 'arrival_notification': OptionalTunable(description='\n            A TNS that is displayed pm arrival of the eco-inspector.\n            ', tunable=UiDialogNotification.TunableFactory()), 'leave_notification': OptionalTunable(description='\n            A TNS that after the eco-inspector leaves.\n            ', tunable=UiDialogNotification.TunableFactory()), 'service_npc_hireable': TunableReference(description='\n            Service NPC for the eco-inspector.\n            ', manager=services.service_npc_manager(), pack_safe=True), 'fake_perform_pollution_limit': Tunable(description='\n            Limit down to which to change pollution.  \n            ', tunable_type=float, default=0.0), 'fake_perform_vacuum_delta': TunableRange(description='\n            How much to decrease the pollution level by?  \n            ', tunable_type=float, default=1.0, minimum=0.1)}

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._service_npc = None
        self._service_start_time = None
        reader = self._seed.custom_init_params_reader
        self._service_npc_type = self.service_npc_hireable
        self._hiring_household = services.household_manager().get(reader.read_uint64('household_id', 0))
        if self._hiring_household is None:
            raise ValueError('Invalid household for situation: {}'.format(self))
        self.initial_street_value = EcoInspectorSituation.get_footprint_commodity_value()

    @staticmethod
    def get_footprint_commodity_value():
        street_service = services.street_service()
        policy_provider = street_service.get_provider(services.current_street())
        return policy_provider.commodity_tracker.get_value(eco_footprint_tuning.EcoFootprintTunables.STREET_FOOTPRINT, add=True)

    @staticmethod
    def set_footprint_commodity_value(value):
        street_service = services.street_service()
        policy_provider = street_service.get_provider(services.current_street())
        policy_provider.commodity_tracker.set_value(eco_footprint_tuning.EcoFootprintTunables.STREET_FOOTPRINT, value)
        policy_provider.distribute_neighborhood_update()

    @classmethod
    def _states(cls):
        return (SituationStateData(1, _WaitState), SituationStateData(2, _EcoInspectState, factory=cls.eco_inspect_state), SituationStateData(3, _ExpellingSmogState, factory=cls.expelling_smog_state), SituationStateData(4, _LeavingState, factory=cls.leaving_state))

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

    @classmethod
    def default_job(cls):
        return cls.inspector_job_and_role_state.job

    @classmethod
    def fake_perform_job(cls):
        _inspector = EcoInspectorSituation
        _commodity_val = _inspector.get_footprint_commodity_value()
        if _commodity_val < cls.fake_perform_pollution_limit:
            return
        no_steps = math.ceil((_commodity_val - cls.fake_perform_pollution_limit)/cls.fake_perform_vacuum_delta)
        _inspector.set_footprint_commodity_value(_commodity_val - no_steps*cls.fake_perform_vacuum_delta)

    def _save_custom_situation(self, writer):
        super()._save_custom_situation(writer)
        writer.write_uint64('household_id', self._hiring_household.id)

    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.inspector_person() is not None:
            self._change_state(self.eco_inspect_state())

    def inspector_person(self):
        sim = next(self.all_sims_in_job_gen(self.inspector_job_and_role_state.job), None)
        return sim

    def _on_set_sim_job(self, sim, job_type):
        super()._on_set_sim_job(sim, job_type)
        self._service_npc = sim
        services.current_zone().service_npc_service.cancel_service(self._hiring_household, self._service_npc_type)

    def start_situation(self):
        super().start_situation()
        self._change_state(_WaitState())
class BirthdayPartySituation(SituationComplexCommon):
    __qualname__ = 'BirthdayPartySituation'
    INSTANCE_TUNABLES = {
        'celebrant':
        sims4.tuning.tunable.TunableTuple(
            situation_job=SituationJob.TunableReference(
                description=
                '\n                        The SituationJob for the celebrant.'
            ),
            celebrant_gather_role_state=RoleState.TunableReference(
                description=
                "\n                        Celebrant's role state before the celebration (gather phase)."
            ),
            celebrant_reception_role_state=RoleState.TunableReference(
                description=
                "\n                        Celebrant's role state after the celebration (eat, drink, socialize, dance)."
            ),
            tuning_group=GroupNames.ROLES),
        'bartender':
        sims4.tuning.tunable.TunableTuple(
            situation_job=SituationJob.TunableReference(
                description=
                '\n                        The SituationJob for the Bartender.'
            ),
            bartender_pre_reception_role_state=RoleState.TunableReference(
                description=
                "\n                        Bartender's role state to prepare drinks and socialize with guests."
            ),
            bartender_reception_role_state=RoleState.TunableReference(
                description=
                "\n                        Bartender's role state to prepare drinks, socialize, etc. during the reception."
            ),
            tuning_group=GroupNames.ROLES),
        'caterer':
        sims4.tuning.tunable.TunableTuple(
            situation_job=SituationJob.TunableReference(
                description=
                '\n                        The SituationJob for the caterer.'),
            caterer_prep_role_state=RoleState.TunableReference(
                description=
                "\n                        Caterer's role state for preparing cake and meal for guests."
            ),
            caterer_serve_role_state=RoleState.TunableReference(
                description=
                "\n                        Caterer's role state for serving the guests."
            ),
            tuning_group=GroupNames.ROLES),
        'entertainer':
        sims4.tuning.tunable.TunableTuple(
            situation_job=SituationJob.TunableReference(
                description=
                '\n                        The SituationJob for the entertainer.'
            ),
            entertainer_prep_reception_state=RoleState.TunableReference(
                description=
                "\n                        Entertainer's role state before reception."
            ),
            entertainer_reception_role_state=RoleState.TunableReference(
                description=
                "\n                        Entertainer's role state during reception."
            ),
            tuning_group=GroupNames.ROLES),
        'guest':
        sims4.tuning.tunable.TunableTuple(
            situation_job=SituationJob.TunableReference(
                description=
                '\n                        The SituationJob for the Guests.'),
            guest_gather_role_state=RoleState.TunableReference(
                description=
                "\n                        Guest's role state before the celebration (gather phase)."
            ),
            guest_gather_impatient_role_state=RoleState.TunableReference(
                description=
                "\n                        Guest's role state if it is taking too long for the celebration to start."
            ),
            guest_reception_role_state=RoleState.TunableReference(
                description=
                "\n                        Guest's role state after the celebration (now they can eat the cake)."
            ),
            tuning_group=GroupNames.ROLES),
        'start_reception':
        TunableInteractionOfInterest(
            description=
            '\n                        This is a birthday cake interaction where starting this interaction starts \n                        the cake reception phase.',
            tuning_group=GroupNames.TRIGGERS),
        'guests_become_impatient_timeout':
        TunableSimMinute(
            description=
            '\n                        If the celebration is not started in this amount of time the guests will grow impatient.',
            default=120,
            tuning_group=GroupNames.TRIGGERS)
    }
    REMOVE_INSTANCE_TUNABLES = (
        '_NPC_host_filter', '_NPC_hosted_player_tests',
        'NPC_hosted_situation_start_message',
        'NPC_hosted_situation_player_job',
        'NPC_hosted_situation_use_player_sim_as_filter_requester',
        'venue_invitation_message', 'venue_situation_player_job')

    @staticmethod
    def _states():
        return [(1, GatherState), (2, ImpatientGatherState),
                (3, ReceptionState)]

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return [(cls.celebrant.situation_job,
                 cls.celebrant.celebrant_gather_role_state),
                (cls.bartender.situation_job,
                 cls.bartender.bartender_pre_reception_role_state),
                (cls.caterer.situation_job,
                 cls.caterer.caterer_prep_role_state),
                (cls.entertainer.situation_job,
                 cls.entertainer.entertainer_prep_reception_state),
                (cls.guest.situation_job, cls.guest.guest_gather_role_state)]

    @classmethod
    def default_job(cls):
        return cls.guest.situation_job

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

    def start_situation(self):
        super().start_situation()
        self._change_state(GatherState())

    def _on_set_sim_job(self, sim, job_type):
        super()._on_set_sim_job(sim, job_type)
        if job_type is self.celebrant.situation_job:
            self._celebrant_id = sim.sim_id

    def _is_birthday_starting(self, event, resolver):
        if event == TestEvent.InteractionStart and resolver(
                self.start_reception):
            participants = resolver.get_participants(ParticipantType.Actor)
            while True:
                for sim_info in participants:
                    while sim_info.id == self._celebrant_id:
                        return True
        return False
示例#20
0
class _ReceivingServiceState(_VetCustomerGroupSituationStateBase):
    FACTORY_TUNABLES = {
        'correct_treatment_interaction_test':
        TunableParticipantRanInteractionTest(
            description=
            '\n            Keep track of the cost of the correct treatment so the clinic can\n            be charged the expense and the customer can be billed for the treatment.\n            ',
            locked_args={
                'running_time': None,
                'tooltip': None
            }),
        'acceptable_treatment_interaction_test':
        TunableParticipantRanInteractionTest(
            description=
            '\n            Keep track of the cost of an acceptable treatment so the clinic can\n            be charged the expense and the customer can be billed for the treatment.\n            \n            Unlike the Correct Treatments, this does not charge any bonuses for difficulty.\n            ',
            locked_args={
                'running_time': None,
                'tooltip': None
            }),
        'vet_reassignment_interaction':
        TunableInteractionOfInterest(
            description=
            '\n            When this interaction is run by the player on the customer, this allows\n            this customer group to be assigned to the player.\n            ',
            tuning_group=GroupNames.SITUATION)
    }

    def __init__(self, correct_treatment_interaction_test,
                 acceptable_treatment_interaction_test,
                 vet_reassignment_interaction, **kwargs):
        super().__init__(**kwargs)
        self.correct_treatment_interaction_test = correct_treatment_interaction_test
        self.acceptable_treatment_interaction_test = acceptable_treatment_interaction_test
        self.vet_reassignment_interaction = vet_reassignment_interaction

    def _on_set_sim_role_state(self, sim, *args, **kwargs):
        super()._on_set_sim_role_state(sim, *args, **kwargs)
        if sim is self.owner.get_pet():
            if sim.sim_info.current_sickness is None:
                logger.warn(
                    'Sim {} is not and may never have been sick during this situation',
                    sim)
                self.owner._sickness_difficulty = 0
            else:
                self.owner._sickness_difficulty = sim.sim_info.current_sickness.difficulty_rating
            for interaction in sim.get_all_running_and_queued_interactions():
                if not interaction.queued:
                    continue
                interaction.cancel(FinishingType.SITUATIONS,
                                   cancel_reason_msg='Pet examination.')
            for interaction in tuple(sim.interaction_refs):
                if not interaction.queued:
                    continue
                if interaction.context.sim.sim_info is not self.owner.assigned_vet:
                    interaction.cancel(FinishingType.SITUATIONS,
                                       cancel_reason_msg='Pet examination.')

    def _on_interaction_of_interest_complete(self, **kwargs):
        self.owner.on_service_complete()
        super()._on_interaction_of_interest_complete()

    def _next_state(self):
        return self.owner._complete_state()

    def on_activate(self, reader=None):
        super().on_activate(reader)
        for (
                _, custom_key
        ) in self.acceptable_treatment_interaction_test.get_custom_event_registration_keys(
        ):
            self._test_event_register(TestEvent.InteractionComplete,
                                      custom_key)
        for (
                _, custom_key
        ) in self.correct_treatment_interaction_test.get_custom_event_registration_keys(
        ):
            self._test_event_register(TestEvent.InteractionComplete,
                                      custom_key)
        for custom_key in self.vet_reassignment_interaction.custom_keys_gen():
            self._test_event_register(TestEvent.InteractionStart, custom_key)

    def handle_event(self, sim_info, event, resolver):
        super().handle_event(sim_info, event, resolver)
        if event == TestEvent.InteractionComplete:
            if resolver(self.correct_treatment_interaction_test):
                self.owner.track_treatment_cost(
                    resolver._interaction.get_simoleon_cost(), True)
            if resolver(self.acceptable_treatment_interaction_test):
                self.owner.track_treatment_cost(
                    resolver._interaction.get_simoleon_cost(), False)
        elif event == TestEvent.InteractionStart and resolver(
                self.vet_reassignment_interaction
        ) and self.owner.is_sim_info_in_situation(
                resolver.get_participant(ParticipantType.TargetSim)):
            self.owner.assign_to_vet(
                resolver.get_participant(ParticipantType.Actor))
class VetManagedEmployeeSituationState(CommonSituationState):
    FACTORY_TUNABLES = {
        'transition_out_interaction':
        OptionalTunable(
            description=
            '\n             When this interaction is run, this state can be transitioned out of;\n             we will try to advance to another state.  This can be used as a way \n             to switch states before the timeout occurs.\n             ',
            tunable=TunableInteractionOfInterest()),
        'state_specific_transitions':
        TunableMapping(
            description=
            '\n            Mapping to allow direct transitions to other states using interactions.\n            ',
            key_type=TunableEnumEntry(
                VetEmployeeSituationStates,
                default=VetEmployeeSituationStates.DEFAULT),
            value_type=TunableInteractionOfInterest()),
        'locked_args': {
            'allow_join_situation': False
        }
    }

    def __init__(self,
                 state_type,
                 *args,
                 enable_disable=None,
                 transition_out_interaction=None,
                 state_specific_transitions=None,
                 **kwargs):
        super().__init__(*args, **kwargs)
        self._state_type = state_type
        self._transition_out_interaction = transition_out_interaction
        self._state_specific_transitions = state_specific_transitions
        self._test_custom_keys = set()
        if self._transition_out_interaction is not None:
            self._transition_out_interaction = transition_out_interaction
            self._test_custom_keys.update(
                self._transition_out_interaction.custom_keys_gen())
        for state_specific_transition in self._state_specific_transitions.values(
        ):
            self._test_custom_keys.update(
                state_specific_transition.custom_keys_gen())

    @property
    def state_type(self):
        return self._state_type

    def on_activate(self, reader=None):
        super().on_activate(reader=reader)
        for custom_key in self._test_custom_keys:
            self._test_event_register(TestEvent.InteractionComplete,
                                      custom_key)

    def handle_event(self, sim_info, event, resolver):
        if not self.owner.is_sim_info_in_situation(sim_info):
            target_sim_info = resolver.get_participant(
                ParticipantType.TargetSim)
            if target_sim_info is None or not self.owner.is_sim_info_in_situation(
                    target_sim_info):
                return
        if event == TestEvent.InteractionComplete:
            for (state_type, state_specific_transition
                 ) in self._state_specific_transitions.items():
                if resolver(state_specific_transition):
                    self.owner.try_set_next_state(state_type)
                    return
            if self._transition_out_interaction is not None and resolver(
                    self._transition_out_interaction):
                self.owner.try_set_next_state()

    def timer_expired(self):
        self.owner.try_set_next_state()
示例#22
0
class _WaitingForServiceState(_VetCustomerGroupSituationStateBase):
    FACTORY_TUNABLES = {
        'service_request_interaction':
        TunableInteractionOfInterest(
            description=
            '\n            When this interaction is run by the player on the customer, we will pop up\n            the service request dialog.\n            ',
            tuning_group=GroupNames.SITUATION),
        'service_request_dialog':
        UiDialogOkCancel.TunableFactory(
            description=
            '\n            The dialog to display when service_request_interaction runs.\n            \n            The tokens passed in will be the Pet Sim, and the Pet Owner Sim,\n            in that order.\n            ',
            tuning_group=GroupNames.SITUATION),
        'take_on_customer_interaction':
        TunableReference(
            description=
            '\n            When the service request dialog is accepted, the vet will \n            run the specified interaction on the pet.',
            manager=services.get_instance_manager(Types.INTERACTION),
            class_restrictions=('SuperInteraction', ),
            tuning_group=GroupNames.SITUATION)
    }

    def __init__(self, service_request_interaction, service_request_dialog,
                 take_on_customer_interaction, **kwargs):
        super().__init__(**kwargs)
        self._showing_dialog = False
        self.service_request_interaction = service_request_interaction
        self.service_request_dialog = service_request_dialog
        self.take_on_customer_interaction = take_on_customer_interaction

    def on_activate(self, reader=None):
        super().on_activate(reader)
        for custom_key in self._interaction_of_interest.custom_keys_gen():
            self._test_event_register(TestEvent.InteractionStart, custom_key)
        for custom_key in self.service_request_interaction.custom_keys_gen():
            self._test_event_register(TestEvent.InteractionComplete,
                                      custom_key)
        self.owner.began_waiting()

    def handle_event(self, sim_info, event, resolver):
        if not self._additional_tests(sim_info, event, resolver):
            return
        if event == TestEvent.InteractionStart and resolver(
                self._interaction_of_interest):
            assigned_vet = resolver.get_participant(ParticipantType.Actor)
            if not assigned_vet.is_selectable:
                self.owner.assign_to_vet(assigned_vet)
        if event == TestEvent.InteractionComplete:
            actor_sim_info = resolver.get_participant(ParticipantType.Actor)
            if actor_sim_info.is_selectable and resolver(
                    self.service_request_interaction):
                self._on_player_interaction_complete(actor_sim_info)
            elif resolver(self._interaction_of_interest):
                self._on_interaction_complete_by_actor(actor_sim_info)

    def _on_player_interaction_complete(self, actor_sim_info):
        if self._showing_dialog:
            return
        self._showing_dialog = True
        target_sim = self.owner.get_pet_owner()
        pet_sim = self.owner.get_pet()
        if target_sim is None or pet_sim is None:
            self.owner._self_destruct()
        self.owner.log_flow_entry('Presenting Service Request Dialog')
        dialog = self.service_request_dialog(actor_sim_info,
                                             resolver=DoubleSimResolver(
                                                 pet_sim, target_sim))
        dialog.show_dialog(on_response=self._on_dialog_response)

    def _on_interaction_complete_by_actor(self, actor_sim_info):
        if not self._showing_dialog:
            if self.owner.assigned_vet is not actor_sim_info:
                self.owner.assign_to_vet(actor_sim_info)
            self._go_to_next_state()

    def _additional_tests(self, sim_info, event, resolver):
        target_sim = resolver.get_participant(ParticipantType.TargetSim)
        if not self.owner.is_sim_info_in_situation(target_sim):
            return False
        return True

    def _on_dialog_response(self, dialog):
        self._showing_dialog = False
        if dialog.response == dialog.response == ButtonType.DIALOG_RESPONSE_OK:
            assigned_vet = dialog.owner.get_sim_instance()
            pet = self.owner.get_pet()
            if assigned_vet is None or pet is None:
                self.owner._self_destruct()
                return
            context = InteractionContext(
                assigned_vet,
                InteractionContext.SOURCE_SCRIPT_WITH_USER_INTENT,
                Priority.High,
                insert_strategy=QueueInsertStrategy.NEXT)
            assigned_vet.push_super_affordance(
                self.take_on_customer_interaction, pet, context)
            self.owner.assign_to_vet(assigned_vet)
            self._go_to_next_state()

    def _next_state(self):
        return self.owner._service_state()
class RetailCustomerSituation(BusinessSituationMixin, SituationComplexCommon):
    INSTANCE_TUNABLES = {
        'customer_job':
        SituationJob.TunableReference(
            description=
            '\n            The situation job for the customer.\n            '),
        'role_state_go_to_store':
        RoleState.TunableReference(
            description=
            '\n            The role state for getting the customer inside the store. This is\n            the default role state and will be run first before any other role\n            state can start.\n            '
        ),
        'role_state_browse':
        OptionalTunable(
            description=
            '\n            If enabled, the customer will be able to browse items.\n            ',
            tunable=TunableTuple(
                role_state=RoleState.TunableReference(
                    description=
                    '\n                    The role state for the customer browsing items.\n                    '
                ),
                browse_time_min=TunableSimMinute(
                    description=
                    '\n                    The minimum amount of time, in sim minutes, the customer\n                    will browse before moving on to the next state. When the\n                    customer begins browsing, a random time will be chosen\n                    between the min and max browse time.\n                    ',
                    default=10),
                browse_time_max=TunableSimMinute(
                    description=
                    '\n                    The maximum amount of time, in sim minutes, the customer\n                    will browse before moving on to the next state. When the\n                    customer begins browsing, a random time will be chosen\n                    between the min and max browse time.\n                    ',
                    default=20),
                browse_time_extension_tunables=OptionalTunable(
                    TunableTuple(
                        description=
                        '\n                    A set of tunables related to browse time extensions.\n                    ',
                        extension_perk=TunableReference(
                            description=
                            '\n                        Reference to a perk that, if unlocked, will increase\n                        browse time by a set amount.\n                        ',
                            manager=services.get_instance_manager(
                                sims4.resources.Types.BUCKS_PERK)),
                        time_extension=TunableSimMinute(
                            description=
                            '\n                        The amount of time, in Sim minutes, that browse time\n                        will be increased by if the specified "extension_perk"\n                        is unlocked.\n                        ',
                            default=30))))),
        'role_state_buy':
        OptionalTunable(
            description=
            '\n            If enabled, the customer will be able to buy items.\n            ',
            tunable=TunableTuple(
                role_state=RoleState.TunableReference(
                    description=
                    '\n                    The role state for the customer buying items.\n                    '
                ),
                price_range=TunableInterval(
                    description=
                    '\n                    The minimum and maximum price of items this customer will\n                    buy.\n                    ',
                    tunable_type=int,
                    default_lower=1,
                    default_upper=100,
                    minimum=1))),
        'role_state_loiter':
        RoleState.TunableReference(
            description=
            '\n            The role state for the customer loitering. If Buy Role State and\n            Browse Role State are both disabled, the Sim will fall back to\n            loitering until Total Shop Time runs out.\n            '
        ),
        'go_to_store_interaction':
        TunableInteractionOfInterest(
            description=
            '\n            The interaction that, when run by a customer, will switch the\n            situation state to start browsing, buying, or loitering.\n            '
        ),
        'total_shop_time_max':
        TunableSimMinute(
            description=
            "\n            The maximum amount of time, in sim minutes, a customer will shop.\n            This time starts when they enter the store. At the end of this\n            time, they'll finish up whatever their current interaction is and\n            leave.\n            ",
            default=30),
        'total_shop_time_min':
        TunableSimMinute(
            description=
            "\n            The minimum amount of time, in sim minutes, a customer will shop.\n            This time starts when they enter the store. At the end of this\n            time, they'll finish up whatever their current interaction is and\n            leave.\n            ",
            default=1),
        'buy_interaction':
        TunableInteractionOfInterest(
            description=
            '\n            The interaction that, when run by a customer, buys an object.\n            '
        ),
        'initial_purchase_intent':
        TunableInterval(
            description=
            "\n            The customer's purchase intent statistic is initialized to a random\n            value in this interval when they enter the store.\n            ",
            tunable_type=int,
            default_lower=0,
            default_upper=100),
        'purchase_intent_extension_tunables':
        OptionalTunable(
            TunableTuple(
                description=
                '\n            A set of tunables related to purchase intent extensions.\n            ',
                extension_perk=TunableReference(
                    description=
                    '\n                Reference to a perk that, if unlocked, will increase purchase\n                intent by a set amount.\n                ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.BUCKS_PERK)),
                purchase_intent_extension=TunableRange(
                    description=
                    '\n                The amount to increase the base purchase intent statistic by if\n                the specified "extension_perk" is unlocked.\n                ',
                    tunable_type=int,
                    default=5,
                    minimum=0,
                    maximum=100))),
        'purchase_intent_empty_notification':
        TunableUiDialogNotificationSnippet(
            description=
            '\n            Notification shown by customer when purchase intent hits bottom and\n            the customer leaves.\n            '
        ),
        'nothing_in_price_range_notification':
        TunableUiDialogNotificationSnippet(
            description=
            "\n            Notification shown by customers who are ready to buy but can't find\n            anything in their price range.\n            "
        ),
        '_situation_start_tests':
        TunableCustomerSituationInitiationSet(
            description=
            '\n            A set of tests that will be run when determining if this situation\n            can be chosen to start. \n            '
        )
    }
    CONTINUE_SHOPPING_THRESHOLD = TunableSimMinute(
        description=
        "\n        If the customer has this much time or more left in their total shop\n        time, they'll start the browse/buy process over again after purchasing\n        something. If they don't have this much time remaining, they'll quit\n        shopping.\n        ",
        default=30)
    PRICE_RANGE = TunableTuple(
        description=
        '\n        Statistics that are set to the min and max price range statistics.\n        These are automatically added to the customer in this situation and\n        will be updated accordingly.\n        \n        The stats should not be persisted -- the situation will readd them\n        on load.\n        ',
        min=Statistic.TunablePackSafeReference(),
        max=Statistic.TunablePackSafeReference())
    PURCHASE_INTENT_STATISTIC = Statistic.TunablePackSafeReference(
        description=
        "\n        A statistic added to customers that track their intent to purchase\n        something. At the minimum value they will leave, and at max value they\n        will immediately try to buy something. Somewhere in between, there's a\n        chance for them to not buy something when they go to the buy state.\n        "
    )
    PURCHASE_INTENT_CHANCE_CURVE = TunableCurve(
        description=
        '\n        A mapping of Purchase Intent Statistic value to the chance (0-1) that\n        the customer will buy something during the buy state.\n        ',
        x_axis_name='Purchase Intent',
        y_axis_name='Chance')
    REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES

    @classmethod
    def can_start_situation(cls, resolver):
        return cls._situation_start_tests.run_tests(resolver)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._customer = None
        self._showing_purchase_intent = False
        reader = self._seed.custom_init_params_reader
        if reader is None:
            self._saved_purchase_intent = None
        else:
            self._saved_purchase_intent = reader.read_int64(
                'purchase_intent', None)
        self._min_price_range_multiplier = 1
        self._max_price_range_multiplier = 1
        self._total_shop_time_multiplier = 1
        self._purchase_intent_watcher_handle = None

    def _save_custom_situation(self, writer):
        super()._save_custom_situation(writer)
        if self._customer is not None:
            purchase_intent = self._customer.get_stat_value(
                self.PURCHASE_INTENT_STATISTIC)
            writer.write_int64('purchase_intent', int(purchase_intent))

    @classmethod
    def _states(cls):
        return (SituationStateData(1, _GoToStoreState),
                SituationStateData(2, _BrowseState),
                SituationStateData(3, _BuyState),
                SituationStateData(4, _LoiterState))

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return [(cls.customer_job, cls.role_state_go_to_store)]

    @classmethod
    def default_job(cls):
        return cls.customer_job

    def start_situation(self):
        super().start_situation()
        self._change_state(_GoToStoreState())

    @classmethod
    def get_sims_expected_to_be_in_situation(cls):
        return 1

    @classproperty
    def situation_serialization_option(cls):
        return situations.situation_types.SituationSerializationOption.LOT

    def validate_customer(self, sim_info):
        if self._customer is None:
            return False
        return self._customer.sim_info is sim_info

    def _on_set_sim_job(self, sim, job_type):
        super()._on_set_sim_job(sim, job_type)
        self._customer = sim
        self._update_price_range_statistics()
        self._initialize_purchase_intent()

    def _on_remove_sim_from_situation(self, sim):
        sim_job = self.get_current_job_for_sim(sim)
        super()._on_remove_sim_from_situation(sim)
        self._remove_purchase_intent()
        self._customer = None
        services.get_zone_situation_manager().add_sim_to_auto_fill_blacklist(
            sim.id, sim_job)
        self._self_destruct()

    def _situation_timed_out(self, *args, **kwargs):
        if not isinstance(self._cur_state, _BuyState):
            super()._situation_timed_out(*args, **kwargs)

    def adjust_browse_time(self, multiplier):
        if type(self._cur_state) is _BrowseState:
            self._cur_state.adjust_timeout(multiplier)

    def adjust_total_shop_time(self, multiplier):
        if multiplier == 0:
            self._self_destruct()
        elif type(self._cur_state) is _GoToStoreState:
            self._total_shop_time_multiplier *= multiplier
        else:
            remaining_minutes = self._get_remaining_time_in_minutes()
            remaining_minutes *= multiplier
            self.change_duration(remaining_minutes)

    def adjust_price_range(self, min_multiplier=1, max_multiplier=1):
        if self.role_state_buy is None:
            return
        self._min_price_range_multiplier *= min_multiplier
        self._max_price_range_multiplier *= max_multiplier
        self._update_price_range_statistics()

    def _update_price_range_statistics(self):
        (min_price, max_price) = self._get_min_max_price_range()
        if self.PRICE_RANGE.min is not None:
            min_stat = self._customer.get_statistic(self.PRICE_RANGE.min)
            min_stat.set_value(min_price)
        if self.PRICE_RANGE.max is not None:
            max_stat = self._customer.get_statistic(self.PRICE_RANGE.max)
            max_stat.set_value(max_price)

    def _get_min_max_price_range(self):
        price_range = self.role_state_buy.price_range
        return (max(0, price_range.lower_bound *
                    self._min_price_range_multiplier),
                max(1, price_range.upper_bound *
                    self._max_price_range_multiplier))

    def _initialize_purchase_intent(self):
        if self.role_state_buy is None:
            return
        if self._saved_purchase_intent is None:
            purchase_intent = random.randint(
                self.initial_purchase_intent.lower_bound,
                self.initial_purchase_intent.upper_bound)
            if self.purchase_intent_extension_tunables is not None:
                active_household = services.active_household()
                if active_household is not None:
                    if active_household.bucks_tracker.is_perk_unlocked(
                            self.purchase_intent_extension_tunables.
                            extension_perk):
                        purchase_intent += self.purchase_intent_extension_tunables.purchase_intent_extension
            purchase_intent = sims4.math.clamp(
                self.PURCHASE_INTENT_STATISTIC.min_value + 1, purchase_intent,
                self.PURCHASE_INTENT_STATISTIC.max_value - 1)
        else:
            purchase_intent = self._saved_purchase_intent
        tracker = self._customer.get_tracker(self.PURCHASE_INTENT_STATISTIC)
        tracker.set_value(self.PURCHASE_INTENT_STATISTIC,
                          purchase_intent,
                          add=True)
        self._purchase_intent_watcher_handle = tracker.add_watcher(
            self._purchase_intent_watcher)
        if self._on_social_group_changed not in self._customer.on_social_group_changed:
            self._customer.on_social_group_changed.append(
                self._on_social_group_changed)

    def _remove_purchase_intent(self):
        if self._customer is not None:
            if self._purchase_intent_watcher_handle is not None:
                tracker = self._customer.get_tracker(
                    self.PURCHASE_INTENT_STATISTIC)
                tracker.remove_watcher(self._purchase_intent_watcher_handle)
                self._purchase_intent_watcher_handle = None
                tracker.remove_statistic(self.PURCHASE_INTENT_STATISTIC)
            if self._on_social_group_changed in self._customer.on_social_group_changed:
                self._customer.on_social_group_changed.remove(
                    self._on_social_group_changed)
            self._set_purchase_intent_visibility(False)

    def _on_social_group_changed(self, sim, group):
        if self._customer in group:
            if self._on_social_group_members_changed not in group.on_group_changed:
                group.on_group_changed.append(
                    self._on_social_group_members_changed)
        elif self._on_social_group_members_changed in group.on_group_changed:
            group.on_group_changed.remove(
                self._on_social_group_members_changed)

    def _on_social_group_members_changed(self, group):
        if self._customer is not None:
            employee_still_in_group = False
            business_manager = services.business_service(
            ).get_business_manager_for_zone()
            if self._customer in group:
                for sim in group:
                    if not business_manager.is_household_owner(
                            sim.household_id):
                        if business_manager.is_employee(sim.sim_info):
                            employee_still_in_group = True
                            break
                    employee_still_in_group = True
                    break
            if employee_still_in_group:
                self._set_purchase_intent_visibility(True)
            else:
                self._set_purchase_intent_visibility(False)

    def on_sim_reset(self, sim):
        super().on_sim_reset(sim)
        if isinstance(self._cur_state, _BuyState) and self._customer is sim:
            new_buy_state = _BuyState()
            new_buy_state.object_id = self._cur_state.object_id
            self._change_state(new_buy_state)

    def _set_purchase_intent_visibility(self, toggle):
        if self._showing_purchase_intent is not toggle and (
                not toggle or isinstance(self._cur_state, _BrowseState)):
            self._showing_purchase_intent = toggle
            stat = self._customer.get_statistic(self.PURCHASE_INTENT_STATISTIC,
                                                add=False)
            if stat is not None:
                value = stat.get_value()
                self._send_purchase_intent_message(stat.stat_type, value,
                                                   value, toggle)

    def _purchase_intent_watcher(self, stat_type, old_value, new_value):
        if stat_type is not self.PURCHASE_INTENT_STATISTIC:
            return
        self._send_purchase_intent_message(stat_type, old_value, new_value,
                                           self._showing_purchase_intent)
        if new_value == self.PURCHASE_INTENT_STATISTIC.max_value:
            self._on_purchase_intent_max()
        elif new_value == self.PURCHASE_INTENT_STATISTIC.min_value:
            self._on_purchase_intent_min()

    def _send_purchase_intent_message(self, stat_type, old_value, new_value,
                                      toggle):
        business_manager = services.business_service(
        ).get_business_manager_for_zone()
        if business_manager is not None and business_manager.is_owner_household_active:
            op = PurchaseIntentUpdate(
                self._customer.sim_id,
                stat_type.convert_to_normalized_value(old_value),
                stat_type.convert_to_normalized_value(new_value), toggle)
            distributor.system.Distributor.instance().add_op(
                self._customer, op)

    def _on_purchase_intent_max(self):
        if isinstance(self._cur_state, _BuyState):
            return
        if isinstance(self._cur_state, _GoToStoreState):
            self._set_shop_duration()
        self._change_state(_BuyState())

    def _on_purchase_intent_min(self):
        resolver = SingleSimResolver(self._customer)
        dialog = self.purchase_intent_empty_notification(
            self._customer, resolver)
        dialog.show_dialog()
        self._self_destruct()

    def _choose_starting_state(self):
        if self.role_state_browse is not None:
            return _BrowseState()
        if self.role_state_buy is not None:
            return _BuyState()
        return _LoiterState()

    def _choose_post_browse_state(self):
        if self._customer is None:
            return
        if self.role_state_buy is not None:
            stat = self._customer.get_statistic(self.PURCHASE_INTENT_STATISTIC,
                                                add=False)
            if stat is not None:
                value = stat.get_value()
                chance = self.PURCHASE_INTENT_CHANCE_CURVE.get(value)
                if random.random() > chance:
                    return _BrowseState()
            self._set_purchase_intent_visibility(False)
            return _BuyState()
        return _LoiterState()

    def _choose_post_buy_state(self):
        minutes_remaining = self._get_remaining_time_in_minutes()
        if minutes_remaining < self.CONTINUE_SHOPPING_THRESHOLD:
            return
        if self.role_state_browse is not None:
            return _BrowseState()
        return _LoiterState()

    def _set_shop_duration(self):
        shop_time = random.randint(self.total_shop_time_min,
                                   self.total_shop_time_max)
        shop_time *= self._total_shop_time_multiplier
        self.change_duration(shop_time)
class MotherPlantBattleSituation(SituationComplexCommon):
    MOTHER_PLANT_METER_ID = 1
    PLAYER_HEALTH_METER_ID = 2
    INSTANCE_TUNABLES = {
        'player_job':
        TunableReference(
            description=
            '\n            Job for the main player sim that fights the plant.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.SITUATION_JOB)),
        'player_sim_role_state':
        TunableReference(
            description=
            '\n            Role state for the main player sim Role.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.ROLE_STATE)),
        'other_player_jobs':
        TunableReference(
            description=
            '\n            Job for the other player Sims that are not the main Sim and are not\n            participating as helpers.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.SITUATION_JOB)),
        'other_player_sims_role_state':
        TunableReference(
            description=
            '\n            Role state for the other player Sims.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.ROLE_STATE)),
        'helper_1_job':
        TunableReference(
            description=
            '\n            Job for one of the helper Sims for the fight.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.SITUATION_JOB)),
        'helper_2_job':
        TunableReference(
            description=
            '\n            Job for one of the helper Sims for the fight.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.SITUATION_JOB)),
        'helper_3_job':
        TunableReference(
            description=
            '\n            Job for one of the helper Sims for the fight.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.SITUATION_JOB)),
        'helper_sim_prepare_role_state_1':
        TunableReference(
            description=
            '\n            Role state for helper Sim 1 when preparing for battle.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.ROLE_STATE)),
        'helper_sim_prepare_role_state_2':
        TunableReference(
            description=
            '\n            Role state for helper Sim 2 when preparing for battle.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.ROLE_STATE)),
        'helper_sim_prepare_role_state_3':
        TunableReference(
            description=
            '\n            Role state for helper Sim 3 when preparing for battle.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.ROLE_STATE)),
        'zombie_job':
        TunableReference(
            description=
            '\n            Job for the Zombies for the fight.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.SITUATION_JOB)),
        'zombie_prepare_role_state':
        TunableReference(
            description=
            '\n            Role state for the zombie Sims when preparing for battle.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.ROLE_STATE)),
        'zombie_fight_interaction':
        TunableReference(
            description=
            '\n            Interaction pushed on zombies to get them to fight a Sim.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.INTERACTION)),
        'zombie_fight_interaction_timer':
        TunableSimMinute(
            description=
            '\n            Timer for the amount of time between zombie attacks.\n            ',
            minimum=1,
            default=30),
        'player_health_statistic':
        TunableReference(
            description=
            "\n            The statistic that we will use in order to determine the Sim's\n            health for the motherplant.\n            ",
            manager=services.get_instance_manager(
                sims4.resources.Types.STATISTIC)),
        'motherplant_health_statisic':
        TunableReference(
            description=
            "\n            The statistic that we will use in order to determine the Sim's\n            health for the motherplant.\n            ",
            manager=services.get_instance_manager(
                sims4.resources.Types.STATISTIC)),
        'victory_interaction_of_interest':
        TunableInteractionOfInterest(
            description=
            '\n            The interaction of interest that we are looking for to determine\n            victory.\n            '
        ),
        'retreat_interaction_of_interest':
        TunableInteractionOfInterest(
            description=
            '\n            The interaction of interest that we are looking for to determine\n            retreat.\n            '
        ),
        'loss_interaction_mixer':
        TunableReference(
            description=
            '\n            The affordance that will be pushed on the primary Sims if they\n            lose.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.INTERACTION)),
        'fight_affordance':
        TunableReference(
            description=
            '\n            The primary fight interaction that we will use to run the defeat\n            mixer the player Sim.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.INTERACTION)),
        'helper_victory_affordance':
        TunableReference(
            description=
            '\n            The affordance that will be pushed on the helper Sims if they\n            achieve victory.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.INTERACTION)),
        'helper_lose_affordance':
        TunableReference(
            description=
            '\n            The affordance that will be pushed on the helper Sims if they\n            lose.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.INTERACTION)),
        'mother_plant_definition':
        TunableReference(
            description=
            '\n            The actual mother plant itself.\n            ',
            manager=services.definition_manager()),
        'base_battle_situation_state':
        BattleThePlantSituationState.TunableFactory(
            locked_args={
                'allow_join_situation': True,
                'time_out': None
            },
            tuning_group=GroupNames.STATE),
        'attack_battle_situation_state':
        AttackBattleThePlantSituationState.TunableFactory(
            locked_args={'allow_join_situation': True},
            tuning_group=GroupNames.STATE),
        'inspire_battle_situation_state':
        InspireBattleThePlantSituationState.TunableFactory(
            locked_args={'allow_join_situation': True},
            tuning_group=GroupNames.STATE),
        'rally_battle_sitaution_state':
        RallyBattleThePlantSituationState.TunableFactory(
            locked_args={'allow_join_situation': True},
            tuning_group=GroupNames.STATE),
        'warbling_warcry_battle_situation_state':
        WarblingWarcryBattleThePlantSituationState.TunableFactory(
            locked_args={'allow_join_situation': True},
            tuning_group=GroupNames.STATE),
        'save_lock_tooltip':
        TunableLocalizedString(
            description=
            '\n            The tooltip/message to show when the player tries to save the game\n            while this situation is running. Save is locked when situation starts.\n            ',
            tuning_group=GroupNames.UI),
        'mother_plant_meter_settings':
        StatBasedSituationMeterData.TunableFactory(
            description=
            '\n            The meter used to track the health of the mother plant.\n            ',
            tuning_group=GroupNames.SITUATION,
            locked_args={'_meter_id': MOTHER_PLANT_METER_ID}),
        'player_health_meter_settings':
        StatBasedSituationMeterData.TunableFactory(
            description=
            '\n            The meter used to track the health of the player team.\n            ',
            tuning_group=GroupNames.SITUATION,
            locked_args={'_meter_id': PLAYER_HEALTH_METER_ID}),
        'mother_plant_icon':
        TunableResourceKey(
            description=
            '\n            Icon to be displayed in the situation UI beside the mother plant\n            health bar.\n            ',
            resource_types=sims4.resources.CompoundTypes.IMAGE,
            default=None,
            allow_none=True,
            tuning_group=GroupNames.SITUATION),
        'states_to_set_on_start':
        TunableList(
            description=
            '\n            A list of states to set on the motherplant on start.\n            ',
            tunable=TunableStateValueReference(
                description=
                '\n                The state to set.\n                ')),
        'states_to_set_on_end':
        TunableList(
            description=
            '\n            A list of states to set on the motherplant on end.\n            ',
            tunable=TunableStateValueReference(
                description=
                '\n                The state to set.\n                ')),
        'victory_reward':
        TunableReference(
            description=
            '\n            The Reward received when the Sim wins the situation.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.REWARD)),
        'victory_audio_sting':
        TunableResourceKey(
            description=
            '\n            The sound to play when the Sim wins the battle.\n            ',
            resource_types=(sims4.resources.Types.PROPX, ),
            default=None,
            tuning_group=GroupNames.AUDIO),
        'defeat_audio_sting':
        TunableResourceKey(
            description=
            '\n            The sound to play when the Sim loses the battle.\n            ',
            resource_types=(sims4.resources.Types.PROPX, ),
            default=None,
            tuning_group=GroupNames.AUDIO),
        'possessed_buff':
        TunableBuffReference(
            description=
            '\n            Possessed Buff for zombie Sims. \n            ')
    }

    @property
    def user_facing_type(self):
        return SituationUserFacingType.MOTHER_PLANT_EVENT

    @property
    def situation_display_type(self):
        return SituationDisplayType.VET

    @property
    def situation_display_priority(self):
        return SituationDisplayPriority.VET

    @classmethod
    def _states(cls):
        return (SituationStateData(1, PrepareForBattleSituationState),
                SituationStateData.from_auto_factory(
                    2, cls.base_battle_situation_state),
                SituationStateData.from_auto_factory(
                    3, cls.attack_battle_situation_state),
                SituationStateData.from_auto_factory(
                    4, cls.inspire_battle_situation_state),
                SituationStateData.from_auto_factory(
                    5, cls.rally_battle_sitaution_state),
                SituationStateData.from_auto_factory(
                    6, cls.warbling_warcry_battle_situation_state))

    @classmethod
    def default_job(cls):
        pass

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return ((cls.player_job, cls.player_sim_role_state),
                (cls.other_player_jobs, cls.other_player_sims_role_state),
                (cls.helper_1_job, cls.helper_sim_prepare_role_state_1),
                (cls.helper_2_job, cls.helper_sim_prepare_role_state_2),
                (cls.helper_3_job, cls.helper_sim_prepare_role_state_3),
                (cls.zombie_job, cls.zombie_prepare_role_state))

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._zombie_attack_alarm_handle = None
        self._registered_test_events = set()
        self._player_health_tracking_situation_goal = None
        self._statistic_watcher_handle = None
        self._victory = False

    @property
    def end_audio_sting(self):
        if self._victory:
            return self.victory_audio_sting
        return self.defeat_audio_sting

    def _get_reward(self):
        if self._victory:
            return self.victory_reward

    def _get_motherplant(self):
        return next(
            iter(services.object_manager().get_objects_of_type_gen(
                self.mother_plant_definition)))

    def _push_loss_on_player(self):
        motherplant = self._get_motherplant()
        for (sim, situation_sim) in self._situation_sims.items():
            if situation_sim.current_job_type is self.player_job:
                parent_si = sim.si_state.get_si_by_affordance(
                    self.fight_affordance)
                if parent_si is not None:
                    interaction_context = InteractionContext(
                        sim, InteractionSource.PIE_MENU, Priority.Critical)
                    aop = AffordanceObjectPair(self.loss_interaction_mixer,
                                               motherplant,
                                               self.fight_affordance,
                                               parent_si)
                    if not aop.test_and_execute(interaction_context):
                        logger.error(
                            'Attempting to push Motherplant Battle Ending Interaction, but failed.'
                        )
        self._push_interaction_on_all_helpers(self.helper_lose_affordance)

    def on_goal_completed(self, goal):
        super().on_goal_completed(goal)
        self._push_loss_on_player()
        self._self_destruct()

    def _on_set_sim_job(self, sim, job_type):
        super()._on_set_sim_job(sim, job_type)
        if job_type is self.zombie_job:
            sim.add_buff_from_op(self.possessed_buff.buff_type,
                                 buff_reason=self.possessed_buff.buff_reason)

    def _on_statistic_updated(self, stat_type, old_value, new_value):
        if stat_type is self.player_health_statistic:
            self._player_health_tracking_situation_goal.set_count(new_value)
            self._player_health_meter.send_update_if_dirty()
        elif stat_type is self.motherplant_health_statisic:
            self._mother_plant_meter.send_update_if_dirty()

    def _zombie_attack(self, _):
        if not self._cur_state.zombie_attack_valid:
            return
        zombies = []
        for (sim, situation_sim) in self._situation_sims.items():
            if situation_sim.current_job_type is self.zombie_job:
                zombies.append(sim)
        zombie_to_attack = random.choice(zombies)
        context = InteractionContext(
            sim,
            InteractionContext.SOURCE_SCRIPT,
            interactions.priority.Priority.High,
            insert_strategy=QueueInsertStrategy.NEXT,
            bucket=interactions.context.InteractionBucketType.DEFAULT)
        zombie_to_attack.push_super_affordance(self.zombie_fight_interaction,
                                               None, context)

    def _push_interaction_on_all_helpers(self, interaction_to_push):
        for (sim, situation_sim) in self._situation_sims.items():
            if not situation_sim.current_job_type is self.helper_1_job:
                if not situation_sim.current_job_type is self.helper_2_job:
                    if situation_sim.current_job_type is self.helper_3_job:
                        context = InteractionContext(
                            sim,
                            InteractionContext.SOURCE_SCRIPT,
                            interactions.priority.Priority.High,
                            insert_strategy=QueueInsertStrategy.NEXT,
                            bucket=interactions.context.InteractionBucketType.
                            DEFAULT)
                        sim.push_super_affordance(interaction_to_push, None,
                                                  context)
            context = InteractionContext(
                sim,
                InteractionContext.SOURCE_SCRIPT,
                interactions.priority.Priority.High,
                insert_strategy=QueueInsertStrategy.NEXT,
                bucket=interactions.context.InteractionBucketType.DEFAULT)
            sim.push_super_affordance(interaction_to_push, None, context)

    def handle_event(self, sim_info, event, resolver):
        super().handle_event(sim_info, event, resolver)
        if event != TestEvent.InteractionComplete:
            return
        if resolver(self.victory_interaction_of_interest):
            self._push_interaction_on_all_helpers(
                self.helper_victory_affordance)
            self._victory = True
            self._self_destruct()
        elif resolver(self.retreat_interaction_of_interest):
            self._push_loss_on_player()
            self._self_destruct()

    def start_situation(self):
        services.get_persistence_service().lock_save(self)
        super().start_situation()
        self._change_state(PrepareForBattleSituationState())
        motherplant = self._get_motherplant()
        motherplant.set_stat_value(self.player_health_statistic, 0, add=True)
        motherplant.set_stat_value(self.motherplant_health_statisic,
                                   self.motherplant_health_statisic.max_value,
                                   add=True)
        for state_value in self.states_to_set_on_start:
            motherplant.set_state(state_value.state, state_value)
        statistic_tracker = motherplant.statistic_tracker
        self._statistic_watcher_handle = statistic_tracker.add_watcher(
            self._on_statistic_updated)
        self._setup_situation_meters()
        self._zombie_attack_alarm_handle = alarms.add_alarm(
            self,
            create_time_span(minutes=self.zombie_fight_interaction_timer),
            self._zombie_attack,
            repeating=True)
        for custom_key in itertools.chain(
                self.victory_interaction_of_interest.custom_keys_gen(),
                self.retreat_interaction_of_interest.custom_keys_gen()):
            custom_key_tuple = (TestEvent.InteractionComplete, custom_key)
            self._registered_test_events.add(custom_key_tuple)
            services.get_event_manager().register_with_custom_key(
                self, TestEvent.InteractionComplete, custom_key)

    def _setup_situation_meters(self):
        motherplant = self._get_motherplant()
        self._mother_plant_meter = self.mother_plant_meter_settings.create_meter_with_sim_info(
            self, motherplant)
        self._player_health_meter = self.player_health_meter_settings.create_meter_with_sim_info(
            self, motherplant)

    def build_situation_start_message(self):
        msg = super().build_situation_start_message()
        with ProtocolBufferRollback(msg.meter_data) as meter_data_msg:
            self.mother_plant_meter_settings.build_data_message(meter_data_msg)
        with ProtocolBufferRollback(msg.meter_data) as meter_data_msg:
            self.player_health_meter_settings.build_data_message(
                meter_data_msg)
        build_icon_info_msg(IconInfoData(icon_resource=self.mother_plant_icon),
                            None, msg.icon_info)
        return msg

    def _destroy(self):
        super()._destroy()
        services.get_persistence_service().unlock_save(self)
        for (event_type, custom_key) in self._registered_test_events:
            services.get_event_manager().unregister_with_custom_key(
                self, event_type, custom_key)
        motherplant = self._get_motherplant()
        statistic_tracker = motherplant.statistic_tracker
        statistic_tracker.remove_watcher(self._statistic_watcher_handle)
        for state_value in self.states_to_set_on_end:
            motherplant.set_state(state_value.state, state_value)
        self._registered_test_events.clear()
        if self._mother_plant_meter is not None:
            self._mother_plant_meter.destroy()
        if self._player_health_meter is not None:
            self._player_health_meter.destroy()

    def get_lock_save_reason(self):
        return self.save_lock_tooltip

    def set_motherplant_situation_state(self, motherplant_battle_state):
        if motherplant_battle_state == MotherplantBattleStates.ATTACK:
            self._change_state(self.attack_battle_situation_state())
        elif motherplant_battle_state == MotherplantBattleStates.INSPIRE:
            self._change_state(self.inspire_battle_situation_state())
        elif motherplant_battle_state == MotherplantBattleStates.RALLY:
            self._change_state(self.rally_battle_sitaution_state())
        elif motherplant_battle_state == MotherplantBattleStates.WARBLING_WARCRY:
            self._change_state(self.warbling_warcry_battle_situation_state())

    def _on_proxy_situation_goal_added(self, goal):
        self._player_health_tracking_situation_goal = goal

    def _issue_requests(self):
        super()._issue_requests()
        request = SelectableSimRequestFactory(
            self,
            _RequestUserData(),
            self.other_player_jobs,
            self.exclusivity,
            request_priority=BouncerRequestPriority.EVENT_DEFAULT_JOB)
        self.manager.bouncer.submit_request(request)