class LandlordTuning: LANDLORD_FILTER = TunableSimFilter.TunablePackSafeReference( description= '\n The Sim Filter used to find/create a Landlord for the game.\n ' ) LANDLORD_REL_BIT = RelationshipBit.TunablePackSafeReference( description= '\n The rel bit to add between a landlord and apartment tenants. This will\n be removed if a tenant moves out of an apartment.\n ' ) TENANT_REL_BIT = RelationshipBit.TunablePackSafeReference( description= '\n The rel bit to add between an apartment Tenant and their Landlord. This\n will be removed if a tenant moves out of an apartment.\n ' ) LANDLORD_TRAIT = Trait.TunablePackSafeReference( description= '\n The Landlord Trait used in testing and Sim Filters.\n ' ) LANDLORD_FIRST_PLAY_RENT_REMINDER_NOTIFICATION = TunableUiDialogNotificationSnippet( description= '\n The notification to show a household if they are played on a new\n apartment home.\n ' ) HOUSEHOLD_LANDLORD_EXCEPTION_TESTS = TunableTestSet( description= '\n Tests to run when determining if a household requires a landlord.\n ' )
class _SimFilterSimInfoSource(_SlotSpawningSimInfoSource): FACTORY_TUNABLES = { 'filter': TunableSimFilter.TunableReference( description= '\n Sim filter that is used to create or find a Sim that matches\n this filter request.\n ' ) } def get_sim_filter_gsi_name(self): return str(self) def get_sim_infos_and_positions(self, resolver, household): use_fgl = True sim_info = resolver.get_participant(self.participant) filter_result = services.sim_filter_service( ).submit_matching_filter( sim_filter=self.filter, requesting_sim_info=sim_info, allow_yielding=False, gsi_source_fn=self.get_sim_filter_gsi_name) if not filter_result: return () (position, location) = (None, None) spawning_object = self._get_spawning_object(resolver) if spawning_object is not None: (position, location) = self._get_position_and_location( spawning_object, resolver) use_fgl = position is None return ((filter_result[0].sim_info, position, location, use_fgl), )
class TravelTuning: ENTER_LOT_AFFORDANCE = TunableReference(services.affordance_manager(), description='SI to push when sim enters the lot.') EXIT_LOT_AFFORDANCE = TunableReference(services.affordance_manager(), description='SI to push when sim is exiting the lot.') NPC_WAIT_TIME = TunableSimMinute(15, description='Delay in sim minutes before pushing the ENTER_LOT_AFFORDANCE on a NPC at the spawn point if they have not moved.') TRAVEL_AVAILABILITY_SIM_FILTER = TunableSimFilter.TunableReference(description='Sim Filter to show what Sims the player can travel with to send to Game Entry.') TRAVEL_SUCCESS_AUDIO_STING = TunablePlayAudio(description='\n The sound to play when we finish loading in after the player has traveled.\n ') NEW_GAME_AUDIO_STING = TunablePlayAudio(description='\n The sound to play when we finish loading in from a new game, resume, or\n household move in.\n ') GO_HOME_INTERACTION = TunableReference(description='\n The interaction to push a Sim to go home.\n ', manager=services.get_instance_manager(sims4.resources.Types.INTERACTION))
class Organization(DisplaySnippet): INSTANCE_TUNABLES = { 'progress_statistic': TunableReference( description= '\n The Ranked Statistic represents Organization Progress.\n ', manager=services.get_instance_manager( sims4.resources.Types.STATISTIC), class_restrictions='RankedStatistic', export_modes=ExportModes.All), 'hidden': Tunable( description= '\n If True, then the organization is hidden from the organization panel.\n ', tunable_type=bool, default=False, export_modes=ExportModes.All), 'organization_task_data': TunableList( description= '\n List of possible tested organization tasks that can be offered to \n active organization members.\n ', tunable=TunableTuple( description= '\n Tuple of test and aspirations that is run on activating\n organization tasks.\n ', tests=event_testing.tests.TunableTestSet( description= '\n Tests run when the task is activated. If tests do not pass,\n the tasks are not considered for assignment.\n ' ), organization_task=TunableReference( description= '\n An aspiration to use for task completion.\n ', manager=services.get_instance_manager( sims4.resources.Types.ASPIRATION), class_restrictions='AspirationOrganizationTask'))), 'organization_filter': TunableSimFilter.TunableReference( description= "\n Terms to add a member to the Organization's membership list.\n " ), 'no_events_are_scheduled_string': OptionalTunable( description= '\n If enabled and the organization has no scheduled events, this text\n will be displayed in the org panel background.\n ', tunable=TunableLocalizedString( description= '\n The string to show in the organization panel if there are no scheduled\n events.\n ' )) }
class _StoryProgressionDemographicWithFilter(_StoryProgressionDemographic): FACTORY_TUNABLES = { 'population_filter': TunableSimFilter.TunableReference( description= '\n The subset of Sims this action can operate on.\n ' ) } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for sim_info_agent in self._get_sims_from_filter(): self._add_sim_info_agent_internal(sim_info_agent) def add_sim_info_agent(self, sim_info_agent): if self._is_valid_agent(sim_info_agent): self._add_sim_info_agent_internal(sim_info_agent) def _add_sim_info_agent_internal(self, sim_info_agent): raise NotImplementedError def remove_sim_info_agent(self, sim_info_agent): if self._is_valid_agent(sim_info_agent): self._remove_sim_info_agent_internal(sim_info_agent) def _remove_sim_info_agent_internal(self, sim_info_agent): raise NotImplementedError def _is_valid_agent(self, sim_info_agent): def get_sim_filter_gsi_name(): return 'Request to check if {} matches filter from {}'.format( sim_info_agent, self) return services.sim_filter_service().does_sim_match_filter( sim_info_agent.sim_id, sim_filter=self.population_filter, gsi_source_fn=get_sim_filter_gsi_name) def get_sim_filter_gsi_name(self): return str(self) def _get_sims_from_filter(self): results = services.sim_filter_service().submit_filter( self.population_filter, None, allow_yielding=False, gsi_source_fn=self.get_sim_filter_gsi_name) return tuple(StoryProgressionAgentSimInfo(r.sim_info) for r in results)
class _StoryProgressionFilterAction(_StoryProgressionAction): FACTORY_TUNABLES = {'sim_filter': TunableSimFilter.TunableReference(description='\n The subset of Sims this action can operate on.\n ')} def _get_filter(self): return self.sim_filter() def _apply_action(self, sim_info): raise NotImplementedError def _pre_apply_action(self): pass def _post_apply_action(self): pass def _allow_instanced_sims(self): return False def get_sim_filter_gsi_name(self): return str(self) def process_action(self, story_progression_flags): def _on_filter_request_complete(results, *_, **__): if results is None: return self._pre_apply_action() for result in results: sim_info = result.sim_info if sim_info is None: continue if not self._allow_instanced_sims(): if not sim_info.is_instanced(allow_hidden_flags=ALL_HIDDEN_REASONS): self._apply_action(sim_info) self._apply_action(sim_info) self._post_apply_action() services.sim_filter_service().submit_filter(self._get_filter(), _on_filter_request_complete, household_id=services.active_household_id(), gsi_source_fn=self.get_sim_filter_gsi_name)
class _PregnancyParentFilter(HasTunableSingletonFactory, AutoFactoryInit): FACTORY_TUNABLES = { 'filter': TunableSimFilter.TunableReference( description= '\n The filter to use to find a parent.\n ' ) } def get_sim_filter_gsi_name(self): return str(self) def get_parent(self, interaction, pregnancy_subject_sim_info): filter_results = services.sim_filter_service( ).submit_matching_filter( sim_filter=self.filter, allow_yielding=False, requesting_sim_info=pregnancy_subject_sim_info, gsi_source_fn=self.get_sim_filter_gsi_name) if filter_results: parent = random.choice([ filter_result.sim_info for filter_result in filter_results ]) return parent
class RelationshipCommandTuning: INTRODUCE_BIT = TunableReference( description= '\n Relationship bit to add to all Sims when running the introduce command.\n ', manager=services.get_instance_manager( sims4.resources.Types.RELATIONSHIP_BIT)) INTRODUCE_TRACK = TunableReference( description= '\n Relationship track for friendship used by cheats to introduce sims. \n ', manager=services.get_instance_manager(sims4.resources.Types.STATISTIC), class_restrictions=relationships.relationship_track.RelationshipTrack) INTRODUCE_VALUE = Tunable( description= '\n The value to add to the relationship to introduce the sims.\n ', tunable_type=int, default=0) CREATE_FRIENDS_COMMAND_QUANTITY = Tunable( description= '\n The number of friendly sims to generate \n using command |relationships.create_friends_for_sim.\n ', tunable_type=int, default=1) CREATE_FRIENDS_COMMAND_FILTER = TunableSimFilter.TunableReference( description= '\n The sim-filter for generating friendly sims.\n ')
class LoudNeighborSituation(SituationComplexCommon): INSTANCE_TUNABLES = {'loud_neighbor_state': _LoudNeighborState.TunableFactory(description='\n The situation state used for when a neighbor starts being loud.\n This will listen for a Sim to bang on the door and complain about\n the noise before transitioning to the complain state.\n ', tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP, display_name='01_loud_neighbor_situation_state'), 'complain_state': _ComplainState.TunableFactory(description="\n The situation state used for when a player Sim has banged on the\n neighbor's door and we are waiting for them to complain to the\n neighbor.\n ", tuning_group=SituationComplexCommon.SITUATION_STATE_GROUP, display_name='02_complain_situation_state'), 'loud_neighbor_filters': TunableList(description='\n Sim filters that fit the description of loud neighbor(s). We run\n through them until we find someone matching the filter.\n ', tunable=TunableVariant(description='\n The filter we want to use to find loud neighbors.\n ', single_filter=TunableSimFilter.TunablePackSafeReference(description='\n A Sim Filter to find a loud neighbor.\n '), aggregate_filter=TunableSimFilter.TunablePackSafeReference(description='\n An aggregate Sim Filter to find a loud neighbor that has\n other Sims.\n ', class_restrictions=filters.tunable.TunableAggregateFilter), default='single_filter')), 'loud_door_state_on': TunableStateValueReference(description='\n State to set on the apartment door of the loud neighbor when the\n situation starts.\n '), 'loud_door_state_off': TunableStateValueReference(description='\n State to set on the apartment door of the loud neighbor when they\n are no longer being loud.\n ')} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) reader = self._seed.custom_init_params_reader if reader is not None: self._neighbor_sim_id = reader.read_uint64(NEIGHBOR_TOKEN, None) self._neighbor_door_id = reader.read_uint64(DOOR_TOKEN, None) else: self._neighbor_sim_id = None self._neighbor_door_id = None @classproperty def allow_user_facing_goals(cls): return False @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [] @classmethod def _states(cls): return (SituationStateData(1, _LoudNeighborState, factory=cls.loud_neighbor_state), SituationStateData(2, _ComplainState, factory=cls.complain_state)) @classmethod def default_job(cls): pass @classmethod def situation_meets_starting_requirements(cls, **kwargs): neighbor_sim_id = cls._get_loud_neighbor() if neighbor_sim_id is None: return False return True def start_situation(self): super().start_situation() neighbor_sim_id = self._get_loud_neighbor() self._set_loud_neighbor_and_door(neighbor_sim_id) if self._neighbor_sim_id is not None: self._change_state(self.loud_neighbor_state()) def _save_custom_situation(self, writer): super()._save_custom_situation(writer) if self._neighbor_sim_id is not None: writer.write_uint64(NEIGHBOR_TOKEN, self._neighbor_sim_id) if self._neighbor_door_id is not None: writer.write_uint64(DOOR_TOKEN, self._neighbor_door_id) def _destroy(self): if self._neighbor_door_id is not None: apartment_door = services.object_manager().get(self._neighbor_door_id) if apartment_door is not None: apartment_door.set_state(self.loud_door_state_off.state, self.loud_door_state_off) services.get_zone_situation_manager().remove_sim_from_auto_fill_blacklist(self._neighbor_sim_id) super()._destroy() @classmethod def _get_loud_neighbor(cls): neighbor_sim_id = None blacklist_sim_ids = {sim_info.sim_id for sim_info in services.active_household()} blacklist_sim_ids.update(set(sim_info.sim_id for sim_info in services.sim_info_manager().instanced_sims_gen())) loud_neighbor_filters = sorted(cls.loud_neighbor_filters, key=lambda *args: random.random()) for neighbor_filter in loud_neighbor_filters: neighbors = services.sim_filter_service().submit_matching_filter(sim_filter=neighbor_filter, allow_yielding=False, blacklist_sim_ids=blacklist_sim_ids, gsi_source_fn=cls.get_sim_filter_gsi_name) neighbor_sim_infos_at_home = [result.sim_info for result in neighbors if result.sim_info.is_at_home] if len(neighbor_sim_infos_at_home) > 1 or len(neighbor_sim_infos_at_home): if neighbor_filter.is_aggregate_filter(): continue neighbor_sim_id = neighbor_sim_infos_at_home[0].sim_id if neighbor_sim_infos_at_home else None if neighbor_sim_id is not None: break return neighbor_sim_id def _set_loud_neighbor_and_door(self, neighbor_sim_id): neighbor_sim_info = services.sim_info_manager().get(neighbor_sim_id) if neighbor_sim_info is None: self._self_destruct() return self._neighbor_sim_id = neighbor_sim_id door_service = services.get_door_service() plex_door_infos = door_service.get_plex_door_infos() object_manager = services.object_manager() for door_info in plex_door_infos: door = object_manager.get(door_info.door_id) if door is not None: if door.household_owner_id == neighbor_sim_info.household_id: self._neighbor_door_id = door_info.door_id break else: logger.error('Could not find door object that belongs to {}', neighbor_sim_info.household.name) self._self_destruct()
class CareerTone(AwayAction): INSTANCE_TUNABLES = { 'dominant_tone_loot_actions': TunableList( description= '\n Loot to apply at the end of a work period if this tone ran for the\n most amount of time out of all tones.\n ', tunable=TunableReference( manager=services.get_instance_manager( sims4.resources.Types.ACTION), class_restrictions=('LootActions', 'RandomWeightedLoot'))), 'performance_multiplier': Tunable( description= '\n Performance multiplier applied to work performance gain.\n ', tunable_type=float, default=1), 'periodic_sim_filter_loot': TunableList( description= '\n Loot to apply periodically to between the working Sim and other\n Sims, specified via a Sim filter.\n \n Example Usages:\n -Gain relationship with 2 coworkers every hour.\n -Every hour, there is a 5% chance of meeting a new coworker.\n ', tunable=TunableTuple( chance=SuccessChance.TunableFactory( description= '\n Chance per hour of loot being applied.\n ' ), sim_filter=TunableSimFilter.TunableReference( description= '\n Filter for specifying who to set at target Sims for loot\n application.\n ' ), max_sims=OptionalTunable( description= '\n If enabled and the Sim filter finds more than the specified\n number of Sims, the loot will only be applied a random\n selection of this many Sims.\n ', tunable=TunableRange(tunable_type=int, default=1, minimum=1)), loot=LootActions.TunableReference( description= '\n Loot actions to apply to the two Sims. The Sim in the \n career is Actor, and if Targeted is enabled those Sims\n will be TargetSim.\n ' ))) } runtime_commodity = None @classmethod def _tuning_loaded_callback(cls): if cls.runtime_commodity is not None: return commodity = RuntimeCommodity.generate(cls.__name__) commodity.decay_rate = 0 commodity.convergence_value = 0 commodity.remove_on_convergence = True commodity.visible = False commodity.max_value_tuning = date_and_time.SECONDS_PER_WEEK commodity.min_value_tuning = 0 commodity.initial_value = 0 commodity._time_passage_fixup_type = CommodityTimePassageFixupType.DO_NOT_FIXUP cls.runtime_commodity = commodity def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._update_alarm_handle = None self._last_update_time = None def run(self, callback): super().run(callback) self._last_update_time = services.time_service().sim_now time_span = clock.interval_in_sim_minutes( Career.CAREER_PERFORMANCE_UPDATE_INTERVAL) self._update_alarm_handle = alarms.add_alarm( self, time_span, lambda alarm_handle: self._update(), repeating=True) def stop(self): if self._update_alarm_handle is not None: alarms.cancel_alarm(self._update_alarm_handle) self._update_alarm_handle = None self._update() super().stop() def _update(self): career = self.sim_info.career_tracker.get_at_work_career() if career is None: logger.error( 'CareerTone {} trying to update performance when Sim {} not at work', self, self.sim_info, owner='tingyul') return if career._upcoming_gig is not None and career._upcoming_gig.odd_job_tuning is not None: return now = services.time_service().sim_now elapsed = now - self._last_update_time self._last_update_time = now career.apply_performance_change(elapsed, self.performance_multiplier) career.resend_career_data() resolver = SingleSimResolver(self.sim_info) for entry in self.periodic_sim_filter_loot: chance = entry.chance.get_chance(resolver) * elapsed.in_hours() if random.random() > chance: continue services.sim_filter_service().submit_filter( entry.sim_filter, self._sim_filter_loot_response, callback_event_data=entry, requesting_sim_info=self.sim_info, gsi_source_fn=self.get_sim_filter_gsi_name) def get_sim_filter_gsi_name(self): return str(self) def _sim_filter_loot_response(self, filter_results, callback_event_data): entry = callback_event_data if entry.max_sims is None: targets = tuple(result.sim_info for result in filter_results) else: sample_size = min(len(filter_results), entry.max_sims) targets = tuple( result.sim_info for result in random.sample(filter_results, sample_size)) for target in targets: resolver = DoubleSimResolver(self.sim_info, target) entry.loot.apply_to_resolver(resolver) def apply_dominant_tone_loot(self): resolver = self.get_resolver() for loot in self.dominant_tone_loot_actions: loot.apply_to_resolver(resolver)
class Gig(HasTunableReference, _GigDisplayMixin, PrepTaskTrackerMixin, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager( sims4.resources.Types.CAREER_GIG)): INSTANCE_TUNABLES = { 'career': TunableReference( description= '\n The career this gig is associated with.\n ', manager=services.get_instance_manager( sims4.resources.Types.CAREER)), 'gig_time': WeeklySchedule.TunableFactory( description= '\n A tunable schedule that will determine when you have to be at work.\n ', export_modes=ExportModes.All), 'gig_prep_time': TunableTimeSpan( description= '\n The amount of time between when a gig is selected and when it\n occurs.\n ', default_hours=5), 'gig_prep_tasks': TunableList( description= '\n A list of prep tasks the Sim can do to improve their performance\n during the gig. \n ', tunable=PrepTask.TunableFactory()), 'loots_on_schedule': TunableList( description= '\n Loot actions to apply when a sim gets a gig.\n ', tunable=LootActions.TunableReference()), 'audio_on_prep_task_completion': OptionalTunable( description= '\n A sting to play at the time a prep task completes.\n ', tunable=TunablePlayAudio( locked_args={ 'immediate_audio': True, 'joint_name_hash': None, 'play_on_active_sim_only': True })), 'gig_pay': TunableVariant( description= '\n Base amount of pay for this gig. Can be either a flat amount or a\n range.\n ', range=TunableInterval(tunable_type=int, default_lower=0, default_upper=100, minimum=0), flat_amount=TunableIntervalLiteral(tunable_type=int, default=0, minimum=0), default='range'), 'additional_pay_per_overmax_level': OptionalTunable( description= '\n If checked, overmax levels will be considered when calculating pay\n for this gig. The actual implementation of this may vary by gig\n type.\n ', tunable=TunableRange(tunable_type=int, default=0, minimum=0)), 'result_based_gig_pay_multipliers': OptionalTunable( description= '\n A set of multipliers for gig pay. The multiplier used depends on the\n GigResult of the gig. The meanings of each GigResult may vary by\n gig type.\n ', tunable=TunableMapping( description= '\n A map between the result type of the gig and the additional pay\n the sim will receive.\n ', key_type=TunableEnumEntry(tunable_type=GigResult, default=GigResult.SUCCESS), value_type=TunableMultiplier.TunableFactory())), 'initial_result_based_career_performance': OptionalTunable( description= "\n A mapping between the GigResult for this gig and the initial\n career performance for the Sim's first gig.\n ", tunable=TunableMapping( description= '\n A map between the result type of the gig and the initial career\n performance the Sim will receive.\n ', key_type=TunableEnumEntry( description= '\n The GigResult enum that represents the outcome of the Gig.\n ', tunable_type=GigResult, default=GigResult.SUCCESS), value_type=Tunable( description= '\n The initial performance value that will be applied.\n ', tunable_type=float, default=0))), 'result_based_career_performance': OptionalTunable( description= '\n A mapping between the GigResult for this gig and the change in\n career performance for the sim.\n ', tunable=TunableMapping( description= '\n A map between the result type of the gig and the career\n performance the sim will receive.\n ', key_type=TunableEnumEntry( description= '\n The GigResult enum that represents the outcome of the Gig.\n ', tunable_type=GigResult, default=GigResult.SUCCESS), value_type=Tunable( description= '\n The performance modification.\n ', tunable_type=float, default=0))), 'result_based_career_performance_multiplier': OptionalTunable( description= '\n A mapping between the GigResult and the multiplier for the career \n performance awarded.\n ', tunable=TunableMapping( description= '\n A map between the result type of the gig and the career\n performance multiplier.\n ', key_type=TunableEnumEntry( description= '\n The GigResult enum that represents the outcome of the Gig.\n ', tunable_type=GigResult, default=GigResult.SUCCESS), value_type=TunableMultiplier.TunableFactory( description= '\n The performance modification multiplier.\n ' ))), 'result_based_loots': OptionalTunable( description= '\n A mapping between the GigResult for this gig and a loot list to\n optionally apply. The resolver for this loot list is either a\n SingleSimResolver of the working sim or a DoubleSimResolver with the\n target being the customer if there is a customer sim.\n ', tunable=TunableMapping( description= '\n A map between the result type of the gig and the loot list.\n ', key_type=TunableEnumEntry(tunable_type=GigResult, default=GigResult.SUCCESS), value_type=TunableList( description= '\n Loot actions to apply.\n ', tunable=LootActions.TunableReference( description= '\n The loot action applied.\n ', pack_safe=True)))), 'payout_stat_data': TunableMapping( description= '\n Stats, and its associated information, that are gained (or lost) \n when sim finishes this gig.\n ', key_type=TunableReference( description= '\n Stat for this payout.\n ', manager=services.get_instance_manager( sims4.resources.Types.STATISTIC)), value_type=TunableTuple( description= '\n Data about this payout stat. \n ', base_amount=Tunable( description= '\n Base amount (pre-modifiers) applied to the sim at the end\n of the gig.\n ', tunable_type=float, default=0.0), medal_to_payout=TunableMapping( description= '\n Mapping of medal -> stat multiplier.\n ', key_type=TunableEnumEntry( description= '\n Medal achieved in this gig.\n ', tunable_type=SituationMedal, default=SituationMedal.TIN), value_type=TunableMultiplier.TunableFactory( description= '\n Mulitiplier on statistic payout if scorable situation\n ends with the associate medal.\n ' )), ui_threshold=TunableList( description= '\n Thresholds and icons we use for this stat to display in \n the end of day dialog. Tune in reverse of highest threshold \n to lowest threshold.\n ', tunable=TunableTuple( description= '\n Threshold and icon for this stat and this gig.\n ', threshold_icon=TunableIcon( description= '\n Icon if the stat is of this threshold.\n ' ), threshold_description=TunableLocalizedStringFactory( description= '\n Description to use with icon\n ' ), threshold=Tunable( description= '\n Threshold that the stat must >= to.\n ', tunable_type=float, default=0.0))))), 'career_events': TunableList( description= '\n A list of available career events for this gig.\n ', tunable=TunableReference(manager=services.get_instance_manager( sims4.resources.Types.CAREER_EVENT))), 'gig_cast_rel_bit_collection_id': TunableEnumEntry( description= '\n If a rel bit is applied to the cast member, it must be of this collection id.\n We use this to clear the rel bit when the gig is over.\n ', tunable_type=RelationshipBitCollectionUid, default=RelationshipBitCollectionUid.Invalid, invalid_enums=(RelationshipBitCollectionUid.All, )), 'gig_cast': TunableList( description= '\n This is the list of sims that need to spawn for this gig. \n ', tunable=TunableTuple( description= '\n Data for cast members. It contains a test which tests against \n the owner of this gig and spawn the necessary sims. A bit\n may be applied through the loot action to determine the type \n of cast members (costars, directors, etc...) \n ', filter_test=TunableTestSet( description= '\n Test used on owner sim.\n ' ), sim_filter=TunableSimFilter.TunableReference( description= '\n If filter test is passed, this sim is created and stored.\n ' ), cast_member_rel_bit=OptionalTunable( description= '\n If tuned, this rel bit will be applied on the spawned cast \n member.\n ', tunable=RelationshipBit.TunableReference( description= '\n Rel bit to apply.\n ' )))), 'end_of_gig_dialog': OptionalTunable( description= '\n A results dialog to show. This dialog allows a list\n of icons with labels. Stats are added at the end of this icons.\n ', tunable=UiDialogLabeledIcons.TunableFactory()), 'disabled_tooltip': OptionalTunable( description= '\n If tuned, the tooltip when this row is disabled.\n ', tunable=TunableLocalizedStringFactory(), tuning_group=GroupNames.UI), 'end_of_gig_notifications': OptionalTunable( description= '\n If enabled, a notification to show at the end of the gig instead of\n a normal career message. Tokens are:\n * 0: The Sim owner of the career\n * 1: The level name (e.g. Chef)\n * 2: The career name (e.g. Culinary)\n * 3: The company name (e.g. Maids United)\n * 4: The pay for the gig\n * 5: The gratuity for the gig\n * 6: The customer (sim) of the gig, if there is a customer.\n * 7: A bullet list of loots and payments as a result of this gig.\n This list uses the text tuned on the loots themselves to create\n bullets for each loot. Those texts will generally have tokens 0\n and 1 be the subject and target sims (of the loot) but may\n have additional tokens depending on the type of loot.\n ', tunable=TunableMapping( description= '\n A map between the result type of the gig and the post-gig\n notification.\n ', key_type=TunableEnumEntry(tunable_type=GigResult, default=GigResult.SUCCESS), value_type=_get_career_notification_tunable_factory()), tuning_group=GroupNames.UI), 'end_of_gig_overmax_notification': OptionalTunable( description= '\n If tuned, the notification that will be used if the sim gains an\n overmax level during this gig. Will override the overmax\n notification in career messages. The following tokens are provided:\n * 0: The Sim owner of the career\n * 1: The level name (e.g. Chef)\n * 2: The career name (e.g. Culinary)\n * 3: The company name (e.g. Maids United)\n * 4: The overmax level\n * 5: The pay for the gig\n * 6: Additional pay tuned at additional_pay_per_overmax_level \n * 7: The overmax rewards in a bullet-point list, in the form of a\n string. These are tuned on the career_track\n ', tunable=_get_career_notification_tunable_factory(), tuning_group=GroupNames.UI), 'end_of_gig_overmax_rewardless_notification': OptionalTunable( description= '\n If tuned, the notification that will be used if the sim gains an\n overmax level with no reward during this gig. Will override the\n overmax rewardless notification in career messages.The following\n tokens are provided:\n * 0: The Sim owner of the career\n * 1: The level name (e.g. Chef)\n * 2: The career name (e.g. Culinary)\n * 3: The company name (e.g. Maids United)\n * 4: The overmax level\n * 5: The pay for the gig\n * 6: Additional pay tuned at additional_pay_per_overmax_level \n ', tunable=_get_career_notification_tunable_factory(), tuning_group=GroupNames.UI), 'end_of_gig_promotion_text': OptionalTunable( description= '\n A string that, if enabled, will be pre-pended to the bullet\n list of results in the promotion notification. Tokens are:\n * 0 : The Sim owner of the career\n ', tunable=TunableLocalizedStringFactory(), tuning_group=GroupNames.UI), 'end_of_gig_demotion_text': OptionalTunable( description= '\n A string that, if enabled, will be pre-pended to the bullet\n list of results in the promotion notification. Tokens are:\n * 0 : The Sim owner of the career\n ', tunable=TunableLocalizedStringFactory(), tuning_group=GroupNames.UI), 'odd_job_tuning': OptionalTunable( description= '\n Tuning specific to odd jobs. Leave untuned if this gig is not an\n odd job.\n ', tunable=TunableTuple( customer_description=TunableLocalizedStringFactory( description= '\n The description of the odd job written by the customer.\n Token 0 is the customer sim.\n ' ), use_customer_description_as_gig_description=Tunable( description= '\n If checked, the customer description will be used as the\n gig description. This description is used as the tooltip\n for the gig icon in the career panel.\n ', tunable_type=bool, default=False), result_based_gig_gratuity_multipliers=TunableMapping( description= '\n A set of multipliers for the gig gratuity. This maps the\n result type of the gig and the gratuity multiplier (a \n percentage). The base pay will be multiplied by this \n multiplier in order to determine the actual gratuity \n amount.\n ', key_type=TunableEnumEntry( description= '\n The GigResult enum that represents the outcome of the \n Gig.\n ', tunable_type=GigResult, default=GigResult.SUCCESS), value_type=TunableMultiplier.TunableFactory( description= '\n The gratuity multiplier to be calculated for this \n GigResult.\n ' )), result_based_gig_gratuity_chance_multipliers=TunableMapping( description= '\n A set of multipliers for determining the gig gratuity \n chance (i.e., the probability the Sim will receive gratuity \n in addition to the base pay). The gratuity chance depends \n on the GigResult of the gig. This maps the result type of \n the gig and the gratuity chance/percentage. If this map\n (or a GigResult) is left untuned, then no gratuity is \n added.\n ', key_type=TunableEnumEntry( description= '\n The GigResult enum that represents the outcome of the \n Gig.\n ', tunable_type=GigResult, default=GigResult.SUCCESS), value_type=TunableMultiplier.TunableFactory( description= '\n The multiplier to be calculated for this GigResult. \n This represents the percentage chance the Sim will \n receive gratuity. If the Sim is to not receive \n gratuity, the base value should be 0 (without further\n tests). If this Sim is guaranteed to receive gratuity,\n the base value should be 1 (without further tests).\n ' )), gig_gratuity_bullet_point_text=OptionalTunable( description= '\n If enabled, the gig gratuity will be a bullet point in the\n bullet pointed list of loots and money supplied to the end\n of gig notification (this is token 7 of that notification).\n If disabled, gratuity will be omitted from that list.\n Tokens:\n * 0: The sim owner of the career\n * 1: The customer\n * 2: the gratuity amount \n ', tunable=TunableLocalizedStringFactory()))), 'tip': OptionalTunable( description= '\n A tip that is displayed with the gig in pickers and in the career\n panel. Can produce something like "Required Skill: Fitness 2".\n ', tunable=TunableTuple( tip_title=TunableLocalizedStringFactory( description= '\n The title string of the tip. Could be something like "Required\n Skill.\n ' ), tip_text=TunableLocalizedStringFactory( description= '\n The text string of the tip. Could be something like "Fitness 2".\n ' ), tip_icon=OptionalTunable(tunable=TunableIcon( description= '\n An icon to show along with the tip.\n ' )))), 'critical_failure_test': OptionalTunable( description= '\n The tests for checking whether or not the Sim should receive the \n CRITICAL_FAILURE outcome. This will override other GigResult \n behavior.\n ', tunable=TunableTestSet( description= '\n The tests to be performed on the Sim (and any customer). If \n the tests pass, the outcome will be CRITICAL_FAILURE. \n ' )) } def __init__(self, owner, customer=None, *args, **kwargs): super().__init__(*args, **kwargs) self._owner = owner self._customer_id = customer.id if customer is not None else None self._upcoming_gig_time = None self._gig_result = None self._gig_pay = None self._gig_gratuity = None self._loot_strings = None self._gig_attended = False @classmethod def get_aspiration(cls): pass @classmethod def get_time_until_next_possible_gig(cls, starting_time): required_prep_time = cls.gig_prep_time() start_considering_prep = starting_time + required_prep_time (time_until, _) = cls.gig_time().time_until_next_scheduled_event( start_considering_prep) if not time_until: return return time_until + required_prep_time def register_aspiration_callbacks(self): aspiration = self.get_aspiration() if aspiration is None: return aspiration.register_callbacks() aspiration_tracker = self._owner.aspiration_tracker if aspiration_tracker is None: return aspiration_tracker.validate_and_return_completed_status(aspiration) aspiration_tracker.process_test_events_for_aspiration(aspiration) def notify_gig_attended(self): self._gig_attended = True def has_attended_gig(self): return self._gig_attended def notify_canceled(self): self._gig_result = GigResult.CANCELED self._send_gig_telemetry(TELEMETRY_GIG_PROGRESS_CANCEL) def get_career_performance(self, first_gig=False): if not self.result_based_career_performance: return 0 if self.initial_result_based_career_performance is not None and first_gig and self._gig_result in self.initial_result_based_career_performance: return self.initial_result_based_career_performance[ self._gig_result] performance_modifier = 1 if self.result_based_career_performance_multiplier: if self._gig_result in self.result_based_career_performance_multiplier: resolver = self.get_resolver_for_gig() performance_modifier = self.result_based_career_performance_multiplier[ self._gig_result].get_multiplier(resolver) return self.result_based_career_performance.get( self._gig_result, 0) * performance_modifier def treat_work_time_as_due_date(self): return False @classmethod def create_picker_row(cls, description=None, scheduled_time=None, owner=None, gig_customer=None, enabled=True, **kwargs): tip = cls.tip description = cls.gig_picker_localization_format( cls.gig_pay.lower_bound, cls.gig_pay.upper_bound, scheduled_time, tip.tip_title(), tip.tip_text(), gig_customer) if not enabled and cls.disabled_tooltip is not None: row_tooltip = lambda *_: cls.disabled_tooltip(owner) elif cls.display_description is None: row_tooltip = None else: row_tooltip = lambda *_: cls.display_description(owner) if cls.odd_job_tuning is not None: customer_description = cls.odd_job_tuning.customer_description( gig_customer) row = OddJobPickerRow(customer_id=gig_customer.id, customer_description=customer_description, tip_title=tip.tip_title(), tip_text=tip.tip_text(), tip_icon=tip.tip_icon, name=cls.display_name(owner), icon=cls.display_icon, row_description=description, row_tooltip=row_tooltip, is_enable=enabled) else: row = ObjectPickerRow(name=cls.display_name(owner), icon=cls.display_icon, row_description=description, row_tooltip=row_tooltip, is_enable=enabled) return row def get_gig_time(self): return self._upcoming_gig_time def get_gig_customer(self): return self._customer_id def clean_up_gig(self): if self.gig_prep_tasks: self.prep_task_cleanup() def save_gig(self, gig_proto_buff): gig_proto_buff.gig_type = self.guid64 gig_proto_buff.gig_time = self._upcoming_gig_time if hasattr(gig_proto_buff, 'gig_attended'): gig_proto_buff.gig_attended = self._gig_attended if self._customer_id: gig_proto_buff.customer_sim_id = self._customer_id def load_gig(self, gig_proto_buff): self._upcoming_gig_time = DateAndTime(gig_proto_buff.gig_time) if self.gig_prep_tasks: self.prep_time_start(self._owner, self.gig_prep_tasks, self.guid64, self.audio_on_prep_task_completion, from_load=True) if gig_proto_buff.HasField('customer_sim_id'): self._customer_id = gig_proto_buff.customer_sim_id if gig_proto_buff.HasField('gig_attended'): self._gig_attended = gig_proto_buff.gig_attended def set_gig_time(self, upcoming_gig_time): self._upcoming_gig_time = upcoming_gig_time def get_resolver_for_gig(self): if self._customer_id is not None: customer_sim_info = services.sim_info_manager().get( self._customer_id) if customer_sim_info is not None: return DoubleSimResolver(self._owner, customer_sim_info) return SingleSimResolver(self._owner) def set_up_gig(self): if self.gig_prep_tasks: self.prep_time_start(self._owner, self.gig_prep_tasks, self.guid64, self.audio_on_prep_task_completion) if self.loots_on_schedule: resolver = self.get_resolver_for_gig() for loot_actions in self.loots_on_schedule: loot_actions.apply_to_resolver(resolver) self._send_gig_telemetry(TELEMETRY_GIG_PROGRESS_STARTED) def collect_rabbit_hole_rewards(self): pass def _get_additional_loots(self): if self.result_based_loots is not None and self._gig_result is not None: loots = self.result_based_loots.get(self._gig_result) if loots is not None: return loots return () def collect_additional_rewards(self): loots = self._get_additional_loots() if loots: self._loot_strings = [] resolver = self.get_resolver_for_gig() for loot_actions in loots: self._loot_strings.extend( loot_actions.apply_to_resolver_and_get_display_texts( resolver)) def _determine_gig_outcome(self): raise NotImplementedError def get_pay(self, overmax_level=None, **kwargs): self._determine_gig_outcome() pay = self.gig_pay.lower_bound if self.additional_pay_per_overmax_level: pay = pay + overmax_level * self.additional_pay_per_overmax_level resolver = self.get_resolver_for_gig() if self.result_based_gig_pay_multipliers: if self._gig_result in self.result_based_gig_pay_multipliers: multiplier = self.result_based_gig_pay_multipliers[ self._gig_result].get_multiplier(resolver) pay = int(pay * multiplier) gratuity = 0 if self.odd_job_tuning: if self.odd_job_tuning.result_based_gig_gratuity_multipliers: if self.odd_job_tuning.result_based_gig_gratuity_chance_multipliers: gratuity_multiplier = 0 gratuity_chance = 0 if self._gig_result in self.odd_job_tuning.result_based_gig_gratuity_chance_multipliers: gratuity_chance = self.odd_job_tuning.result_based_gig_gratuity_chance_multipliers[ self._gig_result].get_multiplier(resolver) if random.random() <= gratuity_chance: if self._gig_result in self.odd_job_tuning.result_based_gig_gratuity_multipliers: gratuity_multiplier = self.odd_job_tuning.result_based_gig_gratuity_multipliers[ self._gig_result].get_multiplier(resolver) gratuity = int(pay * gratuity_multiplier) self._gig_pay = pay self._gig_gratuity = gratuity return pay + gratuity def get_promotion_evaluation_result(self, reward_text, *args, first_gig=False, **kwargs): if self._gig_result is not None and self.end_of_gig_notifications is not None: notification = self.end_of_gig_notifications.get( self._gig_result, None) if notification: customer_sim_info = services.sim_info_manager().get( self._customer_id) if self.end_of_gig_promotion_text and not first_gig: results_list = self.get_results_list( self.end_of_gig_promotion_text(self._owner)) else: results_list = self.get_results_list() return EvaluationResult(Evaluation.PROMOTED, notification, self._gig_pay, self._gig_gratuity, customer_sim_info, results_list) def get_demotion_evaluation_result(self, *args, first_gig=False, **kwargs): if self._gig_result is not None and self.end_of_gig_notifications is not None: notification = self.end_of_gig_notifications.get( self._gig_result, None) if notification: customer_sim_info = services.sim_info_manager().get( self._customer_id) if self.end_of_gig_demotion_text and not first_gig: results_list = self.get_results_list( self.end_of_gig_demotion_text(self._owner)) else: results_list = self.get_results_list() return EvaluationResult(Evaluation.DEMOTED, notification, self._gig_pay, self._gig_gratuity, customer_sim_info, results_list) def get_overmax_evaluation_result(self, overmax_level, reward_text, *args, **kwargs): if reward_text and self.end_of_gig_overmax_notification: return EvaluationResult(Evaluation.PROMOTED, self.end_of_gig_overmax_notification, overmax_level, self._gig_pay, self.additional_pay_per_overmax_level, reward_text) elif self.end_of_gig_overmax_rewardless_notification: return EvaluationResult( Evaluation.PROMOTED, self.end_of_gig_overmax_rewardless_notification, overmax_level, self._gig_pay, self.additional_pay_per_overmax_level) def _get_strings_for_results_list(self): strings = [] if self._gig_pay is not None: strings.append(LocalizationHelperTuning.MONEY(self._gig_pay)) if self.odd_job_tuning is not None and self._gig_gratuity: gratuity_text_factory = self.odd_job_tuning.gig_gratuity_bullet_point_text if gratuity_text_factory is not None: customer_sim_info = services.sim_info_manager().get( self._customer_id) gratuity_text = gratuity_text_factory(self._owner, customer_sim_info, self._gig_gratuity) strings.append(gratuity_text) if self._loot_strings: strings.extend(self._loot_strings) return strings def get_results_list(self, *additional_tokens): return LocalizationHelperTuning.get_bulleted_list( None, *additional_tokens, *self._get_strings_for_results_list()) def get_end_of_gig_evaluation_result(self, **kwargs): if self._gig_result is not None and self.end_of_gig_notifications is not None: notification = self.end_of_gig_notifications.get( self._gig_result, None) if notification: customer_sim_info = services.sim_info_manager().get( self._customer_id) return EvaluationResult(Evaluation.ON_TARGET, notification, self._gig_pay, self._gig_gratuity, customer_sim_info, self.get_results_list()) @classmethod def _get_base_pay_for_gig_owner(cls, owner): overmax_pay = cls.additional_pay_per_overmax_level if overmax_pay is not None: career = owner.career_tracker.get_career_by_uid(cls.career.guid64) if career is not None: overmax_pay *= career.overmax_level return (cls.gig_pay.lower_bound + overmax_pay, cls.gig_pay.upper_bound + overmax_pay) return (cls.gig_pay.lower_bound, cls.gig_pay.upper_bound) @classmethod def build_gig_msg(cls, msg, sim, gig_time=None, gig_customer=None): msg.gig_type = cls.guid64 msg.gig_name = cls.display_name(sim) (pay_lower, pay_upper) = cls._get_base_pay_for_gig_owner(sim) msg.min_pay = pay_lower msg.max_pay = pay_upper msg.gig_icon = ResourceKey() msg.gig_icon.instance = cls.display_icon.instance msg.gig_icon.group = cls.display_icon.group msg.gig_icon.type = cls.display_icon.type if cls.odd_job_tuning is not None and cls.odd_job_tuning.use_customer_description_as_gig_description and gig_customer is not None: customer_sim_info = services.sim_info_manager().get(gig_customer) if customer_sim_info is not None: msg.gig_description = cls.odd_job_tuning.customer_description( customer_sim_info) else: msg.gig_description = cls.display_description(sim) if gig_time is not None: msg.gig_time = gig_time if gig_customer is not None: msg.customer_id = gig_customer if cls.tip is not None: msg.tip_title = cls.tip.tip_title() if cls.tip.tip_icon is not None or cls.tip.tip_text is not None: build_icon_info_msg( IconInfoData(icon_resource=cls.tip.tip_icon), None, msg.tip_icon, desc=cls.tip.tip_text()) def send_prep_task_update(self): if self.gig_prep_tasks: self._prep_task_tracker.send_prep_task_update() def _apply_payout_stat(self, medal, payout_display_data=None): owner_sim = self._owner resolver = SingleSimResolver(owner_sim) payout_stats = self.payout_stat_data for stat in payout_stats.keys(): stat_tracker = owner_sim.get_tracker(stat) if not owner_sim.get_tracker(stat).has_statistic(stat): continue stat_data = payout_stats[stat] stat_multiplier = 1.0 if medal in stat_data.medal_to_payout: multiplier = stat_data.medal_to_payout[medal] stat_multiplier = multiplier.get_multiplier(resolver) stat_total = stat_data.base_amount * stat_multiplier stat_tracker.add_value(stat, stat_total) if payout_display_data is not None: for threshold_data in stat_data.ui_threshold: if stat_total >= threshold_data.threshold: payout_display_data.append(threshold_data) break def _send_gig_telemetry(self, progress): with telemetry_helper.begin_hook(gig_telemetry_writer, TELEMETRY_HOOK_GIG_PROGRESS, sim_info=self._owner) as hook: hook.write_int(TELEMETRY_CAREER_ID, self.career.guid64) hook.write_int(TELEMETRY_GIG_ID, self.guid64) hook.write_int(TELEMETRY_GIG_PROGRESS_NUMBER, progress)
class _OutfitActionApplyCareerOutfit(HasTunableSingletonFactory, AutoFactoryInit): FACTORY_TUNABLES = { 'picker_dialog': UiSimPicker.TunableFactory( description= '\n The picker dialog to show when selecting Sims to apply this\n outfit on.\n ' ), 'sim_filter': TunableSimFilter.TunableReference( description= '\n The set of available Sims to show in the Sim picker.\n ' ), 'pie_menu_test_tooltip': OptionalTunable( description= '\n If enabled, then a greyed-out tooltip will be displayed if there\n are no valid choices.\n ', tunable=TunableLocalizedStringFactory( description= '\n The tooltip text to show in the greyed-out tooltip when no\n valid choices exist.\n ' )) } def get_disabled_tooltip(self): if self.pie_menu_test_tooltip is None: return else: filter_results = self._get_filter_results() if not filter_results: return self.pie_menu_test_tooltip def get_sim_filter_gsi_name(self): return str(self) def _get_filter_results(self): return services.sim_filter_service().submit_filter( self.sim_filter, None, allow_yielding=False, gsi_source_fn=self.get_sim_filter_gsi_name) def _get_on_sim_choice_selected(self, interaction, picked_items): def _on_sim_choice_selected(dialog): if dialog.accepted: outfit_source = interaction.outfit_sim_info.get_outfit_sim_info( interaction) for sim_info in dialog.get_result_tags(): sim_info.generate_merged_outfit( outfit_source, (OutfitCategory.CAREER, 0), sim_info.get_current_outfit(), picked_items[0]) sim_info.resend_current_outfit() return _on_sim_choice_selected def on_choice_selected(self, interaction, picked_items, **kwargs): dialog = self.picker_dialog(interaction.sim, title=lambda *_, **__: interaction. get_name(apply_name_modifiers=False), resolver=interaction.get_resolver()) for filter_result in self._get_filter_results(): dialog.add_row( SimPickerRow(filter_result.sim_info.sim_id, tag=filter_result.sim_info)) dialog.add_listener( self._get_on_sim_choice_selected(interaction, picked_items)) dialog.show_dialog()
class AdoptionPickerInteraction(SuperInteraction, PickerSuperInteractionMixin): __qualname__ = 'AdoptionPickerInteraction' INSTANCE_TUNABLES = { 'picker_dialog': TunablePickerDialogVariant( description='\n Sim Picker Dialog\n ', available_picker_flags=ObjectPickerTuningFlags.SIM, tuning_group=GroupNames.PICKERTUNING), 'sim_filters': TunableList( description= "\n A list of tuples of number of sims to find and filter to find\n them. If there aren't enough sims to be found from a filter\n then the filter is used to create the sims. Sims that are\n found from one filter are placed into the black list for\n running the next filter in order to make sure that sims don't\n double dip creating one filter to the next.\n ", tunable=TunableTuple( number_of_sims=TunableRange( description= '\n The number of sims to find using the filter. If no\n sims are found then sims will be created to fit the\n filter.\n ', tunable_type=int, default=1, minimum=1), filter=TunableSimFilter.TunableReference( description= '\n Sim filter that is used to create find the number of\n sims that we need for this filter request.\n ' ), description= '\n Tuple of number of sims that we want to find and filter\n that will be used to find them.\n ' ), tuning_group=GroupNames.PICKERTUNING), 'actor_continuation': TunableContinuation( description= '\n A continuation that is pushed when the acting sim is selected.\n ', locked_args={'actor': ParticipantType.Actor}, tuning_group=GroupNames.PICKERTUNING) } def __init__(self, *args, **kwargs): super().__init__( choice_enumeration_strategy=SimPickerEnumerationStrategy(), *args, **kwargs) self._picked_sim_id = None self.sim_ids = [] def _run_interaction_gen(self, timeline): yield self._get_valid_choices_gen(timeline) self._show_picker_dialog(self.sim, target_sim=self.sim, target=self.target) yield element_utils.run_child(timeline, element_utils.soft_sleep_forever()) if self._picked_sim_id is None: self.remove_liability(PaymentLiability.LIABILITY_TOKEN) return False picked_item_set = {self._picked_sim_id} self.interaction_parameters['picked_item_ids'] = frozenset( picked_item_set) self.push_tunable_continuation(self.actor_continuation, picked_item_ids=picked_item_set) return True def _get_valid_choices_gen(self, timeline): self.sim_ids = [] requesting_sim_info = self.sim.sim_info blacklist = { sim_info.id for sim_info in services.sim_info_manager().instanced_sims_gen( allow_hidden_flags=ALL_HIDDEN_REASONS) } for sim_filter in self.sim_filters: for _ in range(sim_filter.number_of_sims): sim_infos = services.sim_filter_service( ).submit_matching_filter( 1, sim_filter.filter, None, blacklist_sim_ids=blacklist, requesting_sim_info=requesting_sim_info, allow_yielding=False, zone_id=0) for sim_info in sim_infos: self.sim_ids.append(sim_info.id) blacklist.add(sim_info.id) yield element_utils.run_child( timeline, element_utils.sleep_until_next_tick_element()) @flexmethod def create_row(cls, inst, tag): return SimPickerRow(sim_id=tag, tag=tag) @flexmethod def picker_rows_gen(cls, inst, target, context, **kwargs): if inst is not None: for sim_id in inst.sim_ids: logger.info('AdoptionPicker: add sim_id:{}', sim_id) row = inst.create_row(sim_id) yield row def _pre_perform(self, *args, **kwargs): if self.sim.household.free_slot_count == 0: self.cancel( FinishingType.FAILED_TESTS, cancel_reason_msg="There aren't any free household slots.") return self.add_liability(ADOPTION_LIABILTIY, AdoptionLiability()) return super()._pre_perform(*args, **kwargs) def on_choice_selected(self, choice_tag, **kwargs): sim_id = choice_tag if sim_id is not None: self._picked_sim_id = sim_id self.add_liability( SIM_FILTER_GLOBAL_BLACKLIST_LIABILITY, SimFilterGlobalBlacklistLiability( (sim_id, ), SimFilterGlobalBlacklistReason.ADOPTION)) self.trigger_soft_stop()