예제 #1
0
 def __init__(self, **kwargs):
     super().__init__(
         bone_name=Tunable(
             description=
             '\n                The name of the bone to which the plumbbob should be attached.\n                ',
             tunable_type=str,
             default=None),
         offset=TunableVector3(
             description=
             '\n                The Vector3 offset from the bone to the plumbbob.\n                ',
             default=TunableVector3.DEFAULT_ZERO),
         target=TunableEnumEntry(
             description=
             '\n                Who to reslot the plumbbob on.\n                ',
             tunable_type=ParticipantTypeSingle,
             default=ParticipantTypeSingle.Object),
         balloon_offset=TunableVector3(
             description=
             '\n                The Vector3 offset from the bone to the thought balloon.\n                ',
             default=TunableVector3.DEFAULT_ZERO),
         **kwargs)
예제 #2
0
 def __init__(self, **kwargs):
     super().__init__(
         bone_name=Tunable(
             str,
             None,
             description=
             'The name of the bone to which the plumbbob should be attached.'
         ),
         offset=TunableVector3(
             TunableVector3.DEFAULT_ZERO,
             description='The Vector3 offset from the bone to the plumbbob.'
         ),
         **kwargs)
예제 #3
0
 def __init__(self, *args, **kwargs):
     super().__init__(
         *args,
         fixed=TunableVector3(
             description=
             '\n                A fixed offset.\n                ',
             default=Vector3.ZERO()),
         random_in_circle=TunableRandomOffsetInCircle(
             description=
             '\n                Specify a random offset within given full/partial circle/donut.\n                '
         ),
         default='fixed',
         **kwargs)
class _WaypointGeneratorEllipse(_WaypointGeneratorBase):
    FACTORY_TUNABLES = {
        'x_radius_interval':
        TunableInterval(
            description=
            '\n            The min and max radius of the x axis. Make the interval 0 to\n            get rid of variance.\n            ',
            tunable_type=float,
            default_lower=1.0,
            default_upper=1.0,
            minimum=1.0),
        'z_radius_interval':
        TunableInterval(
            description=
            '\n            The min and max radius of the z axis. Make the interval 0 to\n            get rid of variance.\n            ',
            tunable_type=float,
            default_lower=1.0,
            default_upper=1.0,
            minimum=1.0),
        'offset':
        TunableVector3(
            description=
            "\n            The offset of the ellipse relative to the target's position.\n            ",
            default=TunableVector3.DEFAULT_ZERO),
        'orientation':
        TunableAngle(
            description=
            "\n            The orientation of the ellipse relative to the target's\n            orientation. The major axis is X if the angle is 0. If the angle is\n            90 degrees, then the major axis is Z.\n            ",
            default=0)
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        ellipse_transform = sims4.math.Transform(
            self.offset, sims4.math.angle_to_yaw_quaternion(self.orientation))
        self.ellipse_transform = sims4.math.Transform.concatenate(
            ellipse_transform, self._target.transform)
        self.start_angle = self.get_start_angle()
        self._start_constraint = None
        self._waypoint_constraints = []

    def transform_point(self, point):
        return self.ellipse_transform.transform_point(point)

    def get_start_angle(self):
        sim_vec = self._context.sim.intended_position - self.ellipse_transform.translation
        return sims4.math.vector3_angle(
            sims4.math.vector_normalize_2d(sim_vec))

    def get_random_ellipse_point_at_angle(self, theta):
        a = self.x_radius_interval.random_float()
        b = self.z_radius_interval.random_float()
        x = a * math.sin(theta)
        z = b * math.cos(theta)
        y = services.terrain_service.terrain_object().get_height_at(x, z)
        return self.transform_point(sims4.math.Vector3(x, y, z))

    def get_ellipse_point_constraint(self, theta):
        position = self.get_random_ellipse_point_at_angle(theta)
        geometry = sims4.geometry.RestrictedPolygon(
            sims4.geometry.CompoundPolygon(sims4.geometry.Polygon(
                (position, ))), ())
        return SmallAreaConstraint(geometry=geometry,
                                   debug_name='EllipsePoint',
                                   routing_surface=self._routing_surface)

    def get_start_constraint(self):
        if self._start_constraint is None:
            self._start_constraint = self.get_ellipse_point_constraint(
                self.start_angle)
            self._start_constraint = self._start_constraint.intersect(
                self.get_water_constraint())
        return self._start_constraint

    def get_waypoint_constraints_gen(self, routing_agent, waypoint_count):
        if not self._waypoint_constraints:
            delta_theta = sims4.math.TWO_PI / waypoint_count
            theta = self.start_angle
            for _ in range(waypoint_count):
                waypoint_constraint = self.get_ellipse_point_constraint(theta)
                self._waypoint_constraints.append(waypoint_constraint)
                theta += delta_theta
            self._waypoint_constraints = self.apply_water_constraint(
                self._waypoint_constraints)
        yield from self._waypoint_constraints
예제 #5
0
 def __init__(self, description='Score by distance from a defined line.', **kwargs):
     super().__init__(initial_point=TunableVector3(description='\n                Position relative to starting location\n                used as first point in the line used for scoring.\n                ', default=TunableVector3.DEFAULT_ZERO, locked_args={'y': 0}), secondary_point=TunableVector3(description='\n                Secondary point used to create a line from initial point.\n                This is relative to the starting location.\n                Distance from this line will be measured.\n                ', default=sims4.math.Vector3(1, 0, 0), locked_args={'y': 0}), optimal_distance=Tunable(description='\n                Optimal distance in meters from the tested location.\n                ', tunable_type=float, default=0), max_distance=Tunable(description='\n                Max distance from optimal before the score becomes zero.\n                ', tunable_type=float, default=1), ignore_surface=Tunable(description='\n                If unset, will ensure the location and the tested position\n                share the same routing surface.  Otherwise, scoring will not\n                care if the two positions have different surfaces.\n                ', tunable_type=bool, default=False), description=description, **kwargs)
예제 #6
0
 def __init__(self, description='Orientation facing in a radius around a circle.', **kwargs):
     super().__init__(angle=TunableAngle(description='\n                Facing range to the circle.\n                ', default=0), radius=Tunable(description='\n                Radius around the given point up to which will be tested.\n                ', tunable_type=float, default=1), target_offset=TunableVector3(description='\n                Offset relative to starting point as center of circle.\n                ', default=TunableVector3.DEFAULT_ZERO, locked_args={'y': 0}), description=description, **kwargs)
예제 #7
0
 def __init__(self, description='A tunable relative facing orientation restriction.', **kwargs):
     super().__init__(angle=TunableAngle(description='\n                Facing range to the object.\n                ', default=0), target_offset=TunableVector3(description='\n                Offset relative to starting point to face.\n                ', default=TunableVector3.DEFAULT_ZERO, locked_args={'y': 0}), description=description, **kwargs)
예제 #8
0
class SpawnPointComponent(Component,
                          HasTunableFactory,
                          AutoFactoryInit,
                          component_name=SPAWN_POINT_COMPONENT,
                          allow_dynamic=True):
    FACTORY_TUNABLES = {
        'spawn_points':
        TunableList(
            description=
            '\n        Spawn points that this object has.\n        ',
            tunable=TunableTuple(
                description=
                '\n            Tuning for spawn point.\n            ',
                spawner_tags=TunableSet(
                    description=
                    "\n                Tags for this spawn point. Sim spawn requests come with a tag.\n                If this spawn point matches a request's tag, then this spawn\n                point is a valid point for the Sim to be positioned at.\n                ",
                    tunable=TunableEnumWithFilter(
                        tunable_type=tag.Tag,
                        filter_prefixes=tag.SPAWN_PREFIX,
                        default=tag.Tag.INVALID,
                        invalid_enums=(tag.Tag.INVALID, ))),
                bone_name=Tunable(
                    description=
                    '\n                The bone on the object to center the spawn point on.\n                ',
                    tunable_type=str,
                    default=''),
                bone_offset=TunableVector3(
                    description=
                    '\n                The offset of the spawn field relative to the bone.\n                ',
                    default=TunableVector3.DEFAULT_ZERO),
                rows=TunableRange(
                    description=
                    '\n                The spawn point has multiple spawn slots arranged in a\n                rectangle. This controls how many rows of spawn slot there are.\n                The total number of Sims that can spawn simultaneously before\n                they start overlapping is number of rows * number of columns.\n                ',
                    tunable_type=int,
                    default=2,
                    minimum=1),
                columns=TunableRange(
                    description=
                    '\n                The spawn point has multiple spawn slots arranged in a\n                rectangle. This controls how many columns of spawn slot there\n                are. The total number of Sims that can spawn simultaneously\n                before they start overlapping is number of rows * number of\n                columns.\n                ',
                    tunable_type=int,
                    default=4,
                    minimum=1),
                scale=TunableRange(
                    description=
                    '\n                The distance between spawn slots.\n                ',
                    tunable_type=float,
                    default=1,
                    minimum=0),
                priority=TunableEnumEntry(
                    description=
                    '\n                The priority of the spawn point.\n                ',
                    tunable_type=SpawnPointPriority,
                    default=SpawnPointPriority.DEFAULT)))
    }

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

    def on_add(self):
        for point in self.spawn_points:
            self._add_spawn_point(point)

    def on_remove(self):
        for spawn_point in tuple(self._spawn_points):
            self._remove_spawn_point(spawn_point)

    def _add_spawn_point(self, point):
        spawn_point = DynamicObjectSpawnPoint(self.owner,
                                              point.spawner_tags,
                                              bone_name=point.bone_name,
                                              bone_offset=point.bone_offset,
                                              rows=point.rows,
                                              columns=point.columns,
                                              scale=point.scale,
                                              priority=point.priority)
        self._spawn_points.add(spawn_point)
        services.current_zone().add_dynamic_spawn_point(spawn_point)

    def _remove_spawn_point(self, spawn_point):
        if spawn_point in self._spawn_points:
            self._spawn_points.remove(spawn_point)
            services.current_zone().remove_dynamic_spawn_point(spawn_point)
예제 #9
0
class Posture(metaclass=TunedInstanceMetaclass, manager=services.posture_manager()):
    __qualname__ = 'Posture'
    ASM_SOURCE = '_asm_key'
    INSTANCE_TUNABLES = {'mobile': Tunable(bool, False, tuning_filter=FilterTag.EXPERT_MODE, description='If True, the Sim can route in this posture.'), 'unconstrained': Tunable(bool, False, description='If True, the Sim can stand anywhere in this posture.'), 'ownable': Tunable(bool, True, description="If True, This posture is ownable by interactions. Ex: A posture like carry_nothing should not be ownable, because it will cause strange cancelations that don't make sense."), 'social_geometry': TunableTuple(social_space=TunablePolygon(description="\n             The special geometry override for socialization in this posture. This defines\n             where the Sim's attention is focused and informs the social positioning system where\n             each Sim should stand to look most natural when interacting with this Sim. \n             Ex: we override the social geometry for a Sim who is bartending to be a wider cone \n             and be in front of the bar instead of embedded within the bar. This encourages Sims \n             to stand on the customer-side of the bar to socialize with this Sim instead of coming \n             around the back."), focal_point=TunableVector3(sims4.math.Vector3.ZERO(), description='Focal point when socializing in this posture, relative to Sim'), tuning_filter=FilterTag.EXPERT_MODE, description='The special geometry for socialization in this posture.'), ASM_SOURCE: TunableResourceKey(None, [sims4.resources.Types.STATEMACHINE], tuning_group=GroupNames.ANIMATION, description='The posture ASM.', category='asm'), '_actor_param_name': Tunable(str, 'x', source_location=ASM_SOURCE, source_query=SourceQueries.ASMActorSim, tuning_group=GroupNames.ANIMATION, description="\n             The name of the actor parameter in this posture's ASM. By default, this is x, and you should probably\n             not change it."), '_target_name': Tunable(str, None, source_location=ASM_SOURCE, source_query=SourceQueries.ASMActorAll, tuning_group=GroupNames.ANIMATION, description="\n             The actor name for the target object of this posture. Leave empty for postures with no target. \n             In the case of a posture that targets an object, it should be the name of the object actor in \n             this posture's ASM. \n             ."), '_enter_state_name': Tunable(str, None, source_location=ASM_SOURCE, source_query=SourceQueries.ASMState, tuning_group=GroupNames.ANIMATION, description='\n             The name of the entry state for the posture in the ASM. \n             All postures should have two public states, not including entry and exit.\n             This should be the first of the two states.'), '_exit_state_name': Tunable(str, 'exit', source_location=ASM_SOURCE, source_query=SourceQueries.ASMState, tuning_group=GroupNames.ANIMATION, description='\n             The name of the exit state in the ASM. By default, this is exit.'), '_state_name': Tunable(str, None, source_location=ASM_SOURCE, source_query=SourceQueries.ASMState, tuning_group=GroupNames.ANIMATION, description='\n             The main state name for the looping posture pose in the ASM.\n             All postures should have two public states, not including entry and exit.\n             This should be the second of the two states.'), '_supported_postures': TunableList(TunableTuple(posture_type=TunableReference(services.posture_manager(), description='A supported posture.'), entry=Tunable(bool, True, description=''), exit=Tunable(bool, True, description=''), transition_cost=OptionalTunable(Tunable(float, 1, description="Cost of the transition to this posture then calculating the Sim's transition sequence.")), preconditions=TunableEnumFlags(PosturePreconditions, PosturePreconditions.NONE), description='A list of postures that this posture supports entrance from and exit to. Defaults to [stand]')), '_supports_carry': Tunable(description='\n            Whether or not there should be a carry version of this posture in\n            the posture graph.\n            ', tunable_type=bool, default=True), 'censor_level': TunableEnumEntry(CensorState, None, tuning_filter=FilterTag.EXPERT_MODE, description="\n                                                                                The type of censor grid that will be applied to any Sim in this posture.  \n                                                                                A censor grid obscures different parts of a Sim's body depending on what censor level it is set at.  \n                                                                                For example, the LHAND censor level will obscure a Sim's left hand.  \n                                                                                By default, postures have no censor level association, which means no censor grid will be applied to them \n                                                                                and every part of their body will be visible when in this posture.\n                                                                                "), 'outfit_change': TunableOutfitChange(description='\n            Define what outfits the Sim is supposed to wear when entering or\n            exiting this posture.\n            '), 'cost': Tunable(float, 0, description='( >= 0 ) The distance a sim is willing to pay to avoid using this posture (higher number discourage using the posture)'), 'idle_animation': TunableAnimationReference(callback=None, tuning_group=GroupNames.ANIMATION, description='The animation for a Sim to play while in this posture and waiting for interaction behavior to start.'), 'jig': OptionalTunable(TunableReference(manager=services.definition_manager(), description='The jig to place while the Sim is in this posture.'), description='An optional Jig to place while the Sim is in this posture.'), 'allow_affinity': Tunable(bool, True, description="\n                            If True, Sims will prefer to use this posture if someone\n                            they're interacting with is using the posture.\n                            \n                            Ex: If you chat with a sitting sim, you will prefer to\n                            sit with them and chat.\n                            "), 'additional_put_down_distance': Tunable(description="\n            An additional distance in front of the Sim to start searching for\n            valid put down locations when in this posture.\n            \n            This tunable is only respected for the Sim's body posture.\n            ", tunable_type=float, default=0.5), 'additional_interaction_jig_fgl_distance': Tunable(description='\n            An additional distance (in meters) in front of the Sim to start \n            searching when using FGL to place a Jig to run an interaction.', tunable_type=float, default=0)}
    DEFAULT_POSTURE = TunableReference(services.get_instance_manager(sims4.resources.Types.POSTURE), description="The default affordance to use as the supported posture if nothing is tuned in a Posture's 'Supported Postures'")
    IS_BODY_POSTURE = True

    def test(self):
        return True

    @classproperty
    def target_name(cls):
        return cls._target_name

    def __init__(self, sim, target, track, animation_context=None):
        self._create_asm(animation_context=animation_context)
        self._source_interaction = None
        self._primitive = None
        self._owning_interactions = set()
        self._sim = None
        self._target = None
        self._target_part = None
        self._surface_target_ref = None
        self._track = None
        self._slot_constraint = UNSET
        self._context = None
        self._asm_registry = defaultdict(dict)
        self._asms_with_posture_info = set()
        self._failed_parts = set()
        self._bind(sim, target, track)
        self._linked_posture = None
        self._entry_anim_complete = False
        self._exit_anim_complete = False
        self.external_transition = False
        self._active_cancel_aops = WeakSet()
        self._saved_exit_clothing_change = None

    @classproperty
    def name(cls):
        return cls._posture_name or cls.__name__

    @property
    def posture_context(self):
        return self._context

    @property
    def animation_context(self):
        return self._animation_context

    @property
    def surface_target(self):
        return self.sim.posture_state.surface_target

    @property
    def source_interaction(self):
        return self._source_interaction

    @source_interaction.setter
    def source_interaction(self, value):
        if value is None:
            logger.error('Posture {} get a None source interaction set', self)
            return
        self._source_interaction = value

    @property
    def owning_interactions(self):
        return self._owning_interactions

    def last_owning_interaction(self, interaction):
        if interaction not in self.owning_interactions:
            return False
        for owning_interaction in self.owning_interactions:
            while owning_interaction is not interaction and not owning_interaction.is_finishing:
                return False
        return True

    def add_owning_interaction(self, interaction):
        self._owning_interactions.add(interaction)

    def remove_owning_interaction(self, interaction):
        self._owning_interactions.remove(interaction)

    def clear_owning_interactions(self):
        from interactions.base.interaction import OWNS_POSTURE_LIABILITY
        try:
            for interaction in list(self._owning_interactions):
                interaction.remove_liability((OWNS_POSTURE_LIABILITY, self.track))
        finally:
            self._owning_interactions.clear()

    def add_cancel_aop(self, cancel_aop):
        self._active_cancel_aops.add(cancel_aop)

    def kill_cancel_aops(self):
        for interaction in self._active_cancel_aops:
            interaction.cancel(FinishingType.INTERACTION_QUEUE, cancel_reason_msg='PostureOwnership. This posture wasgoing to be canceled, but another interaction took ownership over the posture. Most likely the current posture was already valid for the new interaction.')

    def get_idle_behavior(self):
        if self.idle_animation is None:
            logger.error('{} has no idle animation tuning! This tuning is required for all body postures!', self)
            return
        if self.source_interaction is None:
            logger.error('Posture({}) on sim:{} has no source interaction.', self, self.sim, owner='Maxr', trigger_breakpoint=True)
            return
        if self.owning_interactions and not self.multi_sim:
            interaction = list(self.owning_interactions)[0]
        else:
            interaction = self.source_interaction
        idle = self.idle_animation(interaction)
        auto_exit = get_auto_exit((self.sim,), asm=idle.get_asm())
        return build_critical_section(auto_exit, idle, flush_all_animations)

    def log_info(self, phase, msg=None):
        from sims.sim_log import log_posture
        log_posture(phase, self, msg=msg)

    def _create_asm(self, animation_context=None):
        self._animation_context = animation_context or AnimationContext()
        self._animation_context.add_posture_owner(self)
        self._asm = animation.asm.Asm(self._asm_key, self._animation_context)

    _provided_postures = PostureManifest().intern()
    _posture_name = None
    family_name = None

    @classproperty
    def posture_type(cls):
        return cls

    @classmethod
    def is_same_posture_or_family(cls, other_cls):
        if cls == other_cls:
            return True
        return cls.family_name is not None and cls.family_name == other_cls.family_name

    @classmethod
    def _tuning_loading_callback(cls):

        def delclassattr(name):
            if name in cls.__dict__:
                delattr(cls, name)

        delclassattr('_provided_postures')
        delclassattr('_posture_name')
        delclassattr('family_name')

    PostureTransitionData = namedtuple('PostureTransitionData', ('preconditions', 'transition_cost'))
    _posture_transitions = {}

    @staticmethod
    def _add_posture_transition(source_posture, dest_posture, transition_data):
        Posture._posture_transitions[(source_posture, dest_posture)] = transition_data

    @contextmanager
    def __reload_context__(oldobj, newobj):
        posture_transitions = dict(oldobj._posture_transitions)
        yield None
        oldobj._posture_transitions.update(posture_transitions)

    @classmethod
    def _tuning_loaded_callback(cls):
        for posture_data in cls._supported_postures:
            transition_data = cls.PostureTransitionData(posture_data.preconditions, posture_data.transition_cost)
            if posture_data.entry:
                cls._add_posture_transition(posture_data.posture_type, cls, transition_data)
            while posture_data.exit:
                cls._add_posture_transition(cls, posture_data.posture_type, transition_data)
        asm = animation.asm.Asm(cls._asm_key, get_throwaway_animation_context())
        provided_postures = asm.provided_postures
        if not provided_postures:
            return
        specific_name = None
        family_name = None
        for entry in provided_postures:
            entry_specific_name = entry.specific
            if not entry_specific_name:
                raise ValueError('{} must provide a specific posture for all posture definition rows.'.format(asm.name))
            if specific_name is None:
                specific_name = entry_specific_name
            elif entry_specific_name != specific_name:
                raise ValueError('{}: {} provides multiple specific postures: {}'.format(cls, asm.name, [specific_name, entry_specific_name]))
            entry_family_name = entry.family
            while entry_family_name:
                if family_name is None:
                    family_name = entry_family_name
                elif entry_family_name != family_name:
                    raise ValueError('{}: {} provides multiple family postures: {}'.format(cls, asm.name, [family_name, entry_family_name]))
        cls._provided_postures = provided_postures
        cls._posture_name = specific_name
        cls.family_name = family_name
        if cls.idle_animation is None:
            logger.error('{} has no idle_animation tuned. Every posture must have an idle animation suite!', cls)

    @flexmethod
    def get_provided_postures(cls, inst, surface_target=DEFAULT, concrete=False):
        if inst is None:
            return cls._provided_postures
        provided_postures = inst._provided_postures
        surface_target = inst._resolve_surface_target(surface_target)
        if surface_target is None or surface_target == MATCH_NONE:
            surface_restriction = MATCH_NONE
        elif surface_target == MATCH_ANY:
            surface_restriction = surface_target
        else:
            surface_restriction = surface_target if concrete else AnimationParticipant.SURFACE
        if surface_restriction is not None:
            filter_entry = PostureManifestEntry(MATCH_ANY, MATCH_ANY, MATCH_ANY, MATCH_ANY, MATCH_ANY, MATCH_ANY, surface_restriction, True)
            provided_postures = provided_postures.intersection_single(filter_entry)
        return provided_postures

    def _resolve_surface_target(self, surface_target):
        if surface_target is DEFAULT:
            return self.surface_target
        return surface_target

    def _bind(self, sim, target, track):
        if self.sim is sim and self.target is target and self.target_part is None or self.target_part is target and self._track == track:
            return
        if self.target is not None and track == PostureTrack.BODY:
            part_suffix = self.get_part_suffix()
            for asm in self._asms_with_posture_info:
                while not asm.remove_virtual_actor(self._target_name, self.target, suffix=part_suffix):
                    logger.error('Failed to remove previously-bound virtual posture container {} from asm {} on posture {}.', self.target, asm, self)
        if sim is not None:
            self._sim = sim.ref()
        else:
            self._sim = None
        self._intersection = None
        self._asm_registry.clear()
        self._asms_with_posture_info.clear()
        if target is not None:
            if self._target_name is not None and target is not sim:
                (route_type, _) = target.route_target
                if self._target is not None and (self._target() is not None and self._target().parts is not None) and target in self._target().parts:
                    self._target_part = target.ref()
                else:
                    self._target_part = None
                    self._target = target.ref()
            else:
                self._target = target.ref()
        else:
            self._target_part = None
            self._target = None
        if track is not None:
            self._track = track
        else:
            self._track = None
        self._slot_constraint = UNSET

    def rebind(self, target, animation_context=None):
        self._release_animation_context()
        self._create_asm(animation_context=animation_context)
        self._bind(self.sim, target, self.track)

    def reset(self):
        if self._saved_exit_clothing_change is not None:
            self.sim.sim_info.set_current_outfit(self._saved_exit_clothing_change)
            self._saved_exit_clothing_change = None
        self._entry_anim_complete = False
        self._exit_anim_complete = False
        self._release_animation_context()
        self._source_interaction = None

    def _release_animation_context(self):
        if self._animation_context is not None:
            self._animation_context.remove_posture_owner(self)
            self._animation_context = None

    def kickstart_gen(self, timeline, posture_state):
        if PostureTrack.is_carry(self.track):
            is_body = False
            self.asm.set_parameter('location', 'inventory')
        else:
            is_body = True
            self.source_interaction = self.sim.create_default_si()
        idle_arb = animation.arb.Arb()
        self.append_transition_to_arb(idle_arb, None)
        self.append_idle_to_arb(idle_arb)
        begin_element = self.get_begin(idle_arb, posture_state)
        yield element_utils.run_child(timeline, begin_element)
        if is_body:
            default_si = self.source_interaction
            yield default_si.prepare_gen(timeline)
            yield default_si.enter_si_gen(timeline)
            yield default_si.setup_gen(timeline)
            result = yield default_si.perform_gen(timeline)
            if not result:
                raise RuntimeError('Sim: {} failed to enter default si: {}'.format(self, default_si))

    def get_asm(self, animation_context, asm_key, setup_asm_func, use_cache=True, cache_key=DEFAULT, interaction=None, posture_manifest_overrides=None, **kwargs):
        dict_key = animation_context if cache_key is DEFAULT else cache_key
        if use_cache:
            asm_dict = self._asm_registry[dict_key]
            asm = asm_dict.get(asm_key)
            if asm is None:
                asm = animation.asm.Asm(asm_key, context=animation_context, posture_manifest_overrides=posture_manifest_overrides)
                if interaction is not None:
                    asm.on_state_changed_events.append(interaction.on_asm_state_changed)
                asm_dict[asm_key] = asm
        else:
            asm = animation.asm.Asm(asm_key, context=animation_context)
            if interaction is not None:
                asm.on_state_changed_events.append(interaction.on_asm_state_changed)
        if asm.current_state == 'exit':
            asm.set_current_state('entry')
        if not (setup_asm_func is not None and setup_asm_func(asm)):
            return
        return asm

    def remove_from_cache(self, cache_key):
        if cache_key in self._asm_registry:
            for asm in self._asm_registry[cache_key].values():
                del asm._on_state_changed_events[:]
            del self._asm_registry[cache_key]

    def _create_primitive(self, animate_in, dest_state):
        return PosturePrimitive(self, animate_in, dest_state, self._context)

    def _on_reset(self):
        self._primitive = None

    def __str__(self):
        return '{0}:{1}'.format(self.name, self.id)

    def __repr__(self):
        return standard_repr(self, self.id, self.target)

    @property
    def sim(self):
        if self._sim is not None:
            return self._sim()

    @property
    def target(self):
        if self._target_part is not None:
            return self._target_part()
        if self._target is not None:
            return self._target()

    @property
    def target_part(self):
        if self._target_part is not None:
            return self._target_part()

    @property
    def track(self):
        return self._track

    @property
    def is_active_carry(self):
        return PostureTrack.is_carry(self.track) and self.target is not None

    def get_slot_offset_locked_params(self, anim_overrides=None):
        locked_params = self._locked_params
        if anim_overrides is not None:
            locked_params += anim_overrides.params
        locked_params += {'transitionPosture': 'stand'}
        return locked_params

    def build_slot_constraint(self, create_posture_state_spec_fn=None):
        if self.target is not None and PostureTrack.is_body(self.track):
            return interactions.constraints.RequiredSlot.create_slot_constraint(self, create_posture_state_spec_fn=create_posture_state_spec_fn)

    @property
    def slot_constraint_simple(self):
        if self._slot_constraint is UNSET:
            self._slot_constraint = self.build_slot_constraint(create_posture_state_spec_fn=lambda *_, **__: None)
        return self._slot_constraint

    @property
    def slot_constraint(self):
        if self._slot_constraint is UNSET:
            self._slot_constraint = self.build_slot_constraint()
        return self._slot_constraint

    @classproperty
    def multi_sim(cls):
        return False

    @property
    def is_puppet(self):
        return False

    @property
    def is_mirrored(self):
        if self.target is not None and self.target.is_part:
            return self.target.is_mirrored() or False
        return False

    @property
    def linked_posture(self):
        return self._linked_posture

    @linked_posture.setter
    def linked_posture(self, posture):
        self._linked_posture = posture

    @property
    def asm(self):
        return self._asm

    @property
    def _locked_params(self):
        anim_overrides_actor = self.sim.get_anim_overrides(self._actor_param_name)
        params = anim_overrides_actor.params
        if self.target is not None:
            anim_overrides_target = self.target.get_anim_overrides(self.target_name)
            if anim_overrides_target is not None:
                params += anim_overrides_target.params
            if self.target.is_part:
                part_suffix = self.target.part_suffix
                if part_suffix is not None:
                    params += {'subroot': part_suffix}
        if self.is_mirrored is not None:
            params += {'isMirrored': self.is_mirrored}
        return params

    @property
    def locked_params(self):
        if self.slot_constraint is None or self.slot_constraint.locked_params is None:
            return self._locked_params
        return self._locked_params + self.slot_constraint.locked_params

    def _setup_asm_container_parameter(self, asm, target, actor_name, part_suffix, target_name=None):
        if asm in self._asms_with_posture_info:
            return True
        if target_name is None:
            target_name = self._target_name
        result = False
        if target is not None and target_name is not None:
            result = asm.add_potentially_virtual_actor(actor_name, self.sim, target_name, target, part_suffix, target_participant=AnimationParticipant.CONTAINER)
            if not self._setup_custom_posture_target_name(asm, target):
                logger.error('Failed to set custom posture target {}', target)
                result = False
        if result:
            self._asms_with_posture_info.add(asm)
        return result

    def _setup_custom_posture_target_name(self, asm, target):
        _custom_target_name = target.custom_posture_target_name
        if _custom_target_name in asm.actors:
            (_custom_target_actor, _) = asm.get_actor_and_suffix(_custom_target_name)
            if _custom_target_actor is None:
                return asm.set_actor(target.custom_posture_target_name, target, suffix=None, actor_participant=AnimationParticipant.CONTAINER)
        return True

    def _setup_asm_carry_parameter(self, asm, target):
        pass

    def get_part_suffix(self, target=DEFAULT):
        if target is DEFAULT:
            target = self.target
        if target is not None:
            return target.part_suffix

    def setup_asm_posture(self, asm, sim, target, locked_params=frozendict(), actor_param_name=DEFAULT):
        if actor_param_name is DEFAULT:
            actor_param_name = self._actor_param_name
        if asm is None:
            logger.error('Attempt to setup an asm whose value is None.')
            return False
        if sim is None:
            logger.error('Attempt to setup an asm {0} on a sim whose value is None.', asm)
            return False
        if not asm.set_actor(actor_param_name, sim, actor_participant=AnimationParticipant.ACTOR):
            logger.error('Failed to set actor sim: {0} on asm {1}', actor_param_name, asm)
            return False
        sim.set_mood_asm_parameter(asm, actor_param_name)
        sim.set_trait_asm_parameters(asm, actor_param_name)
        if target.is_part:
            is_mirrored = target.is_mirrored()
            if is_mirrored is not None:
                locked_params += {'isMirrored': is_mirrored}
        part_suffix = self.get_part_suffix()
        if not (target is not None and self._target_name is not None and self._setup_asm_container_parameter(asm, target, actor_param_name, part_suffix)):
            logger.error('Failed to set actor target: {0} on asm {1}', self._target_name, asm)
            return False
        if not PostureTrack.is_body(self.track):
            self._update_non_body_posture_asm()
            sim.on_posture_event.append(self._update_on_posture_event)
        if locked_params:
            virtual_actor_map = {self._target_name: self.target}
            asm.update_locked_params(locked_params, virtual_actor_map)
        self._setup_asm_carry_parameter(asm, target)
        return True

    def _update_on_posture_event(self, change, dest_state, track, old_value, new_value):
        if change == PostureEvent.POSTURE_CHANGED:
            if track != self.track:
                if new_value is not None:
                    self._update_non_body_posture_asm()
                    if new_value != self:
                        self.sim.on_posture_event.remove(self._update_on_posture_event)
            elif new_value != self:
                self.sim.on_posture_event.remove(self._update_on_posture_event)

    def _update_non_body_posture_asm(self):
        if self.sim.posture.target is not None:
            (previous_target, previous_suffix) = self.asm.get_virtual_actor_and_suffix(self._actor_param_name, self.sim.posture._target_name)
            if previous_target is not None:
                self.asm.remove_virtual_actor(self.sim.posture._target_name, previous_target, previous_suffix)
        self.sim.posture.setup_asm_interaction(self.asm, self.sim, self.target, self._actor_param_name, self._target_name)

    def _setup_asm_interaction_add_posture_info(self, asm, sim, target, actor_name, target_name, carry_target, carry_target_name, surface_target=DEFAULT, carry_track=DEFAULT):

        def set_posture_param(posture_param_str, carry_param_str, carry_actor_name, surface_actor_name):
            if not asm.set_actor_parameter(actor_name, sim, 'posture', posture_param_str):
                if not asm.set_parameter('posture', posture_param_str):
                    return False
                logger.warn('Backwards compatibility with old posture parameter required by {}', asm.name)
            if not asm.set_actor_parameter(actor_name, sim, PARAM_CARRY_STATE, carry_param_str):
                asm.set_parameter('carry', carry_param_str)
            asm.set_parameter('isMirrored', self.is_mirrored)
            if target_name == carry_actor_name and target is not None:
                set_carry_track_param_if_needed(asm, sim, target_name, target, carry_track=carry_track)
            if carry_actor_name is not None and carry_target_name == carry_actor_name and carry_target is not None:
                set_carry_track_param_if_needed(asm, sim, carry_target_name, carry_target, carry_track=carry_track)
            if surface_actor_name is not None:
                _surface_target = self._resolve_surface_target(surface_target)
                if _surface_target:
                    asm.add_potentially_virtual_actor(actor_name, sim, surface_actor_name, _surface_target, target_participant=AnimationParticipant.SURFACE)
                else:
                    return False
            return True

        def build_carry_str(carry_state):
            if carry_state[0]:
                if carry_state[1]:
                    return 'both'
                return 'left'
            if carry_state[1]:
                return 'right'
            return 'none'

        def setup_asm_container_parameter(chosen_posture_type):
            container_name = chosen_posture_type.target_name
            if not container_name:
                return True
            part_suffix = self.get_part_suffix()
            if self._setup_asm_container_parameter(asm, self.target, actor_name, part_suffix, target_name=container_name):
                return True
            return False

        carry_state = sim.posture_state.get_carry_state()
        supported_postures = asm.get_supported_postures_for_actor(actor_name)
        if supported_postures is None:
            return True
        filtered_supported_postures = self.sim.filter_supported_postures(supported_postures)
        if surface_target is DEFAULT:
            surface_target = self._resolve_surface_target(surface_target)
            if surface_target is not None:
                surface_target_provided = MATCH_ANY
            else:
                surface_target_provided = MATCH_NONE
        elif surface_target is not None:
            surface_target_provided = MATCH_ANY
        else:
            surface_target_provided = MATCH_NONE
        provided_postures = self.get_provided_postures(surface_target=surface_target_provided)
        best_supported_posture = get_best_supported_posture(provided_postures, filtered_supported_postures, carry_state)
        if best_supported_posture is None:
            logger.debug('Failed to find supported posture for actor {} on {} for posture ({}) and carry ({}).  Interaction info claims this should work.', actor_name, asm, self, carry_state)
            return False
        carry_param_str = build_carry_str(carry_state)
        carry_actor_name = best_supported_posture.carry_target
        surface_actor_name = best_supported_posture.surface_target
        if not isinstance(surface_actor_name, str):
            surface_actor_name = None
        param_str_specific = best_supported_posture.posture_param_value_specific
        if best_supported_posture.is_overlay:
            return True
        if param_str_specific and set_posture_param(param_str_specific, carry_param_str, carry_actor_name, surface_actor_name) and setup_asm_container_parameter(best_supported_posture.posture_type_specific):
            return True
        param_str_family = best_supported_posture.posture_param_value_family
        if best_supported_posture.is_overlay:
            return True
        if param_str_family and set_posture_param(param_str_family, carry_param_str, carry_actor_name, surface_actor_name) and setup_asm_container_parameter(best_supported_posture.posture_type_family):
            return True
        return False

    def setup_asm_interaction(self, asm, sim, target, actor_name, target_name, carry_target=None, carry_target_name=None, create_target_name=None, surface_target=DEFAULT, carry_track=DEFAULT, actor_participant=AnimationParticipant.ACTOR, invalid_expected=False):
        if target_name is not None and (target_name == self._target_name and (target is not None and self.target is not None)) and target.id != self.target.id:
            if not invalid_expected:
                logger.error('Animation targets a different object than its posture, but both use the same actor name for the object. This is impossible to resolve. Actor name: {}, posture target: {}, interaction target: {}', target_name, target, self.target)
            return False
        if not asm.set_actor(actor_name, sim, actor_participant=actor_participant):
            logger.error('Failed to set actor: {0} on asm {1}', actor_name, asm)
            return False
        if sim.asm_auto_exit.apply_carry_interaction_mask:
            asm._set_actor_trackmask_override(actor_name, 50000, 'Trackmask_CarryInteraction')
        if target is not None and target_name is not None:
            from sims.sim import Sim
            if isinstance(target, Sim):
                if not target.posture.setup_asm_interaction(asm, target, None, target_name, None, actor_participant=AnimationParticipant.TARGET):
                    return False
            else:
                asm.add_potentially_virtual_actor(actor_name, sim, target_name, target, target_participant=AnimationParticipant.TARGET)
                anim_overrides = target.get_anim_overrides(target_name)
                if anim_overrides is not None and anim_overrides.params:
                    virtual_actor_map = {self._target_name: self.target}
                    asm.update_locked_params(anim_overrides.params, virtual_actor_map)
            if not self._setup_custom_posture_target_name(asm, target):
                logger.error('Unable to setup custom posture target name for {} on {}', target, asm)
        _carry_target_name = carry_target_name or create_target_name
        if carry_target is not None and _carry_target_name is not None:
            asm.add_potentially_virtual_actor(actor_name, sim, _carry_target_name, carry_target, target_participant=AnimationParticipant.CARRY_TARGET)
        if not self._setup_asm_interaction_add_posture_info(asm, sim, target, actor_name, target_name, carry_target, carry_target_name, surface_target, carry_track):
            return False
        return True

    def get_begin(self, animate_in, dest_state):
        if self._primitive is not None:
            raise RuntimeError('Posture Entry({}) called multiple times without a paired exit.'.format(self))
        self._primitive = self._create_primitive(animate_in, dest_state)
        return self._primitive.next_stage()

    def begin(self, animate_in, dest_state, context):
        self._context = context

        def _do_begin(timeline):
            logger.debug('{} begin Posture: {}', self.sim, self)
            begin = self.get_begin(animate_in, dest_state)
            result = yield element_utils.run_child(timeline, begin)
            return result

        return _do_begin

    def get_end(self):
        if self._primitive is None:
            raise RuntimeError('Posture Exit({}) called multiple times without a paired entry. Sim: {}'.format(self, self.sim))
        exit_behavior = self._primitive.next_stage()
        self._primitive = None
        return exit_behavior

    def end(self):

        def _do_end(timeline):
            logger.debug('{} end Posture: {}', self.sim, self)
            end = self.get_end()
            result = yield element_utils.run_child(timeline, end)
            return result

        return _do_end

    def add_transition_extras(self, sequence):
        return sequence

    def enumerate_goal_list_ids(self, goal_list):
        raise RuntimeError('[bhill] This function is believed to be dead code and is scheduled for pruning. If this exception has been raised, the code is not dead and this exception should be removed.')
        if goal_list is not None:
            for (index, goal) in enumerate(goal_list):
                goal.tag = index

    def get_locked_params(self, source_posture):
        if source_posture is None:
            return self._locked_params
        updates = {TRANSITION_POSTURE_PARAM_NAME: source_posture.name}
        if source_posture.target is None:
            return self._locked_params + updates
        if source_posture.target.is_part and self.target is not None and self.target.is_part:
            if self.target.is_mirrored(source_posture.target):
                direction = 'fromSimLeft'
            else:
                direction = 'fromSimRight'
            updates['direction'] = direction
        return self._locked_params + updates

    def append_transition_to_arb(self, arb, source_posture, locked_params=frozendict(), **kwargs):
        if not self._entry_anim_complete:
            locked_params += self.get_locked_params(source_posture)
            if source_posture is not None:
                locked_params += {TRANSITION_POSTURE_PARAM_NAME: source_posture.name}
            if not self.setup_asm_posture(self.asm, self.sim, self.target, locked_params=locked_params):
                logger.error('Failed to setup the asm for the posture {}', self)
                return
            self._setup_asm_target_for_transition(source_posture)
            self.asm.request(self._enter_state_name, arb)
            linked_posture = self.linked_posture
            if linked_posture is not None:
                locked_params = linked_posture.get_locked_params(source_posture)
                linked_posture.setup_asm_posture(linked_posture._asm, linked_posture.sim, linked_posture.target, locked_params=locked_params)
                if not self.multi_sim:
                    linked_posture._asm.request(linked_posture._enter_state_name, arb)
            self._entry_anim_complete = True

    def append_idle_to_arb(self, arb):
        self.asm.request(self._state_name, arb)
        if self._linked_posture is not None:
            self._linked_posture.append_idle_to_arb(arb)

    def append_exit_to_arb(self, arb, dest_state, dest_posture, var_map, locked_params=frozendict()):
        if not self._exit_anim_complete:
            self._setup_asm_target_for_transition(dest_posture)
            locked_params += self.locked_params
            if dest_posture is not None:
                locked_params += {TRANSITION_POSTURE_PARAM_NAME: dest_posture.name}
            if locked_params:
                virtual_actor_map = {self._target_name: self.target}
                self.asm.update_locked_params(locked_params, virtual_actor_map)
            self.asm.request(self._exit_state_name, arb)
            self._exit_anim_complete = True

    def _setup_asm_target_for_transition(self, transition_posture):
        if transition_posture is not None and transition_posture._target_name != self._target_name and transition_posture._target_name in self.asm.actors:
            (previous_target, previous_suffix) = self.asm.get_virtual_actor_and_suffix(self._actor_param_name, transition_posture._target_name)
            if previous_target is not None:
                self.asm.remove_virtual_actor(transition_posture.target_name, previous_target, previous_suffix)
            if not transition_posture._setup_asm_container_parameter(self.asm, transition_posture.target, self._actor_param_name, transition_posture.get_part_suffix()):
                logger.error('Failed to setup target container {} on {} from transition posture {}', transition_posture._target_name, self, transition_posture)
                return False
        return True

    def post_route_clothing_change(self, interaction, do_spin=True, **kwargs):
        si_outfit_change = interaction.outfit_change
        if si_outfit_change is not None and si_outfit_change.posture_outfit_change_overrides is not None:
            overrides = si_outfit_change.posture_outfit_change_overrides.get(self.posture_type)
            if overrides is not None:
                entry_outfit = overrides.get_on_entry_outfit(interaction)
                if entry_outfit is not None:
                    return overrides.get_on_entry_change(interaction, do_spin=do_spin, **kwargs)
        if self.outfit_change is not None:
            return self.outfit_change.get_on_entry_change(interaction, do_spin=do_spin, **kwargs)

    @property
    def saved_exit_clothing_change(self):
        return self._saved_exit_clothing_change

    def transfer_exit_clothing_change(self, clothing_change):
        self._saved_exit_clothing_change = clothing_change

    def prepare_exit_clothing_change(self, interaction):
        si_outfit_change = interaction.outfit_change
        if si_outfit_change is not None and si_outfit_change.posture_outfit_change_overrides is not None:
            overrides = si_outfit_change.posture_outfit_change_overrides.get(self.posture_type)
            if overrides is not None:
                exit_outfit = overrides.get_on_exit_outfit(interaction)
                if exit_outfit is not None:
                    self._saved_exit_clothing_change = overrides.get_on_exit_outfit(interaction)
                    return
        if self.outfit_change and self._saved_exit_clothing_change is None:
            self._saved_exit_clothing_change = self.outfit_change.get_on_exit_outfit(interaction)

    def exit_clothing_change(self, interaction, *, sim=DEFAULT, do_spin=True, **kwargs):
        if self._saved_exit_clothing_change is None or interaction is None:
            return
        if sim is DEFAULT:
            sim = interaction.sim
        sim_info = sim.sim_info
        return build_critical_section(sim_info.sim_outfits.get_change_outfit_element(self._saved_exit_clothing_change, do_spin=do_spin), flush_all_animations)

    def ensure_exit_clothing_change_application(self):
        if self.sim.posture_state.body is not self and self._saved_exit_clothing_change is not None:
            self.sim.sim_info.set_current_outfit(self._saved_exit_clothing_change)
            self._saved_exit_clothing_change = None

    @classmethod
    def supports_posture_type(cls, posture_type):
        return (cls, posture_type) in cls._posture_transitions or (posture_type, cls) in cls._posture_transitions

    @classmethod
    def is_valid_transition(cls, source_posture_type, destination_posture_type, targets_match):
        transition_data = cls._posture_transitions.get((source_posture_type, destination_posture_type))
        if transition_data is None:
            return False
        if targets_match:
            return True
        preconditions = transition_data.preconditions
        if preconditions is not None and preconditions & PosturePreconditions.SAME_TARGET:
            return False
        return True

    @classmethod
    def get_transition_cost(cls, posture_type):
        transition_data = cls._posture_transitions.get((cls, posture_type))
        if transition_data is None:
            transition_data = cls._posture_transitions.get((posture_type, cls))
        if transition_data is not None:
            return transition_data.transition_cost

    @classmethod
    def is_valid_target(cls, sim, target, **kwargs):
        return True
예제 #10
0
class Mood(HasTunableReference, metaclass=HashedTunedInstanceMetaclass, manager=services.mood_manager()):
    __qualname__ = 'Mood'
    INSTANCE_TUNABLES = {'mood_asm_param': OptionalTunable(description='\n            If set, then this mood will specify an asm parameter to affect\n            animations. If not set, then the ASM parameter will be determined by\n            the second most prevalent mood.\n            ', tunable=Tunable(description="\n                The asm parameter for Sim's mood, if not set, will use 'xxx'\n                from instance name pattern with 'mood_xxx'.\n                ", tunable_type=str, default='', source_query=SourceQueries.SwingEnumNamePattern.format('mood')), enabled_name='Specify', disabled_name='Determined_By_Other_Moods'), 'intensity_thresholds': TunableList(int, description='\n                List of thresholds at which the intensity of this mood levels up.\n                If empty, this mood has a single threshold and all mood tuning lists should\n                have a single item in them.\n                For each threshold added, you may add a new item to the Buffs, Mood Names,\n                Portrait Pose Indexes and Portrait Frames lists.'), 'buffs': TunableList(TunableBuffReference(reload_dependent=True), description='\n                A list of buffs that will be added while this mood is the active mood\n                on a Sim. \n                The first item is applied for the initial intensity, and each\n                subsequent item replaces the previous buff as the intensity levels up.'), 'mood_names': TunableList(TunableLocalizedString(), description='\n                A list of localized names of this mood.\n                The first item is applied for the initial intensity, and each\n                subsequent item replaces the name as the intensity levels up.', export_modes=(ExportModes.ServerXML, ExportModes.ClientBinary)), 'portrait_pose_indexes': TunableList(Tunable(tunable_type=int, default=0), description='\n                A list of the indexes of the pose passed to thumbnail generation on the\n                client to pose the Sim portrait when they have this mood.\n                You can find the list of poses in tuning\n                (Client_ThumnailPoses)\n                The first item is applied for the initial intensity, and each\n                subsequent item replaces the pose as the intensity levels up.', export_modes=(ExportModes.ClientBinary,)), 'portrait_frames': TunableList(Tunable(tunable_type=str, default=''), description='\n                A list of the frame labels (NOT numbers!) from the UI .fla file that the\n                portrait should be set to when this mood is active. Determines\n                background color, font color, etc.\n                The first item is applied for the initial intensity, and each\n                subsequent item replaces the pose as the intensity levels up.', export_modes=(ExportModes.ClientBinary,)), 'environment_scoring_commodity': Commodity.TunableReference(description="\n                Defines the ranges and corresponding buffs to apply for this\n                mood's environmental contribution.\n                \n                Be sure to tune min, max, and the different states. The\n                convergence value is what will remove the buff. Suggested to be\n                0.\n                "), 'descriptions': TunableList(TunableLocalizedString(), description='\n                Description for the UI tooltip, per intensity.', export_modes=(ExportModes.ClientBinary,)), 'icons': TunableList(TunableResourceKey(None, resource_types=sims4.resources.CompoundTypes.IMAGE), description='\n                Icon for the UI tooltip, per intensity.', export_modes=(ExportModes.ClientBinary,)), 'descriptions_age_override': TunableMapping(description='\n                Mapping of age to descriptions text for mood.  If age does not\n                exist in mapping will use default description text.\n                ', key_type=TunableEnumEntry(sim_info_types.Age, sim_info_types.Age.CHILD), value_type=TunableList(description='\n                    Description for the UI tooltip, per intensity.\n                    ', tunable=TunableLocalizedString()), key_name='Age', value_name='description_text', export_modes=(ExportModes.ClientBinary,)), 'descriptions_trait_override': TunableMoodDescriptionTraitOverride(description='\n                Trait override for mood descriptions.  If a Sim has this trait\n                and there is not a valid age override for the Sim, this\n                description text will be used.\n                ', export_modes=(ExportModes.ClientBinary,)), 'audio_stings_on_add': TunableList(description="\n                The audio to play when a mood or it's intensity changes. Tune one for each intensity on the mood.\n                ", tunable=TunableResourceKey(description='\n                    The sound to play.\n                    ', default=None, resource_types=(sims4.resources.Types.PROPX,), export_modes=ExportModes.ClientBinary)), 'mood_colors': TunableList(description='\n                A list of the colors displayed on the steel series mouse when the active Sim has this mood.  The first item is applied for the initial intensity, and each  subsequent item replaces the color as the intensity levels up.\n                ', tunable=TunableVector3(description='\n                    Color.\n                    ', default=sims4.math.Vector3.ZERO(), export_modes=ExportModes.ClientBinary)), 'mood_frequencies': TunableList(description='\n                A list of the flash frequencies on the steel series mouse when the active Sim has this mood.   The first item is applied for the initial intensity, and each  subsequent item replaces the value as the intensity levels up.  0 => solid color, otherwise, value => value hertz.\n                  ', tunable=Tunable(tunable_type=float, default=0.0, description=',\n                    Hertz.\n                    ', export_modes=ExportModes.ClientBinary)), 'buff_polarity': TunableEnumEntry(description='\n                Setting the polarity will determine how up/down arrows\n                appear for any buff that provides this mood.\n                ', tunable_type=BuffPolarity, default=BuffPolarity.NEUTRAL, tuning_group=GroupNames.UI, needs_tuning=True, export_modes=ExportModes.All), 'is_changeable': Tunable(description='\n                If this is checked, any buff with this mood will change to\n                the highest current mood of the same polarity.  If there is no mood\n                with the same polarity it will default to use this mood type\n                ', tunable_type=bool, default=False, needs_tuning=True)}
    _asm_param_name = None
    excluding_traits = None

    @classmethod
    def _tuning_loaded_callback(cls):
        cls._asm_param_name = cls.mood_asm_param
        if not cls._asm_param_name:
            name_list = cls.__name__.split('_', 1)
            if len(name_list) <= 1:
                logger.error("Mood {} has an invalid name for asm parameter, please either set 'mood_asm_param' or change the tuning file name to match the format 'mood_xxx'.", cls.__name__)
            cls._asm_param_name = name_list[1]
        cls._asm_param_name = cls._asm_param_name.lower()
        for buff_ref in cls.buffs:
            my_buff = buff_ref.buff_type
            while my_buff is not None:
                if my_buff.mood_type is not None:
                    logger.error('Mood {} will apply a buff ({}) that affects mood. This can cause mood calculation errors. Please select a different buff or remove the mood change.', cls.__name__, my_buff.mood_type.__name__)
                my_buff.is_mood_buff = True
        prev_threshold = 0
        for threshold in cls.intensity_thresholds:
            if threshold <= prev_threshold:
                logger.error('Mood {} has Intensity Thresholds in non-ascending order.')
                break
            prev_threshold = threshold

    @classmethod
    def _verify_tuning_callback(cls):
        num_thresholds = len(cls.intensity_thresholds) + 1
        if len(cls.buffs) != num_thresholds:
            logger.error('Mood {} does not have the correct number of Buffs tuned. It has {} thresholds, but {} buffs.', cls.__name__, num_thresholds, len(cls.buffs))
        if len(cls.mood_names) != num_thresholds:
            logger.error('Mood {} does not have the correct number of Mood Names tuned. It has {} thresholds, but {} names.', cls.__name__, num_thresholds, len(cls.mood_names))
        if len(cls.portrait_pose_indexes) != num_thresholds:
            logger.error('Mood {} does not have the correct number of Portrait Pose Indexes tuned. It has {} thresholds, but {} poses.', cls.__name__, num_thresholds, len(cls.portrait_pose_indexes))
        if len(cls.portrait_frames) != num_thresholds:
            logger.error('Mood {} does not have the correct number of Portrait Frames tuned. It has {} thresholds, but {} frames.', cls.__name__, num_thresholds, len(cls.portrait_frames))
        for (age, descriptions) in cls.descriptions_age_override.items():
            while len(descriptions) != num_thresholds:
                logger.error('Mood {} does not have the correct number of descriptions age override tuned. For age:({}) It has {} thresholds, but {} descriptions.', cls.__name__, age, num_thresholds, len(descriptions))
        if cls.descriptions_trait_override.trait is not None and len(cls.descriptions_trait_override.descriptions) != num_thresholds:
            logger.error('Mood {} does not have the correct number of trait override descriptions tuned. For trait:({}) It has {} thresholds, but {} descriptions.', cls.__name__, cls.descriptions_trait_override.trait.__name__, num_thresholds, len(cls.descriptions_trait_override.descriptions))

    @classproperty
    def asm_param_name(cls):
        return cls._asm_param_name
예제 #11
0
class ObjectCreationElement(XevtTriggeredElement):
    __qualname__ = 'ObjectCreationElement'
    POSITION = 'position'
    INVENTORY = 'inventory'
    SLOT = 'slot'

    class ObjectDefinition(HasTunableSingletonFactory, AutoFactoryInit):
        __qualname__ = 'ObjectCreationElement.ObjectDefinition'
        FACTORY_TUNABLES = {
            'definition':
            TunableReference(
                description=
                '\n                The definition of the object that is created.\n                ',
                manager=services.definition_manager())
        }

        def get_definition(self):
            return self.definition

        def setup_created_object(self, interaction, created_object):
            pass

    class RecipeDefinition(HasTunableSingletonFactory, AutoFactoryInit):
        __qualname__ = 'ObjectCreationElement.RecipeDefinition'
        FACTORY_TUNABLES = {
            'recipe':
            TunableReference(
                description=
                '\n                The recipe to use to create the object.\n                ',
                manager=services.recipe_manager())
        }

        def get_definition(self):
            return self.recipe.final_product.definition

        def setup_created_object(self, interaction, created_object):
            from crafting.crafting_process import CraftingProcess
            crafting_process = CraftingProcess(crafter=interaction.sim,
                                               recipe=self.recipe)
            crafting_process.setup_crafted_object(created_object,
                                                  is_final_product=True)

    FACTORY_TUNABLES = {
        'description':
        '\n            Create an object as part of an interaction.\n            ',
        'creation_data':
        TunableVariant(
            description='\n            Define what to create.\n            ',
            definition=ObjectDefinition.TunableFactory(),
            recipe=RecipeDefinition.TunableFactory(),
            default='definition'),
        'initial_states':
        TunableList(
            description=
            '\n            A list of states to apply to the object as soon as it is created.\n            ',
            tunable=TunableStateValueReference()),
        'destroy_on_placement_failure':
        Tunable(
            description=
            '\n            If checked, the created object will be destroyed on placement failure.\n            If unchecked, the created object will be placed into an appropriate\n            inventory on placement failure if possible.  If THAT fails, object\n            will be destroyed.\n            ',
            tunable_type=bool,
            default=False),
        'cancel_on_destroy':
        Tunable(
            description=
            '\n            If checked, the interaction will be canceled if object is destroyed\n            due to placement failure or if destroy on placement failure is\n            unchecked and the fallback fails.\n            ',
            tunable_type=bool,
            default=True),
        'transient':
        Tunable(
            description=
            '\n            If checked, the created object will be destroyed when the interaction ends.\n            ',
            tunable_type=bool,
            default=False),
        'location':
        TunableVariant(
            description=
            '\n            Where the object should be created.\n            ',
            default='position',
            position=TunableTuple(
                description=
                '\n                An in-world position based off of the chosen Participant Type.\n                ',
                locked_args={'location': POSITION},
                location_target=TunableEnumEntry(
                    description=
                    '\n                    Who or what to create this object next to.\n                    ',
                    tunable_type=ParticipantType,
                    default=ParticipantType.Actor),
                offset_tuning=TunableTuple(
                    default_offset=TunableVector3(
                        description=
                        "\n                        The default Vector3 offset from the location target's\n                        position.\n                        ",
                        default=sims4.math.Vector3.ZERO()),
                    x_randomization_range=OptionalTunable(
                        TunableInterval(
                            description=
                            '\n                        A random number in this range will be applied to the\n                        default offset along the x axis.\n                        ',
                            tunable_type=float,
                            default_lower=0,
                            default_upper=0)),
                    z_randomization_range=OptionalTunable(
                        TunableInterval(
                            description=
                            '\n                        A random number in this range will be applied to the\n                        default offset along the z axis.\n                        ',
                            tunable_type=float,
                            default_lower=0,
                            default_upper=0))),
                ignore_bb_footprints=Tunable(
                    description=
                    '\n                    Ignores the build buy object footprints when trying to find\n                    a position for creating this object.  This will allow \n                    objects to appear on top of each other.\n                    e.g. Trash cans when tipped over want to place the trash \n                    right under them so it looks like the pile came out from \n                    the object while it was tipped.\n                    ',
                    tunable_type=bool,
                    default=True),
                allow_off_lot_placement=Tunable(
                    description=
                    '\n                    If checked, objects will be allowed to be placed off-lot.\n                    If unchecked, we will always attempt to place created\n                    objects on the active lot.\n                    ',
                    tunable_type=bool,
                    default=False)),
            inventory=TunableTuple(
                description=
                '\n                An inventory based off of the chosen Participant Type.\n                ',
                locked_args={'location': INVENTORY},
                location_target=TunableEnumEntry(
                    description=
                    '\n                    "The owner of the inventory the object will be created in."\n                    ',
                    tunable_type=ParticipantType,
                    default=ParticipantType.Actor)),
            slot=TunableTuple(
                description=
                '\n                Slot the object into the specified slot on the tuned location_target.\n                ',
                locked_args={'location': SLOT},
                location_target=TunableEnumEntry(
                    description=
                    '\n                    The object which will contain the specified slot.\n                    ',
                    tunable_type=ParticipantType,
                    default=ParticipantType.Object),
                parent_slot=TunableVariant(
                    description=
                    '\n                    The slot on location_target where the object should go. This\n                    may be either the exact name of a bone on the location_target or a\n                    slot type, in which case the first empty slot of the specified type\n                    in which the child object fits will be used.\n                    ',
                    by_name=Tunable(
                        description=
                        '\n                        The exact name of a slot on the location_target in which the target\n                        object should go.  \n                        ',
                        tunable_type=str,
                        default='_ctnm_'),
                    by_reference=TunableReference(
                        description=
                        '\n                        A particular slot type in which the target object should go.  The\n                        first empty slot of this type found on the location_target will be used.\n                        ',
                        manager=services.get_instance_manager(
                            sims4.resources.Types.SLOT_TYPE))))),
        'reserve_object':
        OptionalTunable(
            description=
            '\n            If this is enabled, the created object will be reserved for use by\n            the set Sim.\n            ',
            tunable=TunableEnumEntry(
                tunable_type=ParticipantTypeActorTargetSim,
                default=ParticipantTypeActorTargetSim.Actor))
    }

    def __init__(self, interaction, *args, sequence=(), **kwargs):
        super().__init__(interaction, sequence=sequence, *args, **kwargs)
        self._placement_failed = False
        if self.reserve_object is not None:
            reserved_sim = self.interaction.get_participant(
                self.reserve_object)
        else:
            reserved_sim = None
        self._object_helper = CreateObjectHelper(
            reserved_sim,
            self.definition,
            self,
            init=self._setup_created_object)

    @property
    def definition(self):
        return self.creation_data.get_definition()

    @property
    def placement_failed(self):
        return self._placement_failed

    def create_object(self):
        created_object = create_object(self.definition,
                                       init=self._setup_created_object,
                                       post_add=self._place_object)
        if self._placement_failed:
            created_object.destroy(
                source=self.interaction,
                cause='Failed to place object created by basic extra.')
            return
        return created_object

    def _build_outer_elements(self, sequence):
        return self._object_helper.create(sequence)

    def _do_behavior(self):
        self._place_object(self._object_helper.object)
        if self._placement_failed:
            if self.cancel_on_destroy:
                self.interaction.cancel(
                    FinishingType.FAILED_TESTS,
                    cancel_reason_msg='Cannot place object')
                return False
            return True
        if not self.transient:
            self._object_helper.claim()
        return True

    def _setup_created_object(self, created_object):
        self.creation_data.setup_created_object(self.interaction,
                                                created_object)
        for initial_state in self.initial_states:
            created_object.set_state(initial_state.state, initial_state)

    def _get_ignored_object_ids(self):
        pass

    def _place_object_no_fallback(self, created_object):
        participant = self.interaction.get_participant(
            self.location.location_target)
        if self.location.location == self.POSITION:
            offset_tuning = self.location.offset_tuning
            default_offset = sims4.math.Vector3(offset_tuning.default_offset.x,
                                                offset_tuning.default_offset.y,
                                                offset_tuning.default_offset.z)
            x_range = offset_tuning.x_randomization_range
            z_range = offset_tuning.z_randomization_range
            start_orientation = sims4.random.random_orientation()
            if x_range is not None:
                x_axis = start_orientation.transform_vector(
                    sims4.math.Vector3.X_AXIS())
                default_offset += x_axis * random.uniform(
                    x_range.lower_bound, x_range.upper_bound)
            if z_range is not None:
                z_axis = start_orientation.transform_vector(
                    sims4.math.Vector3.Z_AXIS())
                default_offset += z_axis * random.uniform(
                    z_range.lower_bound, z_range.upper_bound)
            offset = sims4.math.Transform(default_offset,
                                          sims4.math.Quaternion.IDENTITY())
            start_position = sims4.math.Transform.concatenate(
                offset, participant.transform).translation
            routing_surface = participant.routing_surface
            active_lot = services.active_lot()
            search_flags = placement.FGLSearchFlagsDefault
            if self.location.allow_off_lot_placement and not active_lot.is_position_on_lot(
                    start_position):
                created_object.location = sims4.math.Location(
                    sims4.math.Transform(start_position, start_orientation),
                    routing_surface)
                polygon = placement.get_accurate_placement_footprint_polygon(
                    created_object.position, created_object.orientation,
                    created_object.scale, created_object.get_footprint())
                context = placement.FindGoodLocationContext(
                    starting_position=start_position,
                    starting_orientation=start_orientation,
                    starting_routing_surface=routing_surface,
                    ignored_object_ids=(created_object.id, ),
                    search_flags=search_flags,
                    object_polygons=(polygon, ))
            else:
                if not self.location.ignore_bb_footprints:
                    search_flags |= placement.FGLSearchFlag.SHOULD_TEST_BUILDBUY | placement.FGLSearchFlag.STAY_IN_CURRENT_BLOCK
                    if not active_lot.is_position_on_lot(start_position):
                        start_position = active_lot.get_default_position(
                            position=start_position)
                context = placement.FindGoodLocationContext(
                    starting_position=start_position,
                    starting_orientation=start_orientation,
                    starting_routing_surface=routing_surface,
                    object_id=created_object.id,
                    ignored_object_ids=self._get_ignored_object_ids(),
                    search_flags=search_flags,
                    object_footprints=(self.definition.get_footprint(0), ))
            (translation, orientation) = placement.find_good_location(context)
            if translation is not None:
                created_object.move_to(routing_surface=routing_surface,
                                       translation=translation,
                                       orientation=orientation)
                return True
        elif self.location.location == self.SLOT:
            parent_slot = self.location.parent_slot
            if participant.slot_object(parent_slot=parent_slot,
                                       slotting_object=created_object):
                return True
        return False

    def _place_object(self, created_object):
        if self._place_object_no_fallback(created_object):
            return True
        if not self.destroy_on_placement_failure:
            participant = self.interaction.get_participant(
                self.location.location_target)
            if participant.inventory_component is not None and created_object.inventoryitem_component is not None:
                if participant.is_sim:
                    participant_household_id = participant.household.id
                else:
                    participant_household_id = participant.get_household_owner_id(
                    )
                created_object.set_household_owner_id(participant_household_id)
                participant.inventory_component.system_add_object(
                    created_object, participant)
                return True
            sim = self.interaction.sim
            if sim is not None:
                if not sim.household.is_npc_household:
                    try:
                        created_object.set_household_owner_id(sim.household.id)
                        build_buy.move_object_to_household_inventory(
                            created_object)
                        return True
                    except KeyError:
                        pass
        self._placement_failed = True
        return False
예제 #12
0
class Street(HasTunableReference, metaclass=HashedTunedInstanceMetaclass, manager=services.street_manager()):
    WORLD_DESCRIPTION_TUNING_MAP = TunableMapping(description='\n        A mapping between Catalog world description and street tuning instance.\n        This way we can find out what world description the current zone\n        belongs to at runtime then grab its street tuning instance.\n        ', key_type=TunableWorldDescription(description='\n            Catalog-side World Description.\n            ', pack_safe=True), value_type=TunableReference(description="\n            Street Tuning instance. This is retrieved at runtime based on what\n            the active zone's world description is.\n            ", pack_safe=True, manager=services.street_manager()), key_name='WorldDescription', value_name='Street')
    INSTANCE_TUNABLES = {'open_street_director': TunablePackSafeReference(description='\n            The Scheduling Open Street Director to use for this world file.\n            This open street director will be able to load object layers and\n            spin up situations.\n            ', manager=services.get_instance_manager(sims4.resources.Types.OPEN_STREET_DIRECTOR)), 'travel_lot': OptionalTunable(description='\n            If enabled then this street will have a specific lot that it will\n            want to travel to when we travel to this "street."\n            ', tunable=TunableLotDescription(description='\n                The specific lot that we will travel to when asked to travel to\n                this street.\n                ')), 'townie_demographics': TunableTuple(description='\n            Townie population demographics for the street.\n            ', target_population=OptionalTunable(description='\n                If enabled, Sims created for other purposes will passively be\n                assigned to live on this street, gaining the filter features.\n                Sims are assigned out in round robin fashion up until all\n                streets have reached their target, after which those streets\n                will be assigned Sims in round robin fashion past their target.\n                \n                If disabled, this street will not passively be assigned townies\n                unless the Lives On Street filter explicitly requires the\n                Sim to be on the street.\n                ', tunable=TunableRange(description="\n                    The ideal number of townies that live on the street.\n                    \n                    0 is valid if you don't want Sims to live on this street\n                    while other streets haven't met their target population.\n                    ", tunable_type=int, default=1, minimum=0)), filter_features=TunableList(description='\n                Sims created as townies living on this street, they will gain\n                one set of features in this list. Features are applied as\n                Sim creation tags and additional filter terms to use.\n                ', tunable=TunableTuple(description='\n                    ', filter_terms=TunableList(description='\n                        Filter terms to inject into the filter.\n                        ', tunable=FilterTermVariant(conform_optional=True)), sim_creator_tags=TunableReference(description="\n                        Tags to inject into the filter's Sim template.\n                        ", manager=services.get_instance_manager(sims4.resources.Types.TAG_SET), allow_none=True, class_restrictions=('TunableTagSet',)), sim_name_type=TunableEnumEntry(description='\n                        What type of name the sim should have.\n                        ', tunable_type=SimNameType, default=SimNameType.DEFAULT), weight=TunableRange(description='\n                        Weighted chance.\n                        ', tunable_type=float, default=1, minimum=0)))), 'valid_conditional_layers': TunableSet(description='\n            A list of all of the conditional_layers on this Street.\n            ', tunable=TunableReference(description='\n                A reference to a conditional layer that exists on this Street.\n                ', manager=services.get_instance_manager(sims4.resources.Types.CONDITIONAL_LAYER), pack_safe=True)), 'tested_conditional_layers': TunableList(description='\n            The list of conditional layers that will load in to the the street\n            if its tests pass.\n            \n            NOTE: Only a subset of tests are registered to listen for updates.\n            Check with your GPE if you expect to have the conditional layers update\n            with test events. Otherwise, they will only be tested on zone-load.\n            ', tunable=TunableTuple(description='\n                A list of all of the conditional_layers to load into this Street.\n                ', conditional_layer=TunableReference(description='\n                    A reference to a conditional layer that exists on this Street.\n                    ', manager=services.get_instance_manager(sims4.resources.Types.CONDITIONAL_LAYER), pack_safe=True), tests=TunableGlobalTestList(description='\n                    The tests that must pass in order for this conditional layer\n                    to be loaded in. If tests are empty, the conditional layer\n                    is considered valid.\n                    '), process_after_event_handled=Tunable(description='\n                     When True, conditional layer requests triggered by the test\n                     events will be ordered to prioritize client-layer requests\n                     first, then gameplay-layers.\n                     ', tunable_type=bool, default=False), test_on_managed_world_edit_mode_load=Tunable(description='\n                    By default, conditional layers are not tested and started when entering\n                    from Managed World Edit Mode.  Those layers which are enabled via this\n                    option will be tested and started even in Managed World Edit Mode.\n                    Generally these should be Client Only layers, but no such restriction is\n                    enforced.\n                    ', tunable_type=bool, default=False))), 'beaches': TunableList(description='\n            List of locations to place beaches.\n            ', tunable=TunableTuple(description='\n                Beach creation data.\n                ', position=TunableVector3(description='\n                    The position to create the beach at.\n                    ', default=TunableVector3.DEFAULT_ZERO), forward=TunableVector2(description='\n                    The forward vector of the beach object.\n                    ', default=Vector2.Y_AXIS())), unique_entries=True), 'civic_policy': StreetProvider.TunableFactory(description='\n            Tuning to control the civic policy voting and enactment process for\n            a street.\n            '), 'initial_street_eco_footprint_override': OptionalTunable(description='\n            If enabled, overrides the initial value of the street eco footprint\n            statistic.\n            ', tunable=Tunable(description='\n                The initial value of the street eco footprint statistic.\n                ', tunable_type=float, default=0))}
    ZONE_IDS_BY_STREET = None
    street_to_lot_id_to_zone_ids = {}

    @classmethod
    def _cls_repr(cls):
        return "Street: <class '{}.{}'>".format(cls.__module__, cls.__name__)

    @classmethod
    def get_lot_to_travel_to(cls):
        if cls is services.current_street():
            return
        import world.lot
        return world.lot.get_lot_id_from_instance_id(cls.travel_lot)

    @classmethod
    def has_conditional_layer(cls, conditional_layer):
        return conditional_layer in cls.valid_conditional_layers

    @classmethod
    def clear_caches(cls):
        Street.street_to_lot_id_to_zone_ids.clear()
        Street.ZONE_IDS_BY_STREET = None
class SocialThrowMixerInteraction(SocialMixerInteraction):
    INSTANCE_TUNABLES = {'throw_impact_data': OptionalTunable(description='\n            If enabled, the object thrown will trigger a reaction at a\n            specific timing of the throw on the target.\n            If disabled, throw will happen but target will not react.\n            ', tunable=TunableTuple(description='\n                Specific tuning defining the target reaction.\n                ', event_id=TunableRange(description='\n                    Id number of the event the ballistic controller will throw\n                    to trigger the reaction.\n                    ', tunable_type=int, default=123, minimum=1, maximum=1000), destroy_event_id=TunableRange(description='\n                    Id number of the event the for the thrown object to be\n                    destroyed.\n                    By default, ballistic controller has an event at 668 at the\n                    end of the throw, unless animation requests it, that\n                    should be used. \n                    ', tunable_type=int, default=668, minimum=1, maximum=1000), event_timing_offset=TunableRange(description='\n                    Offset in seconds when the event_id should trigger with\n                    reference to the ending of the throw.\n                    This means that a value of 0.2 will trigger this event 0.2\n                    seconds before the object thrown hits its target. \n                    ', tunable_type=float, default=0.2, minimum=0.0, maximum=10.0), impact_effect=OptionalTunable(description='\n                    When enabled, effect will play on the thrown object \n                    position at the time given by event_timing_offset.\n                    Effect will not be parented, this is to trigger effects\n                    like a snowball explosion etc.\n                    ', tunable=PlayEffect.TunableFactory(description='\n                        Effect to play.\n                        '), enabled_name='play_effect', disabled_name='no_effect'), asm_state_name=Tunable(description='\n                    State name that will be called on the ASM of the mixer \n                    when the impact happens.\n                    ', tunable_type=str, default=None), impact_offset=TunableMapping(description='\n                    Offsets for the impact position by age of the target Sim.\n                    ', key_type=TunableEnumEntry(description='\n                        Age for which this offset should be applied.\n                        ', tunable_type=Age, default=Age.YOUNGADULT), value_type=TunableVector3(description='\n                        Offset from the impact position where the ballistic\n                        controller should aim the object.\n                        For example, for an impact on a Sims feet, offset should \n                        be (0,0,0), but if we want a miss or another part we \n                        may want to push it higher.\n                        ', default=sims4.math.Vector3.ZERO())))), 'social_group_scoring': OptionalTunable(description='\n            If enabled, the thrower and target of this group will have an \n            additional score to be the next person that generates the social\n            adjustment.\n            The higher the value, the more likely they will move after this\n            mixer has ran.\n            ', tunable=TunableTuple(description='\n                Thrower and target tuning to affect the adjustment scoring.\n                ', thrower_score=TunableRange(description='\n                    Score to be added on the weight the thrower of this\n                    mixer to be more likely to move on the next social\n                    adjustment. \n                    ', tunable_type=int, default=0, minimum=0, maximum=10), receiver_score=TunableRange(description='\n                    Score to be added on the weight the receiver of this\n                    mixer to be more likely to move on the next social\n                    adjustment. \n                    ', tunable_type=int, default=0, minimum=0, maximum=10)))}

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._throw_asm = None
        self._finished = False

    @classmethod
    def get_mixer_key_override(cls, target):
        return target.id

    def on_throw_impact(self, event_data):
        if self._throw_asm is None or self._finished:
            return
        if not self._finished and self.throw_impact_data.asm_state_name is not None:
            impact_arb = animation.arb.Arb(additional_blockers={self.sim})
            self._throw_asm.request(self.throw_impact_data.asm_state_name, impact_arb)
            distribute_arb_element(impact_arb, master=self.target)
        self._finished = True

    def on_throw_destroy(self, event_data):
        prop_id = event_data.event_data.get('event_actor_id')
        if prop_id is not None:
            if self.throw_impact_data.impact_effect is not None:
                thrown_obj = services.prop_manager().get(prop_id)
                if thrown_obj is not None:
                    fade_out_vfx = self.throw_impact_data.impact_effect(thrown_obj)
                    fade_out_vfx.start_one_shot()
            self.animation_context.destroy_prop_from_actor_id(prop_id)

    def build_basic_content(self, *args, **kwargs):
        if self.throw_impact_data is not None:
            self.store_event_handler(self.on_throw_impact, handler_id=self.throw_impact_data.event_id)
            self.store_event_handler(self.on_throw_destroy, handler_id=self.throw_impact_data.destroy_event_id)
        if self.social_group_scoring is not None:
            self.super_interaction.social_group.update_adjustment_scoring(self.sim.id, self.social_group_scoring.thrower_score)
            self.super_interaction.social_group.update_adjustment_scoring(self.target.id, self.social_group_scoring.receiver_score)
        return super().build_basic_content(*args, **kwargs)

    def _get_facing_angle(self, actor, target):
        f1 = actor.forward
        f2 = target.position - actor.position
        angle = sims4.math.vector3_angle(f1) - sims4.math.vector3_angle(f2)
        if angle > sims4.math.PI:
            angle = angle - sims4.math.TWO_PI
        elif angle < -sims4.math.PI:
            angle = sims4.math.TWO_PI + angle
        return sims4.math.rad_to_deg(angle)

    def get_asm(self, *args, **kwargs):
        self._throw_asm = super().get_asm(*args, **kwargs)
        if self._finished:
            return self._throw_asm
        position_offset = self.throw_impact_data.impact_offset.get(self.target.sim_info.age)
        if position_offset is None:
            logger.error('Age {} not supported in throw tuning for mixer {}', self.target.sim_info.age, self)
            return self._throw_asm
        self._throw_asm.set_parameter(animation_constants.ASM_THROW_ANGLE, self._get_facing_angle(self.sim, self.target))
        self._throw_asm.set_parameter(animation_constants.ASM_HIT_ANGLE, -self._get_facing_angle(self.target, self.sim))
        target_position = self.target.position + self.target.orientation.transform_vector(position_offset)
        self._throw_asm.set_parameter(animation_constants.ASM_TARGET_TRANSLATION, target_position)
        self._throw_asm.set_parameter(animation_constants.ASM_TARGET_ORIENTATION, self.target.orientation)
        if self.throw_impact_data is not None:
            self._throw_asm.set_parameter(animation_constants.ASM_SCRIPT_EVENT_ID, self.throw_impact_data.event_id)
            self._throw_asm.set_parameter(animation_constants.ASM_SCRIPT_EVENT_PLACEMENT, self.throw_impact_data.event_timing_offset)
        self._throw_asm.set_parameter(animation_constants.ASM_LANDING_SURFACE, 'None')
        return self._throw_asm
예제 #14
0
class _PlacementStrategyLocation(_PlacementStrategy):
    POSITION_INCREMENT = 0.5
    FACTORY_TUNABLES = {
        'initial_location':
        TunableVariant(
            description=
            '\n            The FGL search initial position is determined by this. If more than\n            one initial position is available, all are considered up to the\n            specified upper bound.\n            ',
            from_participant=_ObjectsFromParticipant.TunableFactory(),
            from_tags=_ObjectsFromTags.TunableFactory(),
            from_lot=_LocationFromLot.TunableFactory(),
            front_door_object=_FrontDoorObject.TunableFactory(),
            default='from_participant'),
        'initial_location_offset':
        TunableTuple(
            default_offset=TunableVector3(
                description=
                "\n                The default Vector3 offset from the location target's\n                position.\n                ",
                default=sims4.math.Vector3.ZERO()),
            x_randomization_range=OptionalTunable(tunable=TunableInterval(
                description=
                '\n                    A random number in this range will be applied to the\n                    default offset along the x axis.\n                    ',
                tunable_type=float,
                default_lower=0,
                default_upper=0)),
            z_randomization_range=OptionalTunable(tunable=TunableInterval(
                description=
                '\n                    A random number in this range will be applied to the\n                    default offset along the z axis.\n                    ',
                tunable_type=float,
                default_lower=0,
                default_upper=0))),
        'facing':
        OptionalTunable(
            description=
            '\n            If enabled, the final location will ensure that the placed object\n            faces a specific location.\n            ',
            tunable=TunableTuple(
                target=OptionalTunable(
                    description=
                    '\n                    The location to face.\n                    ',
                    tunable=TunableEnumEntry(
                        description=
                        '\n                        Specify a participant that needs to be faced.\n                        ',
                        tunable_type=ParticipantType,
                        default=ParticipantType.Actor),
                    disabled_name='face_initial_location'),
                angle=TunableAngle(
                    description=
                    '\n                    The angle that facing will trying to keep inside while test\n                    FGL. The larger the number is, the more offset the facing\n                    could be, but the chance will be higher to succeed in FGL.\n                    ',
                    default=0.5 * sims4.math.PI,
                    minimum=0,
                    maximum=sims4.math.PI))),
        'perform_fgl_check':
        Tunable(
            description=
            '\n            If checked (default), FGL will be used to find an unblocked location \n            for the object to be placed after it is created.\n            \n            If unchecked, FGL will not be performed, and the object will be \n            placed at the location + orientation specified, even if it overlaps\n            other footprints.\n            \n            Ideally this should be used in conjunction with another system that\n            assures objects will not overlap.  This is useful for placing an\n            object on top of a jig, but probably should not be used for placing\n            a build buy object that intersects with another build buy object.\n            \n            It should also be noted that object collisions will not play nice\n            with save/load, and objects that overlap may be deleted when \n            loading a save.  This should be used with objects that are temporary.\n            ',
            tunable_type=bool,
            default=True),
        'ignore_bb_footprints':
        Tunable(
            description=
            '\n            Ignores the build buy object footprints when trying to find a\n            position for creating this object. This will allow objects to appear\n            on top of each other.\n            \n            e.g. Trash cans when tipped over want to place the trash right under\n            them so it looks like the pile came out from the object while it was\n            tipped.\n            ',
            tunable_type=bool,
            default=True),
        'allow_off_lot_placement':
        Tunable(
            description=
            '\n            If checked, objects will be allowed to be placed off-lot. If\n            unchecked, we will always attempt to place created objects on the\n            active lot.\n            ',
            tunable_type=bool,
            default=False),
        'stay_outside_placement':
        Tunable(
            description=
            '\n            If checked, objects will run their placement search only for\n            positions that are considered outside.\n            ',
            tunable_type=bool,
            default=False),
        'in_same_room':
        Tunable(
            description=
            '\n            If checked, objects will be placed in the same block/room of the\n            initial location. If there is not enough space to put down the\n            object in the same block, the placement will fail.\n            ',
            tunable_type=bool,
            default=False),
        'terrain_tags':
        OptionalTunable(
            description=
            '\n            If enabled, a set of allowed terrain tags. At least one tag must\n            match the terrain under each vertex of the footprint of the supplied\n            object.\n            ',
            tunable=TunableEnumSet(enum_type=TerrainTag,
                                   enum_default=TerrainTag.INVALID)),
        'stay_in_connected_connectivity_group':
        Tunable(
            description=
            '\n            If unchecked then the object will be allowed to be placed in\n            a connectivity group that is currently disconnected from\n            the starting location.\n            \n            If checked then the placement will fail if there is not a\n            position inside a connected connectivity group from the\n            starting position that can be used for placement.\n            ',
            tunable_type=bool,
            default=True),
        'surface_type_override':
        OptionalTunable(
            description=
            '\n            If enabled, we will override the routing surface type of the\n            location to whatever is tuned here. Otherwise, we use the routing\n            surface of the initial location.\n            \n            Example: The initial location may be in a pool but you want the\n            created object to be placed on the ground.\n            ',
            tunable=TunableEnumEntry(
                description=
                '\n                The routing surface we want to force the object to be placed on.\n                ',
                tunable_type=SurfaceType,
                default=SurfaceType.SURFACETYPE_WORLD)),
        'min_water_depth':
        OptionalTunable(
            description=
            '\n            (float) If provided, each vertex of the test polygon along with its centroid will\n            be tested to determine whether the ocean water at the test location is at least this deep.\n            Values <= 0 indicate placement on land is valid.\n            ',
            tunable=Tunable(
                description=
                '\n                Value of the min water depth allowed.\n                ',
                tunable_type=float,
                default=-1.0)),
        'max_water_depth':
        OptionalTunable(
            description=
            '\n            (float) If provided, each vertex of the test polygon along with its centroid will\n            be tested to determine whether the ocean water at the test location is at most this deep.\n            Values <= 0 indicate placement in ocean is invalid.\n            ',
            tunable=Tunable(
                description=
                '\n                Value of the max water depth allowed.\n                ',
                tunable_type=float,
                default=1000.0)),
        'ignore_sim_positions':
        Tunable(
            description=
            '\n            If checked, the FGL search will ignore current Sim positions and \n            any positions Sims are intending to go.  If unchecked, these Sim\n            positions will be taken into account when placing the object.\n            ',
            tunable_type=bool,
            default=True),
        'raytest':
        OptionalTunable(
            description=
            '\n            If enabled then we will run a raytest to make sure that the object remains within LOS.\n            ',
            tunable=TunableTuple(
                description=
                '\n                Data related to running a raytest to a position.\n                ',
                starting_position=OptionalTunable(
                    description=
                    '\n                    Use either the previously calculated starting point or perform a raytest in regards to a specific\n                    participant.\n                    ',
                    tunable=TunableEnumEntry(
                        description=
                        "\n                        A partcipant's location that we will perform the raytest to.\n                        ",
                        tunable_type=ParticipantType,
                        default=ParticipantType.Actor),
                    enabled_name='use_participants_location',
                    disabled_name='use_initial_location'),
                raytest_radius=Tunable(
                    description=
                    '\n                    The radius of the ray.  0.1 is acceptable for a line of sight check.\n                    ',
                    tunable_type=float,
                    default=0.1),
                raytest_start_height_offset=Tunable(
                    description=
                    '\n                    The height offset of the start height.  1.5 is the height of a Sim.\n                    ',
                    tunable_type=float,
                    default=1.5),
                raytest_end_height_offset=Tunable(
                    description=
                    '\n                    The height offset of end point of the ray.  1.5 is the height of a Sim.\n                    ',
                    tunable_type=float,
                    default=1.5)))
    }

    def _get_reference_objects_gen(self, obj, resolver, **kwargs):
        yield from self.initial_location.get_objects_gen(resolver)

    def _try_place_object_internal(self,
                                   obj,
                                   target_obj,
                                   resolver,
                                   ignored_object_ids=None,
                                   **kwargs):
        offset_tuning = self.initial_location_offset
        default_offset = sims4.math.Vector3(offset_tuning.default_offset.x,
                                            offset_tuning.default_offset.y,
                                            offset_tuning.default_offset.z)
        x_range = offset_tuning.x_randomization_range
        z_range = offset_tuning.z_randomization_range
        start_orientation = sims4.random.random_orientation()
        if x_range is not None:
            x_axis = start_orientation.transform_vector(
                sims4.math.Vector3.X_AXIS())
            default_offset += x_axis * random.uniform(x_range.lower_bound,
                                                      x_range.upper_bound)
        if z_range is not None:
            z_axis = start_orientation.transform_vector(
                sims4.math.Vector3.Z_AXIS())
            default_offset += z_axis * random.uniform(z_range.lower_bound,
                                                      z_range.upper_bound)
        offset = sims4.math.Transform(default_offset,
                                      sims4.math.Quaternion.IDENTITY())
        start_position = sims4.math.Transform.concatenate(
            offset, target_obj.transform).translation
        routing_surface = target_obj.routing_surface
        active_lot = services.active_lot()
        search_flags = FGLSearchFlag.CALCULATE_RESULT_TERRAIN_HEIGHTS | FGLSearchFlag.DONE_ON_MAX_RESULTS
        if self.surface_type_override is not None:
            routing_surface = SurfaceIdentifier(routing_surface.primary_id,
                                                routing_surface.secondary_id,
                                                self.surface_type_override)
        else:
            search_flags |= FGLSearchFlag.SHOULD_TEST_ROUTING
        if self.ignore_sim_positions:
            search_flags |= FGLSearchFlag.ALLOW_GOALS_IN_SIM_POSITIONS | FGLSearchFlag.ALLOW_GOALS_IN_SIM_INTENDED_POSITIONS
        if self.in_same_room:
            search_flags |= FGLSearchFlag.STAY_IN_CURRENT_BLOCK
        if self.stay_in_connected_connectivity_group:
            search_flags |= FGLSearchFlag.STAY_IN_CONNECTED_CONNECTIVITY_GROUP
        if self.stay_outside_placement:
            search_flags |= FGLSearchFlag.STAY_OUTSIDE
        raytest_kwargs = {}
        if self.raytest:
            search_flags |= FGLSearchFlag.SHOULD_RAYTEST
            raytest_kwargs.update({
                'raytest_radius':
                self.raytest.raytest_radius,
                'raytest_start_offset':
                self.raytest.raytest_start_height_offset,
                'raytest_end_offset':
                self.raytest.raytest_end_height_offset
            })
            if self.raytest.starting_position is not None:
                raytest_target = resolver.get_participant(
                    self.raytest.starting_position)
                if raytest_target is not None:
                    if raytest_target.is_sim:
                        raytest_target = raytest_target.get_sim_instance(
                            allow_hidden_flags=ALL_HIDDEN_REASONS)
                    raytest_kwargs[
                        'raytest_start_point_override'] = raytest_target.transform.translation
        restrictions = None
        if self.facing is not None:
            if self.facing.target is None:
                facing_target = target_obj
            else:
                facing_target = resolver.get_participant(self.facing.target)
            if facing_target is not None:
                restriction = sims4.geometry.RelativeFacingRange(
                    facing_target.position, self.facing.angle)
                restrictions = (restriction, )
        terrain_tags = list(self.terrain_tags) if self.terrain_tags else []
        if self.allow_off_lot_placement and not active_lot.is_position_on_lot(
                start_position):
            obj.location = sims4.math.Location(
                sims4.math.Transform(start_position, start_orientation),
                routing_surface)
            starting_location = create_starting_location(
                position=start_position,
                orientation=start_orientation,
                routing_surface=routing_surface)
            context = create_fgl_context_for_object_off_lot(
                starting_location,
                obj,
                terrain_tags=terrain_tags,
                search_flags=search_flags,
                ignored_object_ids=(obj.id, ),
                restrictions=restrictions,
                min_water_depth=self.min_water_depth,
                max_water_depth=self.max_water_depth,
                **raytest_kwargs)
        else:
            if not self.allow_off_lot_placement and not active_lot.is_position_on_lot(
                    start_position):
                return False
            if not self.ignore_bb_footprints:
                if routing_surface.type != SurfaceType.SURFACETYPE_WORLD:
                    search_flags |= FGLSearchFlag.SHOULD_TEST_BUILDBUY
                else:
                    search_flags |= FGLSearchFlag.SHOULD_TEST_BUILDBUY | FGLSearchFlag.STAY_IN_CURRENT_BLOCK
                if not active_lot.is_position_on_lot(start_position):
                    start_position = active_lot.get_default_position(
                        position=start_position)
                else:
                    position_inside_plex = self._get_plex_postion_for_object_creation(
                        start_position, routing_surface.secondary_id)
                    if position_inside_plex is not None:
                        start_position = position_inside_plex
            starting_location = create_starting_location(
                position=start_position,
                orientation=start_orientation,
                routing_surface=routing_surface)
            pos_increment_info = PositionIncrementInfo(
                position_increment=self.POSITION_INCREMENT,
                from_exception=False)
            context = create_fgl_context_for_object(
                starting_location,
                obj,
                terrain_tags=terrain_tags,
                search_flags=search_flags,
                ignored_object_ids=ignored_object_ids,
                position_increment_info=pos_increment_info,
                restrictions=restrictions,
                min_water_depth=self.min_water_depth,
                max_water_depth=self.max_water_depth,
                **raytest_kwargs)
        if self.perform_fgl_check:
            (translation, orientation) = find_good_location(context)
            if translation is not None:
                obj.move_to(routing_surface=routing_surface,
                            translation=translation,
                            orientation=orientation)
                return True
        elif starting_location is not None:
            obj.move_to(routing_surface=routing_surface,
                        translation=starting_location.position,
                        orientation=starting_location.orientation)
            return True
        return False

    def _get_plex_postion_for_object_creation(self, start_position, level):
        plex_service = services.get_plex_service()
        is_active_zone_a_plex = plex_service.is_active_zone_a_plex()
        if not is_active_zone_a_plex:
            return
        if plex_service.get_plex_zone_at_position(start_position,
                                                  level) is not None:
            return
        front_door = services.get_door_service().get_front_door()
        if front_door is not None:
            (front_position, back_position) = front_door.get_door_positions()
            front_zone_id = plex_service.get_plex_zone_at_position(
                front_position, front_door.level)
            if front_zone_id is not None:
                return front_position
            else:
                back_zone_id = plex_service.get_plex_zone_at_position(
                    back_position, front_door.level)
                if back_zone_id is not None:
                    return back_position