class CreateCarriedObjectSuperInteraction(SuperInteraction):
    __qualname__ = 'CreateCarriedObjectSuperInteraction'
    INSTANCE_TUNABLES = {'definition': TunableReference(description='\n            The object to create.\n            ', tuning_group=GroupNames.CREATE_CARRYABLE, manager=services.definition_manager()), 'carry_track_override': OptionalTunable(description='\n            If enabled, specify which carry track the Sim must use to carry the\n            created object.\n            ', tuning_group=GroupNames.CREATE_CARRYABLE, tunable=TunableEnumEntry(description='\n                Which hand to carry the object in.\n                ', tunable_type=PostureTrack, default=PostureTrack.RIGHT)), 'initial_states': TunableList(description='\n            A list of states to apply to the finished object as soon as it is\n            created.\n            ', tuning_group=GroupNames.CREATE_CARRYABLE, tunable=TunableStateValueReference()), 'continuation': SuperInteraction.TunableReference(description='\n            An interaction to push as a continuation to the carry.\n            '), 'continuation_with_affordance_overrides': OptionalTunable(description="\n            If enabled, allows you to specify a continuation to the\n            carry based on a participant's object definition.\n            This continuation will be pushed in addition to the tunable continuation,\n            although you will rarely need to tune both at the same time.\n            ", tunable=TunableTuple(continuation=TunableContinuation(description='\n                    A tunable continuation to push based on the parameters provided.\n                    '), participant=TunableEnumEntry(description='\n                    When using the affordance_override mapping, this\n                    is the participant we will use to get the definition.\n                    ', tunable_type=ParticipantType, default=ParticipantType.PickedObject), affordance_override=TunableMapping(description="\n                    Based on the participants's object definition, you can override\n                    the affordance on the tunable continuation.\n                    ", key_type=TunableReference(description='\n                        The object definition to look for.\n                        ', manager=services.definition_manager()), value_type=SuperInteraction.TunableReference())), tuning_group=GroupNames.CREATE_CARRYABLE)}

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

    @property
    def create_target(self):
        return self.definition

    @property
    def created_target(self):
        if self._object_create_helper is not None:
            return self._object_create_helper.object

    def _get_create_continuation_affordance(self):

        def create_continuation_affordance():
            context = self.context.clone_for_continuation(self)
            aop = AffordanceObjectPair(self.continuation, self.created_target, self.continuation, None)
            return (aop, context)

        if self.continuation is not None:
            return create_continuation_affordance

    def build_basic_content(self, sequence, **kwargs):
        super_build_basic_content = super().build_basic_content

        def setup_object(obj):
            for initial_state in reversed(self.initial_states):
                obj.set_state(initial_state.state, initial_state)
            obj.set_household_owner_id(self.sim.household.id)

        self._object_create_helper = CreateObjectHelper(self.sim, self.definition, self, init=setup_object, tag='CreateCarriedObjectSuperInteraction')

        def claim_object(*_, **__):
            self._object_create_helper.claim()

        def set_carry_target(_):
            if self.carry_track_override:
                self.track = self.carry_track_override
            else:
                self.track = DEFAULT
            if self.track is None:
                return False
            self.context.carry_target = self.created_target

        def push_tunable_continuation_with_affordance_overrides(_):
            if self.continuation_with_affordance_overrides is None:
                return
            obj = self.get_participant(self.continuation_with_affordance_overrides.participant)
            if obj is not None:
                affordance_override = self.continuation_with_affordance_overrides.affordance_override.get(obj.definition)
            else:
                affordance_override = None
            interaction_parameters = {}
            if 'picked_item_ids' in self.interaction_parameters:
                interaction_parameters['picked_item_ids'] = self.interaction_parameters['picked_item_ids']
            self.push_tunable_continuation(self.continuation_with_affordance_overrides.continuation, affordance_override=affordance_override, **interaction_parameters)

        def enter_carry(timeline):
            result = yield element_utils.run_child(timeline, enter_carry_while_holding(self, self.created_target, callback=claim_object, create_si_fn=self._get_create_continuation_affordance(), track=self.track, sequence=build_critical_section(super_build_basic_content(sequence, **kwargs), flush_all_animations)))
            return result

        return (self._object_create_helper.create(set_carry_target, enter_carry, push_tunable_continuation_with_affordance_overrides), lambda _: self._object_create_helper.claimed)
Example #2
0
class CollectManyInteraction(SuperInteraction):
    INTERACTION_TARGET = 'interaction_target'
    INSTANCE_TUNABLES = {
        'aggregate_object':
        TunableVariant(
            description=
            '\n            The type of object to use as the aggregate object.  If a definition\n            is specified, the aggregate object will be created using that\n            definition.  If "interaction_target" is specified, the aggregate object\n            will be created using the definition of the interaction target.\n            ',
            definitions=TunableList(
                description=
                '\n                A list of object definitions. One of them will be chosen \n                randomly and created as part of this interaction to represent \n                the many collected objects the participant has picked up.\n                ',
                tunable=TunableReference(
                    manager=services.definition_manager()),
                unique_entries=True),
            locked_args={
                'interaction_target': INTERACTION_TARGET,
                'no_aggregate_object': None
            },
            default='no_aggregate_object'),
        'aggregate_object_owner':
        TunableEnumEntry(
            description=
            '\n            Specify the owner of the newly created aggregate object.\n            ',
            tunable_type=AggregateObjectOwnership,
            default=AggregateObjectOwnership.SAME_AS_TARGET),
        'destroy_original_object':
        Tunable(
            description=
            "\n            If checked, the original object (the target of this interaction),\n            will be destroyed and replaced with the specified aggregate object.\n            If unchecked, the aggregate object will be created in the Sim's\n            hand, but the original object will not be destroyed.\n            ",
            tunable_type=bool,
            default=True)
    }
    DIRTY_DISH_ACTOR_NAME = 'dirtydish'
    ITEMS_PARAM = 'items'
    _object_create_helper = None
    _collected_targets = WeakSet()

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

    @property
    def create_object_owner(self):
        return self.sim

    @property
    def _aggregate_object_definition(self):
        if self.aggregate_object is None:
            return
        if self.aggregate_object == self.INTERACTION_TARGET:
            return self.target.definition
        return random.choice(self.aggregate_object)

    @property
    def create_target(self):
        if self.context.carry_target is not None:
            return
        return self._aggregate_object_definition

    @property
    def created_target(self):
        return self.context.carry_target

    @classmethod
    def _test(cls, target, context, **interaction_parameters):
        if target is not None and target in cls._collected_targets:
            return TestResult(False, 'Target was already collected.')
        if cls.destroy_original_object and context.sim.posture_state.is_carrying(
                target):
            return TestResult(
                False, 'Target to destroy is being carried by this Sim.')
        return super()._test(target, context, **interaction_parameters)

    def setup_asm_default(self, asm, *args, **kwargs):
        result = super().setup_asm_default(asm, *args, **kwargs)
        if self.target is not None:
            surface_height = get_surface_height_parameter_for_object(
                self.target, sim=self.sim)
            asm.set_parameter('surfaceHeight', surface_height)
        if self._original_carry_target is not None:
            param_overrides = self._original_carry_target.get_param_overrides(
                self.DIRTY_DISH_ACTOR_NAME, only_for_keys=(self.ITEMS_PARAM, ))
            if param_overrides is not None:
                asm.update_locked_params(param_overrides)
        return result

    def build_basic_content(self, sequence=(), **kwargs):
        self.store_event_handler(self._xevt_callback,
                                 handler_id=SCRIPT_EVENT_ID_START_CARRY)
        if self._aggregate_object_definition is None or self.carry_target is not None and self._aggregate_object_definition is self.carry_target.definition:
            return super().build_basic_content(sequence, **kwargs)
        if self.carry_target is not None:
            swap_carry = True
            self._original_carry_target = self.carry_target
        else:
            swap_carry = False
        self._object_create_helper = CreateObjectHelper(
            self.sim,
            self._aggregate_object_definition.id,
            self,
            tag='Aggregate object created for a CollectManyInteraction.',
            init=self._setup_created_object)
        super_build_basic_content = super().build_basic_content

        def grab_sequence(timeline):
            nonlocal sequence
            sequence = super_build_basic_content(sequence)
            if swap_carry:
                sequence = swap_carry_while_holding(
                    self,
                    self._original_carry_target,
                    self.created_target,
                    callback=self._object_create_helper.claim,
                    sequence=sequence)
            else:
                sequence = enter_carry_while_holding(
                    self,
                    self.created_target,
                    callback=self._object_create_helper.claim,
                    create_si_fn=None,
                    sequence=sequence)
            _ = yield from element_utils.run_child(timeline, sequence)

        return self._object_create_helper.create(grab_sequence)

    def _setup_created_object(self, created_object):
        if self.aggregate_object_owner & AggregateObjectOwnership.SAME_AS_TARGET:
            if self.target is not None:
                created_object.set_household_owner_id(
                    self.target.get_household_owner_id())
        elif self.aggregate_object_owner & AggregateObjectOwnership.ACTIVE_HOUSEHOLD:
            active_household_id = services.active_household_id()
            if active_household_id is not None:
                created_object.set_household_owner_id(active_household_id)

    def _xevt_callback(self, *_, **__):
        if self.carry_target is not None:
            if self.target is not None:
                if self._object_create_helper is None:
                    for statistic in self.target.statistic_tracker:
                        self.carry_target.statistic_tracker.add_value(
                            statistic.stat_type, statistic.get_value())
                elif self._original_carry_target is not None:
                    for statistic in self._original_carry_target.statistic_tracker:
                        self.carry_target.statistic_tracker.add_value(
                            statistic.stat_type, statistic.get_value())
                elif self.aggregate_object is self.INTERACTION_TARGET:
                    self.carry_target.copy_state_values(self.target)
                else:
                    for statistic in self.target.statistic_tracker:
                        self.carry_target.statistic_tracker.set_value(
                            statistic.stat_type, statistic.get_value())
        if self.destroy_original_object and self.target is not None:
            self._collected_targets.add(self.target)
            self.target.transient = True
            self.target.remove_from_client()
        if self._original_carry_target is not None:
            self._collected_targets.add(self._original_carry_target)
            self._original_carry_target.transient = True
            self._original_carry_target.remove_from_client()

    @classproperty
    def requires_target_support(cls):
        return False
class CreateCarriedObjectSuperInteraction(SuperInteraction):
    __qualname__ = 'CreateCarriedObjectSuperInteraction'
    INSTANCE_TUNABLES = {
        'definition':
        TunableReference(
            description='\n            The object to create.\n            ',
            tuning_group=GroupNames.CREATE_CARRYABLE,
            manager=services.definition_manager()),
        'carry_track_override':
        OptionalTunable(
            description=
            '\n            If enabled, specify which carry track the Sim must use to carry the\n            created object.\n            ',
            tuning_group=GroupNames.CREATE_CARRYABLE,
            tunable=TunableEnumEntry(
                description=
                '\n                Which hand to carry the object in.\n                ',
                tunable_type=PostureTrack,
                default=PostureTrack.RIGHT)),
        'initial_states':
        TunableList(
            description=
            '\n            A list of states to apply to the finished object as soon as it is\n            created.\n            ',
            tuning_group=GroupNames.CREATE_CARRYABLE,
            tunable=TunableStateValueReference()),
        'continuation':
        SuperInteraction.TunableReference(
            description=
            '\n            An interaction to push as a continuation to the carry.\n            '
        ),
        'continuation_with_affordance_overrides':
        OptionalTunable(
            description=
            "\n            If enabled, allows you to specify a continuation to the\n            carry based on a participant's object definition.\n            This continuation will be pushed in addition to the tunable continuation,\n            although you will rarely need to tune both at the same time.\n            ",
            tunable=TunableTuple(
                continuation=TunableContinuation(
                    description=
                    '\n                    A tunable continuation to push based on the parameters provided.\n                    '
                ),
                participant=TunableEnumEntry(
                    description=
                    '\n                    When using the affordance_override mapping, this\n                    is the participant we will use to get the definition.\n                    ',
                    tunable_type=ParticipantType,
                    default=ParticipantType.PickedObject),
                affordance_override=TunableMapping(
                    description=
                    "\n                    Based on the participants's object definition, you can override\n                    the affordance on the tunable continuation.\n                    ",
                    key_type=TunableReference(
                        description=
                        '\n                        The object definition to look for.\n                        ',
                        manager=services.definition_manager()),
                    value_type=SuperInteraction.TunableReference())),
            tuning_group=GroupNames.CREATE_CARRYABLE)
    }

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

    @property
    def create_target(self):
        return self.definition

    @property
    def created_target(self):
        if self._object_create_helper is not None:
            return self._object_create_helper.object

    def _get_create_continuation_affordance(self):
        def create_continuation_affordance():
            context = self.context.clone_for_continuation(self)
            aop = AffordanceObjectPair(self.continuation, self.created_target,
                                       self.continuation, None)
            return (aop, context)

        if self.continuation is not None:
            return create_continuation_affordance

    def build_basic_content(self, sequence, **kwargs):
        super_build_basic_content = super().build_basic_content

        def setup_object(obj):
            for initial_state in reversed(self.initial_states):
                obj.set_state(initial_state.state, initial_state)
            obj.set_household_owner_id(self.sim.household.id)

        self._object_create_helper = CreateObjectHelper(
            self.sim,
            self.definition,
            self,
            init=setup_object,
            tag='CreateCarriedObjectSuperInteraction')

        def claim_object(*_, **__):
            self._object_create_helper.claim()

        def set_carry_target(_):
            if self.carry_track_override:
                self.track = self.carry_track_override
            else:
                self.track = DEFAULT
            if self.track is None:
                return False
            self.context.carry_target = self.created_target

        def push_tunable_continuation_with_affordance_overrides(_):
            if self.continuation_with_affordance_overrides is None:
                return
            obj = self.get_participant(
                self.continuation_with_affordance_overrides.participant)
            if obj is not None:
                affordance_override = self.continuation_with_affordance_overrides.affordance_override.get(
                    obj.definition)
            else:
                affordance_override = None
            interaction_parameters = {}
            if 'picked_item_ids' in self.interaction_parameters:
                interaction_parameters[
                    'picked_item_ids'] = self.interaction_parameters[
                        'picked_item_ids']
            self.push_tunable_continuation(
                self.continuation_with_affordance_overrides.continuation,
                affordance_override=affordance_override,
                **interaction_parameters)

        def enter_carry(timeline):
            result = yield element_utils.run_child(
                timeline,
                enter_carry_while_holding(
                    self,
                    self.created_target,
                    callback=claim_object,
                    create_si_fn=self._get_create_continuation_affordance(),
                    track=self.track,
                    sequence=build_critical_section(
                        super_build_basic_content(sequence, **kwargs),
                        flush_all_animations)))
            return result

        return (self._object_create_helper.create(
            set_carry_target, enter_carry,
            push_tunable_continuation_with_affordance_overrides),
                lambda _: self._object_create_helper.claimed)
Example #4
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
class CreateCarriedObjectMixin:
    INTERACTION_PARAM_KEY = 'CreateCarriedObjectRuntimeObjectDefinition'
    INSTANCE_TUNABLES = {
        'definition':
        OptionalTunable(
            description=
            "\n            The object to create; this can be set at runtime.\n            \n            If 'runtime parameter' is chosen, it will look at the parameter \n            passed in at runtime to determine which object to create.\n            The primary use of the 'runtime parameter' option is if\n            the interaction is pushed from code so consult a GPE before using it.\n            ",
            tunable=TunableTestedVariant(
                description=
                '\n                The object to create.\n                ',
                tunable_type=ObjectDefinition(pack_safe=True)),
            tuning_group=GroupNames.CREATE_CARRYABLE,
            disabled_name='runtime_parameter',
            enabled_name='tuned_definition',
            enabled_by_default=True),
        'carry_track_override':
        OptionalTunable(
            description=
            '\n            If enabled, specify which carry track the Sim must use to carry the\n            created object.\n            ',
            tuning_group=GroupNames.CREATE_CARRYABLE,
            tunable=TunableEnumEntry(
                description=
                '\n                Which hand to carry the object in.\n                ',
                tunable_type=PostureTrack,
                default=PostureTrack.RIGHT)),
        'initial_states':
        TunableList(
            description=
            '\n            A list of states to apply to the finished object as soon as it is\n            created.\n            ',
            tuning_group=GroupNames.CREATE_CARRYABLE,
            tunable=TunableStateValueReference()),
        'continuation':
        SuperInteraction.TunableReference(
            description=
            '\n            An interaction to push as a continuation to the carry.\n            ',
            allow_none=True),
        'continuation_with_affordance_overrides':
        OptionalTunable(
            description=
            "\n            If enabled, allows you to specify a continuation to the\n            carry based on a participant's object definition.\n            This continuation will be pushed in addition to the tunable continuation,\n            although you will rarely need to tune both at the same time.\n            ",
            tunable=TunableTuple(
                continuation=TunableContinuation(
                    description=
                    '\n                    A tunable continuation to push based on the parameters provided.\n                    '
                ),
                participant=TunableEnumEntry(
                    description=
                    '\n                    When using the affordance_override mapping, this\n                    is the participant we will use to get the definition.\n                    ',
                    tunable_type=ParticipantType,
                    default=ParticipantType.PickedObject),
                affordance_override=TunableMapping(
                    description=
                    "\n                    Based on the participants's object definition, you can override\n                    the affordance on the tunable continuation.\n                    ",
                    key_type=TunableReference(
                        description=
                        '\n                        The object definition to look for.\n                        ',
                        manager=services.definition_manager()),
                    value_type=SuperInteraction.TunableReference())),
            tuning_group=GroupNames.CREATE_CARRYABLE)
    }

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

    @property
    def create_target(self):
        return self._get_chosen_definition()

    def _get_chosen_definition(self):
        if self._chosen_definition is None:
            if self.definition is None:
                self._chosen_definition = self.interaction_parameters.get(
                    self.INTERACTION_PARAM_KEY, None)
            else:
                self._chosen_definition = self.definition(
                    resolver=self.get_resolver())
        return self._chosen_definition

    def _get_create_continuation_affordance(self):
        def create_continuation_affordance():
            context = self.context.clone_for_continuation(self)
            aop = AffordanceObjectPair(self.continuation, self.created_target,
                                       self.continuation, None)
            return (aop, context)

        def create_continuation_affordance_with_overrides():
            obj = self.get_participant(
                self.continuation_with_affordance_overrides.participant)
            if obj is not None:
                affordance_override = self.continuation_with_affordance_overrides.affordance_override.get(
                    obj.definition)
            else:
                affordance_override = None
            interaction_parameters = {}
            if 'picked_item_ids' in self.interaction_parameters:
                interaction_parameters[
                    'picked_item_ids'] = self.interaction_parameters[
                        'picked_item_ids']
            for continuation in reversed(
                    self.continuation_with_affordance_overrides.continuation):
                local_actors = self.get_participants(continuation.actor)
                if self.sim in local_actors:
                    aops_and_context = self.get_continuation_aop_and_context(
                        continuation,
                        self.sim,
                        affordance_override=affordance_override,
                        **interaction_parameters)
                    if aops_and_context:
                        return aops_and_context
            return (None, None)

        if self.continuation_with_affordance_overrides is not None:
            return create_continuation_affordance_with_overrides
        elif self.continuation is not None:
            return create_continuation_affordance

    def build_basic_content(self, sequence, **kwargs):
        super_build_basic_content = super().build_basic_content

        def setup_object(obj):
            for initial_state in reversed(self.initial_states):
                obj.set_state(initial_state.state, initial_state)
            obj.set_household_owner_id(self.sim.household.id)

        self._object_create_helper = CreateObjectHelper(
            self.sim,
            self._get_chosen_definition(),
            self,
            init=setup_object,
            tag='CreateCarriedObjectMixin')

        def claim_object(*_, **__):
            self._object_create_helper.claim()

        def set_carry_target(_):
            if self.carry_track_override:
                self.track = self.carry_track_override
            else:
                self.track = DEFAULT
            if self.track is None:
                return False
            self.map_create_target(self.created_target)

        def enter_carry(timeline):
            result = yield from element_utils.run_child(
                timeline,
                enter_carry_while_holding(
                    self,
                    self.created_target,
                    callback=claim_object,
                    create_si_fn=self._get_create_continuation_affordance(),
                    track=self.track,
                    sequence=build_critical_section(
                        super_build_basic_content(sequence, **kwargs),
                        flush_all_animations)))
            return result
            yield

        return (self._object_create_helper.create(set_carry_target,
                                                  enter_carry),
                lambda _: self._object_create_helper.claimed)
Example #6
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
class CollectManyInteraction(SuperInteraction):
    __qualname__ = 'CollectManyInteraction'
    INTERACTION_TARGET = 'interaction_target'
    INSTANCE_TUNABLES = {
        'aggregate_object':
        TunableVariant(
            description=
            '\n            The type of object to use as the aggregate object.  If a definition\n            is specified, the aggregate object will be created using that\n            definition.  If "interaction_target" is specified, the aggregate object\n            will be created using the definition of the interaction target.\n            ',
            definition=TunableReference(
                description=
                '\n                A reference to the type of object that will be created as part\n                of this interaction to represent the many collected objects the\n                participant has picked up.\n                ',
                manager=services.definition_manager()),
            locked_args={'interaction_target': INTERACTION_TARGET},
            default='definition'),
        'destroy_original_object':
        Tunable(
            description=
            "\n            If checked, the original object (the target of this interaction),\n            will be destroyed and replaced with the specified aggregate object.\n            If unchecked, the aggregate object will be created in the Sim's\n            hand, but the original object will not be destroyed.\n            ",
            tunable_type=bool,
            default=True)
    }
    DIRTY_DISH_ACTOR_NAME = 'dirtydish'
    ITEMS_PARAM = 'items'
    _object_create_helper = None
    _collected_targets = WeakSet()

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

    @property
    def create_object_owner(self):
        return self.sim

    @property
    def _aggregate_object_definition(self):
        if self.aggregate_object is self.INTERACTION_TARGET:
            return self.target.definition
        return self.aggregate_object

    @property
    def create_target(self):
        if self.context.carry_target is not None:
            return
        return self._aggregate_object_definition

    @property
    def created_target(self):
        return self.context.carry_target

    @classmethod
    def _test(cls, target, context, **interaction_parameters):
        if target is not None and target in cls._collected_targets:
            return TestResult(False, 'Target was already collected.')
        return super()._test(target, context, **interaction_parameters)

    def _setup_collected_object(self, obj):
        self.context.carry_target = obj

    def setup_asm_default(self, asm, *args, **kwargs):
        result = super().setup_asm_default(asm, *args, **kwargs)
        if self.target is not None:
            surface_height = get_surface_height_parameter_for_object(
                self.target)
            asm.set_parameter('surfaceHeight', surface_height)
            if self.target.parent is not None:
                asm.set_actor('surface', self.target.parent)
        if self._original_carry_target is not None:
            param_overrides = self._original_carry_target.get_param_overrides(
                self.DIRTY_DISH_ACTOR_NAME, only_for_keys=(self.ITEMS_PARAM, ))
            if param_overrides is not None:
                asm.update_locked_params(param_overrides)
        return result

    def build_basic_content(self, sequence=(), **kwargs):
        self.animation_context.register_event_handler(
            self._xevt_callback, handler_id=SCRIPT_EVENT_ID_START_CARRY)
        if self._aggregate_object_definition is None or self.carry_target is not None and self._aggregate_object_definition is self.carry_target.definition:
            if self.context.carry_target is None:
                self._setup_collected_object(self.target)
            return super().build_basic_content(sequence, **kwargs)
        if self.carry_target is not None:
            swap_carry = True
            self._original_carry_target = self.carry_target
        else:
            swap_carry = False
        self._object_create_helper = CreateObjectHelper(
            self.sim,
            self._aggregate_object_definition.id,
            self,
            post_add=self._setup_collected_object,
            tag='Aggregate object created for a CollectManyInteraction.')
        super_build_basic_content = super().build_basic_content

        def grab_sequence(timeline):
            nonlocal sequence
            sequence = super_build_basic_content(sequence)
            if swap_carry:
                sequence = swap_carry_while_holding(
                    self,
                    self._original_carry_target,
                    self.created_target,
                    callback=self._object_create_helper.claim,
                    sequence=sequence)
            else:
                sequence = enter_carry_while_holding(
                    self,
                    self.created_target,
                    callback=self._object_create_helper.claim,
                    create_si_fn=None,
                    sequence=sequence)
            result = yield element_utils.run_child(timeline, sequence)

        return self._object_create_helper.create(grab_sequence)

    def _xevt_callback(self, *_, **__):
        if self.carry_target is not None and self.target is not None:
            if self._object_create_helper is None:
                for statistic in self.target.statistic_tracker:
                    self.carry_target.statistic_tracker.add_value(
                        statistic.stat_type, statistic.get_value())
            elif self._original_carry_target is not None:
                for statistic in self._original_carry_target.statistic_tracker:
                    self.carry_target.statistic_tracker.add_value(
                        statistic.stat_type, statistic.get_value())
            elif self.aggregate_object is self.INTERACTION_TARGET:
                self.carry_target.copy_state_values(self.target)
            else:
                for statistic in self.target.statistic_tracker:
                    self.carry_target.statistic_tracker.set_value(
                        statistic.stat_type, statistic.get_value())
        if self.destroy_original_object and self.target is not None:
            self._collected_targets.add(self.target)
            self.target.transient = True
            self.target.remove_from_client()

    @classproperty
    def requires_target_support(cls):
        return False
class CollectManyInteraction(SuperInteraction):
    __qualname__ = 'CollectManyInteraction'
    INTERACTION_TARGET = 'interaction_target'
    INSTANCE_TUNABLES = {'aggregate_object': TunableVariant(description='\n            The type of object to use as the aggregate object.  If a definition\n            is specified, the aggregate object will be created using that\n            definition.  If "interaction_target" is specified, the aggregate object\n            will be created using the definition of the interaction target.\n            ', definition=TunableReference(description='\n                A reference to the type of object that will be created as part\n                of this interaction to represent the many collected objects the\n                participant has picked up.\n                ', manager=services.definition_manager()), locked_args={'interaction_target': INTERACTION_TARGET}, default='definition'), 'destroy_original_object': Tunable(description="\n            If checked, the original object (the target of this interaction),\n            will be destroyed and replaced with the specified aggregate object.\n            If unchecked, the aggregate object will be created in the Sim's\n            hand, but the original object will not be destroyed.\n            ", tunable_type=bool, default=True)}
    DIRTY_DISH_ACTOR_NAME = 'dirtydish'
    ITEMS_PARAM = 'items'
    _object_create_helper = None
    _collected_targets = WeakSet()

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

    @property
    def create_object_owner(self):
        return self.sim

    @property
    def _aggregate_object_definition(self):
        if self.aggregate_object is self.INTERACTION_TARGET:
            return self.target.definition
        return self.aggregate_object

    @property
    def create_target(self):
        if self.context.carry_target is not None:
            return
        return self._aggregate_object_definition

    @property
    def created_target(self):
        return self.context.carry_target

    @classmethod
    def _test(cls, target, context, **interaction_parameters):
        if target is not None and target in cls._collected_targets:
            return TestResult(False, 'Target was already collected.')
        return super()._test(target, context, **interaction_parameters)

    def _setup_collected_object(self, obj):
        self.context.carry_target = obj

    def setup_asm_default(self, asm, *args, **kwargs):
        result = super().setup_asm_default(asm, *args, **kwargs)
        if self.target is not None:
            surface_height = get_surface_height_parameter_for_object(self.target)
            asm.set_parameter('surfaceHeight', surface_height)
            if self.target.parent is not None:
                asm.set_actor('surface', self.target.parent)
        if self._original_carry_target is not None:
            param_overrides = self._original_carry_target.get_param_overrides(self.DIRTY_DISH_ACTOR_NAME, only_for_keys=(self.ITEMS_PARAM,))
            if param_overrides is not None:
                asm.update_locked_params(param_overrides)
        return result

    def build_basic_content(self, sequence=(), **kwargs):
        self.animation_context.register_event_handler(self._xevt_callback, handler_id=SCRIPT_EVENT_ID_START_CARRY)
        if self._aggregate_object_definition is None or self.carry_target is not None and self._aggregate_object_definition is self.carry_target.definition:
            if self.context.carry_target is None:
                self._setup_collected_object(self.target)
            return super().build_basic_content(sequence, **kwargs)
        if self.carry_target is not None:
            swap_carry = True
            self._original_carry_target = self.carry_target
        else:
            swap_carry = False
        self._object_create_helper = CreateObjectHelper(self.sim, self._aggregate_object_definition.id, self, post_add=self._setup_collected_object, tag='Aggregate object created for a CollectManyInteraction.')
        super_build_basic_content = super().build_basic_content

        def grab_sequence(timeline):
            nonlocal sequence
            sequence = super_build_basic_content(sequence)
            if swap_carry:
                sequence = swap_carry_while_holding(self, self._original_carry_target, self.created_target, callback=self._object_create_helper.claim, sequence=sequence)
            else:
                sequence = enter_carry_while_holding(self, self.created_target, callback=self._object_create_helper.claim, create_si_fn=None, sequence=sequence)
            result = yield element_utils.run_child(timeline, sequence)

        return self._object_create_helper.create(grab_sequence)

    def _xevt_callback(self, *_, **__):
        if self.carry_target is not None and self.target is not None:
            if self._object_create_helper is None:
                for statistic in self.target.statistic_tracker:
                    self.carry_target.statistic_tracker.add_value(statistic.stat_type, statistic.get_value())
            elif self._original_carry_target is not None:
                for statistic in self._original_carry_target.statistic_tracker:
                    self.carry_target.statistic_tracker.add_value(statistic.stat_type, statistic.get_value())
            elif self.aggregate_object is self.INTERACTION_TARGET:
                self.carry_target.copy_state_values(self.target)
            else:
                for statistic in self.target.statistic_tracker:
                    self.carry_target.statistic_tracker.set_value(statistic.stat_type, statistic.get_value())
        if self.destroy_original_object and self.target is not None:
            self._collected_targets.add(self.target)
            self.target.transient = True
            self.target.remove_from_client()

    @classproperty
    def requires_target_support(cls):
        return False