def _run(self, timeline): global logged_missing_interaction_callstack if self.interaction is None: if not logged_missing_interaction_callstack: logger.callstack( 'Attempting to run an animation {} without a corresponding interaction.', self, level=sims4.log.LEVEL_ERROR) logged_missing_interaction_callstack = True return False if self.asm_key is None: return True asm = self.get_asm() if asm is None: return False self._set_alternative_prop_overrides(asm) if self.overrides.balloons: balloon_requests = TunableBalloon.get_balloon_requests( self.interaction, self.overrides) else: balloon_requests = None success = timeline.run_child( animate_states(asm, self.begin_states, self.end_states, overrides=self.overrides, balloon_requests=balloon_requests, repeat_begin_states=self.repeat, interaction=self.interaction, **self.animate_kwargs)) return success
def _register_balloon_requests(asm, interaction, overrides, balloon_requests, repeat=False): if not balloon_requests: return remaining_balloons = list(balloon_requests) for balloon_request in balloon_requests: balloon_delay = balloon_request.delay or 0 if balloon_request.delay_randomization > 0: balloon_delay += random.random( ) * balloon_request.delay_randomization if asm.context.register_custom_event_handler( _create_balloon_request_callback( balloon_request=balloon_request), None, balloon_delay, allow_stub_creation=True): remaining_balloons.remove(balloon_request) if repeat: break if remaining_balloons and not repeat: logger.error('Failed to schedule all requested balloons for {}', asm) elif repeat: balloon_requests = TunableBalloon.get_balloon_requests( interaction, overrides) return balloon_requests
def append_to_arb(cls, inst, asm, arb): if inst is not None and inst.overrides: inst.overrides.override_asm(asm) balloon_requests = TunableBalloon.get_balloon_requests( inst.interaction, inst.overrides) _register_balloon_requests(asm, inst.interaction, inst.overrides, balloon_requests) for state_name in cls.begin_states: asm.request(state_name, arb)
def route_failure(sim, interaction, failure_reason, failure_object_id): global ROUTE_FAILURE_OVERRIDE_MAP if not sim.should_route_fail: return overrides = None if failure_reason is not None: if ROUTE_FAILURE_OVERRIDE_MAP is None: ROUTE_FAILURE_OVERRIDE_MAP = { TransitionFailureReasons.BLOCKING_OBJECT: RouteFailureTunables.route_fail_overrides_object, TransitionFailureReasons.RESERVATION: RouteFailureTunables.route_fail_overrides_reservation, TransitionFailureReasons.BUILD_BUY: RouteFailureTunables.route_fail_overrides_build, TransitionFailureReasons.NO_DESTINATION_NODE: RouteFailureTunables.route_fail_overrides_no_dest_node, TransitionFailureReasons.NO_PATH_FOUND: RouteFailureTunables.route_fail_overrides_no_path_found, TransitionFailureReasons.NO_VALID_INTERSECTION: RouteFailureTunables. route_fail_overrides_no_valid_intersection, TransitionFailureReasons.NO_GOALS_GENERATED: RouteFailureTunables.route_fail_overrides_no_goals_generated, TransitionFailureReasons.NO_CONNECTIVITY_TO_GOALS: RouteFailureTunables.route_fail_overrides_no_connectivity, TransitionFailureReasons.PATH_PLAN_FAILED: RouteFailureTunables.route_fail_overrides_path_plan_fail, TransitionFailureReasons.GOAL_ON_SLOPE: RouteFailureTunables.route_fail_overrides_goal_on_slope } if failure_reason in ROUTE_FAILURE_OVERRIDE_MAP: overrides = ROUTE_FAILURE_OVERRIDE_MAP[failure_reason]() if failure_object_id is not None: fail_obj = services.object_manager().get(failure_object_id) if fail_obj is not None: if fail_obj.blocking_balloon_overrides is not None: overrides.balloons = fail_obj.blocking_balloon_overrides else: overrides.balloon_target_override = fail_obj route_fail_anim = RouteFailureTunables.route_fail_animation( sim.posture.source_interaction, overrides=overrides, sequence=()) supported_postures = route_fail_anim.get_supported_postures() if supported_postures: return build_element((route_fail_anim, flush_all_animations)) else: balloon_requests = TunableBalloon.get_balloon_requests( interaction, route_fail_anim.overrides) return balloon_requests
class ContentSetWithOverrides(ContentSet): FACTORY_TUNABLES = { 'balloon_overrides': OptionalTunable( TunableList( description= '\n Balloon Overrides lets you override the mixer balloons.\n EX: Each of the comedy routine performances have a set of balloons.\n However, the animation/mixer content is the same. We want to play\n the same mixer content, but just have the balloons be different.\n ', tunable=TunableBalloon())), 'additional_mixers_to_cache': TunableInterval( description= "\n Additional number of mixers to cache during a subaction request. For\n mixer autonomy, we cache mixer for performance reasons. The baseline\n cache size is determined by the mixer_interaction_cache_size tunable\n on the Sim's autonomy component.\n \n An example for reason to add more mixers to cache if there are\n large number of mixers tuned in this content set such as socials,\n you may need to increase this number. \n \n Please talk to GPE if you are about to add additional mixers.\n ", tunable_type=int, minimum=0, default_lower=0, default_upper=0) } def __init__(self, balloon_overrides, additional_mixers_to_cache, *args, **kwargs): super().__init__(*args, **kwargs) self.balloon_overrides = balloon_overrides self.additional_mixers_to_cache = additional_mixers_to_cache
def create_passive_ballon_request(sim, balloon_data): if gsi_handlers.balloon_handlers.archiver.enabled: gsi_entries = [] else: gsi_entries = None resolver = SingleSimResolver(sim.sim_info) balloon_icon = TunableBalloon.select_balloon_icon( balloon_data.balloon_choices, resolver, gsi_entries=gsi_entries, gsi_interaction=None, gsi_balloon_target_override=None) category_icon = None if balloon_icon is not None: icon_info = balloon_icon.icon(resolver, balloon_target_override=None) if balloon_icon.category_icon is not None: category_icon = balloon_icon.category_icon( resolver, balloon_target_override=None) else: icon_info = None if gsi_handlers.balloon_handlers.archiver.enabled: gsi_handlers.balloon_handlers.archive_balloon_data( sim, None, balloon_icon, icon_info, gsi_entries) if balloon_icon is not None and (icon_info[0] is not None or icon_info[1] is not None): (balloon_type, priority) = BALLOON_TYPE_LOOKUP[balloon_icon.balloon_type] balloon_overlay = balloon_icon.overlay request = BalloonRequest(sim, icon_info[0], icon_info[1], balloon_overlay, balloon_type, priority, TunableBalloon.BALLOON_DURATION, balloon_data.balloon_delay, balloon_data.balloon_delay_random_offset, category_icon) return request
class WaypointInteraction(SuperInteraction): INSTANCE_TUNABLES = {'waypoint_constraint': TunableWaypointGeneratorVariant(tuning_group=GroupNames.ROUTING), 'waypoint_count': TunableRange(description='\n The number of waypoints to select, from spawn points in the zone, to\n visit for a Jog prior to returning to the original location.\n ', tunable_type=int, default=2, minimum=2, tuning_group=GroupNames.ROUTING), 'waypoint_walk_style': WalkStyleRequest.TunableFactory(description='\n The walkstyle to use when routing between waypoints.\n ', tuning_group=GroupNames.ROUTING), 'waypoint_stitching': WaypointStitchingVariant(tuning_group=GroupNames.ROUTING), 'waypoint_randomize_orientation': Tunable(description='\n Make Waypoint orientation random. Default is velocity aligned.\n ', tunable_type=bool, default=False, tuning_group=GroupNames.ROUTING), 'waypoint_clear_locomotion_mask': Tunable(description='\n If enabled, override the locomotion queue mask. This mask controls\n which Animation Requests and XEvents get blocked during locomotion.\n By default, the mask blocks everything. If cleared, it blocks\n nothing. It also lowers the animation track used by locomotion to \n 9,999 from the default of 10,000. Use with care, ask your GPE.\n ', tunable_type=bool, default=False, tuning_group=GroupNames.ROUTING), 'waypoint_override_agent_radius': OptionalTunable(description='\n If enabled, use the specified value as the agent radius when\n generating goals for the waypoints. The agent radius is restored\n for the actual route.\n ', tunable=TunableRange(description='\n The value to use as the agent radius when generating goals. \n ', tunable_type=float, minimum=0, maximum=1.0, default=0.123), tuning_group=GroupNames.ROUTING), 'waypoint_route_fail_balloon': OptionalTunable(description='\n Tuning for balloon to show when failing to plan a aroute for this waypoint interaction. \n ', tunable=TunableBalloon(locked_args={'balloon_delay': 0, 'balloon_delay_random_offset': 0, 'balloon_chance': 100}), tuning_group=GroupNames.ROUTING)} def __init__(self, aop, *args, **kwargs): super().__init__(aop, *args, **kwargs) waypoint_info = kwargs.get('waypoint_info') if waypoint_info is not None: self._waypoint_generator = _WaypointGeneratorRallyable(waypoint_info) else: if aop.target is None and self.target_type is TargetType.ACTOR: target = self.sim else: target = aop.target self._waypoint_generator = self.waypoint_constraint(self.context, target) self._routing_infos = None self._goal_size = 0.0 self.register_on_finishing_callback(self._clean_up_waypoint_generator) @classmethod def _test(cls, target, context, **interaction_parameters): sim = context.sim routing_master = sim.routing_master if routing_master is not None and sim.parent is not routing_master: return TestResult(False, '{} cannot run Waypoint interactions because they are following {}', sim, routing_master) return super()._test(target, context, **interaction_parameters) def _get_starting_constraint(self, *args, **kwargs): constraint = ANYWHERE target = self.target if self._waypoint_generator.is_for_vehicle and (target is not None and target.vehicle_component is not None) and not target.is_in_inventory(): constraint = Circle(target.position, target.vehicle_component.minimum_route_distance, routing_surface=target.routing_surface) constraint = constraint.intersect(self._waypoint_generator.get_water_constraint()) else: constraint = self._waypoint_generator.get_start_constraint() posture_constraint = self._waypoint_generator.get_posture_constraint() if posture_constraint is not None: constraint = constraint.intersect(posture_constraint) return constraint @flexmethod def _constraint_gen(cls, inst, *args, **kwargs): inst_or_cls = inst if inst is not None else cls if inst is not None: constraint = inst._get_starting_constraint(*args, **kwargs) yield constraint yield from super(__class__, inst_or_cls)._constraint_gen(*args, **kwargs) def cancel(self, *args, **kwargs): for sim_primitive in list(self.sim.primitives): if isinstance(sim_primitive, FollowPath): sim_primitive.detach() return super().cancel(*args, **kwargs) def _clean_up_waypoint_generator(self, _): self._waypoint_generator.clean_up() def _get_goals_for_constraint(self, constraint, routing_agent): goals = [] handles = constraint.get_connectivity_handles(routing_agent) for handle in handles: goals.extend(handle.get_goals(always_reject_invalid_goals=True)) return goals def _show_route_fail_balloon(self): balloon_tuning = self.waypoint_route_fail_balloon if balloon_tuning is None: return if not self.is_user_directed: return balloon_requests = balloon_tuning(self) if balloon_requests: chosen_balloon = random.random.choice(balloon_requests) if chosen_balloon is not None: chosen_balloon.distribute() def _run_interaction_gen(self, timeline): all_sims = self.required_sims() if not all_sims: return self._routing_infos = [] routing_agent = self.sim for sim in all_sims: routing_context = sim.routing_context routing_agent = sim vehicle = None if not sim.posture.is_vehicle else sim.parent if vehicle is not None: if vehicle.vehicle_component is not None: routing_agent = vehicle routing_context = vehicle.routing_component.pathplan_context self._routing_infos.append((routing_agent, routing_context)) waypoints = [] default_agent_radius = None if self.waypoint_override_agent_radius is not None: if routing_agent.routing_component is not None: default_agent_radius = routing_agent.routing_component._pathplan_context.agent_radius routing_agent.routing_component._pathplan_context.agent_radius = self.waypoint_override_agent_radius try: for constraint in self._waypoint_generator.get_waypoint_constraints_gen(routing_agent, self.waypoint_count): goals = self._get_goals_for_constraint(constraint, routing_agent) if not goals: continue if self.waypoint_randomize_orientation: for goal in goals: goal.orientation = sims4.math.angle_to_yaw_quaternion(random.uniform(0.0, sims4.math.TWO_PI)) waypoints.append(goals) finally: if default_agent_radius is not None: routing_agent.routing_component._pathplan_context.agent_radius = default_agent_radius if not waypoints: return False yield self._goal_size = max(info[0].routing_component.get_routing_context().agent_goal_radius for info in self._routing_infos) self._goal_size *= self._goal_size if self.staging: for route_waypoints in itertools.cycle(self.waypoint_stitching(waypoints, self._waypoint_generator.loops)): result = yield from self._do_route_to_constraint_gen(route_waypoints, timeline) if not result: return result yield else: return result yield else: for route_waypoints in self.waypoint_stitching(waypoints, self._waypoint_generator.loops): result = yield from self._do_route_to_constraint_gen(route_waypoints, timeline) return result yield return True yield def _do_route_to_constraint_gen(self, waypoints, timeline): if self.is_finishing: return False yield plan_primitives = [] for (i, routing_info) in enumerate(self._routing_infos): routing_agent = routing_info[0] routing_context = routing_info[1] route = routing.Route(routing_agent.routing_location, waypoints[-1], waypoints=waypoints[:-1], routing_context=routing_context) plan_primitive = PlanRoute(route, routing_agent, interaction=self) result = yield from element_utils.run_child(timeline, plan_primitive) if not result: self._show_route_fail_balloon() return False yield if not (plan_primitive.path.nodes and plan_primitive.path.nodes.plan_success): self._show_route_fail_balloon() return False yield plan_primitive.path.blended_orientation = self.waypoint_randomize_orientation plan_primitives.append(plan_primitive) if i == len(self._routing_infos) - 1: continue for node in plan_primitive.path.nodes: position = Vector3(*node.position) for goal in itertools.chain.from_iterable(waypoints): if goal.routing_surface_id != node.routing_surface_id: continue dist_sq = (Vector3(*goal.position) - position).magnitude_2d_squared() if dist_sq < self._goal_size: goal.cost = routing.get_default_obstacle_cost() route_primitives = [] track_override = None mask_override = None if self.waypoint_clear_locomotion_mask: mask_override = 0 track_override = 9999 for plan_primitive in plan_primitives: sequence = get_route_element_for_path(plan_primitive.sim, plan_primitive.path, interaction=self, force_follow_path=True, track_override=track_override, mask_override=mask_override) walkstyle_request = self.waypoint_walk_style(plan_primitive.sim) sequence = walkstyle_request(sequence=sequence) route_primitives.append(sequence) result = yield from element_utils.run_child(timeline, do_all(*route_primitives)) return result yield @classmethod def get_rallyable_aops_gen(cls, target, context, **kwargs): key = 'waypoint_info' if key not in kwargs: waypoint_generator = cls.waypoint_constraint(context, target) kwargs[key] = waypoint_generator yield from super().get_rallyable_aops_gen(target, context, rally_constraint=waypoint_generator.get_start_constraint(), **kwargs)
def __init__ (self, **kwargs): super().__init__(add_to_ensemble = AddToEnsemble.TunableFactory(), add_to_household = AddToHouseholdElement.TunableFactory(), add_to_travel_group = TravelGroupAdd.TunableFactory(), adventure = Adventure.TunableFactory(), audio_modification = TunableAudioModificationElement(), audio_sting = TunableAudioSting.TunableFactory(), balloon = TunableBalloon(), broadcaster = BroadcasterRequest.TunableFactory(), buff = TunableBuffElement(), buff_fire_and_forget = BuffFireAndForgetElement.TunableFactory(), business_buy_lot = BusinessBuyLot.TunableFactory(), business_employee_action = BusinessEmployeeAction.TunableFactory(), call_to_action_turn_off = TurnOffCallToAction.TunableFactory(), camera_focus = CameraFocusElement.TunableFactory(), career_selection = careers.career_tuning.CareerSelectElement.TunableFactory(), change_age = ChangeAgeElement.TunableFactory(), change_outfit = ChangeOutfitElement.TunableFactory(), conditional_layer = ManipulateConditionalLayer.TunableFactory(), create_object = ObjectCreationElement.TunableFactory(), create_photo_memory = CreatePhotoMemory.TunableFactory(), create_sim = SimCreationElement.TunableFactory(), create_situation = CreateSituationElement.TunableFactory(), deliver_bill = DeliverBill.TunableFactory(), destroy_ensemble = DestroyEnsemble.TunableFactory(), destroy_object = ObjectDestructionElement.TunableFactory(), destroy_situations_by_tags = DestroySituationsByTagsElement.TunableFactory(), destroy_specified_objects_from_target_inventory = DestroySpecifiedObjectsFromTargetInventory.TunableFactory(), display_notebook_ui = NotebookDisplayElement.TunableFactory(), do_command = DoCommand.TunableFactory(), dynamic_spawn_point = DynamicSpawnPointElement.TunableFactory(), end_vacation = TravelGroupEnd.TunableFactory(), exit_carry_while_holding = TunableExitCarryWhileHolding(), extend_vacation = TravelGroupExtend.TunableFactory(), enter_carry_while_holding = EnterCarryWhileHolding.TunableFactory(), fade_children = FadeChildrenElement.TunableFactory(), familiar_bind = BindFamiliarElement.TunableFactory(), familiar_dismiss = DismissFamiliarElement.TunableFactory(), focus = TunableFocusElement(), inventory_transfer = InventoryTransfer.TunableFactory(), invite = InviteSimElement.TunableFactory(), join_situation = JoinSituationElement.TunableFactory(), leave_situation = LeaveSituationElement.TunableFactory(), loot = LootElement.TunableFactory(), lot_decoration = LotDecorationElement.TunableFactory(), life_event = TunableLifeEventElement(), mark_object_as_stolen = MarkObjectAsStolen.TunableFactory(), notification = NotificationElement.TunableFactory(), npc_summon = TunableSummonNpc(), object_relationship_social = ObjectRelationshipSocialTrigger.TunableFactory(), painting_state_transfer = PaintingStateTransfer.TunableFactory(), parent_object = ParentObjectElement.TunableFactory(), payment = PaymentElement.TunableFactory(), play_stored_audio_from_source = TunablePlayStoredAudioFromSource.TunableFactory(), pregnancy = PregnancyElement.TunableFactory(), put_near = PutNearElement.TunableFactory(), put_object_in_mail = PutObjectInMail.TunableFactory(), put_sim_in_rabbit_hole = RabbitHoleElement.TunableFactory(), record_trends = RecordTrendsElement.TunableFactory(), remove_from_ensemble = RemoveFromEnsemble.TunableFactory(), remove_from_travel_group = TravelGroupRemove.TunableFactory(), replace_object = ReplaceObject.TunableFactory(), retail_customer_action = RetailCustomerAction.TunableFactory(), return_stolen_object = ReturnStolenObject.TunableFactory(), route_events = RouteEventProviderRequest.TunableFactory(), routing_formation = RoutingFormationElement.TunableFactory(), royalty_payment = TunableRoyaltyPayment.TunableFactory(), save_participant = SaveParticipantElement.TunableFactory(), send_to_inventory = SendToInventory.TunableFactory(), service_npc_request = ServiceNpcRequest.TunableFactory(), set_as_head = SetAsHeadElement.TunableFactory(), set_game_speed = TunableSetClockSpeed.TunableFactory(), set_goodbye_notification = SetGoodbyeNotificationElement.TunableFactory(), set_photo_filter = SetPhotoFilter.TunableFactory(), set_posture = SetPosture.TunableFactory(), set_visibility_state = SetVisibilityStateElement.TunableFactory(), slot_objects_from_inventory = SlotObjectsFromInventory.TunableFactory(), slot_item_transfer = SlotItemTransfer.TunableFactory(), stat_transfer_remove = TunableStatisticTransferRemove(), state_change = TunableStateChange(), store_sim = StoreSimElement.TunableFactory(), submit_to_festival_contest = FestivalContestSubmitElement.TunableFactory(), switch_occult = SwitchOccultElement.TunableFactory(), take_photo = TakePhoto.TunableFactory(), track_diagnostic_action = TrackDiagnosticAction.TunableFactory(), transfer_carry_while_holding = TransferCarryWhileHolding.TunableFactory(), transfer_name = NameTransfer.TunableFactory(), transfer_stored_audio_component = TransferStoredAudioComponent.TunableFactory(), transience_change = TunableTransienceChange(), trigger_reaction = ReactionTriggerElement.TunableFactory(), university_enrollment_ui = UniversityEnrollmentElement.TunableFactory(), update_family_portrait = UpdateFamilyPortrait.TunableFactory(), update_object_value = UpdateObjectValue.TunableFactory(), update_physique = UpdatePhysique.TunableFactory(), update_display_number = UpdateDisplayNumber.TunableFactory(), walls_up_override = SetWallsUpOverrideElement.TunableFactory(), vfx = PlayVisualEffectElement.TunableFactory(), **kwargs)
class PassiveBalloons: @staticmethod def _validate_tuning(instance_class, tunable_name, source, value): if PassiveBalloons.BALLOON_LOCKOUT + PassiveBalloons.BALLOON_RANDOM >= PassiveBalloons.BALLOON_LONG_LOCKOUT: logger.error( 'PassiveBalloons tuning value error! BALLOON_LONG_LOCKOUT must be tuned to be greater than BALLOON_LOCKOUT + BALLOON_RANDOM' ) BALLOON_LOCKOUT = Tunable( description= '\n The duration, in minutes, for the lockout time between displaying passive balloons.\n ', tunable_type=int, default=10) BALLOON_RANDOM = Tunable( description= '\n The duration, in minutes, for a random amount to be added to the lockout\n time between displaying passive balloons.\n ', tunable_type=int, default=20) BALLOON_LONG_LOCKOUT = Tunable( description= '\n The duration, in minutes, to indicate that a long enough time has passed\n since the last balloon, to trigger a delay of the next balloon by the\n random amount of time from BALLOON_RANDOM. The reason for this is so\n that newly spawned walkby sims that begin routing do not display their\n first routing balloon immediately. Make sure that this is always higher\n than the tuned values in BALLOON_LOCKOUT + BALLOON_RANDOM, or it will\n not work as intended.\n ', tunable_type=int, default=120, callback=_validate_tuning) MAX_NUM_BALLOONS = Tunable( description= '\n The maximum number of passive balloon tuning data entries to process per\n balloon display attempt\n ', tunable_type=int, default=25) ROUTING_BALLOONS = TunableList( description= '\n A weighted list of passive routing balloons.\n ', tunable=TunableTuple(balloon=TunableBalloon( locked_args={ 'balloon_delay': 0, 'balloon_delay_random_offset': 0, 'balloon_chance': 100, 'balloon_target': None }), weight=Tunable(tunable_type=int, default=1))) @staticmethod def request_routing_to_object_balloon(sim, interaction): balloon_tuning = interaction.route_start_balloon if balloon_tuning is None: return if interaction.is_user_directed and not balloon_tuning.also_show_user_directed: return balloon_requests = balloon_tuning.balloon(interaction) if balloon_requests: choosen_balloon = random.choice(balloon_requests) if choosen_balloon is not None: choosen_balloon.distribute() @staticmethod def create_passive_ballon_request(sim, balloon_data): if gsi_handlers.balloon_handlers.archiver.enabled: gsi_entries = [] else: gsi_entries = None resolver = SingleSimResolver(sim.sim_info) balloon_icon = TunableBalloon.select_balloon_icon( balloon_data.balloon_choices, resolver, gsi_entries=gsi_entries, gsi_interaction=None, gsi_balloon_target_override=None) category_icon = None if balloon_icon is not None: icon_info = balloon_icon.icon(resolver, balloon_target_override=None) if balloon_icon.category_icon is not None: category_icon = balloon_icon.category_icon( resolver, balloon_target_override=None) else: icon_info = None if gsi_handlers.balloon_handlers.archiver.enabled: gsi_handlers.balloon_handlers.archive_balloon_data( sim, None, balloon_icon, icon_info, gsi_entries) if balloon_icon is not None and (icon_info[0] is not None or icon_info[1] is not None): (balloon_type, priority) = BALLOON_TYPE_LOOKUP[balloon_icon.balloon_type] balloon_overlay = balloon_icon.overlay request = BalloonRequest(sim, icon_info[0], icon_info[1], balloon_overlay, balloon_type, priority, TunableBalloon.BALLOON_DURATION, balloon_data.balloon_delay, balloon_data.balloon_delay_random_offset, category_icon) return request @staticmethod def request_passive_balloon(sim, time_now): if time_now - sim.next_passive_balloon_unlock_time > create_time_span( minutes=PassiveBalloons.BALLOON_LONG_LOCKOUT): lockout_time = random.randint(0, PassiveBalloons.BALLOON_RANDOM) sim.next_passive_balloon_unlock_time = services.time_service( ).sim_now + create_time_span(minutes=lockout_time) return balloon_requests = [] if len(PassiveBalloons.ROUTING_BALLOONS ) > PassiveBalloons.MAX_NUM_BALLOONS: sampled_balloon_tuning = random.sample( PassiveBalloons.ROUTING_BALLOONS, PassiveBalloons.MAX_NUM_BALLOONS) else: sampled_balloon_tuning = PassiveBalloons.ROUTING_BALLOONS for balloon_weight_pair in sampled_balloon_tuning: balloon_request = PassiveBalloons.create_passive_ballon_request( sim, balloon_weight_pair.balloon) if balloon_request is not None: balloon_requests.append( (balloon_weight_pair.weight, balloon_request)) if len(balloon_requests) > 0: choosen_balloon = sims4.random.weighted_random_item( balloon_requests) if choosen_balloon is not None: choosen_balloon.distribute() lockout_time = PassiveBalloons.BALLOON_LOCKOUT + random.randint( 0, PassiveBalloons.BALLOON_RANDOM) sim.next_passive_balloon_unlock_time = time_now + create_time_span( minutes=lockout_time)