Exemplo n.º 1
0
class ProceduralControlWheel(ProceduralControlBase, TerrainAlignmentMixin):
    FACTORY_TUNABLES = {
        'reference_joint':
        TunableStringHash32(
            description=
            '\n            The joint we use to determine where the wheel is on the bike.\n            '
        ),
        'control_joint':
        TunableStringHash32(
            description=
            "\n            The joint that is controlled and rotates with the actor's velocity.\n            "
        ),
        'bump_sound':
        OptionalTunable(
            description=
            "\n            If enabled, this is the name of the sound to play when the control\n            hits a 'bump' in the terrain.\n            ",
            tunable=Tunable(
                description=
                '\n                The name of the sound to play when the control hits a bump in\n                the terrain. We use a string here instead of a hash so that we\n                can modify the sound name based on the terrain and other\n                factors from locomotion.\n                ',
                tunable_type=str,
                default=''))
    }

    def build_control_msg(self, msg):
        super().build_control_msg(msg)
        self.build_terrain_alignment_msg(msg)
        msg.control_type = ProceduralControlType.WHEEL
        msg.joint_name_hash = self.control_joint
        msg.reference_joint_name_hash = self.reference_joint
        if self.bump_sound:
            msg.bump_sound_name = self.bump_sound
Exemplo n.º 2
0
class PlayVisualEffectMixin:
    FACTORY_TUNABLES = {
        'vfx':
        PlayEffect.TunableFactory(
            description='\n            The effect to play.\n            '),
        'vfx_target':
        OptionalTunable(
            description=
            '\n            If enabled, the visual effect is set to target a specific joint on\n            another object or Sim.\n            ',
            tunable=TunableTuple(
                participant=TunableEnumEntry(
                    description=
                    '\n                    The participant this visual effect targets.\n                    ',
                    tunable_type=ParticipantTypeSingle,
                    default=ParticipantTypeSingle.TargetSim),
                joint_name=TunableStringHash32(
                    description=
                    '\n                    The name of the slot this effect is targeted to.\n                    ',
                    default='_FX_')))
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def _start_vfx(self, participant, target_participant):
        vfx_params = {}
        if target_participant is not None:
            vfx_params['target_actor_id'] = target_participant.id
            vfx_params['target_joint_name_hash'] = self.vfx_target.joint_name
        running_vfx = self.vfx(participant, **vfx_params)
        self.vfx_lifetime.start_visual_effect(running_vfx)
        return running_vfx
Exemplo n.º 3
0
class ConditionalLayer(metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(sims4.resources.Types.CONDITIONAL_LAYER)):
    INSTANCE_TUNABLES = {'layer_name': TunableStringHash32(description='\n            The name of the layer that will be loaded.\n            World Building should tell you what this should be.\n            '), 'conflicting_layers': TunableList(description='\n            A List of Zone Layers that conflict with this layer. If this layer\n            is present and one of the listed Zone Layers attempts to load it \n            will fail.        \n            ', tunable=TunableReference(description='\n                A Zone Layer that conflicts with this Zone Layer.\n                ', manager=services.get_instance_manager(sims4.resources.Types.CONDITIONAL_LAYER)), unique_entries=True), 'client_only': Tunable(description='\n            If checked, this layer is loaded as a client side layer. All \n            objects on the layer will exist as scene models only and have no\n            gameplay (e.g. no interactions, no footprint).\n            \n            This is useful for layers that are purely decorative. And unlike\n            regular game objects, client side objects can be placed outside of\n            routable/interactable areas, e.g. decorative cards in the distance.\n            \n            We do not support mixing game objects and client only objects on\n            the same layer. Please separate them out onto their on layers.\n            ', tunable_type=bool, default=False), 'fade_data': OptionalTunable(description='\n            If enabled, the conditional layer will fade in rather than pop.\n            ', tunable=TunableTuple(fade_duration=Tunable(description='\n                    The duration of the fade in sim minutes.\n                    ', tunable_type=float, default=10.0), delay_min=TunableRange(description='\n                    The minimum length of the fade delay in sim minutes.\n                    ', tunable_type=float, default=10.0, minimum=0.0), delay_max=TunableRange(description='\n                    The maximum length of the fade delay in sim minutes.\n                    ', tunable_type=float, default=20.0, minimum=0.0)))}

    @classmethod
    def _verify_tuning_callback(cls):
        if cls.fade_data is not None and cls.fade_data.delay_min >= cls.fade_data.delay_max:
            logger.error('The fade data for {} has a delay min ({}) greater than the delay max ({}). This is not allowed.', cls, cls.fade_data.delay_min, cls.fade_data.delay_max)
Exemplo n.º 4
0
 def __init__(self, **kwargs):
     super().__init__(
         audio=TunableResourceKey(
             description=
             '\n                The sound to play.\n                ',
             default=None,
             resource_types=(sims4.resources.Types.PROPX, )),
         joint_name_hash=OptionalTunable(
             description=
             "\n                Specify if the audio is attached to a slot and, if so, which\n                slot. Otherwise the audio will be attached to the object's \n                origin.\n                ",
             tunable=TunableStringHash32(
                 description=
                 '\n                    The name of the slot this audio is attached to.\n                    '
             )),
         play_on_active_sim_only=Tunable(
             description=
             '\n                If enabled, and audio target is Sim, the audio will only be \n                played on selected Sim. Otherwise it will be played regardless \n                Sim is selected or not.\n                \n                If audio target is Object, always set this to False. Otherwise\n                the audio will never be played.\n                \n                ex. This will be useful for Earbuds where we want to hear the\n                music only when the Sim is selected.\n                ',
             tunable_type=bool,
             default=False),
         immediate_audio=Tunable(
             description=
             '\n                If checked, this audio will be triggered immediately, nothing\n                will block.\n                \n                ex. Earbuds audio will be played immediately while \n                the Sim is routing or animating.\n                ',
             tunable_type=bool,
             default=False),
         **kwargs)
Exemplo n.º 5
0
class FocusComponent(Component,
                     HasTunableFactory,
                     AutoFactoryInit,
                     component_name=types.FOCUS_COMPONENT):
    FACTORY_TUNABLES = {
        '_focus_bone':
        TunableStringHash32(
            description=
            '\n            The bone Sims direct their attention towards when focusing on an\n            object.\n            ',
            default='_focus_'),
        '_focus_score':
        TunableFocusScoreVariant()
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._current_focus_score = self._focus_score

    @distributor.fields.ComponentField(op=SetFocusScore)
    def focus_score(self):
        return self._current_focus_score

    @focus_score.setter
    def focus_score(self, value):
        self._current_focus_score = value

    @componentmethod
    def get_focus_bone(self):
        return self._focus_bone
Exemplo n.º 6
0
class _PortalTypeDataElevator(_PortalTypeDataBase):
    FACTORY_TUNABLES = {'shell_bone_name': TunableStringHash32(description='\n            The elevator builds a portal between itself and the shell object on\n            the lot. The exact portal end points are positioned based on bone\n            positions on the elevator and shell models.\n            \n            This is the name of the bone on the shell where the shell end of the\n            portal should be.\n            ', default='_route_0'), 'elevator_bone_name': TunableStringHash32(description='\n            The elevator builds a portal between itself and the shell object on\n            the lot. The exact portal end points are positioned based on bone\n            positions on the elevator and shell models.\n            \n            This is the name of the bone on the elevator where the elevator end\n            of the portal should be.\n            ', default='_route_'), 'shell_tag': TunableEnumEntry(description='\n            Tag to find the shell by. There should only be one such object on\n            the lot the elevator is on.\n            ', tunable_type=Tag, default=Tag.INVALID, invalid_enums=(Tag.INVALID,))}

    @property
    def requires_los_between_points(self):
        return False

    @property
    def portal_type(self):
        return PortalType.PortalType_Wormhole

    @staticmethod
    def _get_bone_position(obj, bone_name):
        if obj is None or obj.rig is None or obj.rig == sims4.resources.INVALID_KEY:
            rig_name = str(obj.rig) if obj.rig is not None else 'No Rig'
            logger.error('Setup Portal: Unable to get position for bone {} in object {} with rig {}.', bone_name, str(obj), rig_name)
            return
        joint_transform = get_joint_transform_from_rig(obj.rig, bone_name)
        return obj.transform.transform_point(joint_transform.translation)

    def _get_shell(self):
        candidates = list(services.object_manager().get_objects_with_tag_gen(self.shell_tag))
        if not candidates:
            zone_id = services.current_zone_id()
            if services.get_plex_service().is_zone_an_apartment(zone_id, consider_penthouse_an_apartment=True):
                logger.error('Failed to find shell. Tag: {}.', self.shell_tag)
            return
        if len(candidates) > 1:
            logger.error('Found multiple shells. Candidates: {}. Tag: {}.', candidates, self.shell_tag)
        return candidates[0]

    def get_portal_locations(self, obj):
        shell = self._get_shell()
        if shell is None:
            return ()
        elevator_pos = self._get_bone_position(obj, self.elevator_bone_name)
        if elevator_pos is None:
            return ()
        shell_pos = self._get_bone_position(shell, self.shell_bone_name)
        if shell_pos is None:
            return ()
        elevator_loc = Location(elevator_pos, routing_surface=obj.routing_surface)
        shell_routing_surface = SurfaceIdentifier(services.current_zone_id(), 0, SurfaceType.SURFACETYPE_WORLD)
        shell_loc = Location(shell_pos, routing_surface=shell_routing_surface)
        return ((shell_loc, elevator_loc, elevator_loc, shell_loc, 0),)

    def is_ungreeted_sim_disallowed(self):
        zone_id = services.current_zone_id()
        active_household = services.active_household()
        if active_household is not None and active_household.home_zone_id == zone_id:
            return False
        else:
            plex_service = services.get_plex_service()
            if plex_service.get_plex_building_type(zone_id) != PlexBuildingType.PENTHOUSE_PLEX:
                return False
        return True
Exemplo n.º 7
0
class Subroot(metaclass=TunedInstanceMetaclass,
              manager=services.subroot_manager()):
    INSTANCE_TUNABLES = {
        'bone_names':
        TunableSet(
            description=
            '\n            The list of bone names that make up this subroot. Use this to\n            specify containment slots for the given part.\n            \n            If the part specifies a subroot, the bone name will be automatically\n            postfixed with the subroot index.\n            \n            For example, for part subroot 1:\n                _ctnm_eat_ -> _ctnm_eat_1\n            ',
            tunable=TunableStringHash32(default='_ctnm_XXX_'),
            minlength=1)
    }
Exemplo n.º 8
0
 def __init__(self, **kwargs):
     super().__init__(toggles=TunableList(
         TunableTuple(enable=Tunable(
             bool,
             True,
             description=
             'If checked, we turn on the tuned footprint when the interaction begins, If not checked we turn off the tuned footprint when the interaction begins.'
         ),
                      footprint_hash=TunableStringHash32(
                          description='Name of the footprint to toggle')),
         description='List of footprints to toggle during the Interaction.'
     ),
                      **kwargs)
Exemplo n.º 9
0
class SetAsHead(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'bone_name':
        TunableStringHash32(
            description=
            '\n            Bone where the object will be attached to.\n            ',
            default='b__neck__')
    }

    def apply(self, participant_to_parent, parent_target):
        if parent_target.current_object_set_as_head is not None and parent_target.current_object_set_as_head(
        ) is not None:
            return False
        SetAsHead.set_head_object(parent_target, participant_to_parent,
                                  self.bone_name)

    @classmethod
    def set_head_object(cls, parent, object_to_parent, bone_hash):
        forward_rot = 4 * sims4.math.PI / 3
        up_rot = 3 * sims4.math.PI / 2
        forward_orientation = sims4.math.Quaternion.from_axis_angle(
            forward_rot, sims4.math.FORWARD_AXIS)
        up_orientation = sims4.math.Quaternion.from_axis_angle(
            up_rot, sims4.math.UP_AXIS)
        orientation = sims4.math.Quaternion.concatenate(
            forward_orientation, up_orientation)
        new_transform = sims4.math.Transform.IDENTITY()
        new_transform.orientation = orientation
        parent.current_object_set_as_head = weakref.ref(object_to_parent)
        object_to_parent.set_parent(parent,
                                    new_transform,
                                    joint_name_or_hash=bone_hash)
        if not parent.is_sim:
            logger.error('Parenting {} into a non sim object {}',
                         object_to_parent, parent)
            return
        if object_to_parent.has_component(types.PARENT_TO_SIM_HEAD_COMPONENT):
            object_to_parent.remove_component(
                types.PARENT_TO_SIM_HEAD_COMPONENT)
        object_to_parent.add_dynamic_component(
            types.PARENT_TO_SIM_HEAD_COMPONENT,
            parent_sim_info_id=parent.sim_info.id,
            bone_hash=bone_hash)
Exemplo n.º 10
0
class _PortalBoneLocation(_PortalLocationBase):
    FACTORY_TUNABLES = {
        'bone_name':
        TunableStringHash32(
            description=
            '\n            The bone to use for this portal location.\n            '
        )
    }

    def __init__(self, obj, bone_name, *args, **kwargs):
        self.bone_name = bone_name
        super().__init__(obj, *args, **kwargs)

    def get_translation(self, obj):
        if obj.rig is None or obj.rig == sims4.resources.INVALID_KEY:
            logger.error(
                'Trying to get the translation for a bone {} on obj {} but object has no rig.',
                self.bone, obj)
        bone_transform = get_joint_transform_from_rig(obj.rig, self.bone_name)
        return obj.transform.transform_point(bone_transform.translation)
Exemplo n.º 11
0
class SlotType(HasTunableReference,
               metaclass=TunedInstanceMetaclass,
               manager=services.slot_type_manager()):
    INSTANCE_TUNABLES = {
        'bone_name_hash':
        OptionalTunable(tunable=TunableStringHash32(
            description=
            '\n                  The name of the bone this slot is associated to.\n                  ',
            default=None),
                        enabled_name='referenced_by_animation',
                        disabled_name='not_referenced_by_animation'),
        'is_surface':
        Tunable(
            description=
            '\n            This should be checked if objects can be placed in this slot by Sims\n            or if having this slot would imply the object is considered a\n            surface, such as slots for chairs.\n            ',
            tunable_type=bool,
            default=True),
        'bb_only':
        Tunable(
            description=
            "\n            If checked, this slot is only intended to be use to make placement\n            easier and shouldn't have any gameplay repercussions.  Example: A\n            sim trying  to use a chair slotted to a campfire wil not have the\n            sim pull the chair out when trying to sit near the campfire.\n            ",
            tunable_type=bool,
            default=False)
    }

    @classproperty
    def slot_type(cls):
        return cls

    def __repr__(self):
        return '<SlotType: {} {}>'.format(
            type(self).__name__, self.bone_name_hash)

    def __str__(self):
        return '{}: {}'.format(type(self).__name__, self.bone_name_hash)

    @classproperty
    def is_deco_slot(cls):
        return cls.slot_type in DecorativeSlotTuning.DECORATIVE_SLOT_TYPES
Exemplo n.º 12
0
class InFootprintTest(HasTunableSingletonFactory, AutoFactoryInit,
                      event_testing.test_base.BaseTest):
    BY_PARTICIPANT = 0
    BY_TAG = 1
    test_events = ()
    FACTORY_TUNABLES = {
        'actor':
        TunableEnumEntry(
            description=
            '\n            The actor whose location will be used.\n            ',
            tunable_type=ParticipantTypeSingleSim,
            default=ParticipantTypeSingleSim.Actor),
        'footprint_target':
        TunableVariant(
            description=
            '\n            The object whose footprint to check against.\n            ',
            by_participant=TunableTuple(
                description=
                '\n                Get footprint from a participant.\n                ',
                locked_args={'target_type': BY_PARTICIPANT},
                participant=TunableEnumEntry(
                    description=
                    '\n                    The participant whose required slot count we consider.\n                    ',
                    tunable_type=ParticipantTypeSingle,
                    default=ParticipantTypeSingle.Object)),
            by_tag=TunableTuple(
                description=
                '\n                Get footprint from an object with this tag. If there are\n                multiple, the test passes as long as one passes.\n                ',
                tag=TunableTag(
                    description=
                    '\n                    Tag to find objects by.\n                    '
                ),
                locked_args={'target_type': BY_TAG}),
            default='by_participant'),
        'footprint_names':
        OptionalTunable(
            description=
            "\n            Specific footprints to check against. If left unspecified, we\n            check against the object's default footprints (i.e. the ones\n            enabled in Medator).\n            ",
            tunable=TunableSet(tunable=TunableStringHash32(
                description=
                '\n                    Name of footprint. Can be looked up in Medator. If in\n                    doubt, consult the modeler.\n                    '
            ),
                               minlength=1)),
        'invert':
        Tunable(
            description=
            '\n            If checked, test will pass if the actor is not in the footprint.\n            ',
            tunable_type=bool,
            default=False)
    }

    def get_expected_args(self):
        kwargs = {}
        kwargs['actors'] = self.actor
        if self.footprint_target.target_type == self.BY_PARTICIPANT:
            kwargs['footprint_target'] = self.footprint_target.participant
        return kwargs

    def _test_if_sim_in_target_footprint(self, sim, target):
        if self.footprint_names is None:
            polygon = target.footprint_polygon
        else:
            polygon = target.get_polygon_from_footprint_name_hashes(
                self.footprint_names)
        if polygon is not None and polygon.contains(sim.position):
            return True
        return False

    @cached_test
    def __call__(self, actors=(), footprint_target=None):
        actor = next(iter(actors), None)
        if actor is None:
            return TestResult(False, 'No actors', tooltip=self.tooltip)
        actor_sim = actor.get_sim_instance()
        if actor_sim is None:
            return TestResult(
                False,
                "Actor is not an instantiated Sim. Can't check position: {}",
                actor[0],
                tooltip=self.tooltip)
        if self.footprint_target.target_type == self.BY_PARTICIPANT:
            if footprint_target is None:
                return TestResult(False,
                                  'Missing participant.',
                                  tooltip=self.tooltip)
            targets = (footprint_target, )
        elif self.footprint_target.target_type == self.BY_TAG:
            targets = services.object_manager().get_objects_with_tag_gen(
                self.footprint_target.tag)
        else:
            return TestResult(False,
                              'Unknown target type: {}',
                              self.footprint_target.target_type,
                              tooltip=self.tooltip)
        if self.invert:
            if any(
                    self._test_if_sim_in_target_footprint(actor_sim, target)
                    for target in targets):
                return TestResult(False,
                                  'In footprint, inverted',
                                  tooltip=self.tooltip)
        elif not any(
                self._test_if_sim_in_target_footprint(actor_sim, target)
                for target in targets):
            return TestResult(False, 'Not in footprint', tooltip=self.tooltip)
        return TestResult.TRUE
Exemplo n.º 13
0
 def __init__(self, description="Configure focus on one or more of an interaction's participants.", **kwargs):
     super().__init__(subject=TunableEnumFlags(ParticipantType, ParticipantType.Object, description='Who or what to focus on.'), layer=Tunable(int, None, description='Layer override: Ambient=0, SuperInteraction=3, Interaction=5.'), score=Tunable(int, 1, description='Focus score.  This orders focus elements in the same layer.'), focus_bone_override=TunableStringHash32(description='The bone Sims direct their attention towards when focusing on an object.'), description=description, **kwargs)
class GardeningPlantComponent(
        _GardeningComponent,
        component_name=objects.components.types.GARDENING_COMPONENT,
        persistence_key=protocols.PersistenceMaster.PersistableData.
        GardeningComponent):
    FACTORY_TUNABLES = {
        'shoot_definition':
        TunableReference(
            description=
            '\n            The object definition to use when creating Shoot objects for the\n            splicing system.\n            ',
            manager=services.definition_manager()),
        'prohibited_vertical_plant_slots':
        TunableSet(
            description=
            '\n            The list of slots that are prohibited from spawning fruit if the plant \n            is on the vertical garden.\n            ',
            tunable=TunableStringHash32(
                description=
                '\n                The hashed name of the slot.\n                ',
                default='_ctnm_spawn_1'))
    }

    def on_add(self, *args, **kwargs):
        zone = services.current_zone()
        if not zone.is_zone_loading:
            gardening_service = services.get_gardening_service()
            gardening_service.add_gardening_object(self.owner)
        return super().on_add(*args, **kwargs)

    def on_remove(self, *_, **__):
        gardening_service = services.get_gardening_service()
        gardening_service.remove_gardening_object(self.owner)

    def on_finalize_load(self):
        gardening_service = services.get_gardening_service()
        gardening_service.add_gardening_object(self.owner)
        self._refresh_fruit_states()

    def on_location_changed(self, old_location):
        zone = services.current_zone()
        if not zone.is_zone_loading:
            gardening_service = services.get_gardening_service()
            gardening_service.move_gardening_object(self.owner)
            self._refresh_prohibited_spawn_slots()

    def _refresh_prohibited_spawn_slots(self):
        plant = self.owner
        plant_parent = plant.parent
        fruits = tuple(self.owner.children)
        destroyed_fruit = False
        for fruit in fruits:
            gardening_component = fruit.get_component(
                types.GARDENING_COMPONENT)
            if gardening_component is None:
                continue
            if self.is_prohibited_spawn_slot(fruit.location.slot_hash,
                                             plant_parent):
                fruit.destroy()
                destroyed_fruit = True
        if destroyed_fruit or not fruits:
            return
        empty_slot_count = 0
        for runtime_slot in plant.get_runtime_slots_gen():
            if runtime_slot.empty:
                if not self.is_prohibited_spawn_slot(
                        runtime_slot.slot_name_hash, plant_parent):
                    empty_slot_count += 1
        plant.force_spawn_object(spawn_type=SpawnerTuning.SLOT_SPAWNER,
                                 ignore_firemeter=True,
                                 create_slot_obj_count=empty_slot_count)

    def _refresh_fruit_states(self):
        for fruit_state in GardeningTuning.FRUIT_STATES:
            fruit_state_value = self.owner.get_state(fruit_state)
            self._on_fruit_support_state_changed(fruit_state, None,
                                                 fruit_state_value)

    def _on_fruit_fall_to_ground(self, fruit):
        plant = self.owner
        if fruit.parent is not plant:
            return False
        if not plant.is_on_active_lot():
            return False
        starting_location = placement.create_starting_location(
            position=plant.position, routing_surface=plant.routing_surface)
        fgl_context = placement.create_fgl_context_for_object(
            starting_location, fruit, ignored_object_ids=(fruit.id, ))
        (position, orientation) = placement.find_good_location(fgl_context)
        if position is None or orientation is None:
            return False
        fruit.move_to(parent=None,
                      translation=position,
                      orientation=orientation,
                      routing_surface=plant.routing_surface)
        owner = plant.get_household_owner_id()
        if owner is not None:
            fruit.set_household_owner_id(owner)
        decay_commodity = GardeningTuning.FRUIT_DECAY_COMMODITY
        fruit.set_stat_value(
            decay_commodity,
            GardeningTuning.FRUIT_DECAY_COMMODITY_DROPPED_VALUE)
        return True

    def _on_fruit_support_state_changed(self, state, old_value, new_value):
        if state not in GardeningTuning.FRUIT_STATES:
            return
        fruit_state_data = GardeningTuning.FRUIT_STATES[state]
        if new_value in fruit_state_data.states:
            return
        objs_to_destroy = []
        fruit_state_behavior = fruit_state_data.behavior
        for fruit in tuple(self.owner.children):
            gardening_component = fruit.get_component(
                types.GARDENING_COMPONENT)
            if gardening_component is None:
                continue
            if not gardening_component.is_on_tree:
                continue
            if fruit_state_behavior is not None and random.random(
            ) < fruit_state_behavior and self._on_fruit_fall_to_ground(fruit):
                gardening_component.update_hovertip()
            else:
                objs_to_destroy.append(fruit)
        if objs_to_destroy:
            services.get_reset_and_delete_service().trigger_batch_destroy(
                objs_to_destroy)

    def on_state_changed(self, state, old_value, new_value, from_init):
        self._on_fruit_support_state_changed(state, old_value, new_value)
        self.update_hovertip()

    def is_prohibited_spawn_slot(self, slot, plant_parent):
        if plant_parent is None or plant_parent.definition not in GardeningTuning.VERTICAL_GARDEN_OBJECTS:
            return False
        elif slot in self.prohibited_vertical_plant_slots:
            return True
        return False

    def add_fruit(self, fruit, sprouted_from=False):
        gardening_component = fruit.get_component(types.GARDENING_COMPONENT)
        if sprouted_from:
            state = GardeningTuning.INHERITED_STATE
            state_value = fruit.get_state(state)
            self.owner.set_state(state, state_value)
        else:
            splicing_recipies = self.root_stock_gardening_tuning.splicing_recipies
            if gardening_component.root_stock.main_spawner in splicing_recipies:
                new_fruit = splicing_recipies[
                    gardening_component.root_stock.main_spawner]
                self._add_spawner(new_fruit)
        if gardening_component.is_shoot:
            self._add_spawner(gardening_component.root_stock.main_spawner)
        else:
            self._add_spawner(fruit.definition)
        self.update_hovertip()

    def create_shoot(self):
        root_stock = self._get_root_stock()
        if root_stock is None:
            return
        shoot = self.root_stock.create_spawned_object(self.owner,
                                                      self.shoot_definition)
        gardening_component = shoot.get_component(types.GARDENING_COMPONENT)
        gardening_component.fruit_spawner_data = root_stock
        gardening_component._fruit_spawners.append(root_stock)
        gardening_component.update_hovertip()
        return shoot

    def _get_root_stock(self):
        if self.root_stock is None:
            for spawn_obj_def in self.owner.slot_spawner_definitions():
                self._add_spawner(spawn_obj_def[0])
        return self.root_stock

    def can_splice_with(self, shoot):
        gardening_component = shoot.get_component(types.GARDENING_COMPONENT)
        if gardening_component is not None:
            return gardening_component.is_shoot
        return False

    @componentmethod_with_fallback(lambda: None)
    def get_notebook_information(self, reference_notebook_entry,
                                 notebook_sub_entries):
        root_stock = self._get_root_stock()
        if root_stock is None:
            return ()
        fruit_definition = root_stock.main_spawner
        notebook_entry = reference_notebook_entry(fruit_definition.id)
        return (notebook_entry, )

    def _ui_metadata_gen(self):
        if not self.show_gardening_tooltip():
            self.owner.hover_tip = ui_protocols.UiObjectMetadata.HOVER_TIP_DISABLED
            return
        if self.show_gardening_details():
            state_value = self.owner.get_state(GardeningTuning.EVOLUTION_STATE)
            evolution_value = state_value.range.upper_bound
            yield ('evolution_progress', evolution_value)
            if GardeningTuning.SEASONALITY_STATE is not None:
                sesonality_state_value = self.owner.get_state(
                    GardeningTuning.SEASONALITY_STATE)
                if sesonality_state_value is not None:
                    season_text = sesonality_state_value.display_name
                    seasonality_text = GardeningTuning.get_seasonality_text_from_plant(
                        self.owner.definition)
                    season_text = LocalizationHelperTuning.get_new_line_separated_strings(
                        season_text, seasonality_text)
                    yield (TooltipFields.season_text.name, season_text)
            quality_state_value = self.owner.get_state(
                GardeningTuning.QUALITY_STATE_VALUE)
            if quality_state_value is not None:
                quality_value = quality_state_value.value
                yield ('quality', quality_value)
        yield from super()._ui_metadata_gen()
Exemplo n.º 15
0
class PlayEffect(distributor.ops.ElementDistributionOpMixin, HasTunableFactory,
                 AutoFactoryInit):
    JOINT_NAME_CURRENT_POSITION = 1899928870
    FACTORY_TUNABLES = {
        'effect_name':
        Tunable(description=
                '\n            The name of the effect to play.\n            ',
                tunable_type=str,
                default=''),
        'joint_name':
        OptionalTunable(
            description=
            '\n            Specify if the visual effect is attached to a slot and, if so, which\n            slot.\n            ',
            tunable=TunableStringHash32(
                description=
                '\n                The name of the slot this effect is attached to.\n                ',
                default='_FX_'),
            enabled_by_default=True,
            enabled_name='Slot',
            disabled_name='Current_Position',
            disabled_value=JOINT_NAME_CURRENT_POSITION),
        'play_immediate':
        Tunable(
            description=
            '\n            If checked, this effect will be triggered immediately, nothing\n            will block.\n\n            ex. VFX will be played immediately while \n            the Sim is routing or animating.\n            ',
            tunable_type=bool,
            default=False)
    }

    def __init__(self,
                 target,
                 effect_name='',
                 joint_name=0,
                 target_actor_id=0,
                 target_joint_name_hash=0,
                 mirror_effect=False,
                 auto_on_effect=False,
                 target_joint_offset=None,
                 play_immediate=False,
                 callback_event_id=None,
                 store_target_position=False,
                 transform_override=None,
                 **kwargs):
        super().__init__(effect_name=effect_name,
                         joint_name=joint_name,
                         play_immediate=play_immediate,
                         immediate=play_immediate,
                         **kwargs)
        self.target = target
        if target is not None:
            if target.inventoryitem_component is not None:
                forward_to_owner_list = target.inventoryitem_component.forward_client_state_change_to_inventory_owner
                if forward_to_owner_list:
                    if StateChange.VFX in forward_to_owner_list:
                        inventory_owner = target.inventoryitem_component.inventory_owner
                        if inventory_owner is not None:
                            self.target = inventory_owner
            if target.crafting_component is not None:
                effect_name = target.crafting_component.get_recipe_effect_overrides(
                    effect_name)
        self.target_transform = target.transform if target is not None else transform_override
        self.effect_name = effect_name
        self.auto_on_effect = auto_on_effect
        self.target_actor_id = target_actor_id
        self.target_joint_name_hash = target_joint_name_hash
        self.mirror_effect = mirror_effect
        self._stop_type = SOFT_TRANSITION
        self.target_joint_offset = target_joint_offset
        self.immediate = play_immediate
        self.callback_event_id = callback_event_id
        self.store_target_position = store_target_position

    def __repr__(self):
        return standard_angle_repr(self, self.effect_name)

    @property
    def _is_relative_to_transform(self):
        return self.joint_name == self.JOINT_NAME_CURRENT_POSITION

    def _on_target_location_changed(self, *_, **__):
        self.stop(immediate=True)
        self.start()

    def start(self, *_, **__):
        if self.target is None:
            logger.error(
                'Attempting to attach VFX without a target. Perhaps you mean to use start_one_shot()',
                owner='rmccord')
        if self._is_relative_to_transform:
            self.target.register_on_location_changed(
                self._on_target_location_changed)
        if not self._is_valid_target():
            return
        if not self.is_attached:
            self.attach(self.target)
            logger.info('VFX {} on {} START'.format(self.effect_name,
                                                    self.target))

    def start_one_shot(self):
        if self.target is not None and not self.target.is_terrain:
            distributor.ops.record(self.target, self)
        else:
            Distributor.instance().add_op_with_no_owner(self)

    def stop(self, *_, immediate=False, **kwargs):
        if self.target is None or not self.target.valid_for_distribution:
            return
        if self._is_relative_to_transform:
            self.target.unregister_on_location_changed(
                self._on_target_location_changed)
        if self.is_attached:
            if immediate:
                self._stop_type = HARD_TRANSITION
            else:
                self._stop_type = SOFT_TRANSITION
            self.detach()

    def _is_valid_target(self):
        if not self.target.valid_for_distribution:
            zone = services.current_zone()
            if zone is not None:
                zone_spin_up_service = zone.zone_spin_up_service
                if zone_spin_up_service is None:
                    logger.callstack(
                        'zone_spin_up_service was None in PlayEffect._is_valid_target(), for effect/target: {}/{}',
                        self,
                        self.target,
                        owner='johnwilkinson',
                        level=sims4.log.LEVEL_ERROR)
                    return False
                elif not zone_spin_up_service.is_finished:
                    return False
        return True

    def detach(self, *objects):
        super().detach(*objects)
        if services.current_zone().is_zone_shutting_down:
            return
        op = StopVFX(self.target.id,
                     self.actor_id,
                     stop_type=self._stop_type,
                     immediate=self.immediate)
        distributor.ops.record(self.target, op)
        logger.info('VFX {} on {} STOP'.format(self.effect_name, self.target))

    def write(self, msg):
        start_msg = VFXStart()
        if self.target is not None:
            start_msg.object_id = self.target.id
        start_msg.effect_name = self.effect_name
        start_msg.actor_id = self.actor_id
        start_msg.joint_name_hash = self.joint_name
        start_msg.target_actor_id = self.target_actor_id
        start_msg.target_joint_name_hash = self.target_joint_name_hash
        start_msg.mirror_effect = self.mirror_effect
        start_msg.auto_on_effect = self.auto_on_effect
        if self.target_joint_offset is not None:
            start_msg.target_joint_offset.x = self.target_joint_offset.x
            start_msg.target_joint_offset.y = self.target_joint_offset.y
            start_msg.target_joint_offset.z = self.target_joint_offset.z
        if self.callback_event_id is not None:
            start_msg.callback_event_id = self.callback_event_id
        if self._is_relative_to_transform:
            if self.store_target_position or self.target is None:
                transform = self.target_transform
            else:
                transform = self.target.transform
            start_msg.transform.translation.x = transform.translation.x
            start_msg.transform.translation.y = transform.translation.y
            start_msg.transform.translation.z = transform.translation.z
            start_msg.transform.orientation.x = transform.orientation.x
            start_msg.transform.orientation.y = transform.orientation.y
            start_msg.transform.orientation.z = transform.orientation.z
            start_msg.transform.orientation.w = transform.orientation.w
        self.serialize_op(msg, start_msg, protocols.Operation.VFX_START)