Esempio n. 1
0
 def get_create_params(user_facing=False):
     create_params = {}
     create_params_locked = {}
     if user_facing:
         create_params['user_facing'] = Tunable(
             description=
             "\n                                                   If enabled, we will start the situation as user facing.\n                                                   Note: We can only have one user facing situation at a time,\n                                                   so make sure you aren't tuning multiple user facing\n                                                   situations to occur at once.\n                                                   ",
             tunable_type=bool,
             default=False)
     else:
         create_params_locked['user_facing'] = False
     return {
         'weighted_situations':
         TunableList(
             description=
             '\n            A weighted list of situations to be used while fulfilling the\n            desired Sim count.\n            ',
             tunable=TunableTuple(
                 situation=Situation.TunableReference(pack_safe=True),
                 params=TunableTuple(
                     description=
                     '\n                    Situation creation parameters.\n                    ',
                     locked_args=create_params_locked,
                     **create_params),
                 weight=Tunable(tunable_type=int, default=1),
                 weight_multipliers=TunableMultiplier.TunableFactory(
                     description=
                     "\n                    Tunable tested multiplier to apply to weight.\n                    \n                    *IMPORTANT* The only participants that work are ones\n                    available globally, such as Lot and ActiveHousehold. Only\n                    use these participant types or use tests that don't rely\n                    on any, such as testing all objects via Object Criteria\n                    test or testing active zone with the Zone test.\n                    ",
                     locked_args={'base_value': 1}),
                 tests=TunableTestSet(
                     description=
                     "\n                    A set of tests that must pass for the situation and weight\n                    pair to be available for selection.\n                    \n                    *IMPORTANT* The only participants that work are ones\n                    available globally, such as Lot and ActiveHousehold. Only\n                    use these participant types or use tests that don't rely\n                    on any, such as testing all objects via Object Criteria\n                    test or testing active zone with the Zone test.\n                    "
                 )))
     }
Esempio n. 2
0
class GoDancingZoneDirector(SchedulingZoneDirector):
    INSTANCE_TUNABLES = {
        'go_dancing_background_situation':
        Situation.TunableReference(
            description=
            '\n            The situation that is always runnning at the Cafe to make sure any\n            Sims that show up beyond the schedule tuning will get coffee. These\n            could be Sims the player invites, the player themselves, and clique\n            Sims. \n            \n            Note, the situation that this points to will be a very\n            generic situation that spins up a CafeGenericSimSituation for that\n            individual Sim. This is so that Sims can get coffee on their own\n            autonomy and be independent of one another.\n            ',
            class_restrictions=('GoDancingBackgroundSituation', ))
    }

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

    def create_situations_during_zone_spin_up(self):
        super().create_situations_during_zone_spin_up()
        situation_manager = services.get_zone_situation_manager()
        for situation in situation_manager:
            if type(situation) is self.go_dancing_background_situation:
                self._background_situation_id = situation.id
                break
        else:
            self._background_situation_id = situation_manager.create_situation(
                self.go_dancing_background_situation,
                user_facing=False,
                creation_source=self.instance_name)
Esempio n. 3
0
class EmergencyFrequency(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'max_emergency_situations_per_shift':
        TunableRange(
            description=
            '\n            The maximum number of times during a shift that an emergency\n            situation can be created/started.\n            ',
            tunable_type=int,
            default=1,
            minimum=0),
        'inital_lockout_in_sim_minutes':
        TunableSimMinute(
            description=
            '\n            The time, in Sim minutes, that pass at the beginning of a shift\n            before the first check for creating/starting an emergency happens.\n            ',
            default=60),
        'cool_down_in_sim_minutes':
        TunableSimMinute(
            description=
            '\n            How often a check for whether or not to create/start an emergency\n            happens.\n            ',
            default=60),
        'percent_chance_of_emergency':
        TunablePercent(
            description=
            '\n            The percentage chance that on any given check an emergency is to\n            to be created/started.\n            ',
            default=30),
        'weighted_situations':
        TunableList(
            description=
            '\n            A weighted list of situations to be used as emergencies. When a\n            check passes to create/start an emergency, this is the list\n            of emergency situations that will be chosen from.\n            ',
            tunable=TunableTuple(situation=Situation.TunableReference(),
                                 weight=Tunable(tunable_type=int, default=1)))
    }
class _ComplainState(CommonInteractionStartedSituationState):
    FACTORY_TUNABLES = {'neighbor_situation': Situation.TunableReference(description='\n            The Situation for the loud neighbor to come out and see what the\n            player wants when they bang on the door.\n            ', class_restrictions=('NeighborResponseSituation',)), 'turn_off_loud_door_state': Tunable(description='\n            If enabled, we will set the door to the loud door off state when\n            entering this situation state.\n            ', tunable_type=bool, default=False)}

    def __init__(self, *args, neighbor_situation=None, turn_off_loud_door_state=None, **kwargs):
        super().__init__(*args, **kwargs)
        self._neighbor_situation = neighbor_situation
        self._turn_off_loud_door_state = turn_off_loud_door_state
        self.neighbor_response_situation_id = None

    def on_activate(self, reader=None):
        super().on_activate(reader)

    def _create_neighbor_response_situation(self):
        situation_manager = services.get_zone_situation_manager()
        guest_list = SituationGuestList(invite_only=True)
        guest_list.add_guest_info(SituationGuestInfo(self.owner._neighbor_sim_id, self._neighbor_situation.loud_neighbor_job_and_role_state.job, RequestSpawningOption.MUST_SPAWN, BouncerRequestPriority.EVENT_VIP))
        self.neighbor_response_situation_id = situation_manager.create_situation(self._neighbor_situation, guest_list=guest_list, user_facing=False)

    def _set_loud_door_state_off(self):
        if self._turn_off_loud_door_state and self.owner._neighbor_door_id is not None:
            apartment_door = services.object_manager().get(self.owner._neighbor_door_id)
            if apartment_door is not None:
                apartment_door.set_state(self.owner.loud_door_state_off.state, self.owner.loud_door_state_off)

    def _on_interaction_of_interest_started(self):
        self._create_neighbor_response_situation()
        services.get_zone_situation_manager().remove_sim_from_auto_fill_blacklist(self.owner._neighbor_sim_id)
        self._set_loud_door_state_off()
class GoDancingBackgroundSituation(SituationComplexCommon):
    INSTANCE_TUNABLES = {
        'generic_sim_job':
        TunableSituationJobAndRoleState(
            description=
            "\n            A job and role state that essentially does nothing but filter out\n            Sims that shouldn't be placed in the party-goer situation.\n            "
        ),
        'party_goer_situation':
        Situation.TunableReference(
            description=
            '\n            The individual, party-goer situation we want to use for\n            Sims that show up at the party so they want to dance and more.\n            ',
            class_restrictions=('GoDancingBackgroundPartyGoerSituation', ))
    }
    REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES

    @classmethod
    def _states(cls):
        return (SituationStateData(1, _GoDancingGenericState), )

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

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

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

    def _issue_requests(self):
        request = SelectableSimRequestFactory(
            self,
            callback_data=_RequestUserData(
                role_state_type=self.generic_sim_job.role_state),
            job_type=self.generic_sim_job.job,
            exclusivity=self.exclusivity)
        self.manager.bouncer.submit_request(request)

    def _on_set_sim_job(self, sim, job_type):
        super()._on_set_sim_job(sim, job_type)
        situation_manager = services.get_zone_situation_manager()
        guest_list = SituationGuestList(invite_only=True)
        guest_info = SituationGuestInfo.construct_from_purpose(
            sim.sim_info.id, self.party_goer_situation.default_job(),
            SituationInvitationPurpose.INVITED)
        guest_list.add_guest_info(guest_info)
        situation_manager.create_situation(self.party_goer_situation,
                                           guest_list=guest_list,
                                           user_facing=False)
Esempio n. 6
0
class CafeZoneDirector(SchedulingZoneDirector):
    INSTANCE_TUNABLES = {'cafe_generic_arrival_situation': Situation.TunableReference(description='\n            The situation that is always runnning at the Cafe to make sure any\n            Sims that show up beyond the schedule tuning will get coffee. These\n            could be Sims the player invites, the player themselves, and clique\n            Sims. \n            \n            Note, the situation that this points to will be a very\n            generic situation that spins up a CafeGenericSimSituation for that\n            individual Sim. This is so that Sims can get coffee on their own\n            autonomy and be independent of one another.\n            ', class_restrictions=('CafeGenericBackgroundSituation',))}

    def add_sim_info_into_arrival_situation(self, sim_info, during_spin_up=False):
        situation_manager = services.get_zone_situation_manager()
        situation = situation_manager.get_situation_by_type(self.cafe_generic_arrival_situation)
        if situation is None:
            situation_manager.create_situation(self.cafe_generic_arrival_situation, guest_list=None, user_facing=False, creation_source=self.instance_name)

    def create_situations_during_zone_spin_up(self):
        super().create_situations_during_zone_spin_up()
        situation_manager = services.get_zone_situation_manager()
        situation_manager.create_situation(self.cafe_generic_arrival_situation, guest_list=None, user_facing=False, creation_source=self.instance_name)

    def handle_sim_summon_request(self, sim_info, purpose):
        self.add_sim_info_into_arrival_situation(sim_info)
Esempio n. 7
0
class SubSituationState(CommonSituationState):
    FACTORY_TUNABLES = {
        'sub_situation':
        Situation.TunableReference(
            description=
            '\n            Sub-situation to run within this situation state. When the sub-situation\n            ends, the owning situation state will end.\n            ',
            tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP,
            class_restrictions='SituationSimple')
    }

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

    def start_situation(self):
        if self._sub_situation_id is None:
            sub_situation_id = self.owner.manager.create_situation(
                self.sub_situation)
            self._sub_situation_id = sub_situation_id
        if self._sub_situation_id is not None:
            self.owner.manager.register_for_callback(
                self._sub_situation_id,
                SituationCallbackOption.END_OF_SITUATION,
                self._on_sub_situation_end_callback)
            self.owner.manager.disable_save_to_situation_manager(
                self._sub_situation_id)

    def on_activate(self, *args, **kwargs):
        super().on_activate(*args, **kwargs)
        self.start_situation()

    def _on_sub_situation_end_callback(self, sub_situation_id,
                                       situation_callback_option, data):
        if sub_situation_id == self._sub_situation_id:
            self._sub_situation_id = None
            self._on_sub_situation_end(sub_situation_id)

    def on_deactivate(self, *args, **kwargs):
        super().on_deactivate(*args, **kwargs)
        if self._sub_situation_id is not None:
            situation_manager = services.get_zone_situation_manager()
            situation_manager.destroy_situation_by_id(self._sub_situation_id)

    def _on_sub_situation_end(self, sub_situation_id):
        raise NotImplementedError
class VisitorSituationOnArrivalZoneDirectorMixin:
    INSTANCE_TUNABLES = {'user_sim_arrival_situation': Situation.TunableReference(description='\n            The situation to place all of the Sims from the users household\n            in when they arrive.\n            '), 'place_all_user_sims_in_same_arrival_situation': Tunable(description='\n            If this is enabled then all user sims will be placed in the same\n            situation instead of each in their own situation.\n            ', tunable_type=bool, default=False), 'place_travel_companion_in_same_arrival_situation': Tunable(description='\n            If this is enabled, the travel companion will put into the same\n            situation with user sims. If this checked,\n            place_all_user_sims_in_same_arrival_situation has to be True as\n            well or there will be unit test error.\n            ', tunable_type=bool, default=False), 'travel_companion_arrival_situation': OptionalTunable(description="\n            If enabled then Sims that aren't controllable that travel with the\n            users Sims will be placed in the tuned situation on arrival. If\n            place_travel_companion_in_same_arrival_situation is checked, this\n            needs to be disable or there will be unit test error.\n            ", tunable=Situation.TunableReference(description="\n                If the user invites NPC's to travel with them to this lot then\n                this is the situation that they will be added to.\n                "))}

    @classmethod
    def _verify_tuning_callback(cls):
        if cls.place_travel_companion_in_same_arrival_situation and not cls.place_all_user_sims_in_same_arrival_situation:
            logger.error("{} set place_travel_companion_in_same_arrival_situation to True but doesn't support place_all_user_sims_in_same_arrival_situation, this is invalid.", cls.__name__)
        if cls.place_travel_companion_in_same_arrival_situation and cls.travel_companion_arrival_situation is not None:
            logger.error('{} set place_travel_companion_in_same_arrival_situation to True but specify travel_companion_arrival_situation, this is invalid', cls.__name__)

    def _sim_info_already_in_arrival_situation(self, sim_info, situation_manager):
        seeds = situation_manager.get_zone_persisted_seeds_during_zone_spin_up()
        for seed in seeds:
            if sim_info in seed.invited_sim_infos_gen():
                if seed.situation_type is self.user_sim_arrival_situation:
                    if services.current_zone().time_has_passed_in_world_since_zone_save():
                        if seed.allow_time_jump:
                            return True
                    return True
        return False

    def create_arrival_situation_for_sim(self, sim_info, situation_type=DEFAULT, during_spin_up=False):
        if situation_type is DEFAULT:
            situation_type = self.user_sim_arrival_situation
        situation_manager = services.get_zone_situation_manager()
        if during_spin_up and self._sim_info_already_in_arrival_situation(sim_info, situation_manager):
            return
        guest_list = SituationGuestList(invite_only=True)
        guest_info = SituationGuestInfo.construct_from_purpose(sim_info.id, situation_type.default_job(), SituationInvitationPurpose.INVITED)
        guest_list.add_guest_info(guest_info)
        self.create_arrival_situation(situation_type, guest_list, situation_manager)

    def create_arrival_situation(self, situation_type, guest_list, situation_manager):
        try:
            creation_source = self.instance_name
        except:
            creation_source = str(self)
        situation_manager.create_situation(situation_type, guest_list=guest_list, user_facing=False, creation_source=creation_source)

    def get_all_sim_arrival_guest_list(self, situation_manager, during_spin_up=False):
        sim_infos = [sim_info for sim_info in self.get_user_controlled_sim_infos()]
        if self.place_travel_companion_in_same_arrival_situation:
            sim_infos.extend(self._traveled_sim_infos)
        guest_list = SituationGuestList(invite_only=True)
        for sim_info in sim_infos:
            if during_spin_up and self._sim_info_already_in_arrival_situation(sim_info, situation_manager):
                continue
            guest_info = SituationGuestInfo.construct_from_purpose(sim_info.id, self.user_sim_arrival_situation.default_job(), SituationInvitationPurpose.INVITED)
            guest_list.add_guest_info(guest_info)
        return guest_list

    def create_situations_during_zone_spin_up(self):
        super().create_situations_during_zone_spin_up()
        situation_manager = services.get_zone_situation_manager()
        if self.place_all_user_sims_in_same_arrival_situation:
            guest_list = self.get_all_sim_arrival_guest_list(situation_manager, during_spin_up=True)
            self.create_arrival_situation(self.user_sim_arrival_situation, guest_list, situation_manager)
        else:
            for sim_info in self.get_user_controlled_sim_infos():
                self.create_arrival_situation_for_sim(sim_info, during_spin_up=True)
        if self.travel_companion_arrival_situation:
            for sim_info in self._traveled_sim_infos:
                if not sim_info.is_selectable:
                    self.create_arrival_situation_for_sim(sim_info, situation_type=self.travel_companion_arrival_situation, during_spin_up=True)

    def handle_sim_summon_request(self, sim_info, purpose):
        self.create_arrival_situation_for_sim(sim_info)
class YardSaleSituation(SituationComplexCommon):
    INSTANCE_TUNABLES = {
        'user_job':
        TunableTuple(
            description=
            '\n            The job and role which the Sim is placed into.\n            ',
            situation_job=SituationJob.TunableReference(
                description=
                '\n                A reference to a SituationJob that can be performed at this Situation.\n                '
            ),
            role_state=RoleState.TunableReference(
                description=
                '\n                A role state the sim assigned to the job will perform.\n                '
            )),
        'manage_customers_state':
        ManageCustomersState.TunableFactory(
            tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP),
        'customer_situation':
        Situation.TunableReference(
            description=
            '\n            Customer Situation to spawn that will pull customers to purchase\n            items from the craft sales table.\n            ',
            class_restrictions=('YardSaleCustomerSituation', )),
        'number_of_expected_customers':
        TunableInterval(
            description=
            '\n            The number of customers we expect to have at any given time the\n            yard sale is running. The yard sale will attempt to manage this\n            many customer situations at any given time.\n            ',
            tunable_type=int,
            default_lower=0,
            default_upper=10,
            minimum=0,
            maximum=10)
    }

    def __init__(self, *arg, **kwargs):
        super().__init__(*arg, **kwargs)
        self.scoring_enabled = False
        reader = self._seed.custom_init_params_reader
        if reader is None:
            self.customer_situations = []
        else:
            self.customer_situations = list(
                reader.read_uint32s(CUSTOMER_SITUATIONS_TOKEN, list()))

    @classmethod
    def default_job(cls):
        pass

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return [(cls.user_job.situation_job, cls.user_job.role_state)]

    @classmethod
    def _states(cls):
        return (SituationStateData(1,
                                   ManageCustomersState,
                                   factory=cls.manage_customers_state), )

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

    def _self_destruct(self):
        situation_manager = services.get_zone_situation_manager()
        for situation_id in self.customer_situations:
            situation = situation_manager.get(situation_id)
            if situation is not None:
                situation._self_destruct()
        self.customer_situations.clear()
        super()._self_destruct()

    def get_customer_situations(self):
        customers = []
        situation_manager = services.get_zone_situation_manager()
        for situation_id in self.customer_situations:
            situation = situation_manager.get(situation_id)
            if situation is not None:
                customers.append(situation)
        self.customer_situations = [situation.id for situation in customers]
        return self.customer_situations

    def create_customer_situation(self):
        situation_manager = services.get_zone_situation_manager()
        situation_id = situation_manager.create_situation(
            self.customer_situation, guest_list=None, user_facing=False)
        self.customer_situations.append(situation_id)
Esempio n. 10
0
class InfectedSituation(SubSituationOwnerMixin, SituationComplexCommon):
    INSTANCE_TUNABLES = {'infected_state': _InfectedState.TunableFactory(display_name='Infected State', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP), 'possessed_start_time': TunableTimeOfDay(description='\n            Time of day Sims become possessed.\n            ', tuning_group=GroupNames.SITUATION), 'possessed_duration_hours': TestedSum.TunableFactory(description='\n            How long the possession lasts.\n            ', tuning_group=GroupNames.SITUATION), 'possessed_situation': Situation.TunableReference(description='\n            Possessed situation to place Sim in.\n            ', class_restrictions=('PossessedSituation',), tuning_group=GroupNames.SITUATION), 'default_job_and_role': TunableSituationJobAndRoleState(description='\n            The job/role the infected Sim will be in.\n            ', tuning_group=GroupNames.SITUATION), 'possessed_buff_tag': TunableTag(description='\n            Tag for buffs that can add the Possessed Mood through the Infection\n            System. Possessed status is refreshed when these buffs are added\n            or removed.\n            ', filter_prefixes=('Buff',)), 'possessed_buff_no_animate_tag': TunableTag(description='\n            Possession buffs with this tag will not play the start possession\n            animation.\n            ', filter_prefixes=('Buff',)), 'possession_time_buff': TunableBuffReference(description='\n            The buff to add to the Sim when it is the possessed start time.\n            ')}
    REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._start_possession_alarm = None
        self._end_possession_alarm = None
        self._possession_sources = []

    @classmethod
    def _states(cls):
        return (SituationStateData.from_auto_factory(0, cls.infected_state),)

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return ((cls.default_job_and_role.job, cls.default_job_and_role.role_state),)

    @classmethod
    def default_job(cls):
        pass

    @classproperty
    def situation_serialization_option(cls):
        return SituationSerializationOption.DONT

    def start_situation(self):
        super().start_situation()
        self._change_state(self.infected_state())
        sim_info = self._get_sim_info()
        for buff in sim_info.Buffs.get_all_buffs_with_tag(self.possessed_buff_tag):
            self._request_possession(buff.buff_type, animate_possession_override=False)
        now = services.time_service().sim_now
        time_span_day = create_time_span(days=1)
        start_day_time = self._get_possessed_start_day_time()
        time_span = now.time_till_next_day_time(start_day_time)
        self._start_possession_alarm = alarms.add_alarm(self, time_span, self._on_possession_start, repeating_time_span=time_span_day, repeating=True)
        end_day_time = self._get_possessed_end_day_time()
        sim_info.Buffs.on_buff_added.register(self._on_buff_added)
        sim_info.Buffs.on_buff_removed.register(self._on_buff_removed)
        in_possession_window = now.time_between_day_times(start_day_time, end_day_time)
        if in_possession_window:
            elapsed_time = create_time_span(days=1) - now.time_till_next_day_time(start_day_time)
            self._trigger_possession_time(elapsed_time=elapsed_time)

    def on_remove(self):
        sim_info = self._get_sim_info()
        sim_info.Buffs.on_buff_added.unregister(self._on_buff_added)
        sim_info.Buffs.on_buff_removed.unregister(self._on_buff_removed)
        super().on_remove()

    @property
    def sim_id(self):
        return self._guest_list.host_sim_id

    def _get_sim_info(self):
        sim_info = services.sim_info_manager().get(self.sim_id)
        return sim_info

    def _get_possessed_start_day_time(self):
        return self.possessed_start_time

    def _get_possessed_end_day_time(self):
        sim_info = self._get_sim_info()
        if sim_info is None:
            logger.error('Missing SimInfo for infected sim')
            return
        start_day_time = self._get_possessed_start_day_time()
        resolver = SingleSimResolver(sim_info)
        hours = self.possessed_duration_hours.get_modified_value(resolver)
        end_day_time = start_day_time + create_time_span(hours=hours)
        return end_day_time

    def _on_possession_start(self, _):
        self._trigger_possession_time()

    def _trigger_possession_time(self, elapsed_time=None):
        sim_info = self._get_sim_info()
        if sim_info is None:
            logger.error('Missing SimInfo for infected sim')
            return
        possession_buff = self.possession_time_buff
        sim_info.add_buff_from_op(possession_buff.buff_type, possession_buff.buff_reason)
        buff_commodity = sim_info.get_statistic(possession_buff.buff_type.commodity, add=False)
        if buff_commodity:
            resolver = SingleSimResolver(sim_info)
            hours = self.possessed_duration_hours.get_modified_value(resolver)
            buff_time = hours*60
            if elapsed_time is not None:
                buff_time -= elapsed_time.in_minutes()
            buff_commodity.set_value(buff_time)

    def _on_sub_situation_end(self, sub_situation_id):
        if services.current_zone().is_zone_shutting_down:
            return
        if self._possession_sources:
            sim_info = self._get_sim_info()
            for source in tuple(self._possession_sources):
                sim_info.remove_buff_by_type(source)

    def _start_possession_situation(self, animate_possession_override=None):
        guest_list = SituationGuestList(invite_only=True, host_sim_id=self.sim_id)
        animate_possession = services.current_zone().is_zone_running
        if animate_possession:
            if animate_possession_override is not None:
                animate_possession = animate_possession_override
        self._create_sub_situation(self.possessed_situation, guest_list=guest_list, user_facing=False, animate_possession=animate_possession)

    def _on_possession_sources_changed(self):
        sub_situations = self._get_sub_situations()
        if sub_situations:
            sub_situations[0].on_possession_sources_changed()

    def _request_possession(self, source, animate_possession_override=None):
        if source in self._possession_sources:
            logger.error('Redundant source: {}', source)
            return
        self._possession_sources.append(source)
        if not self._sub_situation_ids:
            self._start_possession_situation(animate_possession_override=animate_possession_override)
        self._on_possession_sources_changed()

    def _remove_possession_request(self, source):
        if source not in self._possession_sources:
            logger.error('Missing source: {}', source)
            return
        self._possession_sources.remove(source)
        self._on_possession_sources_changed()

    def _on_buff_added(self, buff_type, owner_sim_id):
        if self.possessed_buff_tag not in buff_type.tags:
            return
        animate = None
        if self.possessed_buff_no_animate_tag in buff_type.tags:
            animate = False
        self._request_possession(buff_type, animate_possession_override=animate)

    def _on_buff_removed(self, buff_type, owner_sim_id):
        if self.possessed_buff_tag not in buff_type.tags:
            return
        self._remove_possession_request(buff_type)

    def get_possession_source(self):
        sim_info = self._get_sim_info()
        if sim_info is None:
            return (None, None)
        buff_component = sim_info.Buffs
        longest_source = None
        buff_duration = None
        for source in self._possession_sources:
            buff = buff_component.get_buff_by_type(source)
            if buff is None:
                continue
            buff_commodity = buff.get_commodity_instance()
            if buff_commodity is None:
                longest_source = buff
                buff_duration = None
                break
            buff_value = buff_commodity.get_value()
            if not buff_duration is None:
                if buff_value > buff_duration:
                    buff_duration = buff_value
                    longest_source = buff
            buff_duration = buff_value
            longest_source = buff
        return (longest_source, buff_duration)
class SituationManager(DistributableObjectManager):
    __qualname__ = 'SituationManager'
    DEFAULT_LEAVE_SITUATION = sims4.tuning.tunable.TunableReference(
        description=
        '\n                                            The situation type for the background leave situation.\n                                            It collects sims who are not in other situations and\n                                            asks them to leave periodically.\n                                            ',
        manager=services.get_instance_manager(sims4.resources.Types.SITUATION),
        class_restrictions=situations.complex.leave_situation.LeaveSituation)
    DEFAULT_LEAVE_NOW_MUST_RUN_SITUATION = sims4.tuning.tunable.TunableReference(
        description=
        '\n                                            The situation type that drives the sim off the lot pronto.\n                                            ',
        manager=services.get_instance_manager(sims4.resources.Types.SITUATION),
        class_restrictions=situations.complex.single_sim_leave_situation.
        SingleSimLeaveSituation)
    DEFAULT_VISIT_SITUATION = sims4.tuning.tunable.TunableReference(
        description=
        '\n                                            The default visit situation used when you ask someone to \n                                            hang out or invite them in.\n                                            ',
        manager=services.get_instance_manager(sims4.resources.Types.SITUATION))
    DEFAULT_TRAVEL_SITUATION = Situation.TunableReference(
        description=
        ' \n                                            The default situation for when you \n                                            are simply traveling with a group \n                                            of Sims.\n                                            '
    )
    NPC_SOFT_CAP = sims4.tuning.tunable.Tunable(
        description=
        '\n                The base value for calculating the soft cap on the maximum \n                number of NPCs instantiated.\n                \n                The actual value of the NPC soft cap will be\n                this tuning value minus the number of sims in the active household.\n                \n                There is no hard cap because certain types of NPCs must always\n                spawn or the game will be broken. The prime example of a \n                game breaker is the Grim Reaper.\n                \n                If the number of NPCs is:\n                \n                1) At or above the soft cap only game breaker NPCs will be spawned.\n                \n                2) Above the soft cap then low priority NPCs will be driven from the lot.\n                \n                3) Equal to the soft cap and there are pending requests for higher priority\n                NPCs, then lower priority NPCs will be driven from the lot.\n                                \n                ',
        tunable_type=int,
        default=20,
        tuning_filter=sims4.tuning.tunable_base.FilterTag.EXPERT_MODE)
    LEAVE_INTERACTION_TAGS = TunableSet(
        description=
        '\n                The tags indicating leave lot interactions, but not \n                leave lot must run interactions.\n                These are used to determine if a leave lot interaction is running\n                or cancel one if it is.\n                ',
        tunable=TunableEnumWithFilter(tunable_type=tag.Tag,
                                      default=tag.Tag.INVALID,
                                      tuning_filter=FilterTag.EXPERT_MODE,
                                      filter_prefixes=tag.INTERACTION_PREFIX))
    SUPER_SPEED_THREE_REQUEST_BUFF = TunableBuffReference(
        description=
        "\n        The buff to apply to the Sim when we're trying to make them run the\n        leave situation from a super speed three request.\n        ",
        deferred=True)
    _npc_soft_cap_override = None
    _perf_test_cheat_enabled = False

    def __init__(self, manager_id=0):
        super().__init__(manager_id=manager_id)
        self._get_next_session_id = UniqueIdGenerator(1)
        self._added_to_distributor = set()
        self._callbacks = {}
        self._departing_situation_seed = None
        self._arriving_situation_seed = None
        self._zone_seeds_for_zone_spinup = []
        self._open_street_seeds_for_zone_spinup = []
        self._debug_sims = set()
        self._leave_situation_id = 0
        self._player_greeted_situation_id = 0
        self._player_waiting_to_be_greeted_situation_id = 0
        self._sim_being_created = None
        self._sim_data = {}
        self._delay_situation_destruction_ref_count = 0
        self._situations_for_delayed_destruction = set()
        self._bouncer = None
        self._zone_spin_up_greeted_complete = False
        self._pre_bouncer_update = []

    def start(self):
        self._bouncer = Bouncer()

    def destroy_situations_on_teardown(self):
        self.destroy_all_situations(include_system=True)
        self._sim_data.clear()
        self._bouncer.destroy()
        self._bouncer = None

    def reset(self, create_system_situations=True):
        self.destroy_all_situations(include_system=True)
        self._added_to_distributor.clear()
        self._callbacks.clear()
        self._bouncer.reset()
        if create_system_situations:
            self._create_system_situations()

    def add_pre_bouncer_update(self, situation):
        self._pre_bouncer_update.append(situation)

    def update(self):
        if self._bouncer is not None:
            try:
                situations = tuple(self._pre_bouncer_update)
                if situations:
                    self._pre_bouncer_update = []
                    for situation in situations:
                        situation.on_pre_bouncer_update()
                self._bouncer._update()
            except Exception:
                logger.exception('Exception while updating the Bouncer.')

    @property
    def npc_soft_cap(self):
        cap = self.NPC_SOFT_CAP if self._npc_soft_cap_override is None else self._npc_soft_cap_override
        if services.active_household() is None:
            return 0
        return cap - services.active_household().size_of_household

    def set_npc_soft_cap_override(self, override):
        self._npc_soft_cap_override = override

    def enable_perf_cheat(self, enable=True):
        self._perf_test_cheat_enabled = enable
        self._bouncer.spawning_freeze(enable)
        self._bouncer.cap_cheat(enable)

    def get_all(self):
        return [
            obj for obj in self._objects.values()
            if obj._stage == SituationStage.RUNNING
        ]

    def get_new_situation_creation_session(self):
        return self._get_next_session_id()

    @property
    def bouncer(self):
        return self._bouncer

    @property
    def sim_being_created(self):
        return self._sim_being_created

    def add_debug_sim_id(self, sim_id):
        self._debug_sims.add(sim_id)

    def _determine_player_greeted_status_during_zone_spin_up(self):
        if not services.current_zone(
        ).venue_service.venue.requires_visitation_rights:
            return GreetedStatus.NOT_APPLICABLE
        active_household = services.active_household()
        if active_household is None:
            return GreetedStatus.NOT_APPLICABLE
        if active_household.home_zone_id == services.current_zone().id:
            return GreetedStatus.NOT_APPLICABLE
        cur_status = GreetedStatus.WAITING_TO_BE_GREETED
        lot_seeds = list(self._zone_seeds_for_zone_spinup)
        if self._arriving_situation_seed is not None:
            lot_seeds.append(self._arriving_situation_seed)
        for seed in lot_seeds:
            status = seed.get_player_greeted_status()
            logger.debug('Player:{} :{}',
                         status,
                         seed.situation_type,
                         owner='sscholl')
            while status == GreetedStatus.GREETED:
                cur_status = status
                break
        return cur_status

    def get_npc_greeted_status_during_zone_fixup(self, sim_info):
        if not services.current_zone(
        ).venue_service.venue.requires_visitation_rights:
            return GreetedStatus.NOT_APPLICABLE
        if sim_info.lives_here:
            return GreetedStatus.NOT_APPLICABLE
        cur_status = GreetedStatus.NOT_APPLICABLE
        for seed in self._zone_seeds_for_zone_spinup:
            status = seed.get_npc_greeted_status(sim_info)
            logger.debug('NPC:{} :{} :{}',
                         sim_info,
                         status,
                         seed.situation_type,
                         owner='sscholl')
            if status == GreetedStatus.GREETED:
                cur_status = status
                break
            while status == GreetedStatus.WAITING_TO_BE_GREETED:
                cur_status = status
        return cur_status

    def is_player_greeted(self):
        return self._player_greeted_situation_id != 0

    def is_player_waiting_to_be_greeted(self):
        return self._player_waiting_to_be_greeted_situation_id != 0 and self._player_greeted_situation_id == 0

    @property
    def is_zone_spin_up_greeted_complete(self):
        return self._zone_spin_up_greeted_complete

    def create_situation(self,
                         situation_type,
                         guest_list=None,
                         user_facing=True,
                         duration_override=None,
                         custom_init_writer=None,
                         zone_id=0,
                         scoring_enabled=True,
                         spawn_sims_during_zone_spin_up=False):
        if guest_list is None:
            guest_list = SituationGuestList()
        hire_cost = guest_list.get_hire_cost()
        reserved_funds = None
        if guest_list.host_sim is not None:
            reserved_funds = guest_list.host_sim.family_funds.try_remove(
                situation_type.cost() + hire_cost,
                Consts_pb2.TELEMETRY_EVENT_COST, guest_list.host_sim)
            if reserved_funds is None:
                return
            reserved_funds.apply()
        situation_id = id_generator.generate_object_id()
        self._send_create_situation_telemetry(situation_type, situation_id,
                                              guest_list, hire_cost, zone_id)
        if zone_id != 0 and services.current_zone().id != zone_id:
            return self._create_departing_seed_and_travel(
                situation_type,
                situation_id,
                guest_list,
                user_facing,
                duration_override,
                custom_init_writer,
                zone_id,
                scoring_enabled=scoring_enabled)
        situation_seed = SituationSeed(
            situation_type,
            SeedPurpose.NORMAL,
            situation_id,
            guest_list,
            user_facing=user_facing,
            duration_override=duration_override,
            scoring_enabled=scoring_enabled,
            spawn_sims_during_zone_spin_up=spawn_sims_during_zone_spin_up)
        if custom_init_writer is not None:
            situation_seed.setup_for_custom_init_params(custom_init_writer)
        return self.create_situation_from_seed(situation_seed)

    def create_visit_situation_for_unexpected(self, sim):
        duration_override = None
        if self._perf_test_cheat_enabled:
            duration_override = 0
        self.create_visit_situation(sim, duration_override=duration_override)

    def create_visit_situation(self,
                               sim,
                               duration_override=None,
                               visit_type_override=None):
        situation_id = None
        visit_type = visit_type_override if visit_type_override is not None else self.DEFAULT_VISIT_SITUATION
        if visit_type is not None:
            guest_list = situations.situation_guest_list.SituationGuestList(
                invite_only=True)
            guest_info = situations.situation_guest_list.SituationGuestInfo.construct_from_purpose(
                sim.id, visit_type.default_job(), situations.
                situation_guest_list.SituationInvitationPurpose.INVITED)
            guest_list.add_guest_info(guest_info)
            situation_id = self.create_situation(
                visit_type,
                guest_list=guest_list,
                user_facing=False,
                duration_override=duration_override)
        if situation_id is None:
            logger.error('Failed to create visit situation for sim: {}', sim)
            self.make_sim_leave(sim)
        return situation_id

    def create_situation_from_seed(self, seed):
        if not seed.allow_creation:
            return
        if seed.user_facing:
            situation = self.get_user_facing_situation()
            if situation is not None:
                self.destroy_situation_by_id(situation.id)
        if seed.situation_type.is_unique_situation:
            for situation in self.running_situations():
                while type(situation) is seed.situation_type:
                    return
        situation = seed.situation_type(seed)
        try:
            if seed.is_loadable:
                situation._destroy()
                return
            else:
                situation.start_situation()
        except Exception:
            logger.exception('Exception thrown while starting situation')
            situation._destroy()
            return
        self.add(situation)
        if situation.is_user_facing or situation.distribution_override:
            distributor.system.Distributor.instance().add_object(situation)
            self._added_to_distributor.add(situation)
            situation.on_added_to_distributor()
        return situation.id

    def _create_departing_seed_and_travel(self,
                                          situation_type,
                                          situation_id,
                                          guest_list=None,
                                          user_facing=True,
                                          duration_override=None,
                                          custom_init_writer=None,
                                          zone_id=0,
                                          scoring_enabled=True):
        traveling_sim = guest_list.get_traveler()
        if traveling_sim is None:
            logger.error(
                'No traveling sim available for creating departing seed for situation: {}.',
                situation_type)
            return
        if traveling_sim.client is None:
            logger.error(
                'No client on traveling sim: {} for for situation: {}.',
                traveling_sim, situation_type)
            return
        if traveling_sim.household is None:
            logger.error(
                'No household on traveling sim for for situation: {}.',
                situation_type)
            return
        situation_seed = SituationSeed(situation_type,
                                       SeedPurpose.TRAVEL,
                                       situation_id,
                                       guest_list,
                                       user_facing,
                                       duration_override,
                                       zone_id,
                                       scoring_enabled=scoring_enabled)
        if situation_seed is None:
            logger.error('Failed to create departing seed.for situation: {}.',
                         situation_type)
            return
        if custom_init_writer is not None:
            situation_seed.setup_for_custom_init_params(custom_init_writer)
        self._departing_situation_seed = situation_seed
        travel_info = protocolbuffers.InteractionOps_pb2.TravelSimsToZone()
        travel_info.zone_id = zone_id
        travel_info.sim_ids.append(traveling_sim.id)
        traveling_sim_ids = guest_list.get_other_travelers(traveling_sim)
        travel_info.sim_ids.extend(traveling_sim_ids)
        distributor.system.Distributor.instance().add_event(
            protocolbuffers.Consts_pb2.MSG_TRAVEL_SIMS_TO_ZONE, travel_info)
        services.game_clock_service().request_pause('Situation Travel')
        logger.debug('Travel seed creation time {}',
                     services.time_service().sim_now)
        logger.debug('Travel seed future time {}',
                     services.time_service().sim_future)
        return situation_id

    def _create_system_situations(self):
        self._leave_situation_id = 0
        for situation in self.running_situations():
            while type(situation) is self.DEFAULT_LEAVE_SITUATION:
                self._leave_situation_id = situation.id
                break
        if self._leave_situation_id == 0:
            self._leave_situation_id = self.create_situation(
                self.DEFAULT_LEAVE_SITUATION,
                user_facing=False,
                duration_override=0)

    @property
    def auto_manage_distributor(self):
        return False

    def call_on_remove(self, situation):
        super().call_on_remove(situation)
        self._callbacks.pop(situation.id, None)
        if situation in self._added_to_distributor:
            dist = distributor.system.Distributor.instance()
            dist.remove_object(situation)
            self._added_to_distributor.remove(situation)
            situation.on_removed_from_distributor()

    def is_distributed(self, situation):
        return situation in self._added_to_distributor

    def _request_destruction(self, situation):
        if self._delay_situation_destruction_ref_count == 0:
            return True
        self._situations_for_delayed_destruction.add(situation)
        return False

    def destroy_situation_by_id(self, situation_id):
        if situation_id in self:
            if situation_id == self._leave_situation_id:
                self._leave_situation_id = 0
            if situation_id == self._player_greeted_situation_id:
                self._player_greeted_situation_id = 0
            if situation_id == self._player_waiting_to_be_greeted_situation_id:
                self._player_waiting_to_be_greeted_situation_id = 0
            self.remove_id(situation_id)

    def destroy_all_situations(self, include_system=False):
        all_situations = tuple(self.values())
        for situation in all_situations:
            if include_system == False and situation.id == self._leave_situation_id:
                pass
            try:
                self.destroy_situation_by_id(situation.id)
            except Exception:
                logger.error(
                    'Error when destroying situation {}. You are probably screwed.',
                    situation)

    def register_for_callback(self, situation_id, situation_callback_option,
                              callback_fn):
        registrant = _CallbackRegistration(situation_callback_option,
                                           callback_fn)
        registrant_list = self._callbacks.setdefault(situation_id, [])
        registrant_list.append(registrant)

    def create_greeted_npc_visiting_npc_situation(self, npc_sim_info):
        services.current_zone().venue_service.venue.summon_npcs(
            (npc_sim_info, ),
            venues.venue_constants.NPCSummoningPurpose.PLAYER_BECOMES_GREETED)

    def create_greeted_player_visiting_npc_situation(self, sim=None):
        if sim is None:
            guest_list = situations.situation_guest_list.SituationGuestList()
        else:
            guest_list = situations.situation_guest_list.SituationGuestList(
                host_sim_id=sim.id)
        greeted_situation_type = services.current_zone(
        ).venue_service.venue.player_greeted_situation_type
        if greeted_situation_type is None:
            return
        self._player_greeted_situation_id = self.create_situation(
            greeted_situation_type, user_facing=False, guest_list=guest_list)

    def create_player_waiting_to_be_greeted_situation(self):
        self._player_waiting_to_be_greeted_situation_id = self.create_situation(
            services.current_zone(
            ).venue_service.venue.player_ungreeted_situation_type,
            user_facing=False)

    def _handle_player_greeting_situations_during_zone_spin_up(self):
        if self._zone_spin_up_player_greeted_status == GreetedStatus.NOT_APPLICABLE:
            return
        if self._zone_spin_up_player_greeted_status == GreetedStatus.GREETED:
            greeted_situation_type = services.current_zone(
            ).venue_service.venue.player_greeted_situation_type
            for situation in self.running_situations():
                while type(situation) is greeted_situation_type:
                    break
            self.create_greeted_player_visiting_npc_situation()
            return
        waiting_situation_type = services.current_zone(
        ).venue_service.venue.player_ungreeted_situation_type
        for situation in self.running_situations():
            while type(situation) is waiting_situation_type:
                break
        self.create_player_waiting_to_be_greeted_situation()

    def handle_npcs_during_zone_fixup(self):
        if services.game_clock_service(
        ).time_has_passed_in_world_since_zone_save() or services.current_zone(
        ).active_household_changed_between_save_and_load():
            sim_infos_to_fix_up = []
            for sim_info in services.sim_info_manager(
            ).get_sim_infos_saved_in_zone():
                while sim_info.is_npc and not sim_info.lives_here and sim_info.get_sim_instance(
                ) is not None:
                    sim_infos_to_fix_up.append(sim_info)
            if sim_infos_to_fix_up:
                logger.debug('Fixing up {} npcs during zone fixup',
                             len(sim_infos_to_fix_up),
                             owner='sscholl')
                services.current_zone().venue_service.venue.zone_fixup(
                    sim_infos_to_fix_up)

    def make_waiting_player_greeted(self, door_bell_ringing_sim=None):
        for situation in self.running_situations():
            situation._on_make_waiting_player_greeted(door_bell_ringing_sim)
        if self._player_greeted_situation_id == 0:
            self.create_greeted_player_visiting_npc_situation(
                door_bell_ringing_sim)

    def save(self,
             zone_data=None,
             open_street_data=None,
             save_slot_data=None,
             **kwargs):
        if zone_data is None:
            return
        SituationSeed.serialize_travel_seed_to_slot(
            save_slot_data, self._departing_situation_seed)
        zone_seeds = []
        street_seeds = []
        for situation in self.running_situations():
            seed = situation.save_situation()
            while seed is not None:
                if situation.situation_serialization_option == SituationSerializationOption.OPEN_STREETS:
                    street_seeds.append(seed)
                else:
                    zone_seeds.append(seed)
        SituationSeed.serialize_seeds_to_zone(zone_seeds, zone_data)
        SituationSeed.serialize_seeds_to_open_street(street_seeds,
                                                     open_street_data)

    def on_pre_spawning_sims(self):
        zone = services.current_zone()
        save_slot_proto = services.get_persistence_service(
        ).get_save_slot_proto_buff()
        seed = SituationSeed.deserialize_travel_seed_from_slot(save_slot_proto)
        if seed is not None:
            if zone.id != seed.zone_id:
                logger.debug(
                    'Travel situation :{} not loaded. Expected zone :{} but is on zone:{}',
                    seed.situation_type, seed.zone_id,
                    services.current_zone().id)
                seed.allow_creation = False
            else:
                time_since_travel_seed_created = services.time_service(
                ).sim_now - seed.create_time
                if time_since_travel_seed_created > date_and_time.TimeSpan.ZERO:
                    logger.debug(
                        'Not loading traveled situation :{} because time has passed {}',
                        seed.situation_type, time_since_travel_seed_created)
                    seed.allow_creation = False
        self._arriving_situation_seed = seed
        zone_proto = services.get_persistence_service().get_zone_proto_buff(
            zone.id)
        if zone_proto is not None:
            self._zone_seeds_for_zone_spinup = SituationSeed.deserialize_seeds_from_zone(
                zone_proto)
        for seed in self._zone_seeds_for_zone_spinup:
            while not seed.situation_type._should_seed_be_loaded(seed):
                seed.allow_creation = False
        open_street_proto = services.get_persistence_service(
        ).get_open_street_proto_buff(zone.open_street_id)
        if open_street_proto is not None:
            self._open_street_seeds_for_zone_spinup = SituationSeed.deserialize_seeds_from_open_street(
                open_street_proto)
        for seed in self._open_street_seeds_for_zone_spinup:
            while not seed.situation_type._should_seed_be_loaded(seed):
                seed.allow_creation = False
        self._zone_spin_up_player_greeted_status = self._determine_player_greeted_status_during_zone_spin_up(
        )

    def create_situations_during_zone_spin_up(self):
        for seed in self._zone_seeds_for_zone_spinup:
            self.create_situation_from_seed(seed)
        for seed in self._open_street_seeds_for_zone_spinup:
            self.create_situation_from_seed(seed)
        self._create_system_situations()
        if self._arriving_situation_seed is not None:
            self.create_situation_from_seed(self._arriving_situation_seed)
        self._handle_player_greeting_situations_during_zone_spin_up()
        self.handle_npcs_during_zone_fixup()

    def on_all_situations_created_during_zone_spin_up(self):
        self._bouncer.start()

    def get_sim_serialization_option(self, sim):
        result = sims.sim_info_types.SimSerializationOption.UNDECLARED
        for situation in self.get_situations_sim_is_in(sim):
            option = situation.situation_serialization_option
            if option == situations.situation_types.SituationSerializationOption.LOT:
                result = sims.sim_info_types.SimSerializationOption.LOT
                break
            else:
                while option == situations.situation_types.SituationSerializationOption.OPEN_STREETS:
                    result = sims.sim_info_types.SimSerializationOption.OPEN_STREETS
        return result

    def remove_sim_from_situation(self, sim, situation_id):
        situation = self.get(situation_id)
        if situation is None:
            return
        self._bouncer.remove_sim_from_situation(sim, situation)

    def on_reset(self, sim_ref):
        pass

    def on_sim_creation(self, sim):
        sim_data = self._sim_data.setdefault(sim.id,
                                             _SituationManagerSimData(sim.id))
        sim_data.set_created_time(services.time_service().sim_now)
        self._prune_sim_data()
        self._sim_being_created = sim
        if sim.id in self._debug_sims:
            self._debug_sims.discard(sim.id)
            if self._perf_test_cheat_enabled:
                self.create_visit_situation_for_unexpected(sim)
            else:
                services.current_zone().venue_service.venue.summon_npcs(
                    (sim.sim_info, ), NPCSummoningPurpose.DEFAULT)
        self._bouncer.on_sim_creation(sim)
        self._sim_being_created = None

    def get_situations_sim_is_in(self, sim):
        return [
            situation for situation in self.values()
            if situation._stage == SituationStage.RUNNING
        ]

    def is_user_facing_situation_running(self):
        for situation in self.values():
            while situation.is_user_facing:
                return True
        return False

    def get_user_facing_situation(self):
        for situation in self.values():
            while situation.is_user_facing:
                return situation

    def running_situations(self):
        return [
            obj for obj in self._objects.values()
            if obj._stage == SituationStage.RUNNING
        ]

    def is_situation_with_tags_running(self, tags):
        for situation in self.values():
            while situation._stage == SituationStage.RUNNING and situation.tags & tags:
                return True
        return False

    def user_ask_sim_to_leave_now_must_run(self, sim):
        if not sim.sim_info.is_npc or sim.sim_info.lives_here:
            return
        ask_to_leave = True
        for situation in self.get_situations_sim_is_in(sim):
            while not situation.on_ask_sim_to_leave(sim):
                ask_to_leave = False
                break
        if ask_to_leave:
            self.make_sim_leave_now_must_run(sim)

    def make_sim_leave_now_must_run(self,
                                    sim,
                                    super_speed_three_request=False):
        if services.current_zone().is_zone_shutting_down:
            return
        for situation in self.get_situations_sim_is_in(sim):
            while type(situation) is self.DEFAULT_LEAVE_NOW_MUST_RUN_SITUATION:
                return
        if super_speed_three_request:
            sim.add_buff(
                buff_type=self.SUPER_SPEED_THREE_REQUEST_BUFF.buff_type,
                buff_reason=self.SUPER_SPEED_THREE_REQUEST_BUFF.buff_reason)
        leave_now_type = self.DEFAULT_LEAVE_NOW_MUST_RUN_SITUATION
        guest_list = situations.situation_guest_list.SituationGuestList(
            invite_only=True)
        guest_info = situations.situation_guest_list.SituationGuestInfo(
            sim.id,
            leave_now_type.default_job(),
            RequestSpawningOption.CANNOT_SPAWN,
            BouncerRequestPriority.VIP,
            expectation_preference=True)
        guest_list.add_guest_info(guest_info)
        self.create_situation(leave_now_type,
                              guest_list=guest_list,
                              user_facing=False)

    def make_sim_leave(self, sim):
        leave_situation = self.get(self._leave_situation_id)
        if leave_situation is None:
            logger.error(
                'The leave situation is missing. Making the sim leave now must run.'
            )
            self.make_sim_leave_now_must_run(sim)
            return
        leave_situation.invite_sim_to_leave(sim)

    def expedite_leaving(self):
        leave_situation = self.get(self._leave_situation_id)
        if leave_situation is None:
            return
        for sim in leave_situation.all_sims_in_situation_gen():
            self.make_sim_leave_now_must_run(sim)

    def get_time_span_sim_has_been_on_lot(self, sim):
        sim_data = self._sim_data.get(sim.id)
        if sim_data is None:
            return
        if sim_data.created_time is None:
            return
        return services.time_service().sim_now - sim_data.created_time

    def get_remaining_blacklist_time_span(self, sim_id):
        sim_data = self._sim_data.get(sim_id)
        if sim_data is None:
            return date_and_time.TimeSpan.ZERO
        return sim_data.get_remaining_blacklisted_time_span()

    def get_auto_fill_blacklist(self):
        blacklist = set()
        for (sim_id, sim_data) in tuple(self._sim_data.items()):
            while sim_data.is_blacklisted:
                blacklist.add(sim_id)
        return blacklist

    def add_sim_to_auto_fill_blacklist(self, sim_id, blacklist_until=None):
        sim_data = self._sim_data.setdefault(sim_id,
                                             _SituationManagerSimData(sim_id))
        sim_data.blacklist(blacklist_until=blacklist_until)
        self._prune_sim_data()

    def _prune_sim_data(self):
        to_remove_ids = []
        for (sim_id, sim_data) in self._sim_data.items():
            while services.object_manager().get(
                    sim_id) is None and sim_data.is_blacklisted == False:
                to_remove_ids.append(sim_id)
        for sim_id in to_remove_ids:
            del self._sim_data[sim_id]

    def _get_callback_registrants(self, situation_id):
        return list(self._callbacks.get(situation_id, []))

    def _send_create_situation_telemetry(self, situation_type, situation_id,
                                         guest_list, hire_cost, zone_id):
        if hasattr(situation_type, 'guid64'):
            with telemetry_helper.begin_hook(
                    writer, TELEMETRY_HOOK_CREATE_SITUATION) as hook:
                hook.write_int('situ', situation_id)
                hook.write_int('host', guest_list.host_sim_id)
                hook.write_guid('type', situation_type.guid64)
                hook.write_bool('invi', guest_list.invite_only)
                hook.write_bool('hire', hire_cost)
                hook.write_bool(
                    'nzon', zone_id != 0
                    and services.current_zone().id != zone_id)
            sim_info_manager = services.sim_info_manager()
            if sim_info_manager is not None:
                while True:
                    for guest_infos in guest_list._job_type_to_guest_infos.values(
                    ):
                        for guest_info in guest_infos:
                            if guest_info.sim_id == 0:
                                pass
                            guest_sim = sim_info_manager.get(guest_info.sim_id)
                            if guest_sim is None:
                                pass
                            client = services.client_manager(
                            ).get_client_by_household_id(
                                guest_sim.household_id)
                            with telemetry_helper.begin_hook(
                                    writer, TELEMETRY_HOOK_GUEST) as hook:
                                hook.write_int('situ', situation_id)
                                if client is None:
                                    hook.write_int('npcg', guest_info.sim_id)
                                else:
                                    hook.write_int('pcgu', guest_info.sim_id)
                                    hook.write_guid('jobb',
                                                    guest_info.job_type.guid64)
Esempio n. 12
0
class SituationManager(DistributableObjectManager):
    DEFAULT_LEAVE_SITUATION = sims4.tuning.tunable.TunableReference(
        description=
        '\n                                            The situation type for the background leave situation.\n                                            It collects sims who are not in other situations and\n                                            asks them to leave periodically.\n                                            ',
        manager=services.get_instance_manager(sims4.resources.Types.SITUATION),
        class_restrictions=situations.complex.leave_situation.LeaveSituation)
    DEFAULT_LEAVE_NOW_MUST_RUN_SITUATION = sims4.tuning.tunable.TunableReference(
        description=
        '\n                                            The situation type that drives the sim off the lot pronto.\n                                            ',
        manager=services.get_instance_manager(sims4.resources.Types.SITUATION),
        class_restrictions=situations.complex.single_sim_leave_situation.
        SingleSimLeaveSituation)
    DEFAULT_VISIT_SITUATION = sims4.tuning.tunable.TunableReference(
        description=
        '\n                                            The default visit situation used when you ask someone to \n                                            hang out or invite them in.\n                                            ',
        manager=services.get_instance_manager(sims4.resources.Types.SITUATION))
    DEFAULT_TRAVEL_SITUATION = Situation.TunableReference(
        description=
        ' \n                                            The default situation for when you \n                                            are simply traveling with a group \n                                            of Sims.\n                                            '
    )
    LEAVE_INTERACTION_TAGS = TunableSet(
        description=
        '\n                The tags indicating leave lot interactions, but not \n                leave lot must run interactions.\n                These are used to determine if a leave lot interaction is running\n                or cancel one if it is.\n                ',
        tunable=TunableEnumWithFilter(tunable_type=tag.Tag,
                                      default=tag.Tag.INVALID,
                                      tuning_filter=FilterTag.EXPERT_MODE,
                                      filter_prefixes=tag.INTERACTION_PREFIX))
    SUPER_SPEED_THREE_REQUEST_BUFF = TunableBuffReference(
        description=
        "\n        The buff to apply to the Sim when we're trying to make them run the\n        leave situation from a super speed three request.\n        ",
        deferred=True)
    DEFAULT_PLAYER_PLANNED_DRAMA_NODE = sims4.tuning.tunable.TunableReference(
        description=
        '\n        The drama node that will be scheduled when a player plans an event for the future.\n        ',
        manager=services.get_instance_manager(
            sims4.resources.Types.DRAMA_NODE))
    _perf_test_cheat_enabled = False

    def __init__(self, manager_id=0):
        super().__init__(manager_id=manager_id)
        self._get_next_session_id = UniqueIdGenerator(1)
        self._added_to_distributor = set()
        self._callbacks = defaultdict(lambda: defaultdict(CallableList))
        self._departing_situation_seed = None
        self._arriving_situation_seed = None
        self._zone_seeds_for_zone_spinup = []
        self._open_street_seeds_for_zone_spinup = []
        self._debug_sims = set()
        self._leave_situation_id = 0
        self._player_greeted_situation_id = 0
        self._player_waiting_to_be_greeted_situation_id = 0
        self._sim_being_created = None
        self._sim_data = {}
        self._delay_situation_destruction_ref_count = 0
        self._situations_for_delayed_destruction = set()
        self._bouncer = None
        self._pause_handle = None
        self._zone_spin_up_greeted_complete = False

    @classproperty
    def save_error_code(cls):
        return persistence_error_types.ErrorCodes.SERVICE_SAVE_FAILED_SITUATION_MANAGER

    def start(self):
        self._bouncer = Bouncer()

    def stop(self):
        if self._pause_handle is not None:
            pause_handle = self._pause_handle
            self._pause_handle = None
            services.game_clock_service().remove_request(
                pause_handle, source=GameSpeedChangeSource.SITUATION)

    def destroy_situations_on_teardown(self):
        self.destroy_all_situations(include_system=True)
        self._sim_data.clear()
        self._bouncer.destroy()
        self._bouncer = None

    def reset(self, create_system_situations=True):
        self.destroy_all_situations(include_system=True)
        self._added_to_distributor.clear()
        self._callbacks.clear()
        self._bouncer.reset()
        if create_system_situations:
            self._create_system_situations()

    def update(self):
        if self._bouncer is not None:
            try:
                self._bouncer._update()
            except Exception:
                logger.exception('Exception while updating the Bouncer.')

    def enable_perf_cheat(self, enable=True):
        self._perf_test_cheat_enabled = enable
        self._bouncer.spawning_freeze(enable)
        self._bouncer.cap_cheat(enable)

    def get_all(self):
        return [
            obj for obj in self._objects.values()
            if obj._stage == SituationStage.RUNNING
        ]

    def get_new_situation_creation_session(self):
        return self._get_next_session_id()

    @property
    def bouncer(self):
        return self._bouncer

    @property
    def sim_being_created(self):
        return self._sim_being_created

    def add_debug_sim_id(self, sim_id):
        self._debug_sims.add(sim_id)

    def _determine_player_greeted_status_during_zone_spin_up(self):
        if not services.current_zone(
        ).venue_service.active_venue.requires_visitation_rights:
            return GreetedStatus.NOT_APPLICABLE
        active_household = services.active_household()
        if active_household is None:
            return GreetedStatus.NOT_APPLICABLE
        if active_household.considers_current_zone_its_residence():
            return GreetedStatus.NOT_APPLICABLE
        cur_status = GreetedStatus.WAITING_TO_BE_GREETED
        lot_seeds = list(self._zone_seeds_for_zone_spinup)
        if self._arriving_situation_seed is not None:
            lot_seeds.append(self._arriving_situation_seed)
        for seed in lot_seeds:
            status = seed.get_player_greeted_status()
            logger.debug('Player:{} :{}',
                         status,
                         seed.situation_type,
                         owner='sscholl')
            if status == GreetedStatus.GREETED:
                cur_status = status
                break
        return cur_status

    def get_npc_greeted_status_during_zone_fixup(self, sim_info):
        if not services.current_zone(
        ).venue_service.active_venue.requires_visitation_rights:
            return GreetedStatus.NOT_APPLICABLE
        if sim_info.lives_here:
            return GreetedStatus.NOT_APPLICABLE
        cur_status = GreetedStatus.NOT_APPLICABLE
        for seed in self._zone_seeds_for_zone_spinup:
            status = seed.get_npc_greeted_status(sim_info)
            logger.debug('NPC:{} :{} :{}',
                         sim_info,
                         status,
                         seed.situation_type,
                         owner='sscholl')
            if status == GreetedStatus.GREETED:
                cur_status = status
                break
            if status == GreetedStatus.WAITING_TO_BE_GREETED:
                cur_status = status
        return cur_status

    def is_player_greeted(self):
        return self._player_greeted_situation_id != 0

    def is_player_waiting_to_be_greeted(self):
        return self._player_waiting_to_be_greeted_situation_id != 0 and self._player_greeted_situation_id == 0

    def create_situation(self,
                         situation_type,
                         guest_list=None,
                         user_facing=True,
                         duration_override=None,
                         custom_init_writer=None,
                         zone_id=0,
                         scoring_enabled=True,
                         spawn_sims_during_zone_spin_up=False,
                         creation_source=None,
                         travel_request_kwargs=frozendict(),
                         linked_sim_id=GLOBAL_SITUATION_LINKED_SIM_ID,
                         scheduled_time=None,
                         **extra_kwargs):
        zone = services.current_zone()
        if zone.is_zone_shutting_down:
            return
        current_zone_id = services.current_zone_id()
        situation_type = services.narrative_service(
        ).get_possible_replacement_situation(situation_type)
        if services.get_zone_modifier_service().is_situation_prohibited(
                zone_id if zone_id else current_zone_id, situation_type):
            return
        if guest_list is None:
            guest_list = SituationGuestList()
        hire_cost = guest_list.get_hire_cost()
        host_sim_info = guest_list.host_sim_info
        if host_sim_info is not None and not host_sim_info.household.funds.try_remove(
                situation_type.cost() + hire_cost,
                Consts_pb2.TELEMETRY_EVENT_COST, host_sim_info):
            return
        situation_id = id_generator.generate_object_id()
        self._send_create_situation_telemetry(situation_type, situation_id,
                                              guest_list, hire_cost, zone_id)
        if zone_id and zone_id != current_zone_id and scheduled_time is None:
            return self._create_situation_and_travel(
                situation_type,
                situation_id,
                guest_list,
                user_facing,
                duration_override,
                custom_init_writer,
                zone_id,
                scoring_enabled=scoring_enabled,
                creation_source=creation_source,
                linked_sim_id=linked_sim_id,
                travel_request_kwargs=travel_request_kwargs)
        situation_seed = SituationSeed(
            situation_type,
            SeedPurpose.NORMAL,
            situation_id,
            guest_list,
            user_facing=user_facing,
            duration_override=duration_override,
            zone_id=zone_id,
            scoring_enabled=scoring_enabled,
            spawn_sims_during_zone_spin_up=spawn_sims_during_zone_spin_up,
            creation_source=creation_source,
            linked_sim_id=linked_sim_id,
            **extra_kwargs)
        if custom_init_writer is not None:
            situation_seed.setup_for_custom_init_params(custom_init_writer)
        return_id = None
        if scheduled_time is not None:
            uid = services.drama_scheduler_service().schedule_node(
                self.DEFAULT_PLAYER_PLANNED_DRAMA_NODE,
                SingleSimResolver(guest_list.host_sim.sim_info),
                specific_time=scheduled_time,
                situation_seed=situation_seed)
            return_id = situation_id if uid is not None else None
        else:
            return_id = self.create_situation_from_seed(situation_seed)
        return return_id

    def _create_situation_and_travel(self, situation_type, *args,
                                     travel_request_kwargs, **kwargs):
        travel_fn = lambda: self._create_departing_seed_and_travel(
            situation_type, *args, **kwargs)
        travel_request_situtaion = None
        for situation in self.get_user_facing_situations_gen():
            if travel_request_situtaion is None:
                travel_request_situtaion = situation
            elif situation.travel_request_behavior.restrict > travel_request_situtaion.travel_request_behavior.restrict:
                travel_request_situtaion = situation
        if travel_request_situtaion is not None:
            return travel_request_situtaion.travel_request_behavior(
                travel_request_situtaion, situation_type, travel_fn,
                **travel_request_kwargs)
        return travel_fn()

    def create_visit_situation_for_unexpected(self, sim):
        duration_override = None
        if self._perf_test_cheat_enabled:
            duration_override = 0
        self.create_visit_situation(sim, duration_override=duration_override)

    def create_visit_situation(self,
                               sim,
                               duration_override=None,
                               visit_type_override=None):
        situation_id = None
        visit_type = visit_type_override if visit_type_override is not None else self.DEFAULT_VISIT_SITUATION
        if visit_type is not None:
            guest_list = situations.situation_guest_list.SituationGuestList(
                invite_only=True)
            guest_info = situations.situation_guest_list.SituationGuestInfo.construct_from_purpose(
                sim.id, visit_type.default_job(), situations.
                situation_guest_list.SituationInvitationPurpose.INVITED)
            guest_list.add_guest_info(guest_info)
            situation_id = self.create_situation(
                visit_type,
                guest_list=guest_list,
                user_facing=False,
                duration_override=duration_override)
        if situation_id is None:
            logger.error('Failed to create visit situation for sim: {}', sim)
            self.make_sim_leave(sim)
        return situation_id

    def create_situation_from_seed(self, seed):
        if not seed.allow_creation:
            return
        if seed.user_facing:
            for situation in tuple(self.get_user_facing_situations_gen()):
                if seed.linked_sim_id == GLOBAL_SITUATION_LINKED_SIM_ID:
                    if situation.linked_sim_id == GLOBAL_SITUATION_LINKED_SIM_ID:
                        self.destroy_situation_by_id(situation.id)
        if seed.situation_type.is_unique_situation:
            for situation in self.running_situations():
                if type(situation) is seed.situation_type:
                    return
        try:
            situation = seed.situation_type(seed)
        except ValueError:
            logger.exception('Failed to initialize situation: {}',
                             seed.situation_type)
            return
        try:
            if seed.is_loadable:
                if not situation.load_situation():
                    situation._destroy()
                    return
            else:
                situation.start_situation()
        except Exception:
            logger.exception('Exception thrown while starting situation')
            situation._destroy()
            return
        if situation._stage == SituationStage.DYING:
            return
        self.add(situation)
        if situation.is_user_facing or situation.distribution_override:
            distributor.system.Distributor.instance().add_object(situation)
            self._added_to_distributor.add(situation)
            situation.on_added_to_distributor()
        return situation.id

    def travel_existing_situation(self, situation, zone_id):
        seed = situation.save_situation()
        seed.zone_id = zone_id
        self.travel_seed(seed)
        situation._self_destruct()

    def _create_departing_seed_and_travel(
            self,
            situation_type,
            situation_id,
            guest_list=None,
            user_facing=True,
            duration_override=None,
            custom_init_writer=None,
            zone_id=0,
            scoring_enabled=True,
            creation_source=None,
            linked_sim_id=GLOBAL_SITUATION_LINKED_SIM_ID):
        current_zone = services.current_zone()
        if current_zone is not None and not current_zone.is_zone_running:
            logger.error(
                'Unable to travel during spin-up: {}. A travel interaction was save/loaded, which is incorrect. Make it one-shot or non-saveable.',
                situation_type)
            return
        traveling_sim = guest_list.get_traveler()
        if traveling_sim is None:
            logger.error(
                'No traveling Sim available for creating departing seed for situation: {}.',
                situation_type)
            return
        if traveling_sim.client is None:
            logger.error(
                'No client on traveling Sim: {} for for situation: {}.',
                traveling_sim, situation_type)
            return
        if traveling_sim.household is None:
            logger.error(
                'No household on traveling Sim for for situation: {}.',
                situation_type)
            return
        situation_seed = SituationSeed(situation_type,
                                       SeedPurpose.TRAVEL,
                                       situation_id,
                                       guest_list,
                                       user_facing,
                                       duration_override,
                                       zone_id,
                                       scoring_enabled=scoring_enabled,
                                       creation_source=creation_source,
                                       linked_sim_id=linked_sim_id)
        if situation_seed is None:
            logger.error('Failed to create departing seed for situation: {}.',
                         situation_type)
            return
        if custom_init_writer is not None:
            situation_seed.setup_for_custom_init_params(custom_init_writer)
        return self.travel_seed(situation_seed)

    def travel_seed(self, seed):
        self._departing_situation_seed = seed
        traveling_sim = seed.guest_list.get_traveler()
        travel_info = protocolbuffers.InteractionOps_pb2.TravelSimsToZone()
        travel_info.zone_id = seed.zone_id
        travel_info.sim_ids.append(traveling_sim.id)
        traveling_sim_ids = seed.guest_list.get_other_travelers(traveling_sim)
        travel_info.sim_ids.extend(traveling_sim_ids)
        distributor.system.Distributor.instance().add_event(
            protocolbuffers.Consts_pb2.MSG_TRAVEL_SIMS_TO_ZONE, travel_info)
        if self._pause_handle is None:
            self._pause_handle = services.game_clock_service().push_speed(
                ClockSpeedMode.PAUSED,
                reason='Situation Travel',
                source=GameSpeedChangeSource.SITUATION)
        logger.debug('Travel seed now time {}',
                     services.time_service().sim_now)
        logger.debug('Travel seed future time {}',
                     services.time_service().sim_future)
        return seed.situation_id

    def _create_system_situations(self):
        self._leave_situation_id = 0
        for situation in self.running_situations():
            if type(situation) is self.DEFAULT_LEAVE_SITUATION:
                self._leave_situation_id = situation.id
                break
        if self._leave_situation_id == 0:
            self._leave_situation_id = self.create_situation(
                self.DEFAULT_LEAVE_SITUATION,
                user_facing=False,
                duration_override=0)

    @property
    def auto_manage_distributor(self):
        return False

    def call_on_remove(self, situation):
        super().call_on_remove(situation)
        self._callbacks.pop(situation.id, None)
        if situation in self._added_to_distributor:
            dist = distributor.system.Distributor.instance()
            dist.remove_object(situation)
            self._added_to_distributor.remove(situation)
            situation.on_removed_from_distributor()

    def is_distributed(self, situation):
        return situation in self._added_to_distributor

    def _request_destruction(self, situation):
        if self._delay_situation_destruction_ref_count == 0:
            return True
        self._situations_for_delayed_destruction.add(situation)
        return False

    def pre_destroy_situation_by_id(self, situation_id):
        situation = self.get(situation_id)
        if situation is not None:
            situation.pre_destroy()

    def destroy_situation_by_id(self, situation_id):
        if situation_id in self:
            if situation_id == self._leave_situation_id:
                self._leave_situation_id = 0
            if situation_id == self._player_greeted_situation_id:
                self._player_greeted_situation_id = 0
            if situation_id == self._player_waiting_to_be_greeted_situation_id:
                self._player_waiting_to_be_greeted_situation_id = 0
            self.remove_id(situation_id)

    def destroy_all_situations(self, include_system=False):
        all_situations = tuple(self.values())
        for situation in all_situations:
            if include_system == False and situation.id == self._leave_situation_id:
                continue
            try:
                self.destroy_situation_by_id(situation.id)
            except Exception:
                logger.error(
                    'Error when destroying situation {}. You are probably screwed.',
                    situation)

    def register_for_callback(self, situation_id, situation_callback_option,
                              callback_fn):
        if situation_id not in self:
            logger.error(
                "Failed to register situation callback. Situation doesn't exist. {}, {}, {}",
                situation_id,
                situation_callback_option,
                callback_fn,
                owner='rmccord')
            return
        callable_list = self._callbacks[situation_id][
            situation_callback_option]
        if callback_fn not in callable_list:
            callable_list.append(callback_fn)
        self._callbacks[situation_id][
            situation_callback_option] = callable_list

    def unregister_callback(self, situation_id, situation_callback_option,
                            callback_fn):
        if situation_id not in self:
            return
        callable_list = self._callbacks[situation_id][
            situation_callback_option]
        if callback_fn in callable_list:
            callable_list.remove(callback_fn)
        self._callbacks[situation_id][
            situation_callback_option] = callable_list

    def create_greeted_npc_visiting_npc_situation(self, npc_sim_info):
        services.current_zone().venue_service.active_venue.summon_npcs(
            (npc_sim_info, ),
            venues.venue_constants.NPCSummoningPurpose.PLAYER_BECOMES_GREETED)

    def _create_greeted_player_visiting_npc_situation(self, sim=None):
        if sim is None:
            guest_list = situations.situation_guest_list.SituationGuestList()
        else:
            guest_list = situations.situation_guest_list.SituationGuestList(
                host_sim_id=sim.id)
        greeted_situation_type = services.current_zone(
        ).venue_service.active_venue.player_greeted_situation_type
        if greeted_situation_type is None:
            return
        self._player_greeted_situation_id = self.create_situation(
            greeted_situation_type, user_facing=False, guest_list=guest_list)

    def _create_player_waiting_to_be_greeted_situation(self):
        self._player_waiting_to_be_greeted_situation_id = self.create_situation(
            services.current_zone(
            ).venue_service.active_venue.player_ungreeted_situation_type,
            user_facing=False)

    def make_player_waiting_to_be_greeted_during_zone_spin_up(self):
        waiting_situation_type = services.current_zone(
        ).venue_service.active_venue.player_ungreeted_situation_type
        for situation in self.running_situations():
            if type(situation) is waiting_situation_type:
                self._player_waiting_to_be_greeted_situation_id = situation.id
                break
        else:
            self._create_player_waiting_to_be_greeted_situation()

    def make_player_greeted_during_zone_spin_up(self):
        greeted_situation_type = services.current_zone(
        ).venue_service.active_venue.player_greeted_situation_type
        for situation in self.running_situations():
            if type(situation) is greeted_situation_type:
                self._player_greeted_situation_id = situation.id
                break
        else:
            self._create_greeted_player_visiting_npc_situation()

    def destroy_player_waiting_to_be_greeted_situation(self):
        if self._player_waiting_to_be_greeted_situation_id is 0:
            return
        situation = self.get(self._player_waiting_to_be_greeted_situation_id)
        if situation is None:
            return
        situation._self_destruct()
        self._player_waiting_to_be_greeted_situation_id = 0

    def make_waiting_player_greeted(self, door_bell_ringing_sim=None):
        for situation in self.running_situations():
            situation._on_make_waiting_player_greeted(door_bell_ringing_sim)
        if self._player_greeted_situation_id == 0:
            self._create_greeted_player_visiting_npc_situation(
                door_bell_ringing_sim)

    def get_situation_by_type(self, situation_type):
        for situation in self.running_situations():
            if type(situation) is situation_type:
                return situation

    def get_situations_by_type(self, *situation_types):
        found_situations = []
        for situation in self.running_situations():
            if isinstance(situation, situation_types):
                found_situations.append(situation)
        return found_situations

    def get_situations_by_tags(self, situation_tags):
        found_situations = []
        for situation in self.running_situations():
            if situation.tags & situation_tags:
                found_situations.append(situation)
        return found_situations

    def is_situation_running(self, situation_type):
        return any(
            isinstance(situation, situation_type)
            for situation in self.running_situations())

    def disable_save_to_situation_manager(self, situation_id):
        situation = self.get(situation_id)
        if situation is not None:
            situation.save_to_situation_manager = False

    def save(self,
             zone_data=None,
             open_street_data=None,
             save_slot_data=None,
             **kwargs):
        if zone_data is None:
            return
        zone = services.current_zone()
        if zone.venue_service.build_buy_edit_mode:
            return self._save_for_edit_mode(zone_data=zone_data,
                                            open_street_data=open_street_data,
                                            save_slot_data=save_slot_data)
        SituationSeed.serialize_travel_seed_to_slot(
            save_slot_data, self._departing_situation_seed)
        zone_seeds = []
        street_seeds = []
        holiday_seeds = []
        for situation in self.running_situations():
            if not situation.save_to_situation_manager:
                continue
            seed = situation.save_situation()
            if seed is not None:
                if situation.situation_serialization_option == SituationSerializationOption.OPEN_STREETS:
                    street_seeds.append(seed)
                elif situation.situation_serialization_option == SituationSerializationOption.LOT:
                    zone_seeds.append(seed)
                else:
                    holiday_seeds.append(seed)
        SituationSeed.serialize_seeds_to_zone(zone_seeds=zone_seeds,
                                              zone_data_msg=zone_data,
                                              blacklist_data=self._sim_data)
        SituationSeed.serialize_seeds_to_open_street(
            open_street_seeds=street_seeds,
            open_street_data_msg=open_street_data)
        active_household = services.active_household()
        if active_household is not None:
            active_household.holiday_tracker.set_holiday_situation_seeds(
                holiday_seeds)

    def _save_for_edit_mode(self,
                            zone_data=None,
                            open_street_data=None,
                            save_slot_data=None):
        SituationSeed.serialize_travel_seed_to_slot(
            save_slot_data, self._arriving_situation_seed)
        SituationSeed.serialize_seeds_to_zone(
            zone_seeds=self._zone_seeds_for_zone_spinup,
            zone_data_msg=zone_data,
            blacklist_data=self._sim_data)
        SituationSeed.serialize_seeds_to_open_street(
            open_street_seeds=self._open_street_seeds_for_zone_spinup,
            open_street_data_msg=open_street_data)

    def spin_up_for_edit_mode(self):
        self.create_seeds_during_zone_spin_up()

    def load(self, zone_data=None):
        if zone_data is None:
            return
        for blacklist_data in zone_data.gameplay_zone_data.situations_data.blacklist_data:
            sim_id = blacklist_data.sim_id
            sim_data = self._sim_data.setdefault(
                sim_id, _SituationManagerSimData(sim_id))
            sim_data.load(blacklist_data)

    def create_seeds_during_zone_spin_up(self):
        zone = services.current_zone()
        save_slot_proto = services.get_persistence_service(
        ).get_save_slot_proto_buff()
        self._arriving_situation_seed = SituationSeed.deserialize_travel_seed_from_slot(
            save_slot_proto)
        zone_proto = services.get_persistence_service().get_zone_proto_buff(
            zone.id)
        if zone_proto is not None:
            self._zone_seeds_for_zone_spinup = SituationSeed.deserialize_seeds_from_zone(
                zone_proto)
        open_street_proto = services.get_persistence_service(
        ).get_open_street_proto_buff(zone.open_street_id)
        if open_street_proto is not None:
            self._open_street_seeds_for_zone_spinup = SituationSeed.deserialize_seeds_from_open_street(
                open_street_proto)

    def get_arriving_seed_during_zone_spin(self):
        return self._arriving_situation_seed

    def get_zone_persisted_seeds_during_zone_spin_up(self):
        return list(self._zone_seeds_for_zone_spinup)

    def get_open_street_persisted_seeds_during_zone_spin_up(self):
        return list(self._open_street_seeds_for_zone_spinup)

    def create_situations_during_zone_spin_up(self):
        for seed in self._zone_seeds_for_zone_spinup:
            self.create_situation_from_seed(seed)
        for seed in self._open_street_seeds_for_zone_spinup:
            self.create_situation_from_seed(seed)
        self._create_system_situations()
        if self._arriving_situation_seed is not None:
            arrived_id = self.create_situation_from_seed(
                self._arriving_situation_seed)
            situation = self.get(arrived_id)
            if situation is not None:
                situation.on_arrived()

    def on_all_situations_created_during_zone_spin_up(self):
        self._bouncer.request_all_sims_during_zone_spin_up()

    def on_all_sims_spawned_during_zone_spin_up(self):
        self._bouncer.assign_all_sims_during_zone_spin_up()
        for situation in self.running_situations():
            if situation.should_time_jump():
                situation.on_time_jump()

    def on_hit_their_marks_during_zone_spin_up(self):
        self._bouncer.start_full_operations()

    def make_situation_seed_zone_director_requests(self):
        venue_service = services.current_zone().venue_service
        for seed in itertools.chain((self._arriving_situation_seed, ),
                                    self._zone_seeds_for_zone_spinup,
                                    self._open_street_seeds_for_zone_spinup):
            if seed is None:
                continue
            (zone_director,
             request_type) = seed.situation_type.get_zone_director_request()
            if not zone_director is None:
                if request_type is None:
                    continue
                if seed.is_loadable and not seed.situation_type.should_seed_be_loaded(
                        seed):
                    continue
                preserve_state = seed.is_loadable
                venue_service.request_zone_director(
                    zone_director, request_type, preserve_state=preserve_state)

    def get_sim_serialization_option(self, sim):
        result = sims.sim_info_types.SimSerializationOption.UNDECLARED
        for situation in self.get_situations_sim_is_in(sim):
            option = situation.situation_serialization_option
            if option == situations.situation_types.SituationSerializationOption.LOT:
                result = sims.sim_info_types.SimSerializationOption.LOT
                break
            elif option == situations.situation_types.SituationSerializationOption.OPEN_STREETS:
                result = sims.sim_info_types.SimSerializationOption.OPEN_STREETS
        return result

    def remove_sim_from_situation(self, sim, situation_id):
        situation = self.get(situation_id)
        if situation is None:
            return
        self._bouncer.remove_sim_from_situation(sim, situation)

    def on_sim_reset(self, sim):
        for situation in self.running_situations():
            if situation.is_sim_in_situation(sim):
                situation.on_sim_reset(sim)

    def on_begin_sim_creation_notification(self, sim):
        sim_data = self._sim_data.setdefault(sim.id,
                                             _SituationManagerSimData(sim.id))
        sim_data.set_created_time(services.time_service().sim_now)
        self._prune_sim_data()
        self._sim_being_created = sim

    def on_end_sim_creation_notification(self, sim):
        if sim.id in self._debug_sims:
            self._debug_sims.discard(sim.id)
            if self._perf_test_cheat_enabled:
                self.create_visit_situation_for_unexpected(sim)
            else:
                services.current_zone().venue_service.active_venue.summon_npcs(
                    (sim.sim_info, ), NPCSummoningPurpose.DEFAULT)
        self._bouncer._on_end_sim_creation_notification(sim)
        self._sim_being_created = None

    def get_situations_sim_is_in(self, sim):
        return [
            situation for situation in self.values()
            if situation.is_sim_in_situation(sim)
            if situation._stage == SituationStage.RUNNING
        ]

    def get_situations_sim_is_in_by_tag(self, sim, tag):
        return [
            situation for situation in self.get_situations_sim_is_in(sim)
            if tag in situation.tags
        ]

    def is_user_facing_situation_running(self, global_user_facing_only=False):
        for situation in self.values():
            if situation.is_user_facing:
                if not global_user_facing_only:
                    return True
                if situation.linked_sim_id == GLOBAL_SITUATION_LINKED_SIM_ID:
                    return True
        return False

    def get_user_facing_situations_gen(self):
        for situation in self.values():
            if situation.is_user_facing:
                yield situation

    def running_situations(self):
        return [
            obj for obj in self._objects.values()
            if obj._stage == SituationStage.RUNNING
        ]

    def is_situation_with_tags_running(self, tags):
        for situation in self.values():
            if situation._stage == SituationStage.RUNNING:
                if situation.tags & tags:
                    return True
        return False

    def user_ask_sim_to_leave_now_must_run(self, sim):
        if not sim.sim_info.is_npc or sim.sim_info.lives_here:
            return
        ask_to_leave = True
        for situation in self.get_situations_sim_is_in(sim):
            if not situation.on_ask_sim_to_leave(sim):
                ask_to_leave = False
                break
        if ask_to_leave:
            self.make_sim_leave_now_must_run(sim)

    def make_sim_leave_now_must_run(self, sim):
        if services.current_zone().is_zone_shutting_down:
            return
        for situation in self.get_situations_sim_is_in(sim):
            if type(situation) is self.DEFAULT_LEAVE_NOW_MUST_RUN_SITUATION:
                return
        leave_now_type = self.DEFAULT_LEAVE_NOW_MUST_RUN_SITUATION
        guest_list = situations.situation_guest_list.SituationGuestList(
            invite_only=True)
        guest_info = situations.situation_guest_list.SituationGuestInfo(
            sim.id,
            leave_now_type.default_job(),
            RequestSpawningOption.CANNOT_SPAWN,
            BouncerRequestPriority.EVENT_VIP,
            expectation_preference=True)
        guest_list.add_guest_info(guest_info)
        self.create_situation(leave_now_type,
                              guest_list=guest_list,
                              user_facing=False)

    def is_sim_ss3_safe(self, sim):
        for situation in self.get_situations_sim_is_in(sim):
            if not situation.should_send_on_lot_home_in_super_speed_3:
                return False
        return True

    def ss3_make_all_npcs_leave_now(self):
        sim_info_manager = services.sim_info_manager()
        current_zone_id = services.current_zone_id()
        for sim in sim_info_manager.instanced_sims_gen():
            if not sim.is_npc:
                continue
            if sim.is_on_active_lot() and not self.is_sim_ss3_safe(sim):
                continue
            if sim.sim_info.vacation_or_home_zone_id == current_zone_id:
                continue
            sim.add_buff(
                buff_type=self.SUPER_SPEED_THREE_REQUEST_BUFF.buff_type,
                buff_reason=self.SUPER_SPEED_THREE_REQUEST_BUFF.buff_reason)
            self.make_sim_leave_now_must_run(sim)

    def make_sim_leave(self, sim):
        leave_situation = self.get(self._leave_situation_id)
        if leave_situation is None:
            logger.error(
                'The leave situation is missing. Making the sim leave now must run.'
            )
            self.make_sim_leave_now_must_run(sim)
            return
        leave_situation.invite_sim_to_leave(sim)

    def expedite_leaving(self):
        leave_situation = self.get(self._leave_situation_id)
        if leave_situation is None:
            return
        for sim in leave_situation.all_sims_in_situation_gen():
            self.make_sim_leave_now_must_run(sim)

    def get_time_span_sim_has_been_on_lot(self, sim):
        sim_data = self._sim_data.get(sim.id)
        if sim_data is None:
            return
        if sim_data.created_time is None:
            return
        return services.time_service().sim_now - sim_data.created_time

    def get_blacklist_info(self, sim_id):
        sim_data = self._sim_data.get(sim_id)
        if sim_data is None:
            return
        return sim_data.get_blacklist_info()

    def get_auto_fill_blacklist(self, sim_job=None):
        blacklist = set()
        for (sim_id, sim_data) in tuple(self._sim_data.items()):
            if sim_data.is_blacklisted(sim_job=sim_job):
                blacklist.add(sim_id)
        return blacklist

    def add_sim_to_auto_fill_blacklist(self,
                                       sim_id,
                                       sim_job=None,
                                       blacklist_all_jobs_time=None):
        sim_data = self._sim_data.setdefault(sim_id,
                                             _SituationManagerSimData(sim_id))
        sim_data.blacklist(sim_job,
                           blacklist_all_jobs_time=blacklist_all_jobs_time)
        self._prune_sim_data()

    def remove_sim_from_auto_fill_blacklist(self, sim_id, sim_job=None):
        sim_data = self._sim_data.get(sim_id)
        if sim_data is not None:
            sim_data.whitelist(sim_job=sim_job)
        self._prune_sim_data()

    def send_situation_start_ui(self,
                                actor,
                                target=None,
                                situations_available=None,
                                creation_time=None):
        msg = Situations_pb2.SituationPrepare()
        msg.situation_session_id = self.get_new_situation_creation_session()
        msg.creation_time = creation_time if creation_time is not None else 0
        msg.sim_id = actor.id
        if target is not None:
            msg.is_targeted = True
            msg.target_id = target.id
        if situations_available is not None:
            for situation in situations_available:
                msg.situation_resource_id.append(situation.guid64)
        shared_messages.add_message_if_selectable(
            actor, Consts_pb2.MSG_SITUATION_PREPARE, msg, True)

    def _prune_sim_data(self):
        to_remove_ids = []
        for (sim_id, sim_data) in self._sim_data.items():
            sim_info = services.sim_info_manager().get(sim_id)
            if not sim_info is None:
                if not sim_info.is_instanced(
                        allow_hidden_flags=ALL_HIDDEN_REASONS):
                    if sim_data.is_blacklisted == False:
                        to_remove_ids.append(sim_id)
            if sim_data.is_blacklisted == False:
                to_remove_ids.append(sim_id)
        for sim_id in to_remove_ids:
            del self._sim_data[sim_id]

    def _issue_callback(self, situation_id, callback_option, data):
        self._callbacks[situation_id][callback_option](situation_id,
                                                       callback_option, data)

    def _send_create_situation_telemetry(self, situation_type, situation_id,
                                         guest_list, hire_cost, zone_id):
        if hasattr(situation_type, 'guid64'):
            with telemetry_helper.begin_hook(
                    writer, TELEMETRY_HOOK_CREATE_SITUATION) as hook:
                hook.write_int('situ', situation_id)
                hook.write_int('host', guest_list.host_sim_id)
                hook.write_guid('type', situation_type.guid64)
                hook.write_bool('invi', guest_list.invite_only)
                hook.write_bool('hire', hire_cost)
                hook.write_bool(
                    'nzon', zone_id != 0
                    and services.current_zone().id != zone_id)
            sim_info_manager = services.sim_info_manager()
            if sim_info_manager is not None:
                for guest_infos in guest_list._job_type_to_guest_infos.values(
                ):
                    for guest_info in guest_infos:
                        if guest_info.sim_id == 0:
                            continue
                        guest_sim = sim_info_manager.get(guest_info.sim_id)
                        if guest_sim is None:
                            continue
                        client = services.client_manager(
                        ).get_client_by_household_id(guest_sim.household_id)
                        with telemetry_helper.begin_hook(
                                writer, TELEMETRY_HOOK_GUEST) as hook:
                            hook.write_int('situ', situation_id)
                            hook.write_guid('type', situation_type.guid64)
                            if client is None:
                                hook.write_int('npcg', guest_info.sim_id)
                            else:
                                hook.write_int('pcgu', guest_info.sim_id)
                                hook.write_guid('jobb',
                                                guest_info.job_type.guid64)
class ZoneDirectorApb(CareerEventZoneDirectorProxy):
    INSTANCE_TUNABLES = {'detective_career': DetectiveCareer.TunableReference(description='\n            The career that we want to use to spawn the criminal.\n            ', tuning_group=GroupNames.CAREER), 'apb_situation': Situation.TunableReference(description='\n            The situation controlling the APB. This will manage the criminal Sim\n            as well as all the decoys.\n            ', tuning_group=GroupNames.CAREER), 'apb_neutral_situation': Situation.TunableReference(description='\n            The situation controlling all Sims in the zone, including Sims in\n            the APB situation.\n            ', tuning_group=GroupNames.CAREER), 'apb_situation_job_detective': SituationJob.TunableReference(description='\n            The job that the detective is put into for the duration of the APB.\n            ', tuning_group=GroupNames.CAREER), 'apb_situation_job_decoy': SituationJob.TunableReference(description='\n            The job that the decoys are put into for the duration of the APB.\n            ', tuning_group=GroupNames.CAREER), 'apb_situation_job_criminal': SituationJob.TunableReference(description='\n            The job that the criminal is put into for the duration of the APB.\n            ', tuning_group=GroupNames.CAREER)}

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

    def create_situations_during_zone_spin_up(self):
        sim_info = self._career_event.sim_info
        career = sim_info.careers.get(self.detective_career.guid64)
        if career is not None:
            situation_manager = services.get_zone_situation_manager()
            situation_manager.create_situation(self.apb_neutral_situation, user_facing=False, creation_source=self.instance_name)
            guest_list = SituationGuestList(invite_only=True, filter_requesting_sim_id=sim_info.sim_id)
            if not career.active_criminal_sim_id:
                career.create_criminal_fixup()
            guest_list.add_guest_info(SituationGuestInfo(career.active_criminal_sim_id, self.apb_situation_job_criminal, RequestSpawningOption.DONT_CARE, BouncerRequestPriority.EVENT_VIP))
            guest_list.add_guest_info(SituationGuestInfo(sim_info.sim_id, self.apb_situation_job_detective, RequestSpawningOption.DONT_CARE, BouncerRequestPriority.EVENT_VIP))
            decoy_sim_ids = career.get_decoy_sim_ids_for_apb(persisted_sim_ids=self._persisted_decoy_sim_ids)
            for decoy in decoy_sim_ids:
                guest_list.add_guest_info(SituationGuestInfo(decoy, self.apb_situation_job_decoy, RequestSpawningOption.DONT_CARE, BouncerRequestPriority.EVENT_VIP))
            self._persisted_decoy_sim_ids = None
            self._apb_situation_id = situation_manager.create_situation(self.apb_situation, guest_list=guest_list, spawn_sims_during_zone_spin_up=True, user_facing=False, creation_source=self.instance_name)
            with telemetry_helper.begin_hook(detective_apb_telemetry_writer, TELEMETRY_HOOK_APB_CALL, sim_info=sim_info) as hook:
                hook.write_int(TELEMETRY_CLUES_FOUND, len(career.get_discovered_clues()))
        return super().create_situations_during_zone_spin_up()

    def _load_custom_zone_director(self, zone_director_proto, reader):
        super()._load_custom_zone_director(zone_director_proto, reader)
        if reader is not None:
            self._persisted_decoy_sim_ids = reader.read_uint64s(DECOY_SIM_IDS, ())

    def _save_custom_zone_director(self, zone_director_proto, writer):
        super()._save_custom_zone_director(zone_director_proto, writer)
        situation_manager = services.get_zone_situation_manager()
        apb_situation = situation_manager.get(self._apb_situation_id)
        if apb_situation is not None:
            sim_ids = (sim.id for sim in apb_situation.all_sims_in_job_gen(self.apb_situation_job_decoy))
            writer.write_uint64s(DECOY_SIM_IDS, sim_ids)
Esempio n. 14
0
class CreateAndAddToSituation(HasTunableSingletonFactory, AutoFactoryInit):

    @staticmethod
    def _verify_tunable_callback(instance_class, tunable_name, source, value):
        if value.situation_job is not None:
            jobs = value.situation_to_create.get_tuned_jobs()
            if value.situation_job not in jobs:
                logger.error('CreateAndAddToSituation {} references a job {} that is not tuned in the situation {}.', source, value.situation_job, value.situation_to_create, owner='manus')
        elif value.situation_to_create.default_job() is None:
            logger.error('CreateAndAddToSituation {} references a situation {} \n                without referencing a job and the situation does not have a default job.\n                Either tune a default job on the situation or tune a job reference\n                here.', source, value.situation_to_create, owner='sscholl')

    FACTORY_TUNABLES = {'description': 'Create a new situation of this type and add the NPC to its tuned job.', 'situation_to_create': Situation.TunableReference(pack_safe=True), 'situation_job': SituationJob.TunableReference(description="\n            The situation job to assign the sim to. If set to None\n            the sim will be assigned to the situation's default job.\n            ", allow_none=True, pack_safe=True), 'verify_tunable_callback': _verify_tunable_callback}

    def __call__(self, all_sim_infos, purpose=None, host_sim_info=None):
        host_sim_id = host_sim_info.sim_id if host_sim_info is not None else 0
        situation_job = self.situation_job if self.situation_job is not None else self.situation_to_create.default_job()

        def _create_situation(sim_infos):
            guest_list = SituationGuestList(invite_only=True, host_sim_id=host_sim_id)
            for sim_info in sim_infos:
                guest_info = situation_guest_list.SituationGuestInfo.construct_from_purpose(sim_info.sim_id, situation_job, situation_guest_list.SituationInvitationPurpose.INVITED)
                guest_list.add_guest_info(guest_info)
            services.get_zone_situation_manager().create_situation(self.situation_to_create, guest_list=guest_list, user_facing=False)

        if self.situation_to_create.supports_multiple_sims:
            _create_situation(all_sim_infos)
        else:
            for one_sim_info in all_sim_infos:
                _create_situation((one_sim_info,))
class EveryoneTakeATurnOnceSituation(SituationComplexCommon):
    INSTANCE_TUNABLES = {
        'gather_together_state':
        GatherTogetherState.TunableFactory(
            description=
            '\n            The state of the situation when the situation first starts,\n            which lasts until every sim is in place and ready to take a turn.\n            ',
            tuning_group=GroupNames.STATE),
        'taking_turns_state':
        TakingTurnsState.TunableFactory(
            description=
            '\n            The state that means all Sims have gathered and are taking turns.\n            ',
            tuning_group=GroupNames.STATE),
        'job_and_role_state':
        TunableSituationJobAndRoleState(
            description='\n            Job and Role State.\n            '),
        'sim_took_turn_buff':
        Buff.TunableReference(
            description=
            '\n            The buff that is on a Sim that has already taken a turn.\n            '
        ),
        'sub_situation':
        Situation.TunableReference(
            description=
            '\n            Each participating Sim will in their own instance of this situation.\n            ',
            class_restrictions=('EveryoneTakeATurnOnceSubSituation', ))
    }
    REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._guest_sub_situation_dict = {}
        self.gathering_sim_ids = set()
        self._ready_sim_ids = set()
        self.target_object = self._get_target_object()

    def _get_target_object(self):
        target_object = None
        default_target_id = self._seed.extra_kwargs.get(
            'default_target_id', None)
        if default_target_id is not None:
            target_object = services.object_manager().get(default_target_id)
        return target_object

    def _get_sub_situations(self):
        sub_situations = set()
        situation_manager = services.get_zone_situation_manager()
        if situation_manager is None:
            return sub_situations
        for situation_id in self._guest_sub_situation_dict.values():
            situation = situation_manager.get(situation_id)
            if situation is not None:
                sub_situations.add(situation)
        return sub_situations

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

    @classmethod
    def _states(cls):
        return [
            SituationStateData(1,
                               GatherTogetherState,
                               factory=cls.gather_together_state),
            SituationStateData(2,
                               TakingTurnsState,
                               factory=cls.taking_turns_state)
        ]

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

    def _on_set_sim_job(self, sim, job_type):
        super()._on_set_sim_job(sim, job_type)
        situation_manager = services.get_zone_situation_manager()
        guest_list = SituationGuestList(invite_only=True)
        guest_list.add_guest_info(
            SituationGuestInfo(sim.sim_id,
                               self.sub_situation.job_and_role_state.job,
                               RequestSpawningOption.DONT_CARE,
                               BouncerRequestPriority.EVENT_VIP))
        sub_situation_id = situation_manager.create_situation(
            self.sub_situation,
            guest_list=guest_list,
            background_situation_id=self.id,
            user_facing=False)
        self._guest_sub_situation_dict[sim.id] = sub_situation_id
        self.gathering_sim_ids.add(sim.id)

    def _get_sub_situation_for_sim_id(self, sim_id):
        sub_situation = None
        sub_situation_id = self._guest_sub_situation_dict.get(sim_id, None)
        if sub_situation_id is not None:
            situation_manager = services.get_zone_situation_manager()
            if situation_manager is not None:
                sub_situation = situation_manager.get(sub_situation_id)
        return sub_situation

    def _on_remove_sim_from_situation(self, sim):
        super()._on_remove_sim_from_situation(sim)
        self.gathering_sim_ids.discard(sim.id)
        sub_situation = self._get_sub_situation_for_sim_id(sim.id)
        if sub_situation is not None:
            situation_manager = services.get_zone_situation_manager()
            situation_manager.destroy_situation_by_id(sub_situation.id)
        self._guest_sub_situation_dict.pop(sim.id, None)

    def _destroy(self):
        self._cleanup_sub_situations()
        super()._destroy()

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

    def set_sim_as_ready(self, sim_info):
        if sim_info is not None:
            self.gathering_sim_ids.discard(sim_info.id)
            if not sim_info.has_buff(self.sim_took_turn_buff.buff_type):
                self._ready_sim_ids.add(sim_info.id)

    def do_next_turn(self, set_others_waiting=False):
        if self._ready_sim_ids:
            sim_id = self._ready_sim_ids.pop()
            sub_situation = self._get_sub_situation_for_sim_id(sim_id)
            if sub_situation is not None:
                if self._ready_sim_ids:
                    if set_others_waiting:
                        for waiting_sims_id in self._ready_sim_ids:
                            waiting_sub_situation = self._get_sub_situation_for_sim_id(
                                waiting_sims_id)
                            if waiting_sub_situation is not None:
                                waiting_sub_situation.wait_for_turn()
                    sub_situation.take_turn()
                else:
                    sub_situation.take_last_turn()
                return
        self._self_destruct()

    def cleanup_expired_sims(self):
        sim_info_manager = services.sim_info_manager()
        for sim_id in tuple(self.gathering_sim_ids):
            sim_info = sim_info_manager.get(sim_id)
            if sim_info is not None:
                sim = sim_info.get_sim_instance()
                if sim is not None:
                    if self.is_sim_in_situation(sim):
                        self.remove_sim_from_situation(sim)

    def _cleanup_sub_situations(self):
        for situation in self._get_sub_situations():
            if situation is not None:
                situation._self_destruct()
class CafeGenericBackgroundSituation(SituationComplexCommon):
    INSTANCE_TUNABLES = {'generic_sim_job': TunableSituationJobAndRoleState(description="\n            A job and role state that essentially does nothing but filter out\n            Sims that shouldn't be placed in the generic cafe sim situation.\n            "), 'cafe_generic_customer_situation': Situation.TunableReference(description='\n            The individual, generic cafe customer situation we want to use for\n            Sims that show up at the Cafe so they can go get coffee.\n            ', class_restrictions=('CafeGenericCustomerSituation',))}
    REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES

    @classmethod
    def _states(cls):
        return (SituationStateData(1, _CafeGenericState),)

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

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

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

    def _issue_requests(self):
        request = BouncerRequestFactory(self, callback_data=_RequestUserData(role_state_type=self.generic_sim_job.role_state), job_type=self.generic_sim_job.job, request_priority=BouncerRequestPriority.BACKGROUND_MEDIUM, user_facing=False, exclusivity=self.exclusivity)
        self.manager.bouncer.submit_request(request)

    def _on_set_sim_job(self, sim, job_type):
        super()._on_set_sim_job(sim, job_type)
        situation_manager = services.get_zone_situation_manager()
        guest_list = SituationGuestList(invite_only=True)
        guest_info = SituationGuestInfo(sim.sim_info.id, self.cafe_generic_customer_situation.default_job(), RequestSpawningOption.DONT_CARE, BouncerRequestPriority.BACKGROUND_MEDIUM)
        guest_list.add_guest_info(guest_info)
        situation_manager.create_situation(self.cafe_generic_customer_situation, guest_list=guest_list, user_facing=False)
Esempio n. 17
0
class Venue(metaclass=HashedTunedInstanceMetaclass,
            manager=services.get_instance_manager(sims4.resources.Types.VENUE)
            ):
    __qualname__ = 'Venue'
    INSTANCE_TUNABLES = {
        'display_name':
        TunableLocalizedString(
            description=
            '\n            Name that will be displayed for the venue\n            ',
            export_modes=ExportModes.All),
        'display_name_incomplete':
        TunableLocalizedString(
            description=
            '\n            Name that will be displayed for the incomplete venue\n            ',
            export_modes=ExportModes.All),
        'venue_description':
        TunableLocalizedString(
            description='Description of Venue that will be displayed',
            export_modes=ExportModes.All),
        'venue_icon':
        TunableResourceKey(None,
                           resource_types=sims4.resources.CompoundTypes.IMAGE,
                           description='Venue Icon for UI',
                           export_modes=ExportModes.All),
        'venue_thumbnail':
        TunableResourceKey(None,
                           resource_types=sims4.resources.CompoundTypes.IMAGE,
                           description='Image of Venue that will be displayed',
                           export_modes=ExportModes.All),
        'allow_game_triggered_events':
        Tunable(
            description=
            '\n            Whether this venue can have game triggered events. ex for careers\n            ',
            tunable_type=bool,
            default=False),
        'background_event_schedule':
        TunableSituationWeeklyScheduleFactory(
            description=
            '\n            The Background Events that run on this venue. They run underneath\n            any user facing Situations and there can only be one at a time. The\n            schedule times and durations are windows in which background events\n            can start.\n            '
        ),
        'special_event_schedule':
        TunableSituationWeeklyScheduleFactory(
            description=
            '\n            The Special Events that run on this venue. These run on top of\n            Background Events. We run only one user facing event at a time, so\n            if the player started something then this may run in the\n            background, otherwise the player will be invited to join in on this\n            Venue Special Event.\n            '
        ),
        'required_objects':
        TunableList(
            description=
            '\n            A list of objects that are required to be on a lot before\n            that lot can be labeled as this venue.\n            ',
            tunable=TunableVenueObject(
                description=
                "\n                    Specify object tag(s) that must be on this venue.\n                    Allows you to group objects, i.e. weight bench,\n                    treadmill, and basketball goals are tagged as\n                    'exercise objects.'\n                    \n                    This is not the same as automatic objects tuning. \n                    Please read comments for both the fields.\n                    "
            ),
            export_modes=ExportModes.All),
        'npc_summoning_behavior':
        sims4.tuning.tunable.TunableMapping(
            description=
            '\n            Whenever an NPC is summoned to a lot by the player, determine\n            which action to take based on the summoning purpose. The purpose\n            is a dynamic enum: venues.venue_constants.NPCSummoningPurpose.\n            \n            The action will generally involve either adding a sim to an existing\n            situation or creating a situation then adding them to it.\n            \n            \\depot\\Sims4Projects\\Docs\\Design\\Open Streets\\Open Street Invite Matrix.xlsx\n            \n            residential: This is behavior pushed on the NPC if this venue was a residential lot.\n            create_situation: Place the NPC in the specified situation/job pair.\n            add_to_background_situation: Add the NPC the currently running background \n            situation in the venue.\n            ',
            key_type=sims4.tuning.tunable.TunableEnumEntry(
                venues.venue_constants.NPCSummoningPurpose,
                venues.venue_constants.NPCSummoningPurpose.DEFAULT),
            value_type=TunableVariant(
                locked_args={'disabled': None},
                residential=ResidentialLotArrivalBehavior.TunableFactory(),
                create_situation=CreateAndAddToSituation.TunableFactory(),
                add_to_background_situation=AddToBackgroundSituation.
                TunableFactory(),
                default='disabled'),
            tuning_group=GroupNames.TRIGGERS),
        'player_requires_visitation_rights':
        OptionalTunable(
            description=
            'If enabled, then lots of this venue type  \n            will require player Sims that are not on their home lot to go through \n            the process of being greeted before they are\n            given full rights to using the lot.\n            ',
            tunable=TunableTuple(
                ungreeted=Situation.TunableReference(
                    description=
                    '\n                    The situation to create for ungreeted player sims on this lot.',
                    display_name='Player Ungreeted Situation'),
                greeted=Situation
                .TunableReference(
                    description=
                    '\n                    The situation to create for greeted player sims on this lot.',
                    display_name='Player Greeted Situation'))),
        'zone_fixup':
        TunableVariant(
            description=
            '\n            Specify what to do with a non resident NPC\n            when the zone has to be fixed up on load. \n            This fix up will occur if sim time or the\n            active household has changed since the zone was last saved.\n            ',
            residential=ResidentialZoneFixupForNPC.TunableFactory(),
            create_situation=CreateAndAddToSituation.TunableFactory(),
            add_to_background_situation=AddToBackgroundSituation.
            TunableFactory(),
            default='residential',
            tuning_group=GroupNames.SPECIAL_CASES),
        'travel_interaction_name':
        TunableVariant(
            description=
            '\n            Specify what name a travel interaction gets when this Venue is an\n            adjacent lot.\n            ',
            visit_residential=ResidentialTravelDisplayName.TunableFactory(
                description=
                '\n                The interaction name for when the destination lot is a\n                residence.\n                '
            ),
            visit_venue=TunableLocalizedStringFactory(
                description=
                '\n                The interaction name for when the destination lot is a\n                commercial venue.\n                Tokens: 0:ActorSim\n                Example: "Visit The Bar"\n                '
            ),
            tuning_group=GroupNames.SPECIAL_CASES),
        'travel_with_interaction_name':
        TunableVariant(
            description=
            '\n            Specify what name a travel interaction gets when this Venue is an\n            adjacent lot.\n            ',
            visit_residential=ResidentialTravelDisplayName.TunableFactory(
                description=
                '\n                The interaction name for when the destination lot is a\n                residence and the actor Sim is traveling with someone.\n                '
            ),
            visit_venue=TunableLocalizedStringFactory(
                description=
                '\n                The interaction name for when the destination lot is a\n                commercial venue and the actor is traveling with someone.\n                Tokens: 0:ActorSim\n                Example: "Visit The Bar With..."\n                '
            ),
            tuning_group=GroupNames.SPECIAL_CASES),
        'venue_requires_front_door':
        Tunable(
            description=
            '\n            True if this venue should run the front door generation code. \n            If it runs, venue will have the ring doorbell interaction and \n            its additional behavior.\n            ',
            tunable_type=bool,
            default=False),
        'automatic_objects':
        TunableList(
            description=
            '\n            A list of objects that is required to exist on this venue (e.g. the\n            mailbox). If any of these objects are missing from this venue, they\n            will be auto-placed on zone load.',
            tunable=TunableTuple(
                description=
                "\n                An item that is required to be present on this venue. The object's tag \n                will be used to determine if any similar objects are present. If no \n                similar objects are present, then the object's actual definition is used to \n                create an object of this type.\n                \n                This is not the same as required objects tuning. Please read comments \n                for both the fields.\n                \n                E.g. To require a mailbox to be present on a lot, tune a hypothetical basicMailbox \n                here. The code will not trigger as long as a basicMailbox, fancyMailbox, or \n                cheapMailbox are present on the lot. If none of them are, then a basicMailbox \n                will be automatically created.\n                ",
                default_value=TunableReference(
                    manager=services.definition_manager(),
                    description=
                    'The default object to use if no suitably tagged object is present on the lot.'
                ),
                tag=TunableEnumEntry(description='The tag to search for',
                                     tunable_type=tag.Tag,
                                     default=tag.Tag.INVALID))),
        'hide_from_buildbuy_ui':
        Tunable(
            description=
            '\n            If True, this venue type will not be available in the venue picker\n            in build/buy.\n            ',
            tunable_type=bool,
            default=False,
            export_modes=ExportModes.All),
        'allows_fire':
        Tunable(
            description=
            '\n            If True a fire can happen on this venue, \n            otherwise fires will not spawn on this venue.\n            ',
            tunable_type=bool,
            default=False),
        'allow_rolestate_routing_on_navmesh':
        Tunable(
            description=
            '\n            Allow all RoleStates routing permission on lot navmeshes of this\n            venue type. This is particularly useful for outdoor venue types\n            (lots with no walls), where it is awkward to have to "invite a sim\n            in" before they may route on the lot, be called over, etc.\n            \n            This tunable overrides the "Allow Npc Routing On Active Lot"\n            tunable of individual RoleStates.\n            ',
            tunable_type=bool,
            default=False)
    }

    @classmethod
    def _verify_tuning_callback(cls):
        if cls.special_event_schedule is not None:
            for entry in cls.special_event_schedule.schedule_entries:
                while entry.situation.venue_situation_player_job is None:
                    logger.error(
                        'Venue Situation Player Job {} tuned in Situation: {}',
                        entry.situation.venue_situation_player_job,
                        entry.situation)

    def __init__(self, **kwargs):
        self._active_background_event_id = None
        self._active_special_event_id = None
        self._background_event_schedule = None
        self._special_event_schedule = None

    def set_active_event_ids(self,
                             background_event_id=None,
                             special_event_id=None):
        self._active_background_event_id = background_event_id
        self._active_special_event_id = special_event_id

    @property
    def active_background_event_id(self):
        return self._active_background_event_id

    @property
    def active_special_event_id(self):
        return self._active_special_event_id

    def schedule_background_events(self, schedule_immediate=True):
        self._background_event_schedule = self.background_event_schedule(
            start_callback=self._start_background_event,
            schedule_immediate=False)
        if schedule_immediate:
            (
                best_time_span, best_data_list
            ) = self._background_event_schedule.time_until_next_scheduled_event(
                services.time_service().sim_now, schedule_immediate=True)
            if best_time_span is not None and best_time_span == date_and_time.TimeSpan.ZERO:
                while True:
                    for best_data in best_data_list:
                        self._start_background_event(
                            self._background_event_schedule, best_data)

    def schedule_special_events(self, schedule_immediate=True):
        self._special_event_schedule = self.special_event_schedule(
            start_callback=self._try_start_special_event,
            schedule_immediate=schedule_immediate)

    def _start_background_event(self, scheduler, alarm_data, extra_data=None):
        entry = alarm_data.entry
        situation = entry.situation
        situation_manager = services.get_zone_situation_manager()
        if self._active_background_event_id is not None and self._active_background_event_id in situation_manager:
            situation_manager.destroy_situation_by_id(
                self._active_background_event_id)
        situation_id = services.get_zone_situation_manager().create_situation(
            situation, user_facing=False, spawn_sims_during_zone_spin_up=True)
        self._active_background_event_id = situation_id

    def _try_start_special_event(self, scheduler, alarm_data, extra_data):
        entry = alarm_data.entry
        situation = entry.situation
        situation_manager = services.get_zone_situation_manager()
        if self._active_special_event_id is None:
            client_manager = services.client_manager()
            client = next(iter(client_manager.values()))
            invited_sim = client.active_sim
            active_sim_available = situation.is_situation_available(
                invited_sim)

            def _start_special_event(dialog):
                guest_list = None
                if dialog.accepted:
                    start_user_facing = True
                    guest_list = SituationGuestList()
                    guest_info = SituationGuestInfo.construct_from_purpose(
                        invited_sim.id, situation.venue_situation_player_job,
                        SituationInvitationPurpose.INVITED)
                    guest_list.add_guest_info(guest_info)
                else:
                    start_user_facing = False
                situation_id = situation_manager.create_situation(
                    situation,
                    guest_list=guest_list,
                    user_facing=start_user_facing)
                self._active_special_event_id = situation_id

            if not situation_manager.is_user_facing_situation_running(
            ) and active_sim_available:
                dialog = situation.venue_invitation_message(
                    invited_sim, SingleSimResolver(invited_sim))
                dialog.show_dialog(
                    on_response=_start_special_event,
                    additional_tokens=(
                        situation.display_name,
                        situation.venue_situation_player_job.display_name))
            else:
                situation_id = situation_manager.create_situation(
                    situation, user_facing=False)
                self._active_special_event_id = situation_id

    def shut_down(self):
        if self._background_event_schedule is not None:
            self._background_event_schedule.destroy()
        if self._special_event_schedule is not None:
            self._special_event_schedule.destroy()
        situation_manager = services.get_zone_situation_manager()
        if self._active_background_event_id is not None:
            situation_manager.destroy_situation_by_id(
                self._active_background_event_id)
            self._active_background_event_id = None
        if self._active_special_event_id is not None:
            situation_manager.destroy_situation_by_id(
                self._active_special_event_id)
            self._active_special_event_id = None

    @classmethod
    def lot_has_required_venue_objects(cls, lot):
        failure_reasons = []
        for required_object_tuning in cls.required_objects:
            object_test = required_object_tuning.object
            object_list = object_test()
            num_objects = len(object_list)
            while num_objects < required_object_tuning.number:
                pass
        failure_message = None
        failure = len(failure_reasons) > 0
        if failure:
            failure_message = ''
            for message in failure_reasons:
                failure_message += message + '\n'
        return (not failure, failure_message)

    def summon_npcs(self, npc_infos, purpose, host_sim_info=None):
        if self.npc_summoning_behavior is None:
            return
        summon_behavior = self.npc_summoning_behavior.get(purpose)
        if summon_behavior is None:
            summon_behavior = self.npc_summoning_behavior.get(
                venues.venue_constants.NPCSummoningPurpose.DEFAULT)
            if summon_behavior is None:
                return
        summon_behavior(npc_infos, host_sim_info)

    @classproperty
    def requires_visitation_rights(cls):
        return cls.player_requires_visitation_rights is not None

    @classproperty
    def player_ungreeted_situation_type(cls):
        if cls.player_requires_visitation_rights is None:
            return
        return cls.player_requires_visitation_rights.ungreeted

    @classproperty
    def player_greeted_situation_type(cls):
        if cls.player_requires_visitation_rights is None:
            return
        return cls.player_requires_visitation_rights.greeted