예제 #1
0
 def __init__(self,
              default_fade_duration=0,
              default_fade_variation_range=0,
              default_client_fade_delay=0,
              **kwargs):
     super().__init__(
         fade_duration=TunableRealSecond(
             description=
             '\n                Amount of time alloted for fading in a decoration on a lot.\n                ',
             minimum=0,
             default=default_fade_duration),
         fade_variation_range=TunableRealSecond(
             description=
             "\n                Randomized variation time range for fade in that is used \n                so that all deco trims on a lot don't fade in at the same time.\n                ",
             minimum=0,
             default=default_fade_variation_range),
         client_fade_delay=TunableRealSecond(
             description=
             "\n                After an lot decoration request is made, this is the delay \n                amount given to the client decoration system before it starts \n                to fade in the lot decorations.\n                \n                Note when the request is processed, gameplay has already saved \n                that the lot is 'decorated', so there are save/load implications\n                when this is set to a large value.\n                ",
             minimum=0,
             default=default_client_fade_delay),
         **kwargs)
예제 #2
0
class PersistenceTuning:
    SAVE_GAME_COOLDOWN = TunableRealSecond(description='\n        Cooldown on the save game button to prevent users from saving too\n        often.\n        ', default=0, minimum=0)
    MINUTES_STAY_ON_LOT_BEFORE_GO_HOME = TunableInterval(description="\n        For all sims, when the sim is saved NOT on their home lot, we use this\n        interval to determine how many minutes they'll stay on that lot before\n        they go home.\n\n        Then, if we load up the non-home lot past this amount of time, that sim\n        will no longer be on that lot because that sim will have gone home.\n        \n        If the we load up on the sim's home lot -- if less than this amount of\n        time has passed, we set an alarm so that the sim will spawn into their\n        home lot at the saved time. If equal or more than this amount of time\n        has passed, that sim will be spawned in at zone load.\n        \n        The amount of time is a range. When loading, we'll randomly pick\n        between the upper and lower limit of the range.\n        ", tunable_type=TunableSimMinute, default_lower=180, default_upper=240, minimum=0)
    SAVE_FAILED_REASONS = TunableTuple(description='\n        Localized strings to display when the user cannot save.\n        ', generic=TunableLocalizedString(description='\n            Generic message for why game cannot be saved at the moment\n            '), on_cooldown=TunableLocalizedString(description='\n            The message to show when save game failed due to save being on\n            cooldown\n            '), exception_occurred=TunableLocalizedStringFactory(description='\n            The message to show when save game failed due to an exception\n            occuring during save\n            '))
    LOAD_ERROR_REQUEST_RESTART = ui.ui_dialog.UiDialogOk.TunableFactory(description='\n        The dialog that will be triggered when exception occurred during load\n        of zone and ask user to restart game.\n        ')
    LOAD_ERROR = ui.ui_dialog.UiDialogOk.TunableFactory(description='\n        The dialog that will be triggered when exception occurred during load\n        of zone.\n        ')
class ArbAccumulatorService(sims4.service_manager.Service):
    __qualname__ = 'ArbAccumulatorService'
    CUSTOM_EVENT = 901
    MAX_XEVT = 999
    MAXIMUM_TIME_DEBT = TunableRealSecond(1, description='\n    The maximum amount of time in seconds to allow the server to run ahead \n    of the client when running a contiguous block of animation/routing to \n    improve blending. Setting this to 0 will disable this feature but ruin blending.')
    MAXIMUM_SHAVE_FRAMES_PER_ANIMATION = Tunable(int, 5, description='\n    The maximum number of frames to shave off of the must-run duration of each \n    animation until we reach a total amount of time debt equal to MAXIMUM_TIME_DEBT.')
    MAXIMUM_SHAVE_ANIMATION_RATIO = Tunable(float, 2, description='\n    The maximum ratio of an animation to shave off. For example, if this\n    is tuned to 2, we will shave off at most 1/2 of the total must-run\n    duration of an animation.\n    ')

    @staticmethod
    def get_shave_time_given_duration_and_debt(duration, debt):
        shave_time_max = max(0, ArbAccumulatorService.MAXIMUM_TIME_DEBT - debt)
        shave_time_requested = min(duration/ArbAccumulatorService.MAXIMUM_SHAVE_ANIMATION_RATIO, 0.03333333333333333*ArbAccumulatorService.MAXIMUM_SHAVE_FRAMES_PER_ANIMATION)
        shave_time_actual = min(shave_time_max, shave_time_requested)
        return shave_time_actual

    def __init__(self, from_idle_fn, to_idle_fn):
        self._from_idle_func = from_idle_fn
        self._to_idle_func = to_idle_fn
        self._arb_sequence = []
        self._on_done = sims4.callback_utils.CallableList()
        self._in_flush = False
        self._custom_xevt_id_generator = self.CUSTOM_EVENT
        self._sequence_parallel = None
        self._time_debt = WeakKeyDictionary()
        self._shave_time = WeakKeyDictionary()

    def get_time_debt(self, sims):
        max_debt = 0
        for sim in sims:
            if sim not in self._time_debt:
                pass
            sim_debt = self._time_debt[sim]
            while sim_debt > max_debt:
                max_debt = sim_debt
        return max_debt

    def set_time_debt(self, sims, debt):
        for sim in sims:
            self._time_debt[sim] = debt

    def _clear(self):
        self._arb_sequence = []
        self._on_done = sims4.callback_utils.CallableList()
        self._custom_xevt_id_generator = self.CUSTOM_EVENT

    def parallelize(self):
        return _arb_parallelizer(self)

    def add_arb(self, arb, on_done_fn=None):
        if isinstance(arb, list):
            arbs = arb
        else:
            arbs = (arb,)
        for sub_arb in arbs:
            while not sub_arb._actors():
                logger.error('Attempt to play animation that has no connected actors:')
                sub_arb.log_request_history(dump_logger.error)
        if self._in_flush:
            for sub_arb in arbs:
                logger.debug('\n\nEvent-triggered ARB:\n{}\n\n', sub_arb.get_contents_as_string())
                interactions.utils.animation.ArbElement(sub_arb).distribute()
                while on_done_fn is not None:
                    on_done_fn()
            return
        self._arb_sequence.append(arb)
        if on_done_fn is not None:
            self._on_done.append(on_done_fn)

    def claim_xevt_id(self):
        event_id = self._custom_xevt_id_generator
        if self._custom_xevt_id_generator == self.MAX_XEVT:
            logger.warn('Excessive XEVT IDs claimed before a flush. This is likely caused by an error in animation requests. -RS')
        return event_id

    def _add_idles_to_arb_element(self, arb_element, on_done):
        all_actors = arb_element._actors()
        actors_with_idles = set()
        if self._to_idle_func is not None:
            for actor in all_actors:
                while actor.is_sim:
                    if not arb_element.arb._normal_timeline_ends_in_looping_content(actor.id):
                        (to_idle_arb, on_done_func) = self._to_idle_func(actor)
                        if on_done_func is not None:
                            on_done.append(on_done_func)
                        if to_idle_arb is not None:
                            arb_element.execute_and_merge_arb(to_idle_arb, False)
                            actors_with_idles.add(actor)
        return actors_with_idles

    def _begin_arb_element(self, all_actors, actors_with_idles, on_done):
        element = interactions.utils.animation.ArbElement(animation.arb.Arb(), [])
        if actors_with_idles and self._from_idle_func is not None:
            for actor in actors_with_idles:
                (from_idle_arb, on_done_idle) = self._from_idle_func(actor)
                if on_done_idle is not None:
                    on_done.append(on_done_idle)
                while from_idle_arb is not None:
                    self._append_arb_to_element(element, from_idle_arb, all_actors, False)
            actors_with_idles.clear()
        return element

    def _flush_arb_element(self, element_run_queue, arb_element, all_actors, on_done, closes_sequence):
        if not arb_element.arb.empty:
            actors_with_idles = self._add_idles_to_arb_element(arb_element, on_done)
            if not closes_sequence:
                arb_element.enable_optional_sleep_time = False
            if arb_element.arb.empty:
                raise RuntimeError('About to flush an empty Arb')
            element_run_queue.append(arb_element)
            if not closes_sequence:
                return self._begin_arb_element(all_actors, actors_with_idles, on_done)
            return
        return arb_element

    def _append_arb_to_element(self, buffer_arb_element, arb, actors, safe_mode, attach=True):
        if not arb.empty and buffer_arb_element.arb._can_append(arb, safe_mode):
            buffer_arb_element.event_records = buffer_arb_element.event_records or []
            if attach:
                buffer_arb_element.attach(*actors)
            buffer_arb_element.execute_and_merge_arb(arb, safe_mode)
            return True
        return False

    def _append_arb_element_to_element(self, buffer_arb_element, arb_element, actors, safe_mode):
        if not arb_element.arb.empty and buffer_arb_element.arb._can_append(arb_element.arb, safe_mode):
            buffer_arb_element.event_records = buffer_arb_element.event_records or []
            buffer_arb_element.attach(*actors)
            buffer_arb_element.event_records.extend(arb_element.event_records)
            buffer_arb_element.arb.append(arb_element.arb, safe_mode)
            return True
        return False

    def flush(self, timeline, animate_instantly=False):
        arb_sequence = self._arb_sequence
        on_done = self._on_done
        self._clear()
        actors = get_actors_for_arb_sequence(*arb_sequence)
        self._in_flush = True
        try:
            while len(actors) > 0:
                first_unprocessed_arb = 0
                sequence_len = len(arb_sequence)
                buffer_arb_element = None
                element_run_queue = []
                sim_actors = [actor for actor in actors if actor.is_sim]
                with distributor.system.Distributor.instance().dependent_block():
                    while first_unprocessed_arb < sequence_len:
                        if buffer_arb_element is None:
                            buffer_arb_element = self._begin_arb_element(actors, sim_actors, on_done)
                        for i in range(first_unprocessed_arb, sequence_len):
                            arb = arb_sequence[i]
                            if isinstance(arb, list):
                                combined_arb = animation.arb.Arb()
                                for sub_arb in arb:
                                    combined_arb.append(sub_arb, False, True)
                                if not buffer_arb_element.arb._can_append(combined_arb, True):
                                    break
                                buffer_arb_element.attach(*actors)
                                buffer_arb_element_parallel = self._begin_arb_element(actors, None, on_done)
                                result = self._append_arb_to_element(buffer_arb_element_parallel, combined_arb, actors, False, attach=False)
                                arb_sequence[i] = buffer_arb_element_parallel
                                arb = buffer_arb_element_parallel
                                buffer_arb_element_parallel.detach()
                            if isinstance(arb, ArbElement):
                                append_fn = self._append_arb_element_to_element
                            else:
                                append_fn = self._append_arb_to_element
                            if not append_fn(buffer_arb_element, arb, actors, True):
                                first_unprocessed_arb = i
                                break
                            first_unprocessed_arb = i + 1
                        buffer_arb_element = self._flush_arb_element(element_run_queue, buffer_arb_element, actors, on_done, first_unprocessed_arb == sequence_len)
                self._in_flush = False
                arb_sequence_element = ArbSequenceElement(element_run_queue, animate_instantly=animate_instantly)
                yield element_utils.run_child(timeline, arb_sequence_element)
        finally:
            self._in_flush = False
            on_done()
예제 #4
0
class StoryProgressionService(Service):
    __qualname__ = 'StoryProgressionService'
    INTERVAL = TunableRealSecond(
        description=
        '\n        The time between Story Progression actions. A lower number will\n        impact performance.\n        ',
        default=5)
    ACTIONS = TunableList(
        description=
        '\n        A list of actions that are available to Story Progression.\n        ',
        tunable=TunableStoryProgressionActionVariant())

    def __init__(self):
        self._sleep_handle = None
        self._processor = None
        self._alarm_handle = None
        self._next_action_index = 0
        self._story_progression_flags = StoryProgressionFlags.DISABLED

    def load_options(self, options_proto):
        if options_proto is None:
            return
        if options_proto.npc_population_enabled:
            pass

    def setup(self, save_slot_data=None, **kwargs):
        if save_slot_data is not None:
            sims.global_gender_preference_tuning.GlobalGenderPreferenceTuning.enable_autogeneration_same_sex_preference = save_slot_data.gameplay_data.enable_autogeneration_same_sex_preference

    def save(self, save_slot_data=None, **kwargs):
        if save_slot_data is not None:
            save_slot_data.gameplay_data.enable_autogeneration_same_sex_preference = sims.global_gender_preference_tuning.GlobalGenderPreferenceTuning.enable_autogeneration_same_sex_preference

    def enable_story_progression_flag(self, story_progression_flag):
        pass

    def disable_story_progression_flag(self, story_progression_flag):
        pass

    def is_story_progression_flag_enabled(self, story_progression_flag):
        return self._story_progression_flags & story_progression_flag

    def on_client_connect(self, client):
        current_zone = services.current_zone()
        current_zone.register_callback(zone_types.ZoneState.RUNNING,
                                       self._initialize_alarm)
        current_zone.register_callback(zone_types.ZoneState.SHUTDOWN_STARTED,
                                       self._on_zone_shutdown)

    def _on_zone_shutdown(self):
        current_zone = services.current_zone()
        if self._alarm_handle is not None:
            alarms.cancel_alarm(self._alarm_handle)
        current_zone.unregister_callback(zone_types.ZoneState.SHUTDOWN_STARTED,
                                         self._on_zone_shutdown)

    def _initialize_alarm(self):
        current_zone = services.current_zone()
        current_zone.unregister_callback(zone_types.ZoneState.RUNNING,
                                         self._initialize_alarm)
        time_span = clock.interval_in_sim_minutes(self.INTERVAL)
        self._alarm_handle = alarms.add_alarm(self,
                                              time_span,
                                              self._process_next_action,
                                              repeating=True)

    def _process_next_action(self, _):
        action = self.ACTIONS[self._next_action_index]
        logger.info('Attempt to Process - {}', action)
        if action.should_process(self._story_progression_flags):
            logger.info('Processing: {}', action)
            action.process_action(self._story_progression_flags)
            logger.info('Processing - Completed')
        else:
            logger.info('Attempt to Process - Skipped')
        if self._next_action_index >= len(self.ACTIONS):
            self._next_action_index = 0