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 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 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 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 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 AnchoredOpenStreetsAutonomySituation(WalkbyLimitingTagsMixin, GroupAnchoredAutonomySituationCommon): INSTANCE_TUNABLES = {'role': sims4.tuning.tunable.TunableTuple(situation_job=SituationJob.TunableReference(description='\n The situation job for all sims in this situation.\n '), arriving_role_state=RoleState.TunableReference(description='\n The role state for the sim arriving on the spawn point and waiting \n for the rest of the group. This is the initial state.\n '), do_stuff_role_state=RoleState.TunableReference(description='\n The role state for the sim doing stuff.\n '), leave_role_state=RoleState.TunableReference(description='\n The role state for the sim leaving.\n '), tuning_group=GroupNames.ROLES), 'group_filter': sims4.tuning.tunable.TunableReference(description="\n The group filter for this walkby. If set, this filter will be used \n instead of the filter tuned in the walker_job. If it's None, the \n filter in the walker_job will be used. Note that all sims spawned \n with this filter will be put into the walker_job job.\n ", manager=services.get_instance_manager(sims4.resources.Types.SIM_FILTER), class_restrictions=filters.tunable.TunableAggregateFilter, tuning_group=GroupNames.ROLES), 'wait_for_arrival_timeout': sims4.tuning.tunable.TunableSimMinute(description='\n The amount of time the sim waits at the spawn point before doing\n stuff.\n ', default=30, tuning_group=GroupNames.TRIGGERS), 'do_stuff_timeout': sims4.tuning.tunable.TunableSimMinute(description='\n The amount of time the sim does stuff before leaving.\n ', default=180, tuning_group=GroupNames.TRIGGERS)} REMOVE_INSTANCE_TUNABLES = situations.situation.Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES @classmethod def _states(cls): return (SituationStateData(1, _ArrivingState), SituationStateData(2, _DoStuffState), SituationStateData(3, _LeaveState)) @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.role.situation_job, cls.role.arriving_role_state)] @classmethod def default_job(cls): return cls.role.situation_job @property def guests(self): return self._guests def start_situation(self): super().start_situation() def not_on_active_lot(obj): return not obj.is_on_active_lot() self._anchor_position = self.get_new_anchor_position(self.object_anchor_tag, test_func=not_on_active_lot) self._change_state(_ArrivingState()) def _on_set_sim_job(self, sim, job_type): super()._on_set_sim_job(sim, job_type) if self._cur_state.transition_to_doing_stuff_when_full() and (self.group_filter is None or len(self._guests) >= self.group_filter.get_filter_count()): self._change_state(_DoStuffState()) def _on_sim_removed_from_situation_prematurely(self, sim, sim_job): super()._on_sim_removed_from_situation_prematurely(sim, sim_job) self.manager.add_sim_to_auto_fill_blacklist(sim.id, sim_job) @classmethod def get_sims_expected_to_be_in_situation(cls): return cls.group_filter.get_filter_count() @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 GymVisitorSituation(SituationComplexCommon): INSTANCE_TUNABLES = { 'situation_default_job': SituationJob.TunableReference( description= '\n The default job that a visitor will be in during the situation.\n ' ), 'exercise_state': _ExerciseState.TunableFactory( description= '\n The main state of the situation. This is where Sims will do \n everything except for arrive and leave.\n ', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP, display_name='01_exercise_state'), 'take_shower_state': _TakeShowerState.TunableFactory( description= '\n The main state of the situation. This is where Sims will do \n everything except for arrive and leave.\n ', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP, display_name='02_take_shower_state'), 'do_stuff_state': _DoStuffState.TunableFactory( description= '\n The main state of the situation. This is where Sims will do \n everything except for arrive and leave.\n ', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP, display_name='03_do_stuff_state') } REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES @classmethod def default_job(cls): return cls.situation_default_job @classmethod def _states(cls): return [ SituationStateData(1, _ExerciseState, factory=cls.exercise_state), SituationStateData(2, _TakeShowerState, factory=cls.take_shower_state), SituationStateData(3, _DoStuffState, factory=cls.do_stuff_state) ] def start_situation(self): super().start_situation() self._change_state(self.exercise_state()) @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return list( cls.exercise_state._tuned_values.job_and_role_changes.items())
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 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 RelaxationCenterVisitorSituation(SituationComplexCommon): INSTANCE_TUNABLES = {'situation_default_job': SituationJob.TunableReference(description='\n The default job that a visitor will be in during the situation.\n '), 'arriving_situation_state': _ArrivingState.TunableFactory(description='\n The situation state used for when a Sim is arriving as a Massage \n Therapist.\n ', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP, display_name='01_arriving_situation_state'), 'do_stuff_situation_state': _DoStuffState.TunableFactory(description='\n The main state of the situation. This is where Sims will do \n everything except for arrive and leave.\n ', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP, display_name='02_do_stuff_situation_state'), 'change_clothes_leave_situation_state': _ChangeClothesLeave.TunableFactory(description='\n The state that is used to get the Sim to change clothes before \n ending the situation and ending up in the leave lot situation.\n ', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP, display_name='03_change_clothes_leave_situation_state')} REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._visitor = None reader = self._seed.custom_init_params_reader self.apply_situation_outfit = False if reader is not None: self.apply_situation_outfit = reader.read_bool(CUSTOM_SAVE_OUTFIT, False) @classmethod def _states(cls): return [SituationStateData(1, _ArrivingState, factory=cls.arriving_situation_state), SituationStateData(2, _DoStuffState, factory=cls.do_stuff_situation_state), SituationStateData(3, _ChangeClothesLeave, factory=cls.change_clothes_leave_situation_state)] @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return list(cls.arriving_situation_state._tuned_values.job_and_role_changes.items()) @classmethod def default_job(cls): return cls.situation_default_job def start_situation(self): super().start_situation() self._change_state(self.arriving_situation_state()) def _on_set_sim_job(self, sim, job_type): super()._on_set_sim_job(sim, job_type) self._visitor = sim if self.apply_situation_outfit: self.set_job_uniform(sim, job_type, (OutfitCategory.SITUATION, 0)) sim.sim_info.try_set_current_outfit((OutfitCategory.SITUATION, 0)) def _save_custom_situation(self, writer): if self._visitor is not None: writer.write_bool(CUSTOM_SAVE_OUTFIT, self._visitor.get_current_outfit()[0] == OutfitCategory.SITUATION) def sim_of_interest(self, sim_info): if self._visitor is not None and self._visitor.sim_info is sim_info: return True return False def all_sims_of_interest_arrived(self, arrived_sims): return True
class AnchoredRelaxationCenterVisitorSituation(GroupAnchoredAutonomySituationCommon): INSTANCE_TUNABLES = {'situation_default_job': SituationJob.TunableReference(description='\n The default job that a visitor will be in during the situation.\n '), 'arriving_situation_state': _ArrivingState.TunableFactory(description='\n The situation state used for when a Sim is arriving as a Massage \n Therapist.\n ', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP, display_name='01_arriving_situation_state'), 'do_stuff_situation_state': _DoStuffState.TunableFactory(description='\n The main state of the situation. This is where Sims will do \n everything except for arrive and leave.\n ', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP, display_name='02_do_stuff_situation_state'), 'change_clothes_leave_situation_state': _ChangeClothesLeave.TunableFactory(description='\n The state that is used to get the Sim to change clothes before \n ending the situation and ending up in the leave lot situation.\n ', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP, display_name='03_change_clothes_leave_situation_state')} REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES @classmethod def _states(cls): return [SituationStateData(1, _ArrivingState, factory=cls.arriving_situation_state), SituationStateData(2, _DoStuffState, factory=cls.do_stuff_situation_state), SituationStateData(3, _ChangeClothesLeave, factory=cls.change_clothes_leave_situation_state)] @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return list(cls.arriving_situation_state._tuned_values.job_and_role_changes.items()) @classmethod def situation_meets_starting_requirements(cls, **kwargs): object_manager = services.object_manager() for _ in object_manager.get_objects_with_tag_gen(cls.object_anchor_tag): return True return False @classmethod def default_job(cls): return cls.situation_default_job def start_situation(self): super().start_situation() self._anchor_position = self.get_new_anchor_position(self.object_anchor_tag) self._change_state(self.arriving_situation_state()) def sim_of_interest(self, sim_info): for sim in self._situation_sims: if sim.sim_info is sim_info: return True return False def all_sims_of_interest_arrived(self, arrived_sims): for sim in self._situation_sims: if sim.sim_info not in arrived_sims: return False return True
class HostSituation(BusinessEmployeeSituationMixin, StaffedObjectSituationMixin, SituationComplexCommon): INSTANCE_TUNABLES = { 'situation_job': SituationJob.TunableReference( description= '\n The job that a staff member will be in during the situation.\n ' ), '_arriving_situation_state': _ArrivingState.TunableFactory( description= '\n The situation state used for when a Sim is arriving as a staff \n member.\n ', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP, display_name='01_arriving_situation_state'), '_host_station_situation_state': _HostStationState.TunableFactory( description= '\n The situation state used for when a Host is idling at the host station.\n ', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP, display_name='02_host_station_situation_state'), '_show_to_table_situation_state': _ShowToTableState.TunableFactory( description= '\n The situation state used for when a Host is showing a group to\n their table.\n ', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP, display_name='03_show_to_table_situation_state'), '_right_this_way_situation_state': _RightThisWayState.TunableFactory( description= '\n The situation state used to get the Host to run the "Right this way" \n social before routing to the table with the guests.\n ', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP, display_name='04_right_this_way_situation_state'), 'right_this_way_interaction': TunableReference( description= '\n The interaction that the Host runs before both the Host and the\n guests start routing to the table.\n ', manager=services.get_instance_manager( sims4.resources.Types.INTERACTION), class_restrictions='SuperInteraction'), 'host_show_to_table_interaction': TunablePackSafeReference( description= '\n The interaction to push on the host that will "show" the guests\n to their table.\n ', manager=services.get_instance_manager( sims4.resources.Types.INTERACTION)), 'requesting_sim_show_to_table_interaction': TunablePackSafeReference( description= '\n The interaction to push on the sim requesting a table that will \n get the guest close to their table so a social can be pushed on them\n to show them the table.\n ', manager=services.get_instance_manager( sims4.resources.Types.INTERACTION)), 'guest_take_seat_interaction': TunablePackSafeReference( description= '\n The interaction to push on each of the guests in the group that\n targets their chosen seat.\n ', manager=services.get_instance_manager( sims4.resources.Types.INTERACTION)), 'host_head_start_in_sim_minutes': Tunable( description= '\n The number of sim minutes that the guests will wait after the host\n begins to show them to their table before they start to follow.\n ', tunable_type=int, default=1) } REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._alarm_handle = None @classmethod def _states(cls): return [ SituationStateData(1, _ArrivingState, factory=cls._arriving_situation_state), SituationStateData(2, _HostStationState, factory=cls._host_station_situation_state), SituationStateData(3, _ShowToTableState, factory=cls._show_to_table_situation_state), SituationStateData(4, _RightThisWayState, factory=cls._right_this_way_situation_state) ] @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return list(cls._arriving_situation_state._tuned_values. job_and_role_changes.items()) @classmethod def default_job(cls): pass def start_situation(self): super().start_situation() self._change_state(self._arriving_situation_state()) def _on_set_sim_job(self, sim, job_type): super()._on_set_sim_job(sim, job_type) self._start_work_duration() def get_group_to_seat(self): zone_director = get_restaurant_zone_director() if zone_director is None: return return zone_director.get_next_group_id_to_seat() def remove_group_to_seat(self, group_id): zone_director = get_restaurant_zone_director() if zone_director is None: return return zone_director.remove_group_waiting_to_be_seated(group_id) def push_right_this_way_social(self, group): zone_director = get_restaurant_zone_director() if zone_director is None: return False else: host = self.get_staff_member() target = group.get_main_sim() if target is None: target = next(group.all_sims_in_situation_gen()) context = InteractionContext(host, InteractionContext.SOURCE_SCRIPT, Priority.High) if not host.push_super_affordance(self.right_this_way_interaction, target, context): return False return True def push_show_table_interactions(self, group): if self.host_show_to_table_interaction is None or self.guest_take_seat_interaction is None: log_host_action( 'During Show To Table State', 'Failed - No Interactions to push on one of the Sims.') return False zone_director = get_restaurant_zone_director() if zone_director is None: log_host_action('During Show To Table State', "Failed - Can't find the zone director.") return False master_sim = group.get_main_sim() if master_sim is None: master_sim = next(group.all_sims_in_situation_gen()) zone_director.claim_table(master_sim) return self.push_host_show_table_interaction(group, zone_director, master_sim) def push_host_show_table_interaction(self, group, zone_director, master_sim): host = self.get_staff_member() group_tables = zone_director.get_tables_by_group_id(group.id) if not group_tables: log_host_action( 'During Show To Table', 'Failed to push show to table interactions. Unable to find tables the group reserved.' ) return False context = InteractionContext(host, InteractionContext.SOURCE_SCRIPT, Priority.High) master_sim_context = InteractionContext( master_sim, InteractionContext.SOURCE_SCRIPT, Priority.High) object_manager = services.current_zone().object_manager table_id = group_tables.pop() table = object_manager.get(table_id) (master_sim_chair_id, _) = zone_director.get_sims_seat(master_sim) master_sim_chair = object_manager.get(master_sim_chair_id) if not host.push_super_affordance( self.host_show_to_table_interaction, table, context, saved_participants=[master_sim, master_sim_chair, None, None]): log_host_action( 'During Show To Table State', 'Failed to Push Host Interaction on {}'.format(host)) return False log_host_action('During Show To Table State', 'Pushed Host Interaction on {}'.format(host)) def timer_expired(handle): self.push_master_sim_interaction(group, table, master_sim, master_sim_context, host, master_sim_chair) self._alarm_handle = None self._alarm_handle = alarms.add_alarm( self, clock.interval_in_sim_minutes(self.host_head_start_in_sim_minutes), timer_expired) return True def push_master_sim_interaction(self, group, table, master_sim, master_sim_context, host, master_sim_chair): if not master_sim.push_super_affordance( self.requesting_sim_show_to_table_interaction, table, master_sim_context, saved_participants=[host, master_sim_chair, None, None]): log_host_action( 'During Show To Table State', 'Failed to Push Master Sim Interaction on {}'.format( master_sim)) else: log_host_action( 'During Show To Table State', 'Pushed Master Sim Interaction on {}'.format(master_sim)) zone_director = get_restaurant_zone_director() if zone_director is None: log_host_action('During Show To Table State', 'Failed - unable to find zone director.') return for guest in group.all_sims_in_situation_gen(): if guest is not master_sim: self.push_guest_take_seat_interaction(guest, zone_director) def push_guest_take_seat_interaction(self, guest, zone_director): (seat_id, _) = zone_director.get_sims_seat(guest) if seat_id is None: log_host_action( 'During Show To Table Seat', "Failed - couldn't find a seat for the Sim {}.".format(guest)) return seat = services.current_zone().object_manager.get(seat_id) if seat is None: log_host_action( 'During Show To Table Seat', "Failed - couldn't find a seat for the Sim {}.".format(guest)) return context = InteractionContext(guest, InteractionContext.SOURCE_SCRIPT, Priority.High) if not guest.push_super_affordance(self.guest_take_seat_interaction, seat, context): log_host_action('During Show To Table State', 'Failed to Push Guest Interaction') else: log_host_action('During Show To Table State', 'Push Guest Interaction on {}'.format(guest)) def on_remove(self): super().on_remove() if self._alarm_handle is not None: self._alarm_handle.cancel() self._alarm_handle = None
class ScarecrowSituation(ObjectBoundSituationMixin, SituationComplexCommon): INSTANCE_TUNABLES = { '_situation_job': SituationJob.TunableReference( description= '\n The situation job for the Sim.\n \n This job should define a spawn affordance that will trigger\n a continuation targeting the object the Sim spawns at.\n ', tuning_group=GroupNames.SITUATION), '_do_stuff_state': _DoStuffState.TunableFactory( description= '\n The state for the Sim doing stuff.\n \n This is the initial state after the Sim spawns onto the lot.\n\n Any on-activate affordances run in this role will target\n the object the Sim spawned near.\n ', display_name='1. Do Stuff', tuning_group=GroupNames.STATE), '_leave_state': _LeaveState.TunableFactory( description= '\n The state for the Sim leaving.\n \n Any on-activate affordances run in this role will target\n the object the Sim spawned near.\n ', display_name='2. Leave', tuning_group=GroupNames.STATE), '_spawn_object_targeting_affordance': TunableInteractionOfInterest( description= "\n Affordance that runs targeting the object that the object that the\n Sim had spawned at. This allows the situation to 'remember' that\n object and when that object is destroyed, the situation will\n be destroyed as well. \n ", tuning_group=GroupNames.SITUATION), '_spawn_object_reset_loots': LootActions.TunableReference( description= '\n Loots used to reset the object from which the scarecrow spawned from,\n to handle cases for when the scarecrow Sim is not on lot during load.\n ', tuning_group=GroupNames.SITUATION) } REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES @classmethod def _states(cls): return (SituationStateData( 1, _DoStuffState, partial(cls._do_stuff_state, situation_job=cls._situation_job)), SituationStateData( 2, _LeaveState, partial(cls._leave_state, situation_job=cls._situation_job))) @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls._situation_job, None)] @classmethod def default_job(cls): return cls._situation_job @classmethod def get_sims_expected_to_be_in_situation(cls): return 1 def _get_role_state_overrides(self, sim, job_type, role_state_type, role_affordance_target): return (role_state_type, services.object_manager().get(self.bound_object_id)) def start_situation(self): super().start_situation() for custom_key in self._spawn_object_targeting_affordance.custom_keys_gen( ): self._register_test_event(TestEvent.InteractionStart, custom_key) self._change_state( self._do_stuff_state(situation_job=self._situation_job)) def load_situation(self): scarecrow_guest_info = next( iter(self._guest_list.get_persisted_sim_guest_infos())) if scarecrow_guest_info is None: self._reset_scarecrow_object() return False scarecrow_sim_info = services.sim_info_manager().get( scarecrow_guest_info.sim_id) if scarecrow_sim_info is None or scarecrow_sim_info.zone_id != services.current_zone_id( ): self._reset_scarecrow_object() return False return super().load_situation() def _reset_scarecrow_object(self): scarecrow_object = services.object_manager().get(self._bound_object_id) resolver = SingleObjectResolver(scarecrow_object) self._spawn_object_reset_loots.apply_to_resolver(resolver) def handle_event(self, sim_info, event, resolver): if event == TestEvent.InteractionStart: if self.is_sim_info_in_situation(sim_info) and resolver( self._spawn_object_targeting_affordance): target = resolver.get_participant(ParticipantType.Object) if target is None: logger.error( '{}: {} target is None, cannot find the object for this situation to bind to!', self, resolver) self._self_destruct() return self.bind_object(target) else: super().handle_event(sim_info, event, resolver) def go_to_leave_state(self): self._change_state( self._leave_state(situation_job=self._situation_job)) def _gsi_additional_data_gen(self): if isinstance(self._cur_state, _DoStuffState): yield ('Time till Leave State', str(self._cur_state.get_time_remaining()))
class 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 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 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 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 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 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 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 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)
class SpectralStalkerSituation(SituationComplexCommon): INSTANCE_TUNABLES = {'enter_state': _SpectralStalkerEnterState.TunableFactory(description='\n The first state the Spectral Stalker starts in. This should handle any\n spawning behavior. When the Interaction Of Interest completes, the \n Stalker will be pushed to chase the target Sim.\n ', tuning_group=STALKER_GROUP), 'chase_state': _SpectralStalkerChaseState.TunableFactory(description='\n The stalker chases the target Sim.\n ', tuning_group=STALKER_GROUP), 'idle_state': _SpectralStalkerIdleState.TunableFactory(description="\n The stalker Idles until it's time to chase again.\n ", tuning_group=STALKER_GROUP), 'exit_state': _SpectralStalkerExitState.TunableFactory(description='\n The Spectral Stalker exits the world.\n ', tuning_group=STALKER_GROUP), 'target_job': SituationJob.TunableReference(description='\n The Situation Job on the target of the Spectral Stalker.\n '), 'time_to_chase': TunableInterval(description='\n The upper and lower limit for the amount of time, in Sim Minutes, to \n chase a Sim before exiting the situation. When the Stalker starts, a \n random time between these values will be chosen.\n ', tunable_type=TunableSimMinute, default_lower=30, default_upper=120, minimum=1, tuning_group=STALKER_GROUP)} REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES TEST_EVENTS = (TestEvent.OnSimReset, TestEvent.SimDeathTypeSet) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._target_sim_info = None self._stalker_sim_info = None self._stalk_finish_ticks = None @classmethod def _states(cls): return [SituationStateData(0, _SpectralStalkerEnterState, cls.enter_state), SituationStateData(1, _SpectralStalkerChaseState, cls.chase_state), SituationStateData(2, _SpectralStalkerIdleState, cls.idle_state), SituationStateData(3, _SpectralStalkerExitState, cls.exit_state)] @classmethod def default_job(cls): pass @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return list(cls.enter_state._tuned_values.job_and_role_changes.items()) def handle_event(self, sim_info, event, resolver): super().handle_event(sim_info, event, resolver) if sim_info is self._target_sim_info and (event == TestEvent.OnSimReset or event == TestEvent.SimDeathTypeSet): self._stalk_finish_ticks = None self._change_state(self.exit_state()) def start_situation(self): super().start_situation() self._change_state(self.enter_state()) def _on_set_sim_job(self, sim, job_type): super()._on_set_sim_job(sim, job_type) if job_type is self.target_job: self._target_sim_info = sim.sim_info services.get_event_manager().register(self, self.TEST_EVENTS) else: self._stalker_sim_info = sim.sim_info self._stalk_finish_ticks = services.time_service().sim_now.absolute_ticks() + create_time_span(minutes=self.time_to_chase.random_int()).in_ticks() def _self_destruct(self): services.get_event_manager().unregister(self, self.TEST_EVENTS) super()._self_destruct() def should_exit_situation(self): if not self._stalk_finish_ticks: return True return services.time_service().sim_now.absolute_ticks() >= self._stalk_finish_ticks
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 GroupInteractionSituation(SituationComplexCommon): INSTANCE_TUNABLES = { 'pre_situation_state': TunableTuple( description= '\n Information related to the pre interaction situation state.\n ', situation_state=_PreSituationState.TunableFactory( description= '\n The pre-interaction situation state. Get everyone to their positions\n and idle.\n ' ), time_out=TunableSimMinute( description= '\n How long this will last.\n ', default=15, minimum=1), tuning_group=GROUP_INTERACTION_TUNING_GROUP), 'constraint_affordance': SuperInteraction.TunableReference( description= '\n The interaction that puts the followers into the constraint.\n ' ), 'constraint_leader_affordance': SuperInteraction.TunableReference( description= '\n The interaction that puts the leader into the constraint.\n ' ), 'leader_job': SituationJob.TunableReference( description= '\n The situation job for leader.\n ', tuning_group=GROUP_INTERACTION_TUNING_GROUP), 'member_job': SituationJob.TunableReference( description= '\n The situation job for member.\n ', tuning_group=GROUP_INTERACTION_TUNING_GROUP), 'interaction_state': _InteractionState.TunableFactory( description= '\n The state that sim is doing the interaction.\n ', tuning_group=GROUP_INTERACTION_TUNING_GROUP), 'affordance': SuperInteraction.TunableReference( description= '\n The affordance for leader sim to run when all sims have gathered.\n ', tuning_group=GROUP_INTERACTION_TUNING_GROUP) } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._target_object = None self._routing_sims = [] @classproperty def situation_serialization_option(cls): return situations.situation_types.SituationSerializationOption.DONT def start_situation(self): super().start_situation() self._create_situation_geometry() self._change_state(self.pre_situation_state.situation_state()) @classmethod def _states(cls): return (SituationStateData( 1, _PreSituationState, factory=cls.pre_situation_state.situation_state), SituationStateData(2, _InteractionState, factory=cls.interaction_state)) @classmethod def default_job(cls): pass @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return list(cls.pre_situation_state.situation_state._tuned_values. job_and_role_changes.items()) @property def should_route_sims_on_add(self): return False def _on_set_sim_job(self, sim, job_type): super()._on_set_sim_job(sim, job_type) self._route_sim(sim) def _get_ignored_object_ids(self): ignored_sim_ids = [sim.id for sim in self.all_sims_in_situation_gen()] return ignored_sim_ids def get_next_interaction_state(self): return self.interaction_state def _create_situation_geometry(self): seed = self._seed default_target_id = seed.extra_kwargs.get('default_target_id', None) if default_target_id is not None: self._target_object = services.object_manager().get( default_target_id) if self._target_object is None: default_location = seed.extra_kwargs.get('default_location', None) if default_location is not None: self._target_object = TerrainPoint(default_location) else: logger.error('Failed to determine target for {}', self) self._self_destruct() return else: leader_sim = self.initiating_sim_info.get_sim_instance() if leader_sim is None: logger.error('No leader sim for {}', self) self._self_destruct() return def _route_sim(self, sim): interaction_context = InteractionContext( sim, InteractionSource.SCRIPT_WITH_USER_INTENT, Priority.High) leader_sim = self.initiating_sim_info.get_sim_instance() if leader_sim is sim: affordance = self.constraint_leader_affordance else: affordance = self.constraint_affordance aop = AffordanceObjectPair(affordance, self._target_object, affordance, None) aop.test_and_execute(interaction_context) self._routing_sims.append(sim.id) def on_sim_finish_routing(self, sim_info): if sim_info.id in self._routing_sims: self._routing_sims.remove(sim_info.id) if not self._routing_sims: next_state = self.get_next_interaction_state() self._change_state(next_state()) else: logger.error( 'Sim {} finishes routing but not in routing sim list of situation {}', sim_info, self) def _cancel_constraint_affordance_for_sim(self, sim): for si in sim.get_all_running_and_queued_interactions(): if si.affordance is self.constraint_affordance: si.cancel( FinishingType.SITUATIONS, cancel_reason_msg='Group Interaction Situation done.') if si.affordance is self.constraint_leader_affordance: si.cancel( FinishingType.SITUATIONS, cancel_reason_msg='Group Interaction Situation done.') def _on_remove_sim_from_situation(self, sim): self._cancel_constraint_affordance_for_sim(sim) super()._on_remove_sim_from_situation(sim) def _destroy(self): for sim in self.all_sims_in_situation_gen(): self._cancel_constraint_affordance_for_sim(sim) self._target_object = None super()._destroy()
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 MultiSimFestivalSituation(BaseGenericFestivalSituation): INSTANCE_TUNABLES = { 'group_filter': TunableReference( description= '\n The aggregate filter that we use to find the sims for this\n situation.\n ', manager=services.get_instance_manager( sims4.resources.Types.SIM_FILTER), class_restrictions=filters.tunable.TunableAggregateFilter), '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', 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))), 'initial_state': MultiSimStartingFestivalSituationState.TunableFactory( description= '\n The first state that the Sims will be put into when this Situation\n Starts.\n ', locked_args={ 'allow_join_situation': True, 'time_out': None }, tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP), 'blacklist_job': SituationJob.TunableReference( description= '\n The default job used for blacklisting Sims from coming back as\n festival goers.\n ' ) } @classmethod def _states(cls): return (SituationStateData(1, MultiSimStartingFestivalSituationState, factory=cls.initial_state), SituationStateData(2, CooldownFestivalSituationState, factory=cls.cooldown_state)) @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return list( cls.initial_state._tuned_values.job_and_role_changes.items()) @classmethod def get_predefined_guest_list(cls): guest_list = SituationGuestList(invite_only=True) situation_manager = services.get_zone_situation_manager() worker_filter = cls.group_filter if cls.group_filter is not None else cls.default_job( ).filter 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 = 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=worker_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 = cls.situation_job_mapping.get(result.tag, cls.default_job()) guest_list.add_guest_info( SituationGuestInfo(result.sim_info.sim_id, job, RequestSpawningOption.DONT_CARE, BouncerRequestPriority.BACKGROUND_HIGH)) return guest_list def start_situation(self): super().start_situation() self._change_state(self.initial_state())
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)