class OpenStreetsAutonomySituation( WalkbyLimitingTagsMixin, situations.situation_complex.SituationComplexCommon): INSTANCE_TUNABLES = { 'role': sims4.tuning.tunable.TunableTuple( situation_job=SituationJob.TunableReference( description= '\n The situation job for the sim.\n ' ), do_stuff_role_state=RoleState.TunableReference( description= '\n The role state for the sim doing stuff. This is the initial state.\n ' ), leave_role_state=RoleState.TunableReference( description= '\n The role state for the sim leaving.\n ' )), 'do_stuff_timeout': sims4.tuning.tunable.TunableSimMinute( description= '\n The amount of time the sim does stuff before leaving.\n ', default=180) } REMOVE_INSTANCE_TUNABLES = situations.situation.Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES @classmethod def _states(cls): return (SituationStateData(1, _DoStuffState), SituationStateData(2, _LeaveState)) @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.role.situation_job, cls.role.do_stuff_role_state)] @classmethod def default_job(cls): return cls.role.situation_job def start_situation(self): super().start_situation() self._change_state(_DoStuffState()) @classmethod def get_sims_expected_to_be_in_situation(cls): return 1 @property def _should_cancel_leave_interaction_on_premature_removal(self): return True @classproperty def situation_serialization_option(cls): return situations.situation_types.SituationSerializationOption.OPEN_STREETS def _get_remaining_time_for_gsi(self): if self._cur_state is not None: return self._cur_state._get_remaining_time_for_gsi() return super()._get_remaining_time_for_gsi()
class GhostSituation(situations.situation_complex.SituationComplexCommon): INSTANCE_TUNABLES = { 'role': sims4.tuning.tunable.TunableTuple( situation_job=SituationJob.TunableReference( description= '\n The situation job for the sim.\n ' ), do_stuff_role_state=RoleState.TunableReference( description= '\n The role state for the sim doing stuff. This is the initial state.\n ' ), leave_role_state=RoleState.TunableReference( description= '\n The role state for the sim leaving.\n ' )), 'do_stuff_timeout': sims4.tuning.tunable.TunableSimMinute( description= '\n The amount of time the sim does stuff before leaving.\n ', default=360) } REMOVE_INSTANCE_TUNABLES = situations.situation.Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES @classmethod def _states(cls): return (SituationStateData(1, _BeGhostState), SituationStateData(2, _LeaveState)) @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.role.situation_job, cls.role.do_stuff_role_state)] @classmethod def default_job(cls): return cls.role.situation_job def start_situation(self): super().start_situation() self._change_state(_BeGhostState()) @classmethod def get_sims_expected_to_be_in_situation(cls): return 1 @classmethod def _can_start_walkby(cls, lot_id: int): return True @property def _should_cancel_leave_interaction_on_premature_removal(self): return True @classproperty def situation_serialization_option(cls): return situations.situation_types.SituationSerializationOption.LOT
class PatientEmergencySituation(PatientSituationBase): INSTANCE_TUNABLES = {'procedure_role_state': RoleState.TunableReference(description='\n A reference to the hospital patients diagnosed\n role state while in the situation. This is\n the state where the patient has been diagnosed \n but it still waiting for the doctor to treat\n them.\n ', tuning_group=PatientSituationBase.JOB_AND_STATE_GROUP, display_name='03_procedure_role_state'), 'procedure_duration_for_time_jump': TunableSimMinute(description='\n The amount of time allowed to pass before a Sim in the procedure\n state will be ignored on load with a time jump.\n ', default=180, tuning_group=PatientSituationBase.TIMEOUT_GROUP), 'force_patient_on_active_career_sim': Tunable(description='\n If true then the patient will be the active career sim, otherwise\n we will let the filter service select a sim.\n ', tunable_type=bool, default=False, tuning_group=PatientSituationBase.JOB_AND_STATE_GROUP)} @classmethod def _states(cls): return (SituationStateData(1, ArrivingState), SituationStateData(2, WaitingState), SituationStateData(3, _ProcedureState), SituationStateData(4, TreatedState)) def _on_done_waiting(self): self._change_state(_ProcedureState()) @classmethod def should_state_type_load_after_time_jump(cls, state_type): if not super().should_state_type_load_after_time_jump(state_type): return False elif state_type is _ProcedureState: elapsed_time = services.current_zone().time_elapsed_since_last_save().in_minutes() if elapsed_time >= cls.procedure_duration_for_time_jump: return False return True def waiting_expired(self): with telemetry_helper.begin_hook(emergency_telemetry_writer, TELEMETRY_EMERGENCY_FAIL) as hook: hook.write_guid('type', self.guid64) @classmethod def get_predefined_guest_list(cls): if not cls.force_patient_on_active_career_sim: return career = services.get_career_service().get_career_in_career_event() if career is None: return guest_list = SituationGuestList(invite_only=True) guest_list.add_guest_info(SituationGuestInfo(career.sim_info.sim_id, cls.default_job(), RequestSpawningOption.CANNOT_SPAWN, BouncerRequestPriority.EVENT_VIP)) return guest_list
class VoodooSummonSituation(situations.situation_complex.SituationComplexCommon ): __qualname__ = 'VoodooSummonSituation' INSTANCE_TUNABLES = { 'summoned_job': sims4.tuning.tunable.TunableTuple( situation_job=SituationJob.TunableReference( description= '\n A reference to the SituationJob used for the Sim summoned.\n ' ), come_to_me_state=RoleState.TunableReference( description= '\n The state for telling the summoned sim to come here.\n ' )), 'come_here_affordance': sims4.tuning.tunable.TunableReference( services.affordance_manager(), description='SI to bring summoned sim to the summoner.') } REMOVE_INSTANCE_TUNABLES = ( '_buff', '_cost', '_NPC_host_filter', '_NPC_hosted_player_tests', 'NPC_hosted_situation_start_message', 'NPC_hosted_situation_use_player_sim_as_filter_requester', 'NPC_hosted_situation_player_job', 'venue_types', 'venue_invitation_message', 'venue_situation_player_job', 'category', 'main_goal', 'minor_goal_chains', 'max_participants', '_initiating_sim_tests', '_icon', 'targeted_situation', '_resident_job', 'situation_description', 'job_display_ordering', 'entitlement', '_jobs_to_put_in_party', '_relationship_between_job_members', 'main_goal_audio_sting', 'audio_sting_on_start', '_level_data', '_display_name') @staticmethod def _states(): return [(1, _ComeHereState)] @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.summoned_job.situation_job, cls.summoned_job.come_to_me_state)] @classmethod def default_job(cls): return cls.summoned_job.situation_job def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._summoned_sim = None def start_situation(self): super().start_situation() self._change_state(_ComeHereState()) def _on_set_sim_job(self, sim, job_type): super()._on_set_sim_job(sim, job_type) self._summoned_sim = sim def _on_sim_removed_from_situation_prematurely(self, sim): super()._on_sim_removed_from_situation_prematurely(sim) self._summoned_sim = None
class SingleJobSituation(SituationComplexCommon): INSTANCE_TUNABLES = { 'job': TunableTuple( description= '\n The job and role which the career 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 ' )) } REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES @classmethod def _states(cls): return (SituationStateData(1, SingleJobSituationState), ) @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.job.situation_job, cls.job.role_state)] @classmethod def default_job(cls): return cls.job.situation_job def start_situation(self): super().start_situation() self._change_state(SingleJobSituationState())
class StayTheNightSituation(VisitingNPCSituation): __qualname__ = 'StayTheNightSituation' INSTANCE_TUNABLES = { 'invited_job': sims4.tuning.tunable.TunableTuple( situation_job=SituationJob.TunableReference( description= '\n The situation job for the sim spending the night.\n ' ), staying_role_state=RoleState.TunableReference( description= '\n The role state for the sim spending the night.\n ' )), 'when_to_leave': tunable_time.TunableTimeOfDay( description= '\n The time of day for the invited sim to leave.\n ', default_hour=7) } @staticmethod def _states(): return [(1, _StayState)] @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.invited_job.situation_job, cls.invited_job.staying_role_state)] @classmethod def default_job(cls): return cls.invited_job.situation_job def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._start_time = None def start_situation(self): super().start_situation() self._start_time = services.time_service().sim_now self._change_state(_StayState()) def _get_duration(self): if self._seed.duration_override is not None: return self._seed.duration_override time_span = self._start_time.time_till_next_day_time( self.when_to_leave) return time_span.in_minutes()
class VoodooSummonSituation(situations.situation_complex.SituationComplexCommon ): INSTANCE_TUNABLES = { 'summoned_job': sims4.tuning.tunable.TunableTuple( situation_job=SituationJob.TunableReference( description= '\n A reference to the SituationJob used for the Sim summoned.\n ' ), come_to_me_state=RoleState.TunableReference( description= '\n The state for telling the summoned sim to come here.\n ' )), 'come_here_affordance': sims4.tuning.tunable.TunableReference( services.affordance_manager(), description='SI to bring summoned sim to the summoner.') } REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES @classmethod def _states(cls): return (SituationStateData(1, _ComeHereState), ) @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.summoned_job.situation_job, cls.summoned_job.come_to_me_state)] @classmethod def default_job(cls): return cls.summoned_job.situation_job def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._summoned_sim = None def start_situation(self): super().start_situation() self._change_state(_ComeHereState()) def _on_set_sim_job(self, sim, job_type): super()._on_set_sim_job(sim, job_type) self._summoned_sim = sim def _on_sim_removed_from_situation_prematurely(self, sim, sim_job): self._summoned_sim = None
class _SingleJobComplexSituationState(CommonSituationState): FACTORY_TUNABLES = { 'role_state': RoleState.TunableReference( description= '\n The role the Sim has while in this state.\n \n This is the initial state.\n ' ), 'locked_args': { 'job_and_role_changes': frozendict() } } def __init__(self, *args, situation_job, role_state, job_and_role_changes, **kwargs): super().__init__(*args, job_and_role_changes={situation_job: role_state}, **kwargs)
class DJSituation(SituationComplexCommon): INSTANCE_TUNABLES = { 'job': TunableTuple( description= '\n The job and role which the career 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 ' )), 'can_start_tag': TunableEnumEntry( description= '\n A specific tag that an object on this lot must have for this\n situation to be allowed to start.\n ', tunable_type=Tag, default=Tag.INVALID, invalid_enums=(Tag.INVALID, )) } REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES @classmethod def _states(cls): return (SituationStateData(1, _DJSituationState), ) @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.job.situation_job, cls.job.role_state)] @classmethod def default_job(cls): pass @classmethod def situation_meets_starting_requirements(cls, **kwargs): object_manager = services.object_manager() for _ in object_manager.get_objects_with_tag_gen(cls.can_start_tag): return True return False def start_situation(self): super().start_situation() self._change_state(_DJSituationState())
class _EmployeeSituationState(HasTunableFactory, AutoFactoryInit, SituationState): FACTORY_TUNABLES = { 'role_state': RoleState.TunableReference( description= '\n The role state that is active on the employee for the duration\n of this state.\n ' ), 'timeout_min': TunableSimMinute( description= '\n The minimum amount of time, in Sim minutes, the employee will be\n in this state before moving on to a new state.\n ', default=10), 'timeout_max': TunableSimMinute( description= '\n The maximum amount of time, in Sim minutes, the employee will be\n in this state before moving on to a new state.\n ', default=30), 'push_interaction': TunableInteractionOfInterest( description= '\n If an interaction of this type is run by the employee, this\n state will activate.\n ' ) } def __init__(self, *args, state_name=None, **kwargs): super().__init__(*args, **kwargs) self.state_name = state_name def on_activate(self, reader=None): super().on_activate(reader) self.owner._set_job_role_state(self.owner.employee_job, self.role_state) timeout = random.randint(self.timeout_min, self.timeout_max) self._create_or_load_alarm(self.state_name, timeout, self._timeout_expired, reader=reader) def _timeout_expired(self, *_, **__): self._change_state(self.owner._choose_next_state())
class WalkbyDogWalker(SituationComplexCommon): INSTANCE_TUNABLES = { 'group_filter': TunableAggregateFilter.TunableReference( description= '\n The aggregate filter that we use to find the sims for this\n situation.\n ' ), 'walk_state': WalkbyWalkState.TunableFactory( description= '\n A state for getting the Sims to \n ', locked_args={'allow_join_situation': False}), 'leave_state': LeaveState.TunableFactory( description= '\n The state for the adoption officer to leave.\n ', locked_args={'allow_join_situation': False}), 'dog_walker': TunableTuple( situation_job=SituationJob.TunableReference( description= '\n The Situation Job of the dog walker.\n ' ), initial_role_state=RoleState.TunableReference( description= '\n The initial Role State of the dog walker.\n ' )), 'dog': TunableTuple( situation_job=SituationJob.TunableReference( description= '\n The Situation Job of the dog.\n ' ), initial_role_state=RoleState.TunableReference( description= '\n The initial Role State of the dog.\n ' )), 'situation_job_mapping': TunableMapping( description= '\n A mapping of filter term tag to situation job.\n \n The filter term tag is returned as part of the sim filters used to \n create the guest list for this particular situation.\n \n The situation job is the job that the Sim will be assigned to in\n the background situation.\n ', key_name='filter_tag', key_type=TunableEnumEntry( description= '\n The filter term tag returned with the filter results.\n ', tunable_type=FilterTermTag, default=FilterTermTag.NO_TAG), value_name='job', value_type=TunableReference( description= '\n The job the Sim will receive when added to the this situation.\n ', manager=services.get_instance_manager( sims4.resources.Types.SITUATION_JOB))), 'sim_spawner_tags': TunableList( description= '\n A list of tags that represent where to spawn Sims for this\n Situation when they come onto the lot. This tuning will be used\n instead of the tuning on the jobs.\n NOTE: Tags will be searched in order of tuning. Tag [0] has\n priority over Tag [1] and so on.\n ', tunable=TunableEnumWithFilter(tunable_type=Tag, default=Tag.INVALID, filter_prefixes=SPAWN_PREFIX)) } REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES @classmethod def _states(cls): return (SituationStateData(1, GetSimsState), SituationStateData(2, WalkbyWalkState, factory=cls.walk_state), SituationStateData(3, LeaveState, factory=cls.leave_state)) @classmethod def default_job(cls): pass @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.dog_walker.situation_job, cls.dog_walker.initial_role_state), (cls.dog.situation_job, cls.dog.initial_role_state)] @classmethod def get_predefined_guest_list(cls): guest_list = SituationGuestList(invite_only=True) situation_manager = services.get_zone_situation_manager() instanced_sim_ids = [ sim.sim_info.id for sim in services.sim_info_manager().instanced_sims_gen() ] household_sim_ids = [ sim_info.id for sim_info in services.active_household().sim_info_gen() ] auto_fill_blacklist_walker = situation_manager.get_auto_fill_blacklist( sim_job=cls.dog_walker.situation_job) auto_fill_blacklist_dog = situation_manager.get_auto_fill_blacklist( sim_job=cls.dog.situation_job) situation_sims = set() for situation in situation_manager.get_situations_by_tags(cls.tags): situation_sims.update(situation.invited_sim_ids) blacklist_sim_ids = set( itertools.chain(situation_sims, instanced_sim_ids, household_sim_ids, auto_fill_blacklist_walker, auto_fill_blacklist_dog)) filter_results = services.sim_filter_service().submit_matching_filter( sim_filter=cls.group_filter, allow_yielding=False, blacklist_sim_ids=blacklist_sim_ids, gsi_source_fn=cls.get_sim_filter_gsi_name) if not filter_results: return if len(filter_results) != cls.group_filter.get_filter_count(): return for result in filter_results: job = cls.situation_job_mapping.get(result.tag, None) if job is None: continue guest_list.add_guest_info( SituationGuestInfo(result.sim_info.sim_id, job, RequestSpawningOption.DONT_CARE, job.sim_auto_invite_allow_priority)) return guest_list def start_situation(self): super().start_situation() if self._guest_list.guest_info_count != self.group_filter.get_filter_count( ): self._self_destruct() else: self._change_state(GetSimsState()) @classmethod def get_sims_expected_to_be_in_situation(cls): return cls.group_filter.get_filter_count() @classmethod def _can_start_walkby(cls, lot_id: int): return True @classproperty def situation_serialization_option(cls): return SituationSerializationOption.OPEN_STREETS @property def _should_cancel_leave_interaction_on_premature_removal(self): return True def on_all_sims_spawned(self): self._change_state(self.walk_state()) def _issue_requests(self): zone = services.current_zone() if SpawnPoint.ARRIVAL_SPAWN_POINT_TAG in self.sim_spawner_tags or SpawnPoint.VISITOR_ARRIVAL_SPAWN_POINT_TAG in self.sim_spawner_tags: lot_id = zone.lot.lot_id else: lot_id = None spawn_point = zone.get_spawn_point( lot_id=lot_id, sim_spawner_tags=self.sim_spawner_tags, spawn_point_request_reason=SpawnPointRequestReason.SPAWN) super()._issue_requests(spawn_point_override=spawn_point)
class FireSituation(SituationComplexCommon): __qualname__ = 'FireSituation' INSTANCE_TUNABLES = { 'victim_job': sims4.tuning.tunable.TunableTuple( situation_job=SituationJob.TunableReference( description= '\n A reference to the SituationJob used during a fire.\n ' ), fire_panic_state=RoleState.TunableReference( description='The state while the sim is panicking due to fire.' ), fire_unaware_state=RoleState.TunableReference( description= '\n The state while the sim is unaware there is a \n fire on the lot.\n ' ), fire_aware_state=RoleState.TunableReference( description= "\n The state while the Sim is aware there is a fire\n but hasn't seen it yet.\n " ), fire_safe_state=RoleState.TunableReference( description= '\n The state while the Sim has made it safely away\n from the fire.\n ' ), post_fire_state=RoleState.TunableReference( description= '\n The state the Sim is in after the fire has gone\n out.\n ' ), tuning_group=GroupNames.SITUATION), 'got_to_safety_interaction': situations.situation_complex.TunableInteractionOfInterest( description= '\n The interaction to look for when a Sim has routed off of the lot\n and safely escaped the fire.\n ' ), 'go_back_to_panic_interactions': situations.situation_complex.TunableInteractionOfInterest( description= '\n The interactions to look for when a Sim has routed back on to a\n lot that is on fire which will cause the Sim to go back into panic\n mode.\n ' ), 'TIME_POST_FIRE_IN_SIM_MINUTES': TunableSimMinute( description= '\n Number of Sim minutes that the situation can be in the _PostFireState\n before the situation ends.\n ', default=60) } REMOVE_INSTANCE_TUNABLES = ( '_buff', '_cost', '_NPC_host_filter', '_NPC_hosted_player_tests', 'NPC_hosted_situation_start_message', 'NPC_hosted_situation_use_player_sim_as_filter_requester', 'NPC_hosted_situation_player_job', 'venue_types', 'venue_invitation_message', 'venue_situation_player_job', 'category', 'main_goal', 'minor_goal_chains', '_level_data', '_display_name', 'max_participants', '_initiating_sim_tests', 'targeted_situation', '_resident_job', '_icon', 'situation_description') @staticmethod def _states(): return [(1, _PanicState), (2, _UnawareState), (3, _AlertedState), (4, _SafeState), (5, _PostFireState)] @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.victim_job.situation_job, cls.victim_job.fire_panic_state) ] @classmethod def default_job(cls): return cls.victim_job.situation_job def start_situation(self): super().start_situation() self._change_state(_UnawareState()) def advance_to_alerted(self): if type(self._cur_state) != _PanicState: self._change_state(_PanicState()) def advance_to_post_fire(self): self._change_state(_PostFireState()) def on_remove(self): fire_service = services.get_fire_service() if fire_service is not None: for sim in self.all_sims_in_situation_gen(): fire_service.remove_fire_situation(sim) super().on_remove()
class CaregiverSituation(SituationComplexCommon): CAREGIVER_EVENTS = (TestEvent.SituationStarted, TestEvent.AvailableDaycareSimsChanged) INSTANCE_TUNABLES = { 'caregiver_data': TunableTuple( description= '\n The relationship bits to apply to Sims.\n ', caregiver_bit=TunableReference( description= "\n The bit that is applied to Sims that are the situation owner's\n Sim's caregiver. This is, for example, a bit on an adult\n targeting a toddler.\n ", manager=services.get_instance_manager( sims4.resources.Types.RELATIONSHIP_BIT)), caregiver_job=SituationJob.TunableReference( description= '\n The situation job that caregivers are assigned when in this situation.\n ' ), caregiver_rolestate=RoleState.TunableReference( description= '\n The role state that caregivers are assigned when in this situation.\n ' ), care_dependent_bit=TunableReference( description= '\n The bit that is applied to Sims that are the situation owner\n This is, for example, a bit on a toddler targeting an adult.\n ', manager=services.get_instance_manager( sims4.resources.Types.RELATIONSHIP_BIT))), 'caregiver_relationships': TunableSet( description= '\n A list of bits that make Sims primary caregivers. If any Sim with\n any of these bits is instantiated and living in the same household \n as the care dependent, they are considered caregivers.\n \n If no primary caregiver exists, and no caregiver service exists,\n active TYAE Sims are made caregivers.\n ', tunable=TunableReference(manager=services.get_instance_manager( sims4.resources.Types.RELATIONSHIP_BIT), pack_safe=True)) } REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._pending_caregivers = WeakSet() @classmethod def default_job(cls): pass @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return ((cls.caregiver_data.caregiver_job, cls.caregiver_data.caregiver_rolestate), ) def _is_valid_caregiver(self, care_dependent, caregiver, ignore_zone=False): if not ignore_zone and care_dependent.zone_id != caregiver.zone_id: return False if caregiver.is_toddler_or_younger: return False if caregiver.is_pet: return False if care_dependent.household_id == caregiver.household_id and any( caregiver.relationship_tracker.has_bit(care_dependent.sim_id, rel_bit) for rel_bit in self.caregiver_relationships): return True else: daycare_service = services.daycare_service() if daycare_service is not None and daycare_service.is_daycare_service_npc_available( sim_info=caregiver, household=care_dependent.household): return True return False def _update_caregiver_status(self): care_dependent = self._guest_list.host_sim if care_dependent is None: return if care_dependent.household is None: return if care_dependent.is_being_destroyed: return available_sims = tuple( sim_info for sim_info in services.daycare_service().get_available_sims_gen()) current_caregivers = set(self._situation_sims) for sim in current_caregivers: self._pending_caregivers.discard(sim) eligible_caregivers = set( sim_info for sim_info in available_sims if self._is_valid_caregiver(care_dependent, sim_info)) if not eligible_caregivers: eligible_caregivers = set( sim_info for sim_info in care_dependent.household.can_live_alone_info_gen() if sim_info in available_sims) for sim in self._pending_caregivers: eligible_caregivers.discard(sim.sim_info) for potential_caregiver in tuple(eligible_caregivers): sim = potential_caregiver.get_sim_instance( allow_hidden_flags=ALL_HIDDEN_REASONS) if sim is None or sim.is_being_destroyed: eligible_caregivers.discard(potential_caregiver) else: if sim in current_caregivers: continue self.invite_sim_to_job(sim, job=self.caregiver_data.caregiver_job) self._pending_caregivers.add(sim) care_dependent.relationship_tracker.add_relationship_bit( potential_caregiver.sim_id, self.caregiver_data.care_dependent_bit) potential_caregiver.relationship_tracker.add_relationship_bit( care_dependent.sim_id, self.caregiver_data.caregiver_bit) for sim in tuple(current_caregivers): if sim.sim_info not in eligible_caregivers: self._remove_caregiver_rel_bits(care_dependent, sim.sim_info) self.remove_sim_from_situation(sim) current_caregivers.discard(sim) def _remove_caregiver_rel_bits(self, care_dependent, other_sim_info=None): if other_sim_info is not None: care_dependent.relationship_tracker.remove_relationship_bit( other_sim_info.id, self.caregiver_data.care_dependent_bit) other_sim_info.relationship_tracker.remove_relationship_bit( care_dependent.id, self.caregiver_data.caregiver_bit) else: for relationship in care_dependent.relationship_tracker: other_sim_id = relationship.get_other_sim_id( care_dependent.sim_id) relationship.remove_bit(care_dependent.sim_id, other_sim_id, self.caregiver_data.care_dependent_bit) relationship.remove_bit(other_sim_id, care_dependent.sim_id, self.caregiver_data.caregiver_bit) def get_care_dependent_if_last_caregiver(self, sim_info, excluding_interaction_types=None): care_dependent = self._guest_list.host_sim if care_dependent.household.home_zone_id == services.current_zone_id(): return if not care_dependent.relationship_tracker.has_relationship( sim_info.id): return for relationship in care_dependent.relationship_tracker: if relationship.get_other_sim_info( care_dependent.sim_id) is sim_info: if not relationship.has_bit( care_dependent.sim_id, self.caregiver_data.care_dependent_bit): return if relationship.has_bit( care_dependent.sim_id, self.caregiver_data.care_dependent_bit): if excluding_interaction_types is not None: other_sim = relationship.get_other_sim( care_dependent.sim_id) if other_sim is None: continue if other_sim.has_any_interaction_running_or_queued_of_types( excluding_interaction_types): continue else: return elif relationship.has_bit(care_dependent.sim_id, self.caregiver_data.care_dependent_bit): if excluding_interaction_types is not None: other_sim = relationship.get_other_sim( care_dependent.sim_id) if other_sim is None: continue if other_sim.has_any_interaction_running_or_queued_of_types( excluding_interaction_types): continue else: return return care_dependent def start_situation(self): self._update_caregiver_status() services.get_event_manager().register(self, self.CAREGIVER_EVENTS) return super().start_situation() def _destroy(self): services.get_event_manager().unregister(self, self.CAREGIVER_EVENTS) care_dependent = self._guest_list.host_sim self._remove_caregiver_rel_bits(care_dependent) super()._destroy() def handle_event(self, sim_info, event, resolver): super().handle_event(sim_info, event, resolver) if event in self.CAREGIVER_EVENTS: self._update_caregiver_status()
class InviteToSituation(situations.situation_complex.SituationComplexCommon): __qualname__ = 'InviteToSituation' INSTANCE_TUNABLES = { 'invited_job': sims4.tuning.tunable.TunableTuple( situation_job=SituationJob.TunableReference( description= '\n A reference to the SituationJob used for the Sims invited to.\n ' ), invited_to_state=RoleState.TunableReference( description= '\n The state for telling a sim to wait. They will momentarily be\n pulled from this situation by a visit or venue situation.\n ' )), 'purpose': sims4.tuning.tunable.TunableEnumEntry( description= '\n The purpose/reason used to perform the venue specific operation\n to get this sim in the appropriate situation.\n This should be tuned to Invite In, but since that is a dynamic enum\n you must do it yourself.\n ', tunable_type=venues.venue_constants.NPCSummoningPurpose, default=venues.venue_constants.NPCSummoningPurpose.DEFAULT) } REMOVE_INSTANCE_TUNABLES = ( '_buff', '_cost', '_NPC_host_filter', '_NPC_hosted_player_tests', 'NPC_hosted_situation_start_message', 'NPC_hosted_situation_use_player_sim_as_filter_requester', 'NPC_hosted_situation_player_job', 'venue_types', 'venue_invitation_message', 'venue_situation_player_job', 'category', 'main_goal', 'minor_goal_chains', 'max_participants', '_initiating_sim_tests', '_icon', 'targeted_situation', '_resident_job', 'situation_description', 'job_display_ordering', 'entitlement', '_jobs_to_put_in_party', '_relationship_between_job_members', 'main_goal_audio_sting', 'audio_sting_on_start', '_level_data', '_display_name') @staticmethod def _states(): return [(1, _WaitState)] @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.invited_job.situation_job, cls.invited_job.invited_to_state)] @classmethod def default_job(cls): return cls.invited_job.situation_job def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._tick_alarm_handle = None def start_situation(self): super().start_situation() self.manager.add_pre_bouncer_update(self) self._change_state(_WaitState()) def _issue_requests(self): pass def on_pre_bouncer_update(self): zone = services.current_zone() venue = zone.venue_service.venue for sim_info in self._seed.invited_sim_infos_gen(): venue.summon_npcs((sim_info, ), self.purpose) @classmethod def get_player_greeted_status_from_seed(cls, situation_seed): for sim_info in situation_seed.invited_sim_infos_gen(): while sim_info.is_npc and sim_info.lives_here: return GreetedStatus.GREETED return GreetedStatus.NOT_APPLICABLE
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 ClubGatheringSituation(SituationComplexCommon): INSTANCE_TUNABLES = {'_default_job': SituationJob.TunableReference(description='\n The default job for all members of this situation.\n '), '_default_role_state': RoleState.TunableReference(description='\n The Role State for Sims in the default job of this situation.\n '), '_default_gathering_vibe': TunableEnumEntry(description='\n The default Club vibe to use for the gathering.\n ', tunable_type=ClubGatheringVibe, default=ClubGatheringVibe.NO_VIBE), '_vibe_buffs': TunableMapping(description=" \n A Mapping of ClubGatheringVibe to List of buffs.\n \n When setting the vibe for the gathering the type is found in the\n mapping and then each buff is processed in order until one of them\n can be added. Then evaluation stops.\n \n Example: The club vibe is getting set to ClubGatheringVibe.Angry.\n That entry has 3 buffs associated with it in the mapping. Angry3,\n Angry2, Angry1 in that order. Angry3 doesn't pass evaluation so it\n is passed. Next Angry2 does pass evaluation and so we add Angry2\n Vibe Buff to the gathering. Angry1 is never evaluated in this\n situation. Angry1 is only ever evaluated if Angry3 and Angry2 both\n fail.\n ", key_type=ClubGatheringVibe, value_type=TunableList(description='\n A List of buff to attempt to use on the gathering. Order is\n important as we do not try to give any buffs after one is given\n to the gathering.\n ', tunable=Buff.TunableReference(), minlength=1)), '_gathering_buff_reason': TunableLocalizedString(description='\n The reason the gathering buff was added. Displayed on the buff\n tooltip.\n '), '_initial_disband_timer': TunableSimMinute(description='\n The number of Sim minutes after a Gathering is created before it\n can disband due to lack of members.\n ', default=30, minimum=1), '_initial_notification': TunableUiDialogNotificationSnippet(description='\n A notification that shows up once the gathering starts.\n '), '_minimum_number_of_sims': TunableRange(description='\n The minimum number of Sims that must be present in a Gathering to\n keep it from disbanding.\n ', tunable_type=int, default=3, minimum=2), 'time_between_bucks_rewards': TunableSimMinute(description='\n The time in Sim Minutes to wait before awarding\n the first club bucks for being in a gathering.\n ', default=10), 'reward_bucks_per_interval': Tunable(description='\n The amount of Club Bucks to award to the associated club at each \n tuned interval.\n ', tunable_type=int, default=1), 'rule_breaking_buff': TunableBuffReference(description='\n Award this buff whenever a Sim breaks the rules.\n ')} REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES def __init__(self, seed): super().__init__(seed) self.associated_club = None self._current_gathering_buff = None self._current_gathering_vibe = None self._sim_gathering_time_checks = {} self._can_disband = False self._initial_disband_timer_handle = None self._rewards_timer = None self._time_tracker_timer = None self._validity_household_id_override = None reader = self._seed.custom_init_params_reader if reader is not None: start_source = reader.read_uint64(ClubGatheringKeys.START_SOURCE, None) disband_ticks = reader.read_uint64(ClubGatheringKeys.DISBAND_TICKS, 0) self._validity_household_id_override = reader.read_uint64(ClubGatheringKeys.HOUSEHOLD_ID_OVERRIDE, None) associated_club_id = reader.read_uint64(ClubGatheringKeys.ASSOCIATED_CLUB_ID, None) if associated_club_id is not None: club_service = services.get_club_service() associated_club = club_service.get_club_by_id(associated_club_id) self.initialize_gathering(associated_club, disband_ticks=disband_ticks, start_source=start_source) current_gathering_buff_guid = reader.read_uint64(ClubGatheringKeys.GATHERING_BUFF, 0) self._current_gathering_buff = services.get_instance_manager(sims4.resources.Types.BUFF).get(current_gathering_buff_guid) vibe = reader.read_uint64(ClubGatheringKeys.GATHERING_VIBE, self._default_gathering_vibe) self.set_club_vibe(vibe) @classmethod def default_job(cls): return cls._default_job @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.default_job(), cls._default_role_state)] @classmethod def _states(cls): return (SituationStateData(1, ClubGatheringSituationState),) def _destroy(self): if self._initial_disband_timer_handle is not None: self._initial_disband_timer_handle.cancel() self._initial_disband_timer_handle = None if self._rewards_timer is not None: self._rewards_timer.cancel() self._rewards_timer = None if self._time_tracker_timer is not None: self._time_tracker_timer.cancel() self._time_tracker_timer = None super()._destroy() def _disband_timer_callback(self, _): self._can_disband = True if self._initial_disband_timer_handle is not None: alarms.cancel_alarm(self._initial_disband_timer_handle) self._initial_disband_timer_handle = None self._disband_if_neccessary() def _disband_if_neccessary(self): if not self._can_disband: return if len(list(self.all_sims_in_situation_gen())) < self._minimum_number_of_sims: self._self_destruct() def on_remove(self): self._can_disband = False super().on_remove() self._cleanup_gathering() def _cleanup_gathering(self): club_service = services.get_club_service() if club_service is None: logger.error("Attempting to end a Gathering but the ClubService doesn't exist.") return op = EndClubGathering(self.associated_club.club_id) Distributor.instance().add_op_with_no_owner(op) club_service.on_gathering_ended(self) def start_situation(self): super().start_situation() self._change_state(ClubGatheringSituationState()) def load_situation(self): result = super().load_situation() if result and (self.associated_club is None or not (not self.is_validity_overridden() and not self.associated_club.is_zone_valid_for_gathering(services.current_zone_id()))): self._cleanup_gathering() return False return result def is_validity_overridden(self): return self._validity_household_id_override == services.active_household_id() and services.active_household_lot_id() == services.active_lot_id() def initialize_gathering(self, associated_club, disband_ticks=None, start_source=None): club_service = services.get_club_service() if club_service is None: logger.error("Attempting to start a Gathering but the ClubService doesn't exist.") return self.associated_club = associated_club if start_source is not None: if start_source == ClubGatheringStartSource.APPLY_FOR_INVITE: invited_sim = services.sim_info_manager().get(self._guest_list.host_sim_id) self.associated_club.show_club_notification(invited_sim, ClubTunables.CLUB_GATHERING_START_DIALOG) elif any(sim_info.is_selectable for sim_info in self._guest_list.invited_sim_infos_gen()): initial_notification = self._initial_notification(services.active_sim_info()) initial_notification.show_dialog(icon_override=IconInfoData(icon_resource=associated_club.icon), additional_tokens=(associated_club.name,)) self._initial_disband_timer_handle = alarms.add_alarm(self.associated_club, interval_in_sim_minutes(self._initial_disband_timer), self._disband_timer_callback) elif disband_ticks > 0: self._initial_disband_timer_handle = alarms.add_alarm(self.associated_club, clock.TimeSpan(disband_ticks), self._disband_timer_callback) time_between_rewards = create_time_span(minutes=self.time_between_bucks_rewards) self._rewards_timer = alarms.add_alarm(self, time_between_rewards, self._award_club_bucks, True) time_between_gathering_checks = create_time_span(minutes=club_tuning.ClubTunables.MINUTES_BETWEEN_CLUB_GATHERING_PULSES) self._time_tracker_timer = alarms.add_alarm(self, time_between_gathering_checks, self._add_time_in_gathering, True) op = StartClubGathering(self.associated_club.club_id) Distributor.instance().add_op_with_no_owner(op) club_service.on_gathering_started(self) def _on_minimum_number_of_members_reached(self): self._can_disband = True if self._initial_disband_timer_handle is not None: alarms.cancel_alarm(self._initial_disband_timer_handle) self._initial_disband_timer_handle = None def _on_add_sim_to_situation(self, sim, *args, **kwargs): super()._on_add_sim_to_situation(sim, *args, **kwargs) club_service = services.get_club_service() if club_service is None: logger.error("Attempting to add a Sim to a Gathering but the ClubService doesn't exist.") return club_service.on_sim_added_to_gathering(sim, self) self.add_club_vibe_buff_to_sim(sim) relationship_tracker = sim.relationship_tracker relationship_tracker.add_create_relationship_listener(self._relationship_added_callback) sim.sim_info.register_for_outfit_changed_callback(self._on_outfit_changed) if not self._can_disband and len(list(self.all_sims_in_situation_gen())) >= self._minimum_number_of_sims: self._on_minimum_number_of_members_reached() op = UpdateClubGathering(GatheringUpdateType.ADD_MEMBER, self.associated_club.club_id, sim.id) Distributor.instance().add_op_with_no_owner(op) if self.associated_club.member_should_spin_into_club_outfit(sim): self._push_spin_into_current_outfit_interaction(sim) self._sim_gathering_time_checks[sim] = services.time_service().sim_timeline.now def _on_remove_sim_from_situation(self, sim): super()._on_remove_sim_from_situation(sim) sim.remove_buff_by_type(self._current_gathering_buff) self._disband_if_neccessary() club_service = services.get_club_service() if club_service is None: logger.error("Attempting to add a Sim to a Gathering but the ClubService doesn't exist.") return club_service.on_sim_removed_from_gathering(sim, self) relationship_tracker = sim.relationship_tracker relationship_tracker.remove_create_relationship_listener(self._relationship_added_callback) sim.sim_info.unregister_for_outfit_changed_callback(self._on_outfit_changed) if self.associated_club.member_should_spin_into_club_outfit(sim): sim.sim_info.register_for_outfit_changed_callback(self._on_outfit_removed) self._push_spin_into_current_outfit_interaction(sim) else: self._remove_apprearance_modifiers(sim.sim_info) if self.associated_club in club_service.clubs_to_gatherings_map: op = UpdateClubGathering(GatheringUpdateType.REMOVE_MEMBER, self.associated_club.club_id, sim.id) Distributor.instance().add_op_with_no_owner(op) if sim in self._sim_gathering_time_checks: self._process_time_in_gathering_event(sim) del self._sim_gathering_time_checks[sim] def set_club_vibe(self, vibe): self._current_gathering_vibe = vibe vibe_buffs = self._vibe_buffs.get(vibe, ()) member = self.associated_club.members[0] for buff in vibe_buffs: if buff.can_add(member): if buff is not self._current_gathering_buff: for sim in self.all_sims_in_situation_gen(): sim.remove_buff_by_type(self._current_gathering_buff) self.add_club_vibe_buff_to_sim(sim, buff) self._current_gathering_buff = buff return def add_club_vibe_buff_to_sim(self, sim, buff=None): buff = self._current_gathering_buff if buff is None else buff if buff is None: return if sim.has_buff(buff): return sim.add_buff(buff, self._gathering_buff_reason) def _relationship_added_callback(self, relationship): resolver = DoubleSimResolver(relationship.find_sim_info_a(), relationship.find_sim_info_b(), additional_participants={ParticipantType.AssociatedClub: (self.associated_club,)}) for (perk, benefit) in ClubTunables.NEW_RELATIONSHIP_MODS.items(): if self.associated_club.bucks_tracker.is_perk_unlocked(perk): if not benefit.test_set.run_tests(resolver=resolver): continue benefit.loot.apply_to_resolver(resolver=resolver) def _award_club_bucks(self, handle): qualified_sims = [sim for sim in self._situation_sims if self._sim_satisfies_requirement_for_bucks(sim)] if not qualified_sims: return if any(sim for sim in self._situation_sims if club_tuning.ClubTunables.CLUB_BUCKS_REWARDS_MULTIPLIER.trait in sim.sim_info.trait_tracker.equipped_traits): multiplier = club_tuning.ClubTunables.CLUB_BUCKS_REWARDS_MULTIPLIER.multiplier else: multiplier = 1 bucks_tracker = self.associated_club.bucks_tracker if bucks_tracker is None: return bucks_tracker.try_modify_bucks(ClubTunables.CLUB_BUCKS_TYPE, int(self.reward_bucks_per_interval*multiplier), reason='Time in club gathering') def _sim_satisfies_requirement_for_bucks(self, sim): if not sim.is_selectable: return False elif not sim.sim_info.is_instanced(): return False return True def _on_outfit_changed(self, sim_info, outfit_category_and_index): club = self.associated_club (cas_parts_add, cas_parts_remove) = club.get_club_outfit_parts(sim_info, outfit_category_and_index) appearance_tracker = sim_info.appearance_tracker appearance_tracker.remove_appearance_modifiers(self.guid, source=self) modifiers = [] for cas_part in cas_parts_add: modifier = AppearanceModifier.SetCASPart(cas_part=cas_part, should_toggle=False, replace_with_random=False, update_genetics=False, _is_combinable_with_same_type=True, remove_conflicting=False, outfit_type_compatibility=None) modifiers.append(modifier) for cas_part in cas_parts_remove: modifier = AppearanceModifier.SetCASPart(cas_part=cas_part, should_toggle=True, replace_with_random=False, update_genetics=False, _is_combinable_with_same_type=True, remove_conflicting=False, outfit_type_compatibility=None) modifiers.append(modifier) for modifier in modifiers: appearance_tracker.add_appearance_modifier(modifier, self.guid, 1, False, source=self) appearance_tracker.evaluate_appearance_modifiers() if sim_info.appearance_tracker.appearance_override_sim_info is not None: sim = sim_info.get_sim_instance() if sim is not None: sim.apply_outfit_buffs_for_sim_info(sim_info.appearance_tracker.appearance_override_sim_info, outfit_category_and_index) def _on_outfit_removed(self, sim_info, outfit_category_and_index): self._remove_apprearance_modifiers(sim_info) def _remove_apprearance_modifiers(self, sim_info): sim_info.appearance_tracker.remove_appearance_modifiers(self.guid, source=self) sim_info.unregister_for_outfit_changed_callback(self._on_outfit_removed) def _push_spin_into_current_outfit_interaction(self, sim): sim.sim_info.set_outfit_dirty(sim.get_current_outfit()[0]) change_outfit_context = InteractionContext(sim, InteractionContext.SOURCE_SCRIPT, priority.Priority.High) return sim.push_super_affordance(ForceChangeToCurrentOutfit, None, change_outfit_context) def remove_all_club_outfits(self): for sim in self.all_sims_in_situation_gen(): self._push_spin_into_current_outfit_interaction(sim) def _add_time_in_gathering(self, handle): qualified_sims = [sim for sim in self._situation_sims if self._sim_satisfies_requirement_for_bucks(sim)] if not qualified_sims: return now = services.time_service().sim_timeline.now for sim in qualified_sims: self._process_time_in_gathering_event(sim, now) self._sim_gathering_time_checks[sim] = now def _process_time_in_gathering_event(self, sim, now=None): if now is None: now = services.time_service().sim_timeline.now elapsed_time = now - self._sim_gathering_time_checks[sim] services.get_event_manager().process_event(test_events.TestEvent.TimeInClubGathering, sim_info=sim.sim_info, amount=int(elapsed_time.in_minutes())) def _save_custom_situation(self, writer): super()._save_custom_situation(writer) writer.write_uint64(ClubGatheringKeys.ASSOCIATED_CLUB_ID, self.associated_club.club_id) if self._initial_disband_timer_handle is not None: current_time = services.time_service().sim_now disband_ticks = max((self._initial_disband_timer_handle.finishing_time - current_time).in_ticks(), 0) else: disband_ticks = 0 writer.write_uint64(ClubGatheringKeys.DISBAND_TICKS, disband_ticks) if self._current_gathering_buff is not None: writer.write_uint64(ClubGatheringKeys.GATHERING_BUFF, self._current_gathering_buff.guid64) writer.write_uint64(ClubGatheringKeys.GATHERING_VIBE, self._current_gathering_vibe) if self._validity_household_id_override is not None: writer.write_uint64(ClubGatheringKeys.HOUSEHOLD_ID_OVERRIDE, self._validity_household_id_override) def _issue_requests(self): super()._issue_requests() request = AssociatedClubRequestFactory(self, callback_data=_RequestUserData(), job_type=self._default_job, request_priority=BouncerRequestPriority.EVENT_DEFAULT_JOB, user_facing=False, exclusivity=self.exclusivity) self.manager.bouncer.submit_request(request)
class FireSituation(SituationComplexCommon): INSTANCE_TUNABLES = {'victim_job': sims4.tuning.tunable.TunableTuple(situation_job=SituationJob.TunableReference(description='\n A reference to the SituationJob used during a fire.\n '), fire_panic_state=RoleState.TunableReference(description='The state while the sim is panicking due to fire.'), fire_unaware_state=RoleState.TunableReference(description='\n The state while the sim is unaware there is a \n fire on the lot.\n '), fire_safe_state=RoleState.TunableReference(description='\n The state while the Sim has made it safely away\n from the fire.\n '), post_fire_state=RoleState.TunableReference(description='\n The state the Sim is in after the fire has gone\n out.\n '), save_toddler_state=RoleState.TunableReference(description='\n The state the Sim is in while they are saving\n a toddler.\n '), tuning_group=GroupNames.SITUATION), 'got_to_safety_interaction': situations.situation_complex.TunableInteractionOfInterest(description='\n The interaction to look for when a Sim has routed off of the lot\n and safely escaped the fire.\n '), 'panic_interaction': situations.situation_complex.TunableInteractionOfInterest(description='\n The interaction that a sim runs while panicking. \n '), 'go_back_to_panic_interactions': situations.situation_complex.TunableInteractionOfInterest(description='\n The interactions to look for when a Sim has routed back on to a\n lot that is on fire which will cause the Sim to go back into panic\n mode.\n '), 'save_toddler_interaction': TunableReference(description='\n The interaction to push on a Sim to save a toddler from the fire.\n ', manager=services.get_instance_manager(sims4.resources.Types.INTERACTION), class_restrictions='SuperInteraction'), 'TIME_POST_FIRE_IN_SIM_MINUTES': TunableSimMinute(description='\n Number of Sim minutes that the situation can be in the _PostFireState\n before the situation ends.\n ', default=60)} REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES @classmethod def _states(cls): return (SituationStateData(1, _PanicState), SituationStateData(2, _UnawareState), SituationStateData(3, _SafeState), SituationStateData(4, _PostFireState), SituationStateData(5, _SaveToddlerState)) @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.victim_job.situation_job, cls.victim_job.fire_panic_state)] @classmethod def default_job(cls): return cls.victim_job.situation_job @classproperty def has_no_klout(cls): return True def start_situation(self): super().start_situation() self._change_state(_UnawareState()) def advance_to_panic(self): curr_state_type = type(self._cur_state) if curr_state_type == _UnawareState or curr_state_type == _PostFireState: self._change_state(_PanicState()) def advance_to_post_fire(self): self._change_state(_PostFireState()) def reset_to_unaware(self): if type(self._cur_state) != _UnawareState: self._cancel_duration_alarm() self._change_state(_UnawareState()) def on_remove(self): fire_service = services.get_fire_service() if fire_service is not None: for sim in self.all_sims_in_situation_gen(): fire_service.remove_fire_situation(sim) super().on_remove()
class AggregateSingleStateSituation(SituationComplexCommon): INSTANCE_TUNABLES = {'group_filter': TunableAggregateFilter.TunableReference(description='\n The aggregate filter that we use to find the sims for this\n situation.\n '), 'situation_job_mapping': TunableMapping(description='\n A mapping of filter term tag to situation job.\n \n The filter term tag is returned as part of the sim filters used to \n create the guest list for this particular background situation.\n \n The situation job is the job that the Sim will be assigned to in\n the background situation.\n ', key_name='filter_tag', key_type=TunableEnumEntry(description='\n The filter term tag returned with the filter results.\n ', tunable_type=FilterTermTag, default=FilterTermTag.NO_TAG), value_name='job_and_role', value_type=TunableTuple(description='\n The job and role state that the Sim will be put 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 '))), 'blacklist_job': SituationJob.TunableReference(description='\n The default job used for blacklisting Sims from being put into this\n AggregateSingleStateSituation.\n '), 'force_leave_on_exit': Tunable(description='\n If checked, then we will force the Sims to leave when they are\n removed from the situation. Otherwise we will just let the leave\n Situation pick them up as normal.\n ', tunable_type=bool, default=False)} REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES @classmethod def _verify_tuning_callback(cls): super()._verify_tuning_callback() for job_role in cls.situation_job_mapping.values(): if job_role.situation_job.sim_auto_invite.upper_bound > 0: logger.error('Situation Job {} used for an aggregate filter specifies Sim Auto Invite. This is not supported and can cause errors.', job_role.situation_job) @classmethod def _states(cls): return (SituationStateData(1, AggregateSingleStateSituationState),) @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(job_role.situation_job, job_role.role_state) for job_role in cls.situation_job_mapping.values()] @classmethod def get_predefined_guest_list(cls): guest_list = SituationGuestList(invite_only=True) situation_manager = services.get_zone_situation_manager() instanced_sim_ids = [sim.sim_id for sim in services.sim_info_manager().instanced_sims_gen()] household_sim_ids = [sim_info.id for sim_info in services.active_household().sim_info_gen()] auto_fill_blacklist = situation_manager.get_auto_fill_blacklist(sim_job=cls.blacklist_job) situation_sims = set() for situation in situation_manager.get_situations_by_tags(cls.tags): situation_sims.update(situation.invited_sim_ids) blacklist_sim_ids = set(itertools.chain(situation_sims, instanced_sim_ids, household_sim_ids, auto_fill_blacklist)) filter_results = services.sim_filter_service().submit_matching_filter(sim_filter=cls.group_filter, allow_yielding=False, blacklist_sim_ids=blacklist_sim_ids, gsi_source_fn=cls.get_sim_filter_gsi_name) if not filter_results: return for result in filter_results: job_role = cls.situation_job_mapping.get(result.tag, None) if job_role is None: continue guest_list.add_guest_info(SituationGuestInfo(result.sim_info.sim_id, job_role.situation_job, RequestSpawningOption.DONT_CARE, job_role.situation_job.sim_auto_invite_allow_priority)) return guest_list @classmethod def default_job(cls): pass def start_situation(self): super().start_situation() self._change_state(AggregateSingleStateSituationState()) def _on_remove_sim_from_situation(self, sim): super()._on_remove_sim_from_situation(sim) if self.force_leave_on_exit: services.get_zone_situation_manager().make_sim_leave_now_must_run(sim)
class LampoonPartySituation(SituationComplexCommon): INSTANCE_TUNABLES = { 'bartender': sims4.tuning.tunable.TunableTuple( situation_job=SituationJob.TunableReference( description= '\n The SituationJob for the Bartender.\n ' ), bartender_party_role_state=RoleState.TunableReference( description= "\n Bartender's role state to prepare drinks, socialize, etc. during the party.\n " ), tuning_group=GroupNames.ROLES), 'host': sims4.tuning.tunable.TunableTuple( situation_job=SituationJob.TunableReference( description= '\n The SituationJob for the host.\n ' ), host_party_role_state=RoleState.TunableReference( description= "\n The host's role state during the party.\n " ), tuning_group=GroupNames.ROLES), 'entertainer': sims4.tuning.tunable.TunableTuple( situation_job=SituationJob.TunableReference( description= '\n The SituationJob for the entertainer.\n ' ), entertainer_party_role_state=RoleState.TunableReference( description= "\n Entertainer's role state during the party.\n " ), tuning_group=GroupNames.ROLES), 'guest': sims4.tuning.tunable.TunableTuple( situation_job=SituationJob.TunableReference( description= '\n The SituationJob for the Guests.\n ' ), guest_party_role_state=RoleState.TunableReference( description= "\n Guest's role state during the party.\n " ), tuning_group=GroupNames.ROLES), 'guest_of_honor': sims4.tuning.tunable.TunableTuple( situation_job=SituationJob.TunableReference( description= '\n The SituationJob for the Guest of Honor.\n ' ), guest_of_honor_party_role_state=RoleState.TunableReference( description= "\n Guest of Honor's role state during the party.\n " ), tuning_group=GroupNames.ROLES) } REMOVE_INSTANCE_TUNABLES = ('venue_invitation_message', 'venue_situation_player_job') @classmethod def _states(cls): return (SituationStateData(1, _RoastState), ) @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.bartender.situation_job, cls.bartender.bartender_party_role_state), (cls.host.situation_job, cls.host.host_party_role_state), (cls.entertainer.situation_job, cls.entertainer.entertainer_party_role_state), (cls.guest.situation_job, cls.guest.guest_party_role_state), (cls.guest_of_honor.situation_job, cls.guest_of_honor.guest_of_honor_party_role_state)] @classmethod def default_job(cls): return cls.guest.situation_job def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def start_situation(self): super().start_situation() self._change_state(_RoastState())
class OpenStreetsAutonomySituation(situations.situation_complex.SituationComplexCommon): __qualname__ = 'OpenStreetsAutonomySituation' INSTANCE_TUNABLES = {'role': sims4.tuning.tunable.TunableTuple(situation_job=SituationJob.TunableReference(description='\n The situation job for the sim.\n '), do_stuff_role_state=RoleState.TunableReference(description='\n The role state for the sim doing stuff. This is the initial state.\n '), leave_role_state=RoleState.TunableReference(description='\n The role state for the sim leaving.\n ')), 'do_stuff_timeout': sims4.tuning.tunable.TunableSimMinute(description='\n The amount of time the sim does stuff before leaving.\n ', default=180), 'can_start_walkby_limiting_tags': TunableSet(description="\n Don't start a situation of this type if another situation is\n already running that has any of these tags in its tags field.\n \n For instance, if you only want one Streaker at a time you would\n create a new tag SITUATION_STREAKER. Then set that in both this\n field and in the tags field of situation_streaker.\n ", tunable=TunableEnumWithFilter(tunable_type=Tag, default=Tag.INVALID, filter_prefixes=['situation']))} REMOVE_INSTANCE_TUNABLES = situations.situation.Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES @staticmethod def _states(): return [(1, _DoStuffState), (2, _LeaveState)] @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.role.situation_job, cls.role.do_stuff_role_state)] @classmethod def default_job(cls): return cls.role.situation_job def start_situation(self): super().start_situation() self._change_state(_DoStuffState()) @classmethod def can_start_walkby(cls, lot_id): return not services.get_zone_situation_manager().is_situation_with_tags_running(cls.can_start_walkby_limiting_tags) @property def _should_cancel_leave_interaction_on_premature_removal(self): return True @classproperty def situation_serialization_option(cls): return situations.situation_types.SituationSerializationOption.OPEN_STREETS
class InviteOverSituation(VisitingNPCSituation): __qualname__ = 'InviteOverSituation' INSTANCE_TUNABLES = {'invited_job': sims4.tuning.tunable.TunableTuple(situation_job=SituationJob.TunableReference(description='\n A reference to the SituationJob used for the Sim invited over.\n '), ring_doorbell_state=RoleState.TunableReference(description='\n The state for telling a Sim to try to ring the doorbell followed by inviting themselves in.\n ')), 'purpose': sims4.tuning.tunable.TunableEnumEntry(description='\n The purpose/reason used to create the venue type specific visit situation,\n after the invited sim attempts to ring the door bell.\n This should be tuned to Invite In, but since that is a dynamic enum\n you must do it yourself.\n ', tunable_type=venues.venue_constants.NPCSummoningPurpose, default=venues.venue_constants.NPCSummoningPurpose.DEFAULT)} @staticmethod def _states(): return [(1, _RingDoorBellState)] @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.invited_job.situation_job, cls.invited_job.ring_doorbell_state)] @classmethod def default_job(cls): return cls.invited_job.situation_job @classproperty def supports_multiple_sims(cls): return False def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._invited_sim = None def start_situation(self): super().start_situation() self._change_state(_RingDoorBellState()) def _on_set_sim_job(self, sim, job_type): super()._on_set_sim_job(sim, job_type) self._invited_sim = sim def _on_sim_removed_from_situation_prematurely(self, sim): super()._on_sim_removed_from_situation_prematurely(sim) self._invited_sim = None def _invite_sim_in(self): if self._invited_sim is not None: services.current_zone().venue_service.venue.summon_npcs((self._invited_sim.sim_info,), self.purpose, self.initiating_sim_info) self._self_destruct()
class HospitalPatientSituation(PatientSituationBase): INSTANCE_TUNABLES = { 'admitted_role_state': RoleState.TunableReference( description= '\n A reference to the hospital patients admitted\n role state while in the situation. This is the\n state where the patient is assigned to a bed \n and the doctor is actively trying to diagnose\n the issue.\n ', tuning_group=PatientSituationBase.JOB_AND_STATE_GROUP, display_name='03_admitted_role_state'), 'diagnosed_role_state': RoleState.TunableReference( description= '\n A reference to the hospital patients diagnosed\n role state while in the situation. This is\n the state where the patient has been diagnosed \n but it still waiting for the doctor to treat\n them.\n ', tuning_group=PatientSituationBase.JOB_AND_STATE_GROUP, display_name='04_diagnosed_role_state'), 'go_to_diagnosed_interactions': TunableInteractionOfInterest( description= '\n The interactions to look for when a Sim has been diagnosed by a \n doctor and is now waiting for treatment.\n ', tuning_group=PatientSituationBase.STATE_ADVANCEMENT_GROUP), 'admitted_duration_for_time_jump': TunableSimMinute( description= '\n The amount of time allowed to pass before a Sim in the admitted\n state will be ignored on load with a time jump.\n ', default=180, tuning_group=PatientSituationBase.TIMEOUT_GROUP), 'diagnosed_duration_for_time_jump': TunableSimMinute( description= '\n The amount of time allowed to pass before a Sim in the diagnosed\n state will be ignored on load with a time jump.\n ', default=180, tuning_group=PatientSituationBase.TIMEOUT_GROUP), 'pre_diagnosed': Tunable( description= '\n If this is true then when the Sim is pre-rolled it will skip to the\n _DiagnosedState(). \n \n If it is False then it will default to pre-rolling\n the Sim to _AdmittedState().\n ', tunable_type=bool, default=False) } @classmethod def _states(cls): return (SituationStateData(1, ArrivingState), SituationStateData(2, WaitingState), SituationStateData(3, _AdmittedState), SituationStateData(4, _DiagnosedState), SituationStateData(5, TreatedState)) def _skip_ahead_for_preroll(self): if self.pre_diagnosed: self._change_state(_DiagnosedState()) else: self._change_state(_AdmittedState()) def _on_done_waiting(self): self._change_state(_AdmittedState()) @classmethod def should_state_type_load_after_time_jump(cls, state_type): if not super().should_state_type_load_after_time_jump(state_type): return False else: elapsed_time = services.current_zone( ).time_elapsed_since_last_save().in_minutes() if state_type is _AdmittedState: timeout = cls.admitted_duration_for_time_jump elif state_type is _DiagnosedState: timeout = cls.diagnosed_duration_for_time_jump else: timeout = None if timeout is not None and elapsed_time >= timeout: return False return True
class WalkbyRingDoorBellSituation(SituationComplexCommon): __qualname__ = 'WalkbyRingDoorBellSituation' INSTANCE_TUNABLES = { 'walker_job': sims4.tuning.tunable.TunableTuple( situation_job=SituationJob.TunableReference( description= '\n A reference to the SituationJob used for the Sim performing the walkby\n ' ), ring_doorbell_state=RoleState.TunableReference( description= '\n The state for telling a Sim to go and ring the doorbell. This is the initial state.\n ' ), mailbox_state=RoleState.TunableReference( description= '\n The state for telling a Sim to go wait by the mailbox. \n This is a fall back for when they cannot reach the front door.\n ' ), wait_for_invitation_state=RoleState.TunableReference( description= '\n The state for telling a Sim to wait for the other Sim to invite them in.\n ' ), leave_state=RoleState.TunableReference( description= "\n The state for the sim leaving if you don't invite them in.\n " ), tuning_group=GroupNames.SITUATION), 'wait_for_invitation_delay': sims4.tuning.tunable.TunableSimMinute( description= '\n The amount of time to wait for a Sim to greet the walker Sim.', default=60, tuning_group=GroupNames.SITUATION), 'can_start_walkby_limiting_tags': TunableSet( description= "\n Don't start a situation of this type if another situation is\n already running that has any of these tags in its tags field.\n Basically we don't want two sims ringing your doorbell at the same\n time because that would be weird.\n ", tunable=TunableEnumWithFilter(tunable_type=Tag, default=Tag.INVALID, filter_prefixes=['situation'])) } REMOVE_INSTANCE_TUNABLES = ( '_buff', '_cost', '_NPC_host_filter', '_NPC_hosted_player_tests', 'NPC_hosted_situation_start_message', 'NPC_hosted_situation_use_player_sim_as_filter_requester', 'NPC_hosted_situation_player_job', 'venue_types', 'venue_invitation_message', 'venue_situation_player_job', 'category', 'main_goal', 'minor_goal_chains', '_level_data', '_display_name', 'max_participants', '_initiating_sim_tests', 'targeted_situation', '_resident_job', '_icon', 'situation_description') @staticmethod def _states(): return [(2, _RingDoorBellState), (3, _MailboxState), (4, _WaitForInvitationState), (5, _LeaveState)] @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.walker_job.situation_job, cls.walker_job.ring_doorbell_state)] @classmethod def default_job(cls): return cls.walker_job.situation_job def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._walker = None self._state_interruptible_by_user_action = True def start_situation(self): super().start_situation() self._change_state(_RingDoorBellState()) def _on_set_sim_job(self, sim, job_type): super()._on_set_sim_job(sim, job_type) self._walker = sim def _on_remove_sim_from_situation(self, sim): super()._on_remove_sim_from_situation(sim) self._walker = None @property def _should_cancel_leave_interaction_on_premature_removal(self): return True def _on_wait_for_invitation_expired(self): pass @classmethod def can_start_walkby(cls, lot_id): if services.get_zone_situation_manager( ).is_situation_with_tags_running(cls.can_start_walkby_limiting_tags): return False active_lot_id = services.active_household_lot_id() if active_lot_id is None: return False return lot_id == active_lot_id @classproperty def situation_serialization_option(cls): return situations.situation_types.SituationSerializationOption.OPEN_STREETS
class WalkbyAmbientSituation(SituationComplexCommon): __qualname__ = 'WalkbyAmbientSituation' INSTANCE_TUNABLES = { 'walker_job': sims4.tuning.tunable.TunableTuple( situation_job=SituationJob.TunableReference( description= '\n A reference to the SituationJob used for the Sim performing the walkby\n ' ), walkby_state=RoleState.TunableReference( description='The state while the sim is walking by.'), flavor_interaction_state=RoleState.TunableReference( description= '\n The role state when the sim does a flavor interaction.\n ' ), tuning_group=GroupNames.SITUATION), 'flavor_affordances': sims4.tuning.tunable.TunableList( description= '\n When selected for walkby flavor the sim runs one of the affordances in\n this list.\n ', tunable=sims4.tuning.tunable.TunableReference( services.affordance_manager())), 'flavor_cooldown': TunableSimMinute( description= '\n The minimum amount of time from the end of one flavor action\n until the walkby sim can perform another.\n ', default=5, minimum=1, maximum=480), 'flavor_chance_to_start': sims4.tuning.tunable.TunablePercent( description= '\n This is the percentage chance that each walkby sim will start a flavor\n interaction, such as using the phone, on an\n ambient service ping. At most one will start per ping.\n ', default=1), 'can_start_walkby_limiting_tags': TunableSet( description= "\n Don't start a situation of this type if another situation is\n already running that has any of these tags in its tags field.\n \n For instance, if you only want one Streaker at a time you would\n create a new tag SITUATION_STREAKER. Then set that in both this\n field and in the tags field of situation_streaker.\n ", tunable=TunableEnumWithFilter(tunable_type=Tag, default=Tag.INVALID, filter_prefixes=['situation'])) } REMOVE_INSTANCE_TUNABLES = situations.situation.Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES @staticmethod def _states(): return [(1, _LeaveState), (2, _FlavorInteractionState), (3, _SocialState)] @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.walker_job.situation_job, cls.walker_job.walkby_state)] @classmethod def default_job(cls): return cls.walker_job.situation_job def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._walker = None self._flavor_cooldown_until = services.time_service( ).sim_now + clock.interval_in_sim_minutes(10) self._social_cooldown_until = services.time_service( ).sim_now + clock.interval_in_sim_minutes(10) self._other_social_situation = None self._social_interaction = None def start_situation(self): super().start_situation() self._change_state(_LeaveState()) def _save_custom_state(self, writer): uid = self._state_type_to_uid(_LeaveState) writer.write_uint32( situations.situation_complex.SituationComplexCommon.STATE_ID_KEY, uid) def _on_set_sim_job(self, sim, job_type): super()._on_set_sim_job(sim, job_type) self._walker = sim def _on_remove_sim_from_situation(self, sim): super()._on_remove_sim_from_situation(sim) self._walker = None def _on_sim_removed_from_situation_prematurely(self, sim): super()._on_sim_removed_from_situation_prematurely(sim) self.manager.add_sim_to_auto_fill_blacklist(sim.id) @classmethod def can_start_walkby(cls, lot_id): return not services.get_zone_situation_manager( ).is_situation_with_tags_running(cls.can_start_walkby_limiting_tags) @property def _should_cancel_leave_interaction_on_premature_removal(self): return True def get_sim_available_for_walkby_flavor(self): if self._cur_state is None or not self._cur_state._is_available_for_interruption( ): return if services.time_service().sim_now < self._flavor_cooldown_until: return if self._walker is not None and self._walker.opacity < 1.0: return if self._walker is not None and terrain.is_position_in_street( self._walker.position): return return self._walker def get_sim_available_for_social(self): if self._cur_state is None or not self._cur_state._is_available_for_interruption( ): return if services.time_service().sim_now < self._social_cooldown_until: return if self._walker is not None and self._walker.opacity < 1.0: return return self._walker def random_chance_to_start_flavor_interaction(self): return sims4.random.random_chance(self.flavor_chance_to_start * 100) def start_flavor_interaction(self): self._change_state(_FlavorInteractionState()) def start_social(self, other_situation, social_interaction=None): self._other_social_situation = other_situation self._social_interaction = social_interaction self._change_state(_SocialState()) def _on_flavor_finished(self): self._flavor_cooldown_until = services.time_service( ).sim_now + clock.interval_in_sim_minutes(self.flavor_cooldown) self._change_state(_LeaveState()) def _on_social_finished(self): self._other_social_situation = None self._social_interaction = None self._social_cooldown_until = services.time_service( ).sim_now + clock.interval_in_sim_minutes( services.current_zone().ambient_service.SOCIAL_COOLDOWN) self._change_state(_LeaveState()) @classproperty def situation_serialization_option(cls): return situations.situation_types.SituationSerializationOption.OPEN_STREETS
class RetailEmployeeSituation(BusinessEmployeeSituationMixin, SituationComplexCommon): class _EmployeeSituationState(HasTunableFactory, AutoFactoryInit, SituationState): FACTORY_TUNABLES = { 'role_state': RoleState.TunableReference( description= '\n The role state that is active on the employee for the duration\n of this state.\n ' ), 'timeout_min': TunableSimMinute( description= '\n The minimum amount of time, in Sim minutes, the employee will be\n in this state before moving on to a new state.\n ', default=10), 'timeout_max': TunableSimMinute( description= '\n The maximum amount of time, in Sim minutes, the employee will be\n in this state before moving on to a new state.\n ', default=30), 'push_interaction': TunableInteractionOfInterest( description= '\n If an interaction of this type is run by the employee, this\n state will activate.\n ' ) } def __init__(self, *args, state_name=None, **kwargs): super().__init__(*args, **kwargs) self.state_name = state_name def on_activate(self, reader=None): super().on_activate(reader) self.owner._set_job_role_state(self.owner.employee_job, self.role_state) timeout = random.randint(self.timeout_min, self.timeout_max) self._create_or_load_alarm(self.state_name, timeout, self._timeout_expired, reader=reader) def _timeout_expired(self, *_, **__): self._change_state(self.owner._choose_next_state()) INSTANCE_TUNABLES = { 'employee_job': SituationJob.TunableReference( description= '\n The situation job for the employee.\n '), 'role_state_go_to_store': RoleState.TunableReference( description= '\n The role state for getting the employee inside the store. This is\n the default role state and will be run first before any other role\n state can start.\n ' ), 'role_state_go_to_store_timeout': TunableSimMinute( description= "\n Automatically advance out of the role state after waiting for this\n duration. There's a number of reasons the employee can fail to exit\n the role state in a timely fashion, such as the register is blocked\n (by another employee clocking, even) and hijacked by a social.\n ", default=60), 'state_socialize': _EmployeeSituationState.TunableFactory( description= '\n The state during which employees socialize with customers.\n ', locked_args={'state_name': 'socialize'}), 'state_restock': _EmployeeSituationState.TunableFactory( description= '\n The state during which employees restock items.\n ', locked_args={'state_name': 'restock'}), 'state_clean': _EmployeeSituationState.TunableFactory( description= '\n The state during which employees clean the store.\n ', locked_args={'state_name': 'clean'}), 'state_slack_off': _EmployeeSituationState.TunableFactory( description= '\n The state during which employees slack off.\n ', locked_args={'state_name': 'slack_off'}), 'state_ring_up_customers': _EmployeeSituationState.TunableFactory( description= '\n The state during which employees will ring up customers.\n ', locked_args={'state_name': 'ring_up_customers'}), 'go_to_store_interaction': TunableInteractionOfInterest( description= '\n The interaction that, when run by an employee, will switch the\n situation state to start cleaning, upselling, restocking, etc.\n ' ), 'go_home_interaction': TunableInteractionOfInterest( description= '\n The interaction that, when run on an employee, will have them end\n this situation and go home.\n ' ) } REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._employee_sim_info = None self._register_test_event_for_keys( TestEvent.InteractionComplete, self.state_socialize.push_interaction.custom_keys_gen()) self._register_test_event_for_keys( TestEvent.InteractionComplete, self.state_restock.push_interaction.custom_keys_gen()) self._register_test_event_for_keys( TestEvent.InteractionComplete, self.state_clean.push_interaction.custom_keys_gen()) self._register_test_event_for_keys( TestEvent.InteractionComplete, self.state_slack_off.push_interaction.custom_keys_gen()) self._register_test_event_for_keys( TestEvent.InteractionComplete, self.state_ring_up_customers.push_interaction.custom_keys_gen()) self._register_test_event_for_keys( TestEvent.InteractionComplete, self.go_home_interaction.custom_keys_gen()) @classmethod def _states(cls): return (SituationStateData(1, _GoToStoreState), SituationStateData(2, cls.state_socialize), SituationStateData(5, cls.state_restock), SituationStateData(6, cls.state_clean), SituationStateData(7, cls.state_slack_off), SituationStateData(8, cls.state_ring_up_customers)) @classmethod def _state_to_uid(cls, state_to_find): state_type_to_find = type(state_to_find) if state_type_to_find is _GoToStoreState: return 1 state_name = getattr(state_to_find, 'state_name', None) if state_name is None: return cls.INVALID_STATE_UID for state_data in cls._states(): if getattr(state_data.state_type, 'state_name', None) == state_name: return state_data.uid return cls.INVALID_STATE_UID def _save_custom_situation(self, writer): super()._save_custom_situation(writer) writer.write_uint64('original_duration', self._original_duration) def handle_event(self, sim_info, event, resolver): if event == TestEvent.InteractionComplete: target_sim = resolver.interaction.get_participant( ParticipantType.TargetSim) if target_sim is None: target_sim = resolver.interaction.get_participant( ParticipantType.Actor) target_sim = getattr(target_sim, 'sim_info', target_sim) if target_sim is self._employee_sim_info: if resolver(self.state_socialize.push_interaction): self._change_state(self.state_socialize()) elif resolver(self.state_restock.push_interaction): self._change_state(self.state_restock()) elif resolver(self.state_clean.push_interaction): self._change_state(self.state_clean()) elif resolver(self.state_slack_off.push_interaction): self._change_state(self.state_slack_off()) elif resolver(self.state_ring_up_customers.push_interaction): self._change_state(self.state_ring_up_customers()) elif resolver(self.go_home_interaction): self._on_business_closed() super().handle_event(sim_info, event, resolver) @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.employee_job, cls.role_state_go_to_store)] @classmethod def default_job(cls): return cls.employee_job def start_situation(self): super().start_situation() self._change_state(_GoToStoreState()) @classmethod def get_sims_expected_to_be_in_situation(cls): return 1 @classproperty def situation_serialization_option(cls): return situations.situation_types.SituationSerializationOption.LOT def get_employee_sim_info(self): if self._employee_sim_info is not None: return self._employee_sim_info return next(self._guest_list.invited_sim_infos_gen(), None) def _on_set_sim_job(self, sim, job_type): super()._on_set_sim_job(sim, job_type) self._employee_sim_info = sim.sim_info self._update_work_buffs(from_load=True) @property def _is_clocked_in(self): business_manager = services.business_service( ).get_business_manager_for_zone() if business_manager is None: return False return business_manager.is_employee_clocked_in(self._employee_sim_info) def _choose_next_state(self): valid_states = [ self.state_socialize, self.state_restock, self.state_clean, self.state_ring_up_customers ] random_state = random.choice(valid_states) return random_state()
class SituationComplexExperiment(SituationComplexCommon): __qualname__ = 'SituationComplexExperiment' INSTANCE_TUNABLES = {'test_job': sims4.tuning.tunable.TunableTuple(situation_job=SituationJob.TunableReference(description='A reference to a SituationJob that can be performed at this Situation.'), friendly_role_state=RoleState.TunableReference(description='A role state the sim assigned to the job will perform'), angry_role_state=RoleState.TunableReference(description='A role state the sim assigned to the job will perform')), '_host_job': sims4.tuning.tunable.TunableTuple(situation_job=SituationJob.TunableReference(description='A reference to a SituationJob that can be performed at this Situation.'), default_role_state=RoleState.TunableReference(description='A role state the sim assigned to the job will perform')), 'mean_test': event_testing.tests_with_data.TunableParticipantRanInteractionTest(locked_args={'participant': ParticipantType.TargetSim, 'interaction_outcome': None, 'running_time': None, 'tooltip': None}, description='Test for a mean interaction that will trigger a state change')} @staticmethod def _states(): return [(1, AngrySituationState), (2, FriendlySituationState)] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) reader = self._seed.custom_init_params_reader if reader is None: self._test_bool = True else: self._test_bool = reader.read_bool('test_bool', True) @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.test_job.situation_job, cls.test_job.friendly_role_state), (cls._host_job.situation_job, cls._host_job.default_role_state)] @classmethod def default_job(cls): return cls.test_job.situation_job @classmethod def resident_job(cls): return cls._host_job.situation_job def _save_custom_situation(self, writer): super()._save_custom_situation(writer) writer.write_bool('test_bool', False) def start_situation(self): super().start_situation() self._change_state(FriendlySituationState())
class BirthdayPartySituation(SituationComplexCommon): __qualname__ = 'BirthdayPartySituation' INSTANCE_TUNABLES = { 'celebrant': sims4.tuning.tunable.TunableTuple( situation_job=SituationJob.TunableReference( description= '\n The SituationJob for the celebrant.' ), celebrant_gather_role_state=RoleState.TunableReference( description= "\n Celebrant's role state before the celebration (gather phase)." ), celebrant_reception_role_state=RoleState.TunableReference( description= "\n Celebrant's role state after the celebration (eat, drink, socialize, dance)." ), tuning_group=GroupNames.ROLES), 'bartender': sims4.tuning.tunable.TunableTuple( situation_job=SituationJob.TunableReference( description= '\n The SituationJob for the Bartender.' ), bartender_pre_reception_role_state=RoleState.TunableReference( description= "\n Bartender's role state to prepare drinks and socialize with guests." ), bartender_reception_role_state=RoleState.TunableReference( description= "\n Bartender's role state to prepare drinks, socialize, etc. during the reception." ), tuning_group=GroupNames.ROLES), 'caterer': sims4.tuning.tunable.TunableTuple( situation_job=SituationJob.TunableReference( description= '\n The SituationJob for the caterer.'), caterer_prep_role_state=RoleState.TunableReference( description= "\n Caterer's role state for preparing cake and meal for guests." ), caterer_serve_role_state=RoleState.TunableReference( description= "\n Caterer's role state for serving the guests." ), tuning_group=GroupNames.ROLES), 'entertainer': sims4.tuning.tunable.TunableTuple( situation_job=SituationJob.TunableReference( description= '\n The SituationJob for the entertainer.' ), entertainer_prep_reception_state=RoleState.TunableReference( description= "\n Entertainer's role state before reception." ), entertainer_reception_role_state=RoleState.TunableReference( description= "\n Entertainer's role state during reception." ), tuning_group=GroupNames.ROLES), 'guest': sims4.tuning.tunable.TunableTuple( situation_job=SituationJob.TunableReference( description= '\n The SituationJob for the Guests.'), guest_gather_role_state=RoleState.TunableReference( description= "\n Guest's role state before the celebration (gather phase)." ), guest_gather_impatient_role_state=RoleState.TunableReference( description= "\n Guest's role state if it is taking too long for the celebration to start." ), guest_reception_role_state=RoleState.TunableReference( description= "\n Guest's role state after the celebration (now they can eat the cake)." ), tuning_group=GroupNames.ROLES), 'start_reception': TunableInteractionOfInterest( description= '\n This is a birthday cake interaction where starting this interaction starts \n the cake reception phase.', tuning_group=GroupNames.TRIGGERS), 'guests_become_impatient_timeout': TunableSimMinute( description= '\n If the celebration is not started in this amount of time the guests will grow impatient.', default=120, tuning_group=GroupNames.TRIGGERS) } REMOVE_INSTANCE_TUNABLES = ( '_NPC_host_filter', '_NPC_hosted_player_tests', 'NPC_hosted_situation_start_message', 'NPC_hosted_situation_player_job', 'NPC_hosted_situation_use_player_sim_as_filter_requester', 'venue_invitation_message', 'venue_situation_player_job') @staticmethod def _states(): return [(1, GatherState), (2, ImpatientGatherState), (3, ReceptionState)] @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.celebrant.situation_job, cls.celebrant.celebrant_gather_role_state), (cls.bartender.situation_job, cls.bartender.bartender_pre_reception_role_state), (cls.caterer.situation_job, cls.caterer.caterer_prep_role_state), (cls.entertainer.situation_job, cls.entertainer.entertainer_prep_reception_state), (cls.guest.situation_job, cls.guest.guest_gather_role_state)] @classmethod def default_job(cls): return cls.guest.situation_job def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._celebrant_id = None def start_situation(self): super().start_situation() self._change_state(GatherState()) def _on_set_sim_job(self, sim, job_type): super()._on_set_sim_job(sim, job_type) if job_type is self.celebrant.situation_job: self._celebrant_id = sim.sim_id def _is_birthday_starting(self, event, resolver): if event == TestEvent.InteractionStart and resolver( self.start_reception): participants = resolver.get_participants(ParticipantType.Actor) while True: for sim_info in participants: while sim_info.id == self._celebrant_id: return True return False
class InviteToSituation(situations.situation_complex.SituationComplexCommon): INSTANCE_TUNABLES = {'invited_job': sims4.tuning.tunable.TunableTuple(situation_job=SituationJob.TunableReference(description='\n A reference to the SituationJob used for the Sims invited to.\n '), invited_to_state=RoleState.TunableReference(description='\n The state for telling a sim to wait. They will momentarily be\n pulled from this situation by a visit or venue situation.\n ')), 'purpose': sims4.tuning.tunable.TunableEnumEntry(description='\n The purpose/reason used to perform the venue specific operation\n to get this sim in the appropriate situation.\n This should be tuned to Invite In, but since that is a dynamic enum\n you must do it yourself.\n ', tunable_type=venues.venue_constants.NPCSummoningPurpose, default=venues.venue_constants.NPCSummoningPurpose.DEFAULT)} REMOVE_INSTANCE_TUNABLES = Situation.SITUATION_USER_FACING_REMOVE_INSTANCE_TUNABLES @classmethod def _states(cls): return (SituationStateData(1, _WaitState),) @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.invited_job.situation_job, cls.invited_job.invited_to_state)] @classmethod def default_job(cls): return cls.invited_job.situation_job def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._tick_alarm_handle = None def start_situation(self): super().start_situation() self._change_state(_WaitState()) def _issue_requests(self): pass def on_arrived(self): zone = services.current_zone() venue = zone.venue_service.active_venue for sim_info in self._seed.invited_sim_infos_gen(): if sim_info.is_npc: venue.summon_npcs((sim_info,), self.purpose) @classmethod def get_player_greeted_status_from_seed(cls, situation_seed): for sim_info in situation_seed.invited_sim_infos_gen(): if sim_info.is_npc: if sim_info.lives_here: return GreetedStatus.GREETED return GreetedStatus.NOT_APPLICABLE
class RetailCustomerSituation(BusinessSituationMixin, SituationComplexCommon): INSTANCE_TUNABLES = { 'customer_job': SituationJob.TunableReference( description= '\n The situation job for the customer.\n '), 'role_state_go_to_store': RoleState.TunableReference( description= '\n The role state for getting the customer inside the store. This is\n the default role state and will be run first before any other role\n state can start.\n ' ), 'role_state_browse': OptionalTunable( description= '\n If enabled, the customer will be able to browse items.\n ', tunable=TunableTuple( role_state=RoleState.TunableReference( description= '\n The role state for the customer browsing items.\n ' ), browse_time_min=TunableSimMinute( description= '\n The minimum amount of time, in sim minutes, the customer\n will browse before moving on to the next state. When the\n customer begins browsing, a random time will be chosen\n between the min and max browse time.\n ', default=10), browse_time_max=TunableSimMinute( description= '\n The maximum amount of time, in sim minutes, the customer\n will browse before moving on to the next state. When the\n customer begins browsing, a random time will be chosen\n between the min and max browse time.\n ', default=20), browse_time_extension_tunables=OptionalTunable( TunableTuple( description= '\n A set of tunables related to browse time extensions.\n ', extension_perk=TunableReference( description= '\n Reference to a perk that, if unlocked, will increase\n browse time by a set amount.\n ', manager=services.get_instance_manager( sims4.resources.Types.BUCKS_PERK)), time_extension=TunableSimMinute( description= '\n The amount of time, in Sim minutes, that browse time\n will be increased by if the specified "extension_perk"\n is unlocked.\n ', default=30))))), 'role_state_buy': OptionalTunable( description= '\n If enabled, the customer will be able to buy items.\n ', tunable=TunableTuple( role_state=RoleState.TunableReference( description= '\n The role state for the customer buying items.\n ' ), price_range=TunableInterval( description= '\n The minimum and maximum price of items this customer will\n buy.\n ', tunable_type=int, default_lower=1, default_upper=100, minimum=1))), 'role_state_loiter': RoleState.TunableReference( description= '\n The role state for the customer loitering. If Buy Role State and\n Browse Role State are both disabled, the Sim will fall back to\n loitering until Total Shop Time runs out.\n ' ), 'go_to_store_interaction': TunableInteractionOfInterest( description= '\n The interaction that, when run by a customer, will switch the\n situation state to start browsing, buying, or loitering.\n ' ), 'total_shop_time_max': TunableSimMinute( description= "\n The maximum amount of time, in sim minutes, a customer will shop.\n This time starts when they enter the store. At the end of this\n time, they'll finish up whatever their current interaction is and\n leave.\n ", default=30), 'total_shop_time_min': TunableSimMinute( description= "\n The minimum amount of time, in sim minutes, a customer will shop.\n This time starts when they enter the store. At the end of this\n time, they'll finish up whatever their current interaction is and\n leave.\n ", default=1), 'buy_interaction': TunableInteractionOfInterest( description= '\n The interaction that, when run by a customer, buys an object.\n ' ), 'initial_purchase_intent': TunableInterval( description= "\n The customer's purchase intent statistic is initialized to a random\n value in this interval when they enter the store.\n ", tunable_type=int, default_lower=0, default_upper=100), 'purchase_intent_extension_tunables': OptionalTunable( TunableTuple( description= '\n A set of tunables related to purchase intent extensions.\n ', extension_perk=TunableReference( description= '\n Reference to a perk that, if unlocked, will increase purchase\n intent by a set amount.\n ', manager=services.get_instance_manager( sims4.resources.Types.BUCKS_PERK)), purchase_intent_extension=TunableRange( description= '\n The amount to increase the base purchase intent statistic by if\n the specified "extension_perk" is unlocked.\n ', tunable_type=int, default=5, minimum=0, maximum=100))), 'purchase_intent_empty_notification': TunableUiDialogNotificationSnippet( description= '\n Notification shown by customer when purchase intent hits bottom and\n the customer leaves.\n ' ), 'nothing_in_price_range_notification': TunableUiDialogNotificationSnippet( description= "\n Notification shown by customers who are ready to buy but can't find\n anything in their price range.\n " ), '_situation_start_tests': TunableCustomerSituationInitiationSet( description= '\n A set of tests that will be run when determining if this situation\n can be chosen to start. \n ' ) } CONTINUE_SHOPPING_THRESHOLD = TunableSimMinute( description= "\n If the customer has this much time or more left in their total shop\n time, they'll start the browse/buy process over again after purchasing\n something. If they don't have this much time remaining, they'll quit\n shopping.\n ", default=30) PRICE_RANGE = TunableTuple( description= '\n Statistics that are set to the min and max price range statistics.\n These are automatically added to the customer in this situation and\n will be updated accordingly.\n \n The stats should not be persisted -- the situation will readd them\n on load.\n ', min=Statistic.TunablePackSafeReference(), max=Statistic.TunablePackSafeReference()) PURCHASE_INTENT_STATISTIC = Statistic.TunablePackSafeReference( description= "\n A statistic added to customers that track their intent to purchase\n something. At the minimum value they will leave, and at max value they\n will immediately try to buy something. Somewhere in between, there's a\n chance for them to not buy something when they go to the buy state.\n " ) PURCHASE_INTENT_CHANCE_CURVE = TunableCurve( description= '\n A mapping of Purchase Intent Statistic value to the chance (0-1) that\n the customer will buy something during the buy state.\n ', x_axis_name='Purchase Intent', y_axis_name='Chance') REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES @classmethod def can_start_situation(cls, resolver): return cls._situation_start_tests.run_tests(resolver) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._customer = None self._showing_purchase_intent = False reader = self._seed.custom_init_params_reader if reader is None: self._saved_purchase_intent = None else: self._saved_purchase_intent = reader.read_int64( 'purchase_intent', None) self._min_price_range_multiplier = 1 self._max_price_range_multiplier = 1 self._total_shop_time_multiplier = 1 self._purchase_intent_watcher_handle = None def _save_custom_situation(self, writer): super()._save_custom_situation(writer) if self._customer is not None: purchase_intent = self._customer.get_stat_value( self.PURCHASE_INTENT_STATISTIC) writer.write_int64('purchase_intent', int(purchase_intent)) @classmethod def _states(cls): return (SituationStateData(1, _GoToStoreState), SituationStateData(2, _BrowseState), SituationStateData(3, _BuyState), SituationStateData(4, _LoiterState)) @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.customer_job, cls.role_state_go_to_store)] @classmethod def default_job(cls): return cls.customer_job def start_situation(self): super().start_situation() self._change_state(_GoToStoreState()) @classmethod def get_sims_expected_to_be_in_situation(cls): return 1 @classproperty def situation_serialization_option(cls): return situations.situation_types.SituationSerializationOption.LOT def validate_customer(self, sim_info): if self._customer is None: return False return self._customer.sim_info is sim_info def _on_set_sim_job(self, sim, job_type): super()._on_set_sim_job(sim, job_type) self._customer = sim self._update_price_range_statistics() self._initialize_purchase_intent() def _on_remove_sim_from_situation(self, sim): sim_job = self.get_current_job_for_sim(sim) super()._on_remove_sim_from_situation(sim) self._remove_purchase_intent() self._customer = None services.get_zone_situation_manager().add_sim_to_auto_fill_blacklist( sim.id, sim_job) self._self_destruct() def _situation_timed_out(self, *args, **kwargs): if not isinstance(self._cur_state, _BuyState): super()._situation_timed_out(*args, **kwargs) def adjust_browse_time(self, multiplier): if type(self._cur_state) is _BrowseState: self._cur_state.adjust_timeout(multiplier) def adjust_total_shop_time(self, multiplier): if multiplier == 0: self._self_destruct() elif type(self._cur_state) is _GoToStoreState: self._total_shop_time_multiplier *= multiplier else: remaining_minutes = self._get_remaining_time_in_minutes() remaining_minutes *= multiplier self.change_duration(remaining_minutes) def adjust_price_range(self, min_multiplier=1, max_multiplier=1): if self.role_state_buy is None: return self._min_price_range_multiplier *= min_multiplier self._max_price_range_multiplier *= max_multiplier self._update_price_range_statistics() def _update_price_range_statistics(self): (min_price, max_price) = self._get_min_max_price_range() if self.PRICE_RANGE.min is not None: min_stat = self._customer.get_statistic(self.PRICE_RANGE.min) min_stat.set_value(min_price) if self.PRICE_RANGE.max is not None: max_stat = self._customer.get_statistic(self.PRICE_RANGE.max) max_stat.set_value(max_price) def _get_min_max_price_range(self): price_range = self.role_state_buy.price_range return (max(0, price_range.lower_bound * self._min_price_range_multiplier), max(1, price_range.upper_bound * self._max_price_range_multiplier)) def _initialize_purchase_intent(self): if self.role_state_buy is None: return if self._saved_purchase_intent is None: purchase_intent = random.randint( self.initial_purchase_intent.lower_bound, self.initial_purchase_intent.upper_bound) if self.purchase_intent_extension_tunables is not None: active_household = services.active_household() if active_household is not None: if active_household.bucks_tracker.is_perk_unlocked( self.purchase_intent_extension_tunables. extension_perk): purchase_intent += self.purchase_intent_extension_tunables.purchase_intent_extension purchase_intent = sims4.math.clamp( self.PURCHASE_INTENT_STATISTIC.min_value + 1, purchase_intent, self.PURCHASE_INTENT_STATISTIC.max_value - 1) else: purchase_intent = self._saved_purchase_intent tracker = self._customer.get_tracker(self.PURCHASE_INTENT_STATISTIC) tracker.set_value(self.PURCHASE_INTENT_STATISTIC, purchase_intent, add=True) self._purchase_intent_watcher_handle = tracker.add_watcher( self._purchase_intent_watcher) if self._on_social_group_changed not in self._customer.on_social_group_changed: self._customer.on_social_group_changed.append( self._on_social_group_changed) def _remove_purchase_intent(self): if self._customer is not None: if self._purchase_intent_watcher_handle is not None: tracker = self._customer.get_tracker( self.PURCHASE_INTENT_STATISTIC) tracker.remove_watcher(self._purchase_intent_watcher_handle) self._purchase_intent_watcher_handle = None tracker.remove_statistic(self.PURCHASE_INTENT_STATISTIC) if self._on_social_group_changed in self._customer.on_social_group_changed: self._customer.on_social_group_changed.remove( self._on_social_group_changed) self._set_purchase_intent_visibility(False) def _on_social_group_changed(self, sim, group): if self._customer in group: if self._on_social_group_members_changed not in group.on_group_changed: group.on_group_changed.append( self._on_social_group_members_changed) elif self._on_social_group_members_changed in group.on_group_changed: group.on_group_changed.remove( self._on_social_group_members_changed) def _on_social_group_members_changed(self, group): if self._customer is not None: employee_still_in_group = False business_manager = services.business_service( ).get_business_manager_for_zone() if self._customer in group: for sim in group: if not business_manager.is_household_owner( sim.household_id): if business_manager.is_employee(sim.sim_info): employee_still_in_group = True break employee_still_in_group = True break if employee_still_in_group: self._set_purchase_intent_visibility(True) else: self._set_purchase_intent_visibility(False) def on_sim_reset(self, sim): super().on_sim_reset(sim) if isinstance(self._cur_state, _BuyState) and self._customer is sim: new_buy_state = _BuyState() new_buy_state.object_id = self._cur_state.object_id self._change_state(new_buy_state) def _set_purchase_intent_visibility(self, toggle): if self._showing_purchase_intent is not toggle and ( not toggle or isinstance(self._cur_state, _BrowseState)): self._showing_purchase_intent = toggle stat = self._customer.get_statistic(self.PURCHASE_INTENT_STATISTIC, add=False) if stat is not None: value = stat.get_value() self._send_purchase_intent_message(stat.stat_type, value, value, toggle) def _purchase_intent_watcher(self, stat_type, old_value, new_value): if stat_type is not self.PURCHASE_INTENT_STATISTIC: return self._send_purchase_intent_message(stat_type, old_value, new_value, self._showing_purchase_intent) if new_value == self.PURCHASE_INTENT_STATISTIC.max_value: self._on_purchase_intent_max() elif new_value == self.PURCHASE_INTENT_STATISTIC.min_value: self._on_purchase_intent_min() def _send_purchase_intent_message(self, stat_type, old_value, new_value, toggle): business_manager = services.business_service( ).get_business_manager_for_zone() if business_manager is not None and business_manager.is_owner_household_active: op = PurchaseIntentUpdate( self._customer.sim_id, stat_type.convert_to_normalized_value(old_value), stat_type.convert_to_normalized_value(new_value), toggle) distributor.system.Distributor.instance().add_op( self._customer, op) def _on_purchase_intent_max(self): if isinstance(self._cur_state, _BuyState): return if isinstance(self._cur_state, _GoToStoreState): self._set_shop_duration() self._change_state(_BuyState()) def _on_purchase_intent_min(self): resolver = SingleSimResolver(self._customer) dialog = self.purchase_intent_empty_notification( self._customer, resolver) dialog.show_dialog() self._self_destruct() def _choose_starting_state(self): if self.role_state_browse is not None: return _BrowseState() if self.role_state_buy is not None: return _BuyState() return _LoiterState() def _choose_post_browse_state(self): if self._customer is None: return if self.role_state_buy is not None: stat = self._customer.get_statistic(self.PURCHASE_INTENT_STATISTIC, add=False) if stat is not None: value = stat.get_value() chance = self.PURCHASE_INTENT_CHANCE_CURVE.get(value) if random.random() > chance: return _BrowseState() self._set_purchase_intent_visibility(False) return _BuyState() return _LoiterState() def _choose_post_buy_state(self): minutes_remaining = self._get_remaining_time_in_minutes() if minutes_remaining < self.CONTINUE_SHOPPING_THRESHOLD: return if self.role_state_browse is not None: return _BrowseState() return _LoiterState() def _set_shop_duration(self): shop_time = random.randint(self.total_shop_time_min, self.total_shop_time_max) shop_time *= self._total_shop_time_multiplier self.change_duration(shop_time)
class CareerEventSituation(SituationComplexCommon): class _SituationTravelRequestCareerEvent(_SituationTravelRequestDisallow): def __call__(self, user_facing_situation, travel_situation_type, travel_request_fn, is_career_event=False, **kwargs): if is_career_event: return travel_request_fn() if self.dialog is not None: dialog = self.dialog(user_facing_situation._sim, SingleSimResolver(user_facing_situation._sim)) dialog.show_dialog() @property def restrict(self): return SituationTravelRequestType.CAREER_EVENT INSTANCE_TUNABLES = {'user_job': TunableTuple(description='\n The job and role which the career 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 ')), 'build_buy_lock_reason': OptionalTunable(description='\n If enabled then build buy will be disabled during this situation.\n ', tunable=TunableLocalizedString(description='\n The reason build buy is locked. For this case, it is because \n build buy is not allowed during active career. Used on the disabled \n buildbuy button in the HUD.\n ')), 'travel_request_behavior': _SituationTravelRequestCareerEvent.TunableFactory(), 'user_facing_type_override': OptionalTunable(description="\n If enabled, sets and override for the type of user facing career\n event this is. If this is disabled, it'll be defaulted to\n CAREER_EVENT. This is mostly used by UI and in general should not be\n necessary. In some cases, like Acting, we want to indicate to UI\n that this is different from other career events.\n ", tunable=TunableEnumEntry(description='\n The situation event type to override this to.\n ', tunable_type=SituationUserFacingType, default=SituationUserFacingType.CAREER_EVENT, invalid_enums=(SituationUserFacingType.CAREER_EVENT,))), 'destroy_situations_on_remove': TunableList(description='\n Other situations to destroy when this situation is being destroyed.\n ', tunable=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.SITUATION)))} REMOVE_INSTANCE_TUNABLES = ('_resident_job', 'recommended_job_object_notification', 'recommended_job_object_text', 'force_invite_only', 'duration', 'targeted_situation', '_relationship_between_job_members', '_implies_greeted_status', '_survives_active_household_change') + Situation.SITUATION_START_FROM_UI_REMOVE_INSTANCE_TUNABLES def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._sim = None self._career_session_extended = False reader = self._seed.custom_init_params_reader if reader is not None: self._career_session_extended = reader.read_bool(CAREER_SESSION_EXTENDED, False) @classmethod def _states(cls): return (SituationStateData(1, CareerEventSituationState),) @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.user_job.situation_job, cls.user_job.role_state)] @classmethod def default_job(cls): return cls.user_job.situation_job def _send_build_buy_lock(self): if self.build_buy_lock_reason is not None: op = BuildBuyLockUnlock(True, self.build_buy_lock_reason) distributor.system.Distributor.instance().add_op_with_no_owner(op) def start_situation(self): super().start_situation() self._change_state(CareerEventSituationState()) self._send_build_buy_lock() def load_situation(self): result = super().load_situation() if result: self._send_build_buy_lock() return result def on_remove(self): super().on_remove() if self.build_buy_lock_reason is not None: op = BuildBuyLockUnlock(False) distributor.system.Distributor.instance().add_op_with_no_owner(op) situation_manager = services.get_zone_situation_manager() if situation_manager is not None: for situation_type in self.destroy_situations_on_remove: for situation_to_destroy in situation_manager.get_situations_by_type(situation_type): if situation_to_destroy is not None: situation_manager.destroy_situation_by_id(situation_to_destroy.id) @property def user_facing_type(self): if self.user_facing_type_override: return self.user_facing_type_override return SituationUserFacingType.CAREER_EVENT def build_situation_start_message(self): msg = super().build_situation_start_message() msg.is_active_career = True msg.has_stayed_late = self._career_session_extended return msg def build_situation_duration_change_op(self): msg = super().build_situation_duration_change_op() msg.has_stayed_late = self._career_session_extended return msg def _on_set_sim_job(self, sim, job_type): super()._on_set_sim_job(sim, job_type) self._sim = sim def _save_custom_situation(self, writer): super()._save_custom_situation(writer) writer.write_bool(CAREER_SESSION_EXTENDED, self._career_session_extended) def get_situation_goal_actor(self): return self._sim.sim_info def on_career_session_extended(self, new_duration): self._career_session_extended = True self.change_duration(new_duration)