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 " ))) }
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)
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)
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)
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)
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)
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)
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)
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