from __future__ import annotations
from collections import OrderedDict, namedtuple, defaultdict
from contextlib import contextmanager
from interactions.priority import Priority
from interactions.object_retrieval_liability import ObjectRetrievalLiability
from interactions.utils.change_clock_speed_liability import ChangeClockSpeedsLiability
from interactions.utils.custom_camera_liability import CustomCameraLiability
from objects.components import types
from routing.path_planner.height_clearance_helper import HeightClearance
from weakref import WeakKeyDictionary, WeakSet
import collections
import copy
import functools
import itertools
import weakref
from animation.animation_constants import InteractionAsmType
from animation.animation_overrides_liability import AnimationOverridesLiability
from animation.animation_utils import StubActor, with_event_handlers, flush_all_animations
from animation.arb_element import distribute_arb_element
from animation.locked_params import LockedParamCategory, LockedParamsContext, create_locked_params_log
from animation.posture_manifest import PostureManifest, MATCH_ANY, MATCH_NONE, PostureManifestEntry, AnimationParticipant, Hand
from animation.tunable_animation_overrides import TunableAnimationOverrides
from balloon.tunable_balloon import TunableBalloon
from buffs.tunable import RemoveBuffLiability
from careers.career_event_liabilities import CareerEventTravelLiability
from carry.carry_tuning import CarryTransitionState
from carry.carry_utils import create_two_handed_carry_constraint, hand_to_track, set_carry_track_param_if_needed, holster_carried_object, interact_with_carried_object, get_carried_objects_gen, PARAM_CONTEXT_CARRY_HAND, is_wing_proxy_object
from crafting.crafting_station_liability import CraftingStationLiability
from date_and_time import TimeSpan, create_time_span
from distributor.shared_messages import IconInfoData, EMPTY_ICON_INFO_DATA
from distributor.system import Distributor
from element_utils import build_critical_section, build_critical_section_with_finally, build_element
from event_testing.resolver import DoubleSimResolver
from event_testing.tests import TestList
from event_testing.pie_menu_color_test import PieMenuColorTestList
from interactions import ParticipantType, PipelineProgress, TargetType, ParticipantTypeSavedActor, ParticipantTypeSingleSim, ParticipantTypeSituationSims, ParticipantTypeObject
from interactions.base.basic import TunableBasicContentSet, TunableBasicExtras, AFFORDANCE_LOADED_CALLBACK_STR
from interactions.constraint_variants import TunableConstraintVariant
from interactions.constraints import ANYWHERE, Nowhere, GEOMETRY_INCOMPATIBLE_ANYWHERE
from interactions.context import InteractionContext, QueueInsertStrategy
from interactions.object_liabilities import TemporaryHiddenInventoryTransferLiability
from interactions.interaction_finisher import FinishingType, InteractionFinisher
from interactions.item_consume import InteractionItemCostVariant
from interactions.liability import SharedLiability
from interactions.rabbit_hole import HideSimLiability
from interactions.utils import sim_focus
from interactions.utils.autonomy_op_list import AutonomyAdList
from interactions.utils.content_score_mixin import ContentScoreMixin
from interactions.utils.display_name import TunableDisplayNameVariant, TunableDisplayNameWrapper
from interactions.utils.forwarding import Forwarding
from interactions.utils.interaction_elements import XevtTriggeredElement
from interactions.utils.interaction_liabilities import AUTONOMY_MODIFIER_LIABILITY, AutonomyModifierLiability, ANIMATION_CONTEXT_LIABILITY, AnimationContextLiability, STAND_SLOT_LIABILITY, RESERVATION_LIABILITY, UNCANCELABLE_LIABILITY
from interactions.utils.lighting_liability import LightingLiability
from interactions.utils.localization_tokens import LocalizationTokens
from interactions.utils.outcome import TunableOutcome
from interactions.utils.reserve import TunableReserveObject
from interactions.utils.route_goal_suppression_liability import RouteGoalSuppressionLiability
from interactions.utils.sim_focus import TunableFocusElement, with_sim_focus, SimFocus, TestedTunableFocusElement
from interactions.utils.statistic_element import ExitCondition, ConditionalInteractionAction
from interactions.utils.teleport_liability import TeleportLiability
from interactions.utils.temporary_state_change_liability import TemporaryStateChangeLiability
from interactions.utils.tunable import TimeoutLiability, TunableStatisticAdvertisements, SaveLockLiability, TunableContinuation, CriticalPriorityLiability, GameSpeedLiability, PushAffordanceOnRouteFailLiability
from interactions.utils.tunable_icon import TunableIconVariant, TunableIcon, TunableIconAllPacks
from interactions.utils.user_cancelable_chain_liability import UserCancelableChainLiability
from interactions.vehicle_liabilities import VehicleLiability
from objects import ALL_HIDDEN_REASONS
from objects.components.game.game_challenge_liability import GameChallengeLiability
from objects.slots import get_surface_height_parameter_for_object, get_placement_height_parameter_for_object
from pets.missing_pets_liability import MissingPetLiability
from postures import ALL_POSTURES, PostureTrack
from postures.posture_specs import PostureSpecVariable
from postures.proxy_posture_owner_liability import ProxyPostureOwnerLiability
from protocolbuffers import Sims_pb2
from restaurants.restaurant_liabilities import RestaurantDeliverFoodLiability
from restaurants.restaurant_tuning import get_restaurant_zone_director
from sims.daycare import DaycareLiability
from sims.funds import FundsSource, get_funds_for_source, FundsTuning
from sims.household_utilities.utility_types import Utilities, UtilityShutoffReasonPriority
from sims.outfits.outfit_change import ChangeOutfitLiability
from sims.sim_info_lod import SimInfoLODLevel
from sims.sim_info_tests import GenderPreferenceTest
from sims.template_affordance_provider.tunable_provided_template_affordance import TunableProvidedTemplateAffordance
from sims4.callback_utils import consume_exceptions, CallableList
from sims4.collections import frozendict
from sims4.localization import TunableLocalizedStringFactory, TunableLocalizedString, LocalizationHelperTuning
from sims4.math import MAX_UINT64
from sims4.sim_irq_service import yield_to_irq
from sims4.tuning.dynamic_enum import DynamicEnum
from sims4.tuning.instances import HashedTunedInstanceMetaclass
from sims4.tuning.tunable import OptionalTunable, TunableTuple, TunableSimMinute, TunableSet, Tunable, TunableList, TunableVariant, TunableEnumEntry, TunableReference, TunableRange, TunableMapping, TunableThreshold, TunablePackSafeReference, TunableEnumSet
from sims4.tuning.tunable_base import GroupNames
from sims4.utils import classproperty, flexproperty, flexmethod, constproperty
from singletons import DEFAULT, EMPTY_SET, UNSET
from situations.situation_liabilities import CreateSituationLiability, RunningSituationLiability, SituationSimParticipantProviderLiability
from socials.social_tests import SocialContextTest
from statistics.skill_loot_data import TunableSkillLootData
from tag import Tag
from teleport.teleport_type_liability import TeleportStyleLiability
from ui.ui_dialog import UiDialogOkCancel
from ui.ui_dialog_element import UiDialogElement
from uid import unique_id
from weather.weather_enums import WeatherType, GroundCoverType
from whims.whims_tracker import HideWhimsLiability
from world.terrain_enums import TerrainTag
import alarms
import animation
import animation.asm
import caches
import clock
import distributor
import element_utils
import enum
import event_testing.resolver
import event_testing.results
import event_testing.test_events as test_events
import event_testing.tests
import gsi_handlers
import interactions.base
import interactions.si_state
import interactions.utils.exit_condition_manager
import objects.components.types
import paths
import services
import sims
import sims4.log
import sims4.resources
import snippets
import telemetry_helper
import terrain
from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from typing import *
    from Localization_pb2 import LocalizedString
    from animation import AnimationContext
    from animation.animation_utils import AnimationOverrides
    from animation.asm import Asm
    from animation.posture_manifest import FrozenPostureManifest, PostureManifestBase, SlotManifest
    from elements import Element
    from event_testing.resolver import Resolver
    from event_testing.test_base import BaseTest
    from game_effect_modifier.affordance_filter_modifier import _AffordanceFilters
    from interactions.aop import AffordanceObjectPair
    from interactions.base.basic import _BasicContent
    from interactions.base.super_interaction import SuperInteraction
    from interactions.constraints import Constraint
    from interactions.context import InteractionContext, InteractionSource
    from interactions.interaction_queue import InteractionQueue
    from interactions.liability import Liability
    from interactions.pie_menu_category import PieMenuCategory
    from interactions.priority import Priority
    from interactions.utils.exit_condition_manager import ConditionGroup
    from interactions.utils.outcome import InteractionOutcome
    from interactions.utils.outcome_enums import OutcomeResult
    from native.animation.arb import ArbEventData
    from objects.helpers.create_object_helper import CreateObjectHelper
    from objects.script_object import ScriptObject
    from postures.posture import Posture, PostureTrack
    from postures.posture_state import PostureState
    from postures.transition_sequence import TransitionSequenceController
    from reservation.reservation_handler import _ReservationHandler
    from routing.formation.formation_data import RoutingFormation
    from scheduling import Timeline
    from sims.sim import Sim
    from sims.sim_info_mixin import HasSimInfoMixin
    from sims4.math import LinearCurve
    from sims4.tuning.tunable import TunableInterval
    from socials.group import SocialGroup
    from statistics.commodity import Commodity
    from statistics.mood import Mood
    from statistics.skill_loot_data import SkillEffectiveness
    from statistics.static_commodity import StaticCommodity
    from statistics.statistic import Statistic
    from statistics.statistic_conditions import Condition
    from statistics.statistic_ops import StatisticOperation
    from event_testing.results import TestResult
    from sims.sim_info import SimInfo
    from interactions.si_state import InteractionLoadData
logger = sims4.log.Logger('Interactions')
TELEMETRY_GROUP_INTERACTION = 'INTR'
TELEMETRY_HOOK_INTERACTION_END = 'IEND'
TELEMETRY_FIELD_INTERACTION_ID = 'intr'
TELEMETRY_FIELD_NEXT_INTERACTION_ID = 'nxti'
TELEMETRY_FIELD_TARGET_SIM_ID = 'tsim'
TELEMETRY_FIELD_INTERACTION_RUNNING_TIME = 'mins'
TELEMETRY_FIELD_NUMBER_OF_SIM_PARTICIPANTS = 'snum'
TELEMETRY_FIELD_SIM_AGE = 'aget'
interaction_telemetry_writer = sims4.telemetry.TelemetryWriter(TELEMETRY_GROUP_INTERACTION)
SIM_YIELD_INTERACTION_GET_PARTICIPANTS_MOD = 1000
interaction_get_particpants_call_count = 0
PARAM_SURFACE_HEIGHT = 'surfaceHeight'
__annotations__['PARAM_SURFACE_HEIGHT'] = 'str'
PARAM_PLACEMENT_HEIGHT = 'placementHeight'
__annotations__['PARAM_PLACEMENT_HEIGHT'] = 'str'
PARAM_TERRAIN_TYPE = 'terrainType'
__annotations__['PARAM_TERRAIN_TYPE'] = 'str'
CancellationOriginatorInfo = namedtuple('CancellationOriginatorInfo', ['affordance', 'target'])

class CancelablePhase(enum.Int, export=False):
    NEVER = 0
    ALWAYS = 1
    RUNNING = 2


class InteractionIntensity(DynamicEnum):
    Default = 0


class InteractionQueueVisualType(enum.Int):
    SIMPLE = 0
    PARENT = 1
    MIXER = 2
    POSTURE = 3

    @staticmethod
    def get_interaction_visual_type(visual_type:"'InteractionQueueVisualType'") -> 'int':
        if visual_type == InteractionQueueVisualType.PARENT:
            return Sims_pb2.Interaction.PARENT
        if visual_type == InteractionQueueVisualType.MIXER:
            return Sims_pb2.Interaction.MIXER
        if visual_type == InteractionQueueVisualType.POSTURE:
            return Sims_pb2.Interaction.POSTURE
        return Sims_pb2.Interaction.SIMPLE


class CancelGroupInteractionType(enum.Int):
    ALL = 0
    USER_DIRECTED_ONLY = 1


class ShowRouteFailBehavior(enum.Int):
    WHEN_VISIBLE = 0
    ALWAYS = 1
    NEVER = 2

ROUTING_POSTURE_INTERACTION_ID = MAX_UINT64 - 1
__annotations__['ROUTING_POSTURE_INTERACTION_ID'] = 'int'

class InteractionFailureOptions:
    FAILURE_REASON_TESTS = TunableList(description='\n            A List in the format of (TunableTestSet, TunableAnimOverrides).\n            When an interaction fails because of its tests, we execute these\n            tests and the first one that passes determines the AnimOverrides\n            that will be used to show failure to the player.\n            ', tunable=TunableTuple(test_set=event_testing.tests.TunableTestSet(), anim_override=TunableAnimationOverrides()))
    ROUTE_FAILURE_AFFORDANCE = TunableReference(description="\n            A Tunable Reference to the Interaction that's pushed on a Sim when\n            their tests fail and they need to display failure to the user.\n            ", manager=services.get_instance_manager(sims4.resources.Types.INTERACTION))


@unique_id('id', 1, MAX_UINT64 - 2)
class Interaction(ContentScoreMixin, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(sims4.resources.Types.INTERACTION)):
    DEBUG_NAME_FACTORY = OptionalTunable(TunableLocalizedStringFactory(description='\n                Format for displaying interaction names for interactions that\n                are debug interactions.', display_name='Debug Interaction Name Pattern'))
    SIMOLEON_DELTA_MODIFIES_AFFORDANCE_NAME = Tunable(description='\n            Enables the display of Simoleon delta information in the choices\n            menu.\n            ', tunable_type=bool, default=True)
    SIMOLEON_DELTA_MODIFIES_INTERACTION_NAME = Tunable(description='\n            Enables the display of Simoleon delta information on running\n            interactions.\n            ', tunable_type=bool, default=True)
    SIMOLEON_COST_NAME_FACTORY = OptionalTunable(TunableLocalizedStringFactory(description='\n                Format for displaying interaction names on interactions that\n                have Simoleon costs.\n                ', display_name='Simoleon Cost Interaction Name Pattern'))
    SIMOLEON_GAIN_NAME_FACTORY = OptionalTunable(TunableLocalizedStringFactory(description='\n                Format for displaying interaction names on interactions that\n                have Simoleon gains.\n                ', display_name='Simoleon Gain Interaction Name Pattern'))
    ITEM_COST_NAME_FACTORY = OptionalTunable(TunableLocalizedStringFactory(description='\n                Format for displaying item cost on the interaction name so\n                player is aware what the interaction will consume.\n                ', display_name='Item Cost Interaction Name Pattern'))
    MAX_NOWHERE_MIXERS = 20
    MAX_DYNAMIC_PIE_MENU_PRIORITY_VALUE = 9
    INSTANCE_SUBCLASSES_ONLY = True
    _is_bucks_tuned = None
    INSTANCE_TUNABLES = {'display_name': TunableLocalizedStringFactory(description='\n            The localized name of this interaction.  It takes two tokens, the\n            actor (0) and target object (1) of the interaction.\n            ', allow_none=True, tuning_group=GroupNames.CORE), 'display_name_in_queue': OptionalTunable(description='\n        If enabled, the interaction has a different display name once it has\n        been selected and is visible in the queue. If disabled, its name in the\n        queue is simply whatever appeared in the Pie Menu, barring any further\n        visual overrides.\n        ', tunable=TunableLocalizedStringFactory(description="\n            The interaction's display name once it is added to the interaction\n            queue.\n            "), tuning_group=GroupNames.CORE), 'display_tooltip': OptionalTunable(description='\n            The tooltip to show on the pie menu option if this interaction\n            passes its tests.\n            ', tunable=TunableLocalizedStringFactory(), tuning_group=GroupNames.UI), 'display_name_text_tokens': LocalizationTokens.TunableFactory(description="\n            Localization tokens to be passed into 'display_name'.\n            For example, you could use a participant or you could also pass in \n            statistic and commodity values.\n            ", tuning_group=GroupNames.UI), 'display_name_overrides': TunableDisplayNameVariant(description='\n            Set name modifiers or random names.\n            ', tuning_group=GroupNames.UI), 'display_name_wrappers': OptionalTunable(description='\n            If enabled, the first wrapper within the list to pass tests will\n            be applied to the display name.\n            ', tunable=TunableDisplayNameWrapper.TunableFactory(), tuning_group=GroupNames.UI), '_icon': TunableIconVariant(description='\n            The icon to be displayed in the interaction queue.\n            ', default_participant_type=ParticipantType.Object, tuning_group=GroupNames.UI), 'pie_menu_icon': OptionalTunable(description='\n        If enabled, there will be an icon on the pie menu.\n        ', tunable=TunableIconVariant(description='\n            The icon to display in the pie menu.\n            ', tuning_group=GroupNames.UI)), 'pie_menu_color': OptionalTunable(description='\n        If enabled, the color will be set on the pie menu using a mood as reference.\n        This is run after all other display overrides and therefore has the lowest priority.\n        ', tunable=TunableTuple(description='\n            ', mood=TunablePackSafeReference(description='\n                The mood to use to set the color.\n                ', manager=services.get_instance_manager(sims4.resources.Types.MOOD)), mood_intensity=TunableRange(description="\n                The intensity of the mood used to set the color. \n                Note: This shouldn't affect the color being set at all and defaults to 1.\n                In all practical cases, this won't need to change, unless new colors are added\n                based on mood intensity.\n                ", tunable_type=int, default=1, minimum=0, maximum=10), tests=PieMenuColorTestList(), tuning_group=GroupNames.UI)), 'pie_menu_priority': TunableRange(description="\n            Higher priority interactions will show up first on the pie menu.\n            Interactions with the same priority will be alphabetical. This will\n            not override the content_score for socials. Socials with a high\n            score will still show on the top-most page of the pie menu. It's\n            suggested that you start with lower numbers instead of\n            automatically tuning something to a 10 just to make it show up on\n            the first page. For EP14 we increased the max priority to 30, however if\n            you are not working on EP14 please stick to a max of 10. If you want to\n            go above level 10 then someone will need to re-evaluate all of the\n            interactions set to 10 and potentially re-tune those as well. Please\n            see a GPE Lead before doing this though.\n            ", tunable_type=int, default=0, minimum=0, maximum=30, tuning_group=GroupNames.UI), 'dynamic_pie_menu_priority_based_on_skill': OptionalTunable(description="\n        Higher priority interactions based on skill will show up first on the pie menu. \n        The maximum possible number will be 9. If enabled this priority will\n        replace the 'pie_menu_priority' field.\n        ", tunable=TunableTuple(description='\n            ', priority_skill=TunablePackSafeReference(description='\n                The skill to use to set the priority.\n                ', manager=services.get_instance_manager(sims4.resources.Types.STATISTIC), class_restrictions=('Skill',)), priority_modifier=TunableRange(description='\n                Modifier to balance it against other interactions in the list.\n                ', tunable_type=float, default=0.9, minimum=0)), tuning_group=GroupNames.UI), 'allow_autonomous': Tunable(description='\n            If checked, this interaction may be chosen by autonomy for Sims to\n            run autonomously.\n            ', tunable_type=bool, default=True, needs_tuning=True, tuning_group=GroupNames.AVAILABILITY), 'allow_user_directed': Tunable(description='\n            If checked, this interaction may appear in the pie menu and be\n            chosen by the player.\n            ', tunable_type=bool, default=True, tuning_group=GroupNames.AVAILABILITY), 'allow_from_world': Tunable(description='\n            If checked, this interaction may be started while the object is in\n            the world (as opposed to being in an inventory).\n            ', tunable_type=bool, default=True, tuning_group=GroupNames.AVAILABILITY), 'allow_from_sim_inventory': Tunable(description="\n            If checked, this interaction may be started while the object is in\n            a Sim's inventory.\n            ", tunable_type=bool, default=False, tuning_group=GroupNames.AVAILABILITY), 'allow_from_object_inventory': Tunable(description="\n            If checked, this interaction may be started while the object is in\n            another object's inventory.\n            ", tunable_type=bool, default=False, tuning_group=GroupNames.AVAILABILITY), '_forwarding': OptionalTunable(description='\n        If enabled, this interaction will be available by clicking on the parent\n        of this object (for example, on an oven containing a backing pan) or by\n        clicking on a Sim using this object (for example, "order drink" is\n        available both on a bar and the Sim tending the bar).\n        ', disabled_name="Don't_Forward", enabled_name='Forward', tunable=Forwarding.TunableFactory(), tuning_group=GroupNames.AVAILABILITY), 'allow_from_portrait': Tunable(description='\n            If checked, this interaction may be surfaced from the portrait icon\n            of a Sim in the Relationship panel.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.AVAILABILITY), 'allow_while_save_locked': Tunable(description='\n            If checked, this interaction is allowed to run while saving is\n            currently blocked. For example, saving is locked when a Sim is in\n            the process of dying and is waiting to be reaped by the grim\n            reaper. While this is happening we do not want you to be able to\n            travel, so all travel interactions have this tunable unchecked.\n            ', tunable_type=bool, default=True, tuning_group=GroupNames.PERSISTENCE), '_cancelable_by_user': TunableVariant(description='\n            Define the ability for the player to cancel this interaction.            \n            ', require_confirmation=UiDialogOkCancel.TunableFactory(description='\n                A dialog prompting the player for confirmation as to whether or\n                not they want to cancel this interaction. The interaction will\n                cancel only if the player responds affirmatively. \n                '), locked_args={'allow_cancelation': CancelablePhase.ALWAYS, 'prohibit_cancelation': CancelablePhase.NEVER, 'only_while_running': CancelablePhase.RUNNING}, default='allow_cancelation', tuning_group=GroupNames.UI), '_must_run': Tunable(description='\n            If checked, nothing may cancel this interaction.  Not to be used\n            lightly.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.UI), 'time_overhead': TunableSimMinute(description="\n            Amount of time, in sim minutes, that autonomy believes that this\n            interaction will take to run. This value does not represent an\n            actual value of how long the interaction should run, but rather an\n            estimation of how long it will run for autonomy calculate the\n            efficiency of the interaction. Efficiency is used to model distance\n            attenuation.  If this value is high, the sim won't care as much how\n            far away it is.\n            ", default=10, minimum=1, tuning_group=GroupNames.AUTONOMY), 'visible': Tunable(description='\n            If checked, this interaction will be visible in the UI when queued\n            or running.\n            ', tunable_type=bool, default=True, tuning_group=GroupNames.UI), 'always_show_route_failure': Tunable(description="\n        If checked, this interaction will always attempt to show route failures\n        even if it's invisible.\n        \n        Deprecated.  Please use show_route_failure tuning instead.\n        ", deprecated=True, tunable_type=bool, default=False, tuning_group=GroupNames.UI), 'show_route_failure': TunableEnumEntry(description='\n        Indicates under what conditions to show a route failure animation if a route failure occurs.\n        ', tunable_type=ShowRouteFailBehavior, default=ShowRouteFailBehavior.WHEN_VISIBLE, tuning_group=GroupNames.UI), 'simless': Tunable(description='\n            If unchecked, there must be an active Sim to run it. If checked, no\n            Sim will be available to the interaction when it runs. Debug\n            interactions are often Simless.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.AVAILABILITY), 'target_type': TunableEnumEntry(description='\n            Indicates the type of target this interaction has: a specific Sim,\n            a group, or no one.\n                                        \n            Setting this value here will determine the animation resource\n            required for interaction to run.\n                                        \n            Examples:\n             * If sim "told a joke" and you want all sims in the group to react,\n             this should be set to GROUP.\n                                        \n             * If sim poke fun at another sim, you want to set this to TARGET.\n            ', tunable_type=TargetType, default=TargetType.GROUP, tuning_group=GroupNames.ANIMATION), 'asm_actor_overrides': TunableList(description='\n            Override ASM actors with tuned participants.\n            \n            Note: This is a very seldom used override. If you think you need it\n            talk to a GPE.\n            ', tunable=TunableTuple(actor_name=Tunable(description='\n                    The name of the parameter to override in the ASM.\n                    ', tunable_type=str, default=None), actor_participant=TunableEnumEntry(description='\n                    The participant type that will be used to get the participant\n                    that will provide the override value.\n                    ', tunable_type=ParticipantType, default=ParticipantType.Object)), tuning_group=GroupNames.ANIMATION), 'set_carry_track_param': Tunable(description='\n            If enabled then if the actor or target of the interaction is carried by another sim,\n            we will set their corresponding carry track.\n            For example, we need this param to let animation play different clips based on whether\n            the infant is being carried at front or back.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.ANIMATION), 'apply_transition_dest_params': Tunable(description='\n            If enabled then apply the locked params of the destination transition spec to the interaction.\n            This is useful when the animation of the interaction is corresponding to the transition destination.\n            For example, at different routing slots of the infant playmat we wanna play different animations, even if\n            the animation itself is not related to the playmat object.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.ANIMATION), 'set_terrain_type_param': OptionalTunable(description='\n        If enabled, will get the TerrainType of the routing surface of the final constraint of the interaction and\n        pass the enum string to the ASM.\n        \n        Will test for floor tiles if enabled. Default is set to False.\n        ', disabled_name='Do_Not_Set', tunable=TunableTuple(test_floor_tiles=Tunable(description='\n                Test for floor tiles.\n                ', tunable_type=bool, default=False)), tuning_group=GroupNames.ANIMATION), 'debug': Tunable(description='\n            If checked, this interaction will only be available from the debug\n            pie menu.  The debug pie menu is not available in release builds and\n            only appears when shift-clicking to bring up the pie menu.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.AVAILABILITY), 'cheat': Tunable(description='\n            If checked, this interaction will only be available from the cheat\n            pie menu. The cheat pie menu is available in all builds when cheats\n            are enabled, and only appears when shift-clicking to bring up the\n            pie menu.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.AVAILABILITY), 'automation': Tunable(description='\n            If checked, this interaction will only be available from the\n            automation mode of the game. Note that this is ignored if the\n            cheat is marked as debug while the game is non-optimized.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.AVAILABILITY), '_static_commodities': TunableList(description='\n            The list of static commodities to which this affordance will\n            advertise.\n            ', tunable=TunableTuple(description='\n                A single chunk of static commodity scoring data.\n                ', static_commodity=TunableReference(description='\n                    The type of static commodity offered by this affordance.\n                    ', manager=services.get_instance_manager(sims4.resources.Types.STATIC_COMMODITY), pack_safe=True, reload_dependent=True), desire=Tunable(description='\n                    The autonomous desire to fulfill this static commodity.\n                    This is how much of the static commodity the Sim thinks\n                    they will get.  This is, of course, a blatant lie.\n                    ', tunable_type=float, default=1)), tuning_group=GroupNames.AUTONOMY), '_affordance_key_override_for_autonomy': OptionalTunable(description='\n        If set, this string will take the place of the affordance as the key\n        used in autonomy scoring.  This will cause autonomy to see two\n        affordances as the same for the purposes of grouping.\n        \n        For example, if you have bed_sleep_single and bed_sleep_double, you can\n        override them to both be "bed_sleep".  Autonomy will see them as the\n        same affordance and will only choose one to consider when throwing the\n        weighted random at the end. It will also apply object preference,\n        treating them as the same affordance.\n        ', tunable=Tunable(tunable_type=str, default=''), tuning_group=GroupNames.AUTONOMY), 'outcome': TunableOutcome(outcome_locked_args={'cancel_si': None}, allow_route_events=True, tuning_group=GroupNames.CORE), 'skill_loot_data': TunableSkillLootData(description='\n            Loot Data for DynamicSkillLootOp. This will only be used if in the\n            loot list of the outcome there is a dynamic loot op.\n            ', tuning_group=GroupNames.STATE), 'provided_template_affordances': OptionalTunable(description='\n        If enabled, allows the Actor Sims running this interaction to provide\n        a suite of interactions that have template data.\n        ', tunable=TunableProvidedTemplateAffordance(description='\n            Sims will provide the tuned list of affordances for the\n            specified duration.\n            '), tuning_group=GroupNames.STATE), '_false_advertisements': TunableStatisticAdvertisements(description='\n            Fake advertisements make the interaction more enticing to autonomy\n            by promising things it will not deliver.\n            The tuned statistic is the per Sim minute rate. \n            ', tuning_group=GroupNames.AUTONOMY), '_hidden_false_advertisements': TunableStatisticAdvertisements(description="\n            Fake advertisements that are hidden from the Sim.  These ads will\n            not be used when determining which interactions solve for a\n            commodity, but it will be used to calculate the final score.\n            \n            For example: You can tune the bubble bath to provide hygiene as\n            normal, but to also have a hidden ad for fun.  Sims will prefer a\n            bubble bath when they want to solve hygiene and their fun is low,\n            but they won't choose to take a bubble bath just to solve for fun.\n            ", tuning_group=GroupNames.AUTONOMY), '_constraints': TunableList(description='\n            A list of constraints that must be fulfilled in order to interact\n            with this object.\n            ', tunable=TunableTuple(constrained_participant=TunableEnumEntry(description='\n                    The participant tuned here will have this constraint \n                    applied to them.\n                    ', tunable_type=ParticipantTypeSingleSim, default=ParticipantTypeSingleSim.Actor), constraints=TunableList(description='\n                    Species-based constraints. Define different constraints\n                    depending on species.\n                    ', tunable=TunableTuple(value=TunableConstraintVariant(description='\n                            A constraint that must be fulfilled in order to interact\n                            with this object.\n                            ')), minlength=1)), tuning_group=GroupNames.CORE), '_constraints_actor': TunableEnumEntry(description='\n            The Actor used to generate _constraints relative to.\n            ', tunable_type=ParticipantType, default=ParticipantType.Object, tuning_group=GroupNames.CORE), '_require_current_posture': Tunable(description="\n            If checked, the actor's current posture will be added to the\n            constraints for this interaction. This means that the Sim will not\n            consider changing posture to satisfy this interaction. This can be\n            useful for stuff like odor reactions, which shouldn't force the Sim\n            to change posture and shouldn't waste time considering every seat\n            on the lot.\n            ", tunable_type=bool, default=False, tuning_group=GroupNames.POSTURE), '_avoid_participants_as_body_target': OptionalTunable(description="\n        Use this tuning to prevent participants in this interaction from being\n        selected as the body target of this interaction's posture constraint. \n        This may be useful for interactions where we plan to manipulate \n        participant objects in ways which would be incompatible with using them\n        as a body target. One example is a putting a chair in an inventory. We \n        can do this while sitting, but we don't want to be sitting in the chair \n        we are putting away (although we might want to sit in a different \n        chair).\n        ", tunable=TunableEnumSet(enum_type=ParticipantTypeObject, enum_default=ParticipantTypeObject.Object), tuning_group=GroupNames.POSTURE), '_ignore_incompatible_carries': Tunable(description='\n        If True, this interaction will ignore checking for and cancelling\n        incompatible carries. This is intended to be set on interactions which\n        are being used to manage an incompatible carry, such as a \n        CarryRouteSatisfyConstraintSuperInteraction or a putdown interaction.\n        \n        Ask GPE if you think you should set this flag.\n        ', tunable_type=bool, default=False, tuning_group=GroupNames.POSTURE), '_prefer_participants_as_body_target': OptionalTunable(description='\n        If enabled, prefer to use the specified participants as a body target \n        when transitioning postures. \n        ', tunable=TunableEnumSet(enum_type=ParticipantTypeObject, enum_default=ParticipantTypeObject.Object), tuning_group=GroupNames.POSTURE), '_prefer_participants_as_carrying_sim': OptionalTunable(description='\n        If enabled, prefer to use the specified participants as the carrying sim when transitioning postures. \n        ', tunable=TunableEnumEntry(tunable_type=ParticipantType, default=ParticipantType.Actor), tuning_group=GroupNames.POSTURE), '_multi_surface': Tunable(description="\n        If checked, this interaction will build all of its constraints to work\n        on multiple surfaces.\n        \n        For example, watch_tv is marked as multi_surface and there is a TV on\n        the ground near a pool. Sims will be able to watch this TV while\n        standing around outside of the pool, but also while swimming in the\n        nearby pool. If watch_tv was not multi_surface, Sims would only be able\n        to stand around outside of the pool in order to watch it.\n        \n        This will only apply to routing surfaces on the same level. A pool and\n        the floor that pool is on will both satisfy this interaction's\n        constraints, but different floors or pools on different floors will\n        not.\n        ", tunable_type=bool, default=True, tuning_group=GroupNames.POSTURE), 'fade_sim_out': Tunable(description='\n            If set to True, this interaction will fade the Sim out as they approach\n            the destination constraint for this interaction.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.SPECIAL_CASES), 'tests': event_testing.tests.TunableTestSet(tuning_group=GroupNames.CORE), 'test_globals': event_testing.tests.TunableGlobalTestSet(description='\n            A set of global tests that are always run before other tests. All\n            tests must pass in order for the interaction to run.\n            ', tuning_group=GroupNames.CORE), 'test_autonomous': event_testing.tests.TunableTestSet(description='\n            A set of tests that are only run for interactions being considered \n            by autonomy.\n            ', tuning_group=GroupNames.AUTONOMY), 'basic_reserve_object': OptionalTunable(description='\n            If enabled, control which objects this interaction reserves for use.\n            If unset, this interaction will not reserve any objects for use.\n            ', tunable=TunableReserveObject(), enabled_by_default=True, tuning_group=GroupNames.STATE), 'basic_focus': TunableVariant(description='\n            Control the focus (gaze) of the actor while running this\n            interaction.\n            ', locked_args={'do_not_change_focus': None, 'disable_focus': False}, default='do_not_change_focus', tunable_focus=TunableFocusElement(), tested_tunable_focus=TestedTunableFocusElement(), tuning_group=GroupNames.ANIMATION), 'basic_liabilities': TunableList(description="\n            Use basic_liablities to tune a list of tunable liabilities.                     \n            \n            A liability is a construct that is associated to an interaction the\n            moment it is added to the queue. This is different from\n            basic_content and basic_extras, which only affect interactions that\n            have started running.\n            \n            e.g. The 'timeout' tunable is a liability, because its behavior is\n            triggered the moment the SI is enqueued - by keeping track of how\n            long it takes for it to start running and canceling if the timeout\n            is hit.\n            ", tunable=TunableVariant(timeout=TimeoutLiability.TunableFactory(), save_lock=SaveLockLiability.TunableFactory(), teleport=TeleportLiability.TunableFactory(), lighting=LightingLiability.TunableFactory(), crafting_station=CraftingStationLiability.TunableFactory(), daycare=DaycareLiability.TunableFactory(), critical_priority=CriticalPriorityLiability.TunableFactory(), career_event_travel=CareerEventTravelLiability.TunableFactory(), game_speed=GameSpeedLiability.TunableFactory(), hide_whims=HideWhimsLiability.TunableFactory(), remove_buff=RemoveBuffLiability.TunableFactory(), push_affordance_on_route_fail=PushAffordanceOnRouteFailLiability.TunableFactory(), route_goal_suppression=RouteGoalSuppressionLiability.TunableFactory(), outfit_change=ChangeOutfitLiability.TunableFactory(), object_retrieval=ObjectRetrievalLiability.TunableFactory(), game_challenge_liability=GameChallengeLiability.TunableFactory(), restaurant_deliver_food_liability=RestaurantDeliverFoodLiability.TunableFactory(), teleport_style_liability=TeleportStyleLiability.TunableFactory(), animation_overrides=AnimationOverridesLiability.TunableFactory(), hide_sim_liability=HideSimLiability.TunableFactory(), user_cancelable_chain=UserCancelableChainLiability.TunableFactory(), create_situation=CreateSituationLiability.TunableFactory(), running_situation=RunningSituationLiability.TunableFactory(), missing_pet=MissingPetLiability.TunableFactory(), proxy_posture_owner=ProxyPostureOwnerLiability.TunableFactory(), vehicles=VehicleLiability.TunableFactory(), temporary_state_change=TemporaryStateChangeLiability.TunableFactory(), temporary_hidden_inventory_transfer=TemporaryHiddenInventoryTransferLiability.TunableFactory(), enable_custom_camera=CustomCameraLiability.TunableFactory(), change_clock_speeds_liability=ChangeClockSpeedsLiability.TunableFactory()), tuning_group=GroupNames.STATE), 'basic_extras': TunableBasicExtras(tuning_group=GroupNames.CORE), 'basic_content': TunableBasicContentSet(description='\n            Use basic_content to define the nature of this interaction. Any\n            looping animation, autonomy, statistic gain, and any other periodic\n            change is tuned in. Also, exit conditions will be specified here.\n            \n            Depending on the type of basic_content you select, some options\n            may or may not be available.\n            \n            Please see the variant elements descriptions to determine how\n            each specific option affects the behavior of this interaction.\n            ', one_shot=True, looping_animation=True, no_content=True, default='no_content', tuning_group=GroupNames.CORE), 'confirmation_dialog': OptionalTunable(tunable=TunableTuple(dialog=UiDialogElement.TunableFactory(description='\n                    Prompts the user with an Ok Cancel Dialog. This will stop the\n                    interaction from running if the user chooses the cancel option.\n                    '), continuation_on_cancel=OptionalTunable(description='\n                    If enabled, and the dialog is not accepted, then this\n                    continuation will be pushed.\n                    ', tunable=TunableContinuation(description='\n                        The tuned continuation to push when the dialog is not\n                        accepted.\n                        '))), tuning_group=GroupNames.UI), 'intensity': TunableEnumEntry(description='\n            The intensity of response animations for this interaction. When we\n            build outcomes for the interaction, we pass this field to the\n            associated ASM.\n            ', tunable_type=InteractionIntensity, default=InteractionIntensity.Default, tuning_group=GroupNames.ANIMATION), 'category': TunablePackSafeReference(description='\n            Pie menu category. Helps declare display name, priority, icon,\n            parent, mood overrides etc.\n            ', manager=services.get_instance_manager(sims4.resources.Types.PIE_MENU_CATEGORY), allow_none=True, tuning_group=GroupNames.UI), 'category_on_forwarded': OptionalTunable(description='\n            Pie menu category when this interaction is forwarded from inventory\n            object to inventory owner.\n            ', tunable=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.PIE_MENU_CATEGORY)), tuning_group=GroupNames.UI), 'carry_back_compatibility_override': OptionalTunable(description='\n            If enabled, overrides the carry back compatibility flags in animation element tuning.\n            ', tunable=TunableTuple(description='\n                Options related to Carry Back and the types of objects we are allowing\n                to be carried. These options respect the parameter for Carry Back in the\n                ASM and will not do anything if Carry Back is set to None.\n                ', infant=Tunable(description='\n                    If checked, a Sim can carry an infant if Carry Back is enabled\n                    in the ASM.\n                    ', tunable_type=bool, default=True), wings=Tunable(description='\n                    If checked, a Sim can show wings if Carry Back is enabled in the\n                    ASM.\n                    ', tunable_type=bool, default=True)), tuning_group=GroupNames.POSTURE), 'allow_defer_back_carry': Tunable(description='\n        If False, this interaction will not attempt to defer back carry. This means wings\n        may get putdown as part of cancelling carry interactions in prepare_gen.\n        ', tunable_type=bool, default=True, tuning_group=GroupNames.POSTURE), 'cancel_wings_for_autonomy': Tunable(description='\n        If True, wings will cancel for autonomy.\n        ', tunable_type=bool, default=False, tuning_group=GroupNames.POSTURE), 'posture_preferences': TunableTuple(description='\n            Options relating to posture preferences for this interaction.\n            ', prefer_surface=Tunable(description='\n                If checked, a Sim will prefer to perform this interaction at a\n                surface.\n                ', tunable_type=bool, default=False), apply_posture_costs=Tunable(description='\n                If checked, posture costs will be applied when selecting a\n                posture in which to perform this interaction.\n                \n                Note that if this is left unchecked, postures will by default have a cost of 0.\n                Be careful if modifying this for legacy interactions, because it will change \n                the way postures are calculated for it.\n                ', tunable_type=bool, default=False), prefer_clicked_parts=Tunable(description='\n                If True, this interaction will prefer to take the Sim to the\n                parts near where you clicked in world.\n                ', tunable_type=bool, default=True), prefer_specific_clicked_part=Tunable(description="\n                If True, this interaction will prefer to take the Sim to the\n                part nearest where you clicked in world. This is different than\n                'prefer clicked part', which will apply the same scoring bonus\n                to the all of the nearest parts to the pick location for each \n                valid posture. \n                ", tunable_type=bool, default=False), require_current_constraint=Tunable(description='\n                If checked, a Sim will never violate its current geometric\n                constraints in order to find a place to run the interaction.\n                ', tunable_type=bool, default=False), posture_cost_overrides=TunableMapping(description='\n                For any posture in this mapping, its cost is overriden by the\n                specified value for the purpose of transitioning for this\n                interaction.\n                \n                For example, Sit is a generally cheap posture. However, some\n                interactions (such as Nap), would want to penalize Sit in favor\n                of something more targeted (such as the two-seated version of\n                nap).\n                ', key_type=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.POSTURE), pack_safe=True), value_type=Tunable(description='\n                    The cost override for the specified posture.\n                    ', tunable_type=int, default=0)), disable_all_scoring=Tunable(description="\n                When this is turned on there will be no scoring of goals at all.\n                Essentially this will make the goals rely totally on distance.\n                This should really only be used for interactions that are only\n                run as continuations. Basically if you run an interaction that\n                gets you to the exact spot you want the Sim and then you want\n                to run something else without them moving you would want to \n                enable this. It's for a very specific use case. Use with\n                caution.\n                \n                Default scoring behavior will happen when this is turned off.\n                ", tunable_type=bool, default=False), tuning_group=GroupNames.POSTURE), 'disable_vehicles': Tunable(description='\n        If enabled, we will disable vehicles for transitions involving this\n        interaction.\n        ', tunable_type=bool, default=False, tuning_group=GroupNames.ROUTING), 'interaction_category_tags': TunableSet(description='\n            This attribute is used to tag an interaction to allow for\n            searching, testing, and categorization. An example would be using a\n            tag to selectively test certain interactions. On each of the\n            interactions you want to test together you would add the same tag,\n            then the test routine would only test interactions with that tag.\n            Interactions can have multiple tags.\n            \n            This attribute has no effect on gameplay.\n            ', tunable=TunableEnumEntry(description='\n                These tag values are used for searching, testing, and\n                categorizing interactions.\n                ', tunable_type=Tag, default=Tag.INVALID, pack_safe=True), tuning_group=GroupNames.STATE), 'utility_info': OptionalTunable(description='\n        Tuning that specifies which utilities this interaction requires\n        to be run.\n        ', tunable=TunableMapping(key_type=TunableEnumEntry(description='\n                The utility which requires to be run.\n                ', tunable_type=Utilities, default=None), value_type=TunableTuple(description='\n                Data associated with utility which requires to be run.\n                ', shutoff_tooltip_override=TunableMapping(description="\n                    Override tooltip to show when an interaction cannot be run due to \n                    utility being shutoff. Otherwise, it'll show generic tooltip.\n                    ", key_type=TunableEnumEntry(description='\n                        Utility shutoff reason which the tooltip will be overridden.\n                        ', tunable_type=UtilityShutoffReasonPriority, default=UtilityShutoffReasonPriority.NO_REASON, invalid_enums=(UtilityShutoffReasonPriority.NO_REASON,)), value_type=TunableLocalizedStringFactory(description='\n                        An override tooltip to show.\n                        ')))), tuning_group=GroupNames.AVAILABILITY), 'test_incest': Tunable(description='\n            If checked, an incest check must pass for this interaction to be\n            available. This test is only valid for interactions that involve\n            two Sims.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.AVAILABILITY), 'visual_type_override': TunableEnumEntry(description='\n            Specify visual type if you want to override how this interaction\n            will appear in the interaction queue.\n            \n            Example: sitting by default will appear as posture interaction set\n            to simple if you want to make this appear as a normal interaction.\n            ', tunable_type=InteractionQueueVisualType, default=None, tuning_group=GroupNames.UI), 'visual_type_override_data': TunableTuple(description='\n            Overrides interaction queue appearance and behavior of this\n            interaction.\n            ', icon=TunableIcon(description='\n                This field is deprecated, please use testable_icons instead.\n                ', allow_none=True, deprecated=True), testable_icons=TunableList(description='\n                A list of test sets and icons that may be shown to the player.\n                ', tunable=TunableTuple(description='\n                    A tuple of a test set and the icon that would be chosen if the test passes.\n                    ', test=event_testing.tests.TunableTestSet(description='\n                        The test to run to see if the icon should be chosen.\n                        '), icon=TunableIconAllPacks(description='\n                        If enabled and the test set passes, we will chose this icon.\n                        '))), tooltip_text=TunableLocalizedStringFactory(description='\n                The localized name of this interaction when it appear in the\n                running section of the interaction queue.\n                ', default=None, allow_none=True), group_tag=TunableEnumEntry(description='\n                This tag is used for Grouping interactions in the\n                running section of the interaction queue.\n                \n                Example:  Sim is running chat then queues up be_affectionate.\n                Once be_affectionate moves into the running of the section of\n                the queue be_affection will disappear since it is grouped\n                together with sim chat\n                ', tunable_type=Tag, default=Tag.INVALID), group_priority=TunableRange(description='\n                When interactions are grouped into one item in the queue, this\n                is the priority of which interaction will be the top item.\n                ', tunable_type=int, default=1, minimum=1), group_cancel_behavior=TunableEnumEntry(description='\n                Cancel all grouped interactions when one of the interaction is\n                canceled for the following behavior\n                ', tunable_type=CancelGroupInteractionType, default=CancelGroupInteractionType.USER_DIRECTED_ONLY), tuning_group=GroupNames.UI), 'item_cost': InteractionItemCostVariant(tuning_group=GroupNames.SPECIAL_CASES), 'missing_funds_additional_description': OptionalTunable(description='\n        If set, this text is inserted on a new line following an insufficient\n        funds tooltip (sims.funds.FundsTuning.Unaffordable Tooltips).\n        ', tunable=TunableLocalizedString(default=None, description='The string key of the text description'), tuning_group=GroupNames.SPECIAL_CASES), 'progress_bar_enabled': TunableTuple(description='\n            Set of tuning to display the progress bar when the interaction runs\n            ', bar_enabled=Tunable(description='\n                If checked, interaction will try to show a progress bar when\n                running. Progress bar functionality also depends on the\n                interaction being tuned with proper exit condition and extras\n                that will lead it to that exit condition. e.g.  An interaction\n                with an exit condition for a statistic that reaches a threshold\n                and a tunable extra that increases that statistic as the\n                interaction runs. e.g.  Interaction with tunable time of\n                execution.\n                ', tunable_type=bool, needs_tuning=True, default=True), remember_progress=Tunable(description="\n                If checked, interaction will use the current progress of the \n                statistic from the exit conditions to display where the bar\n                should start.  \n                This is used for interactions that we don't always want to \n                start the progress bar from zero but to display they had been \n                started previously.\n                ", tunable_type=bool, default=False), force_listen_statistic=OptionalTunable(TunableTuple(description="\n                If this is enabled, the progress bar will listen to a specific\n                statistic on a subject of this interaction instead of \n                looking at the interaction's exit conditions.  This means, \n                instead of sending the UI a rate of change during the \n                interaction we will send a message whenever that statistic \n                changes.\n                ", statistic=TunableReference(description='\n                    Statistic to listen to display on the progress bar.\n                    This should not be a commodity with a decay value.\n                    The reason for this is because we will send a message to \n                    the UI for every update on this commodity and decaying \n                    commodities decay every tick, so that will just overload \n                    the number of messages we send.\n                    ', manager=services.get_instance_manager(sims4.resources.Types.STATISTIC)), subject=TunableEnumEntry(description='\n                    Subject of interaction that the progress bar will listen \n                    for this statistic change.\n                    ', tunable_type=ParticipantType, default=ParticipantType.Actor), target_value=TunableThreshold(description='\n                    Target value of where the progress bar should lead to.\n                    '))), override_min_max_values=OptionalTunable(TunableTuple(description='\n                If this is enabled, we can override the minimum and maximum \n                value of a statistic.  \n                For example, the build rocketship \n                uses a statistic that goes from -100 to 100, but the build \n                interaction only works from 0 to 100.  So for this interaction\n                we want to override the min value to 0 so the progress bar \n                shows properly.  \n                ', statistic=TunableReference(description='\n                    Statistic to look for to override its min or max values \n                    when calculating the progress bar generation.\n                    ', manager=services.get_instance_manager(sims4.resources.Types.STATISTIC)), min_value=OptionalTunable(description='\n                    Override min value\n                    ', tunable=Tunable(description='\n                        Value to use as the new minimum of the specified \n                        statistic \n                        ', tunable_type=float, default=0), enabled_name='specified_min', disabled_name='use_stat_min'), max_value=OptionalTunable(description='\n                    Override max value\n                    ', tunable=Tunable(description='\n                        Value to use as the new maximum of the specified \n                        statistic \n                        ', tunable_type=float, default=0), enabled_name='specified_max', disabled_name='use_stat_max'))), blacklist_statistics=OptionalTunable(description='\n                Set statistics that should be ignored by the progress bar\n                ', tunable=TunableList(description='\n                    List of commodities the progress bar will ignore when \n                    calculating the exit condition that will cause the \n                    interaction to exit.\n                    ', tunable=TunableReference(description='\n                        Statistic to be ignored by the progress bar\n                        ', manager=services.get_instance_manager(sims4.resources.Types.STATISTIC))), enabled_name='specify_blacklist_statistics', disabled_name='consider_all'), interaction_exceptions=TunableTuple(description='\n                Possible exceptions to the normal progress bar rules.\n                For example, music interactions will listen to a tunable \n                time which is hand tuned to match the audio tracks, \n                ', is_music_interaction=Tunable(description='\n                    If checked, interaction will read the tunable track time of\n                    music tracks and use that time to display the progress bar.\n                    e.g.  Piano and violin interactions.\n                    ', tunable_type=bool, default=False)), tuning_group=GroupNames.UI), 'appropriateness_tags': TunableSet(description='\n            A set of tags that define appropriateness for this interaction.  If\n            an appropriateness or inappropriateness test is used for this\n            interaction then it will check the tuned appropriateness tags\n            against the ones that the role state has applied to the actor.\n            ', tunable=TunableEnumEntry(tunable_type=Tag, default=Tag.INVALID, pack_safe=True), tuning_group=GroupNames.AUTONOMY), 'route_start_balloon': OptionalTunable(TunableTuple(description='\n                Allows for a balloon to be played over the actor at the start  \n                of this interaction transition when run autonomously.\n                ', balloon=TunableBalloon(locked_args={'balloon_delay': 0, 'balloon_delay_random_offset': 0}), also_show_user_directed=Tunable(description='\n                    If checked, this balloon also can be shown for this\n                    interaction when it is user-directed.\n                    ', tunable_type=bool, default=False)), tuning_group=GroupNames.UI), 'allowed_to_combine': Tunable(description="\n            If checked, this interaction will be allowed to combine with other\n            interactions we deem are compatible. If unchecked, it will never be\n            allowed to combine.\n            \n            If we combine multiple interactions, we attempt to solve for all\n            their constraints at once. For example, we tell the Sim to eat and\n            they decide to sit in a chair to do so. While they're routing to\n            that chair, we queue up a go-here. Because the Sim can go to that\n            new location and eat at the same time, we derail their current\n            transition and tell them to do both at once.\n            \n            By default this is set to True, but certain interactions might have\n            deceptive or abnormal constraints that could cause them to be\n            combined in unexpected ways.\n            \n            Please consult a GPE if you think you need to tune this to False.\n            ", tunable_type=bool, default=True, tuning_group=GroupNames.AVAILABILITY), 'mood_list': TunableList(description='\n        A list of possible moods this interaction may associate with.\n        ', tunable=TunableReference(description='\n            A mood associated with this interaction.\n            ', manager=services.get_instance_manager(sims4.resources.Types.MOOD)), tuning_group=GroupNames.STATE), 'ignore_animation_context_liability': Tunable(description='\n            This interaction will discard any AnimationContextLiabilities from\n            its source (if a continuation). Use this for interactions that are\n            continuations but share no ASMs or props with their continuation\n            source.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.ANIMATION), '_report_running_time': Tunable(description='    \n            If checked, this interaction will send off telemetry data whenever\n            it ends to report its running time in Sim minutes.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.TELEMETRY), '_report_stored_sims_on_target_count': Tunable(description="\n        If checked, this interaction will send off telemetry data whenever it ends to report the number of stored sims\n        on the interaction's target.\n        This is needed if the interactions are separate, but connected. IE puzzle crafting.\n        ", tunable_type=bool, default=False, tuning_group=GroupNames.TELEMETRY), 'counts_as_inside': Tunable(description='\n        If True, Sims running this interaction will be considered inside. For\n        instance, using the Observatory, while technically outside, should be\n        considered as being inside. This is used for the DayNightTracking system\n        on traits, like for Vampires.\n        ', tunable_type=bool, default=False, tuning_group=GroupNames.SPECIAL_CASES), 'counts_as_in_shade': Tunable(description='\n        If True, Sims running this interaction will be considered in the shade.\n        For instance, sims running sim_CreateCarryUmbrella will be considered\n        in the shade which will keep vampires from being considered in the sun\n        and keep them from burning up.\n        ', tunable_type=bool, default=False, tuning_group=GroupNames.SPECIAL_CASES), '_situation_participant_provider': OptionalTunable(description='\n        If enabled, this interaction and its continuations have access \n        to additional participant types as specified in the provided \n        participant type map.\n        ', tunable=SituationSimParticipantProviderLiability.TunableFactory(), tuning_group=GroupNames.SPECIAL_CASES), 'goal_height_limit': OptionalTunable(description='\n        If enabled geometric constraints will use this value to ignore goals\n        that are generated at a height difference bigger than this limit\n        compared to the height of the interaction target.\n        ', tunable=TunableRange(description='\n            Value in meters that will invalidate any goal inside the \n            constraint.\n            ', tunable_type=float, default=1, minimum=0), tuning_group=GroupNames.ROUTING), 'ignore_slope_restrictions': Tunable(description='\n        If set, we will not enforce CONSTRAINT_HEIGHT_TOLERANCE (leading to "slope" route failures) for this\n        interaction.\n        ', tunable_type=bool, default=False, tuning_group=GroupNames.ROUTING), '_add_actor_sim_as_listener': Tunable(description='\n        If set, this adds the actor Sim as a listener, so it is possible\n        to run reactionlets on the interaction without the need to\n        be in a social group interaction with other Sims.\n        \n        Example: Sim examines a scarecrow object.  The scarecrow plays\n        an animation which the Sim reacts to via a reactionlet.\n        ', tunable_type=bool, default=False, tuning_group=GroupNames.SPECIAL_CASES), 'min_height_clearance': HeightClearance.TunableFactory(description='\n        The distance from the floor to the ceiling that is required for the\n        sim to run this interaction.\n        ', tuning_group=GroupNames.ROUTING), 'phone_notification_tests': OptionalTunable(description='\n        If enabled, run this set of tests to determine whether this interaction\n        has a notification on the phone.\n        If any test passes, then the interaction will receive a badge in the\n        phone informing the user that there may be action required.\n        ', tunable=event_testing.tests.TunableTestSet(), tuning_group=GroupNames.UI), 'phone_notification_control_override': Tunable(description='\n        By default, if this interaction gets badged on the phone then clicking\n        on the interaction turns the notification off. Check this option if you\n        plan on handling that manually (will require GPE hookup).\n        \n        Example: the Open Social Media interaction shows a notification if a\n        Sim has a new message or tagged post. These exist as further\n        badges inside the Social Media dialog, and therefore should not be\n        auto-read just by opening the dialog itself.\n        ', tunable_type=bool, default=False, tuning_group=GroupNames.UI), 'require_reins_for_formation': OptionalTunable(description='\n        If enabled and the actor is currently in the tuned formation, we \n        will try to append this interaction with a reins pickup as \n        tuned in the formation. Note this is only intended for paired formations.\n        ', tunable=TunableReference(description='\n            Reference to the routing formation we care about.\n            ', manager=services.get_instance_manager(sims4.resources.Types.SNIPPET), class_restrictions=('RoutingFormation',), pack_safe=True))}
    _commodity_flags = EMPTY_SET
    _supported_postures = None
    _autonomy_ads = None
    _simoleon_delta_callbacks = None
    _upper_limit_callbacks = None
    _sim_can_violate_privacy_callbacks = None
    _auto_constraints = None
    _auto_constraint_is_canonical = False
    _auto_constraints_history = None
    _additional_conditional_actions = None
    _additional_tests = None
    _static_commodities_set = None
    _actor_role_asm_info_map = None
    _provided_posture_type = None
    _progress_bar_goal = None
    Multiplier = namedtuple('Multiplier', ['curve', 'use_effective_skill'])
    _success_chance_multipliers = {}
    _monetary_payout_multipliers = {}
    _expressed = True
    _animation_data_actors = defaultdict(lambda : InteractionAsmType.Unknown)
    disable_transitions = False
    disable_distance_estimation_and_posture_checks = False
    _additional_static_commodities = None
    _additional_basic_extras = None
    _additional_basic_liabilities = None
    _animation_constraint_dirty = False
    _has_multi_reserve = False
    _cost_string_callback = None
    _gain_string_callback = None

    @constproperty
    def is_put_in_inventory() -> 'bool':
        return False

    @constproperty
    def is_carry_cancel_interaction() -> 'bool':
        return False

    @classproperty
    def is_putdown(cls) -> 'bool':
        return False

    @constproperty
    def is_pickup_requester() -> 'bool':
        return False

    @constproperty
    def needs_compatible_destination_template() -> 'bool':
        return True

    @classproperty
    def is_rally_interaction(cls) -> 'bool':
        return False

    @classproperty
    def is_autonomous_picker_interaction(cls) -> 'bool':
        return False

    @classmethod
    def is_allowed_to_forward(cls, obj:'ScriptObject') -> 'bool':
        if cls._forwarding is None:
            return False
        return cls._forwarding.is_allowed_to_forward(cls, obj)

    @classmethod
    def _tuning_loading_callback(cls) -> 'None':
        cls._commodity_flags = EMPTY_SET
        cls._autonomy_ads = None
        if cls._simoleon_delta_callbacks:
            del cls._simoleon_delta_callbacks[DEFAULT]
        if cls._sim_can_violate_privacy_callbacks:
            del cls._sim_can_violate_privacy_callbacks[DEFAULT]
        cls._auto_constraints = None
        cls._auto_constraint_is_canonical = False
        cls._auto_constraints_history = None
        cls._additional_conditional_actions = None
        cls._static_commodities_set = None

    @classmethod
    def register_tuned_animation(cls, interaction_asm_type:'InteractionAsmType', asm_key:'sims4.resources.Key', actor_name:'Optional[str]', target_name:'Optional[str]', carry_target_name:'Optional[str]', create_target_name:'Optional[str]', overrides:'AnimationOverrides', participant_type_actor:'ParticipantType', participant_type_target:'ParticipantType') -> 'None':
        if cls._actor_role_asm_info_map is None:
            cls._actor_role_asm_info_map = defaultdict(list)
        if asm_key in sims4.resources.localwork_no_groupid:
            cls._animation_constraint_dirty = True
        data = cls._animation_data_actors[participant_type_actor]
        data |= interaction_asm_type
        cls._animation_data_actors[participant_type_actor] = data
        if participant_type_target & ParticipantType.AllSims:
            data_target = cls._animation_data_actors[participant_type_target]
            data_target |= interaction_asm_type
            cls._animation_data_actors[participant_type_target] = data_target
        if participant_type_target is not None and target_name == 'y':
            data = cls._animation_data_actors[ParticipantType.TargetSim]
            data |= interaction_asm_type
            cls._animation_data_actors[ParticipantType.TargetSim] = data
        list_key = (asm_key, overrides, target_name, carry_target_name, create_target_name)
        if interaction_asm_type == InteractionAsmType.Interaction or interaction_asm_type == InteractionAsmType.Outcome or interaction_asm_type == InteractionAsmType.Response:
            actor_list = cls._actor_role_asm_info_map[ParticipantType.Actor]
            actor_list.append((list_key, 'x'))
            target_list = cls._actor_role_asm_info_map[ParticipantType.TargetSim]
            target_list.append((list_key, 'y'))
        elif interaction_asm_type == InteractionAsmType.Reactionlet:
            target_list = cls._actor_role_asm_info_map[ParticipantType.TargetSim]
            target_list.append((list_key, 'x'))
            listener_list = cls._actor_role_asm_info_map[ParticipantType.Listeners]
            listener_list.append((list_key, 'x'))

    @classproperty
    def use_constraint_cache(self) -> 'bool':
        return True

    @classmethod
    def add_auto_constraint(cls, participant_type:'ParticipantType', tuned_constraint:'Constraint', is_canonical:'bool'=False) -> 'None':
        if cls._multi_surface:
            tuned_constraint = tuned_constraint.get_multi_surface_version()
        if cls._auto_constraint_is_canonical and not is_canonical:
            return
        if is_canonical:
            cls._auto_constraint_is_canonical = True
        for ptype in ParticipantType:
            if ptype == participant_type:
                participant_type = ptype
        if cls._auto_constraints is None:
            cls._auto_constraints = {}
        if participant_type in cls._auto_constraints and not is_canonical:
            intersection = cls._auto_constraints[participant_type].intersect(tuned_constraint)
        else:
            intersection = tuned_constraint
        if not intersection.valid:
            logger.error('{}: Interaction is incompatible with itself: {} and {} have no intersection.', cls.__name__, cls._auto_constraints, tuned_constraint)
        cls._auto_constraints[participant_type] = intersection

    @classmethod
    def _add_autonomy_ad(cls, operation:'StatisticOperation', overwrite:'bool'=False) -> 'bool':
        if operation.stat is None:
            logger.error('stat is None in statistic operation {} for {}.', operation, cls, owner='rez')
            return False
        if not operation.stat.is_scored:
            return False
        if cls._autonomy_ads is None:
            cls._autonomy_ads = {}
        ad_list = cls._autonomy_ads.get(operation.stat)
        if ad_list is None or overwrite:
            ad_list = AutonomyAdList(operation.stat)
            cls._autonomy_ads[operation.stat] = ad_list
        ad_list.add_op(operation)
        return True

    @classmethod
    def _remove_autonomy_ad(cls, operation:'StatisticOperation') -> 'bool':
        ad_list = cls._autonomy_ads.get(operation.stat)
        if ad_list is None:
            return False
        return ad_list.remove_op(operation)

    def instance_statistic_operations_gen(self) -> 'Iterator[StatisticOperation]':
        for op in self.aditional_instance_ops:
            yield op
        stat_op_list = self._statistic_operations_gen()
        for op in stat_op_list:
            yield op

    @classmethod
    def _statistic_operations_gen(cls) -> 'Iterator[StatisticOperation]':
        if cls.basic_content is None:
            return
        for op in cls.basic_content.periodic_stat_change.operations:
            yield op
        for operation_list in cls.basic_content.periodic_stat_change.operation_actions.actions:
            for (op, _) in operation_list.get_loot_ops_gen():
                yield op
        if cls.basic_content.periodic_stat_change is not None and cls.basic_content.progressive_stat_change is not None:
            for op in cls.basic_content.progressive_stat_change.additional_operations:
                yield op

    @classmethod
    def get_affordance_key_for_autonomy(cls) -> 'str':
        if cls._affordance_key_override_for_autonomy is not None:
            return cls._affordance_key_override_for_autonomy
        else:
            return cls.__qualname__

    @classmethod
    def check_if_valid(cls, is_bucks:'bool') -> 'bool':
        old_value = cls._is_bucks_tuned
        cls._is_bucks_tuned = is_bucks
        if old_value is not None and old_value != is_bucks:
            return False
        return True

    @classmethod
    def register_simoleon_delta_callback(cls, callback:'Callable[[Interaction, ScriptObject, InteractionContext], Tuple[int, FundsSource]]', object_tuning_id:'Optional[int]'=DEFAULT) -> 'None':
        if not cls._simoleon_delta_callbacks:
            cls._simoleon_delta_callbacks = defaultdict(list)
        cls._simoleon_delta_callbacks[object_tuning_id].append(callback)

    @classmethod
    def register_upper_limit_callback(cls, callback:'Callable[[FundsSource, InteractionContext], int]') -> 'None':
        cls._upper_limit_callback = callback

    @classmethod
    def register_cost_gain_strings_callbacks(cls, cost_string_callback:'Callable[[], Optional[str]]', gain_string_callback:'Callable[[], Optional[str]]') -> 'None':
        cls._cost_string_callback = cost_string_callback
        cls._gain_string_callback = gain_string_callback

    @classmethod
    def register_sim_can_violate_privacy_callback(cls, callback:'Callable[[Interaction, Sim], bool]', object_tuning_id:'Optional[int]'=DEFAULT) -> 'None':
        if not cls._sim_can_violate_privacy_callbacks:
            cls._sim_can_violate_privacy_callbacks = defaultdict(list)
        cls._sim_can_violate_privacy_callbacks[object_tuning_id].append(callback)

    @classmethod
    def clear_registered_callbacks_for_object_tuning_id(cls, object_tuning_id:'int') -> 'None':
        if object_tuning_id in cls._sim_can_violate_privacy_callbacks:
            del cls._sim_can_violate_privacy_callbacks[object_tuning_id]
        if object_tuning_id in cls._simoleon_delta_callbacks:
            del cls._simoleon_delta_callbacks[object_tuning_id]

    @classmethod
    def add_exit_condition(cls, condition_factory_list:'List[Callable[[], Condition]]') -> 'None':
        action = ExitCondition(conditions=condition_factory_list, interaction_action=ConditionalInteractionAction.EXIT_NATURALLY)
        if cls._additional_conditional_actions:
            cls._additional_conditional_actions.append(action)
        else:
            cls._additional_conditional_actions = [action]

    @classmethod
    def add_additional_test(cls, test:'BaseTest') -> 'None':
        if cls._additional_tests:
            cls._additional_tests.append(test)
        else:
            cls._additional_tests = [test]

    @classmethod
    def _tuning_loaded_callback(cls) -> 'None':
        added_statistics_from_ops = set()
        for op in cls._statistic_operations_gen():
            if op.advertise and op.subject == ParticipantType.Actor:
                cls._add_autonomy_ad(op)
                added_statistics_from_ops.add(op.stat)
        for op in cls._false_advertisements_gen():
            overwrite_op = op.stat in added_statistics_from_ops
            if cls._add_autonomy_ad(op, overwrite=overwrite_op) and overwrite_op:
                added_statistics_from_ops.remove(op.stat)
        cls._update_commodity_flags()
        if not paths.SUPPORT_RELOADING_RESOURCES:
            cls._actor_role_asm_info_map = None
        for liability in cls.basic_liabilities:
            liability.factory.on_affordance_loaded_callback(cls, liability)
        for basic_extra in cls.basic_extras:
            if hasattr(basic_extra.factory, AFFORDANCE_LOADED_CALLBACK_STR):
                basic_extra.factory.on_affordance_loaded_callback(cls, basic_extra)
        if cls.outcome is not None:
            for basic_extra in cls.outcome.get_basic_extras_gen():
                if hasattr(basic_extra.factory, AFFORDANCE_LOADED_CALLBACK_STR):
                    basic_extra.factory.on_affordance_loaded_callback(cls, basic_extra)
        constraints = collections.defaultdict(list)
        for entry in cls._constraints:
            constraints[entry.constrained_participant].append(entry.constraints)
        cls._constraints = frozendict({k: tuple(v) for (k, v) in constraints.items()})
        cls._animation_constraint_dirty = cls.resource_key in sims4.resources.localwork_no_groupid

    @classmethod
    def _verify_tuning_callback(cls) -> 'None':
        if cls.immediate and cls.staging:
            logger.error('{} is tuned to be staging but is marked immediate, this is not allowed.  Suggestion: set basic_content to one-shot or uncheck immediate.', cls.__name__)
        if cls.outcome is not None:
            outcome_actions = cls.outcome
            outcome_actions.interaction_cls_name = cls.__name__
            outcome_actions.validate_basic_extra_tuning(cls.__name__)
        for basic_extra in cls.basic_extras:
            if isinstance(basic_extra.factory, XevtTriggeredElement):
                basic_extra.factory.validate_tuning_interaction(cls, basic_extra)
        if cls.visible:
            if cls.display_name is not None and not cls.display_name:
                logger.error('Interaction {} is visible but has no display name', cls.__name__)
            icon_participant_type = getattr(cls._icon, 'participant_type', None)
            if icon_participant_type is not None and cls.target_type == TargetType.ACTOR and icon_participant_type == ParticipantType.Object:
                logger.error("Interaction {} only targets the actor but uses participant Object's icon. Use an icon or participant Actor.", cls.__name__)
        if cls.basic_content:
            cls.basic_content.validate_tuning(cls)
        if cls.autonomy_preference is not None and cls.autonomy_preference.preference.tag is None:
            logger.error('Interaction {} has autonomy preference enabled, but no autonomy preference tag set.  Please come talk to Joshua Jacobson if you have any questions.', cls.__name__, owner='BadTuning')
        progress_bar_tuning = cls.progress_bar_enabled.force_listen_statistic
        if progress_bar_tuning is not None and progress_bar_tuning.statistic is None:
            logger.error('Progress bar forced commodity is none for interaction {}.', cls, owner='camilogarcia')

    @classmethod
    def _get_tuning_suggestions(cls, print_suggestion:'Callable[[str], None]') -> 'None':
        if not cls.posture_preferences.apply_posture_costs:
            print_suggestion('This interaction does not have penalties applied. That means postures will not be considered when calculating goal costs and may result in the Sim choosing unexpected transitions.')

    @classmethod
    def _update_commodity_flags(cls) -> 'None':
        commodity_flags = set()
        if cls._autonomy_ads:
            for stat in cls._autonomy_ads:
                commodity_flags.add(stat)
        static_commodities = cls.static_commodities
        if static_commodities:
            commodity_flags.update(static_commodities)
        if commodity_flags:
            cls._commodity_flags = tuple(commodity_flags)
        object_manager = services.object_manager()
        if object_manager is not None:
            services.object_manager().clear_commodity_flags_for_objs_with_affordance((cls,))

    @classmethod
    def contains_stat(cls, stat:'Statistic') -> 'bool':
        if stat is None:
            logger.warn('Pass in None stat to ask whether {} contains it.', cls.__name__)
            return False
        for op in cls._statistic_operations_gen():
            if stat is op.stat:
                return True
        return False

    def is_adjustment_interaction(self) -> 'bool':
        return False

    @classmethod
    def add_skill_multiplier(cls, multiplier_dict:'Dict[Type[Interaction], Dict[Type[Statistic], Multiplier]]', skill_type:'Type[Statistic]', curve:'LinearCurve', use_effective_skill:'bool') -> 'None':
        if cls not in multiplier_dict:
            multiplier_dict[cls] = {}
        multiplier_dict[cls][skill_type] = cls.Multiplier(curve, use_effective_skill)

    @classmethod
    def get_skill_multiplier(cls, multiplier_dict:'Dict[Type, Dict[Type, Multiplier]]', target:'HasSimInfoMixin'):
        multiplier = 1
        if cls in multiplier_dict:
            for (skill_type, modifier) in multiplier_dict[cls].items():
                skill_or_skill_type = target.get_stat_instance(skill_type) or skill_type
                if modifier.use_effective_skill:
                    value = target.Buffs.get_effective_skill_level(skill_or_skill_type)
                else:
                    value = skill_or_skill_type.get_user_value()
                multiplier *= modifier.curve.get(value)
        return multiplier

    def should_fade_sim_out(self) -> 'bool':
        return self.fade_sim_out

    @classproperty
    def success_chance_multipliers(cls) -> 'Dict[Type, Dict[Type, Multiplier]]':
        return cls._success_chance_multipliers

    @classproperty
    def monetary_payout_multipliers(cls) -> 'Dict[Type, Dict[Type, Multiplier]]':
        return cls._monetary_payout_multipliers

    def _get_conditional_actions_for_content(self, basic_content:'_BasicContent') -> 'List[ExitCondition]':
        conditional_actions = []
        if basic_content is not None:
            if basic_content.conditional_actions:
                actions = snippets.flatten_snippet_list(basic_content.conditional_actions)
                conditional_actions.extend(actions)
            if self._additional_conditional_actions:
                actions = snippets.flatten_snippet_list(self._additional_conditional_actions)
                conditional_actions.extend(actions)
        return conditional_actions

    def get_conditional_actions(self) -> 'List[ExitCondition]':
        actions = self._get_conditional_actions_for_content(self.basic_content)
        if self.target is not None:
            target_basic_content = self.target.get_affordance_basic_content(self)
            if target_basic_content is not None:
                target_actions = self._get_conditional_actions_for_content(target_basic_content)
                actions.extend(target_actions)
        return actions

    def _get_start_as_guaranteed_for_content(self, basic_content:'_BasicContent') -> 'bool':
        if self.sim is not None and self.sim.queue.always_start_inertial:
            return False
        if basic_content is not None:
            if self.is_user_directed:
                return not self.basic_content.start_user_directed_inertial
            else:
                return not self.basic_content.start_autonomous_inertial
        return False

    def get_start_as_guaranteed(self) -> 'bool':
        if self._get_start_as_guaranteed_for_content(self.basic_content):
            return True
        elif self.target is not None:
            target_basic_content = self.target.get_affordance_basic_content(self)
            if self._get_start_as_guaranteed_for_content(target_basic_content):
                return True
        return False

    def __str__(self) -> 'str':
        return 'Interaction {} on {}; id:{}, sim:{}'.format(self.affordance, self.target, self.id, self.sim)

    def __repr__(self) -> 'str':
        return '<Interaction {} id:{} sim:{}>'.format(self.affordance.__name__, self.id, self.sim)

    @classproperty
    def autonomy_preference(cls) -> 'Optional[Any]':
        pass

    @classproperty
    def interaction(cls) -> 'Type[Interaction]':
        return cls

    @classproperty
    def requires_target_support(cls) -> 'bool':
        return True

    @classmethod
    def get_interaction_type(cls) -> 'Type[Interaction]':
        return cls

    def get_linked_interaction_type(self) -> 'None':
        pass

    @classmethod
    def generate_continuation_affordance(cls, affordance:'Interaction') -> 'Interaction':
        return affordance

    def get_target_si(self) -> 'Tuple[Optional[Interaction], TestResult]':
        return (None, event_testing.results.TestResult.TRUE)

    @staticmethod
    def _tunable_tests_enabled() -> 'bool':
        return True

    @flexmethod
    def get_display_tooltip(cls, inst, override:'Optional[Any]'=None, context:'Optional[InteractionContext]'=DEFAULT, **kwargs) -> 'Optional[LocalizedString]':
        inst_or_cls = inst if inst is not None else cls
        context = inst.context if context is DEFAULT else context
        sim = inst.sim if inst is not None else context.sim
        tooltip = inst_or_cls.display_tooltip
        if override.new_display_tooltip is not None:
            tooltip = override.new_display_tooltip
        if override is not None and tooltip is not None:
            tooltip = inst_or_cls.create_localized_string(tooltip, context=context, **kwargs)
        if inst_or_cls.item_cost is not None:
            tooltip = inst_or_cls.item_cost.get_interaction_tooltip(tooltip=tooltip, sim=sim)
        return tooltip

    @flexmethod
    def skip_test_on_execute(cls, inst) -> 'bool':
        if inst is not None:
            return inst.aop.skip_test_on_execute
        return False

    @classmethod
    def _test(cls, target:'ScriptObject', context:'InteractionContext', **interaction_parameters) -> 'TestResult':
        return event_testing.results.TestResult.TRUE

    @classmethod
    def _should_test_affordance_filters(cls, context:'InteractionContext') -> 'bool':
        return False

    @staticmethod
    @caches.clearable_barebones_cache
    def get_affordance_actor_filters(sim_info:'SimInfo') -> 'Tuple[_AffordanceFilters, ...]':
        return tuple(itertools.chain(*(buff.effect_modification.get_affordance_filters_for_actor() for buff in sim_info.Buffs if buff.effect_modification.has_affordance_filter_modifiers())))

    @staticmethod
    @caches.clearable_barebones_cache
    def get_affordance_target_filters(sim_info:'SimInfo') -> 'Tuple[_AffordanceFilters, ...]':
        return tuple(itertools.chain(*(buff.effect_modification.get_affordance_filters_for_target() for buff in sim_info.Buffs if buff.effect_modification.has_affordance_filter_modifiers())))

    @classmethod
    def test_affordance_filters(cls, sim:'Sim', target:'Optional[ScriptObject]') -> 'TestResult':
        actor_affordance_filters = Interaction.get_affordance_actor_filters(sim.sim_info)
        if actor_affordance_filters:
            for actor_filter in actor_affordance_filters:
                result = actor_filter.filter_affordance(cls)
                if not result:
                    return result
        if target.is_sim:
            targeted_affordance_filters = Interaction.get_affordance_target_filters(target.sim_info)
            if targeted_affordance_filters:
                for target_filter in targeted_affordance_filters:
                    result = target_filter.filter_affordance(cls)
                    if not result:
                        return result
        return event_testing.results.TestResult.TRUE

    @flexmethod
    def test(cls, inst, target:'Optional[ScriptObject]'=DEFAULT, context:'Optional[InteractionContext]'=DEFAULT, super_interaction:'Optional[SuperInteraction]'=None, skip_safe_tests:'bool'=False, **interaction_parameters) -> 'TestResult':
        inst_or_cls = inst if inst is not None else cls
        target = target if target is not DEFAULT else inst.target
        context = context if context is not DEFAULT else inst.context
        if inst_or_cls.is_super:
            for obj in inst_or_cls.get_participants(ParticipantType.All, sim=context.sim, target=target, **interaction_parameters):
                if obj.build_buy_lockout:
                    return event_testing.results.TestResult(False, 'Target object has been locked out and cannot be interacted with.')
        if inst is not None:
            interaction_parameters = frozendict(inst.interaction_parameters, interaction_parameters)
            if 'saved_participants' not in interaction_parameters:
                interaction_parameters += {'saved_participants': inst.get_saved_participants()}
            if super_interaction is None:
                super_interaction = inst.super_interaction
        result = event_testing.results.TestResult.TRUE
        try:
            if cls.is_super and (cls.visible and target is None) and cls._icon is None:
                return event_testing.results.TestResult(False, 'Visible interaction has no target, which is invalid for displaying icons.')
            if context.sim is None and not cls.simless:
                return event_testing.results.TestResult(False, 'No Sim specified in context.')
            if target is not None:
                if target.is_in_inventory():
                    if target.is_in_sim_inventory():
                        if not cls.allow_from_sim_inventory:
                            return event_testing.results.TestResult(False, 'Interaction is not valid from sim inventory.')
                    elif not cls.allow_from_object_inventory:
                        return event_testing.results.TestResult(False, 'Interaction is not valid from object inventory.')
                else:
                    is_starting = interaction_parameters.get('interaction_starting', False)
                    if is_starting or not cls.allow_from_world:
                        return event_testing.results.TestResult(False, 'Interaction is not valid from the world.')
                if cls.simless or not (target.parent is not None and (target.parent.is_sim and (target.parent is not context.sim and target is not context.sim)) and (target.is_set_as_head or target.is_sim)):
                    return event_testing.results.TestResult(False, 'Target is being held by another Sim.')
            if target.is_sim:
                if not context.sim.sim_info.incest_prevention_test(target.sim_info):
                    return event_testing.results.TestResult(False, 'Not available because it violates the incest rules.')
            else:
                logger.error('Trying to test for incest with an object, not a Sim. Interaction = {}, Actor = {}, Target = {}', inst_or_cls, context.sim, target, owner='rfleig')
            if inst_or_cls is not None and (inst_or_cls.test_incest and target is not None) and inst_or_cls is not None and not inst_or_cls.debug:
                fire_service = services.get_fire_service()
                if fire_service is not None:
                    fire_interaction_test_result = fire_service.fire_interaction_test(inst_or_cls.affordance, context)
                    if not fire_interaction_test_result:
                        return fire_interaction_test_result
            instance_result = inst_or_cls._test(target, context, skip_safe_tests=skip_safe_tests, **interaction_parameters)
            if instance_result or not instance_result.tooltip:
                return instance_result
            resolver = None
            if inst_or_cls._tunable_tests_enabled():
                search_for_tooltip = context.source == context.SOURCE_PIE_MENU
                resolver = inst_or_cls.get_resolver(target=target, context=context, super_interaction=super_interaction, search_for_tooltip=search_for_tooltip, **interaction_parameters)
                global_result = event_testing.results.TestResult.TRUE
                if context.sim.is_dying:
                    global_result = event_testing.results.TestResult(False, 'Sim [{}] is dying.', context.sim)
                if cls._should_test_affordance_filters(context):
                    global_result &= cls.test_affordance_filters(context.sim, target)
                if (context.shift_held or inst_or_cls.is_super and (context.is_cancel_aop or context.sim is not None and global_result)) and global_result:
                    global_result = cls.test_globals.run_tests(resolver, skip_safe_tests, search_for_tooltip=search_for_tooltip)
                local_result = event_testing.results.TestResult.TRUE
                autonomous_result = event_testing.results.TestResult.TRUE
                target_result = event_testing.results.TestResult.TRUE
                if global_result.tooltip is not None:
                    local_result = cls.tests.run_tests(resolver, skip_safe_tests=skip_safe_tests, search_for_tooltip=search_for_tooltip)
                    if inst_or_cls._additional_tests:
                        additional_tests = TestList(inst_or_cls._additional_tests)
                        local_result = additional_tests.run_tests(resolver, skip_safe_tests=skip_safe_tests, search_for_tooltip=search_for_tooltip)
                    if target is not None:
                        tests = target.get_affordance_tests(inst_or_cls)
                        if tests is not None:
                            target_result = tests.run_tests(resolver, skip_safe_tests=skip_safe_tests, search_for_tooltip=search_for_tooltip)
                    if inst_or_cls.test_autonomous:
                        autonomous_result = cls.test_autonomous.run_tests(resolver, skip_safe_tests=skip_safe_tests, search_for_tooltip=False)
                if not ((global_result or search_for_tooltip) and target_result):
                    result = target_result
                elif not local_result:
                    result = local_result
                elif not global_result:
                    result = global_result
                else:
                    result = autonomous_result
            if not result:
                return result
            result = target_result & local_result & global_result & autonomous_result
            result &= instance_result
            target_of_interest = target or context.sim
            if cls.utility_info is not None:
                utility_manager = services.utilities_manager()
                if target_of_interest and utility_manager.is_affected_object(target_of_interest):
                    if resolver is None:
                        resolver = inst_or_cls.get_resolver(target=target_of_interest, context=context, super_interaction=super_interaction, search_for_tooltip=search_for_tooltip, **interaction_parameters)
                    result &= utility_manager.test_utility_info(cls.utility_info, target_of_interest, resolver, skip_safe_tests=skip_safe_tests)
                    if target.utilities_component is not None:
                        result &= target.utilities_component.test_utility_info(cls.utility_info)
            if not (context.sim is None or context.sim.is_npc):
                for funds_source in FundsSource:
                    if funds_source == FundsSource.STATISTIC:
                        pass
                    else:
                        cost_for_source = inst_or_cls.get_simoleon_cost_for_source(funds_source, target=target, context=context, **interaction_parameters)
                        if cost_for_source > 0:
                            funds_amount = cls.get_upper_limit_on_payment(funds_source, context=context)
                            if funds_amount is None:
                                funds = get_funds_for_source(funds_source, sim=context.sim)
                                funds_amount = None if funds is None else funds.money
                            if not funds_amount is None:
                                if funds_amount < cost_for_source:
                                    unavailable_funds_tooltip = FundsTuning.UNAFFORDABLE_TOOLTIPS.get(funds_source)
                                    if cls.missing_funds_additional_description:
                                        unavailable_funds_text = LocalizationHelperTuning.get_new_line_separated_strings(unavailable_funds_tooltip(context.sim), cls.missing_funds_additional_description)
                                    else:
                                        unavailable_funds_text = LocalizationHelperTuning.get_raw_text(unavailable_funds_tooltip(context.sim))
                                    unavailable_funds_tooltip = lambda *_, **__: unavailable_funds_text
                                    result &= event_testing.results.TestResult(False, "Sim [{}] either has no household funds manager, or doesn't have enough funds.:", context.sim, tooltip=unavailable_funds_tooltip)
                            unavailable_funds_tooltip = FundsTuning.UNAFFORDABLE_TOOLTIPS.get(funds_source)
                            if cls.missing_funds_additional_description:
                                unavailable_funds_text = LocalizationHelperTuning.get_new_line_separated_strings(unavailable_funds_tooltip(context.sim), cls.missing_funds_additional_description)
                            else:
                                unavailable_funds_text = LocalizationHelperTuning.get_raw_text(unavailable_funds_tooltip(context.sim))
                            unavailable_funds_tooltip = lambda *_, **__: unavailable_funds_text
                            result &= event_testing.results.TestResult(False, "Sim [{}] either has no household funds manager, or doesn't have enough funds.:", context.sim, tooltip=unavailable_funds_tooltip)
            if result and result and result:
                result &= inst_or_cls.item_cost.get_test_result(context.sim, inst_or_cls)
            if result and not cls.allow_while_save_locked:
                fail_reason = services.get_persistence_service().get_save_lock_tooltip()
                if fail_reason is not None:
                    error_tooltip = lambda *_, **__: fail_reason
                    return event_testing.results.TestResult(False, 'Interaction is not allowed to run while save is locked.', tooltip=error_tooltip)
            if not result:
                return result
        except Exception as e:
            logger.exception('Exception during call to test method on {0}', cls)
            return event_testing.results.TestResult(False, 'Exception: {}', e)
        if not isinstance(result, event_testing.results.TestResult):
            logger.warn("Interaction test didn't return a TestResult: {}: {}", result, cls.__name__, result)
            return event_testing.results.TestResult(result)
        return result

    @flexmethod
    def test_for_phone_notification(cls, inst, resolver:'Resolver') -> 'bool':
        if cls.phone_notification_tests is None:
            return False
        return bool(cls.phone_notification_tests.run_tests(resolver))

    @flexmethod
    def get_participant(cls, inst, participant_type:'ParticipantType'=ParticipantType.Actor, **kwargs) -> 'Optional[Any]':
        inst_or_cl = inst if inst is not None else cls
        participants = inst_or_cl.get_participants(participant_type=participant_type, **kwargs)
        if not participants:
            return
        if len(participants) > 1:
            logger.warn('{} is ignoring multiple {} since a single object was expected.', inst_or_cl, participant_type)
            return
        return next(iter(participants))

    @flexmethod
    def get_participants(cls, inst, participant_type:'ParticipantType', sim:'Optional[Sim]'=DEFAULT, target:'Optional[ScriptObject]'=DEFAULT, carry_target:'Optional[ScriptObject]'=DEFAULT, listener_filtering_enabled:'bool'=False, target_type:'Optional[TargetType]'=None, **interaction_parameters) -> 'Tuple[Any, ...]':
        global interaction_get_particpants_call_count
        interaction_get_particpants_call_count += 1
        if interaction_get_particpants_call_count % SIM_YIELD_INTERACTION_GET_PARTICIPANTS_MOD == 0:
            yield_to_irq()
        try:
            participant_type = int(participant_type)
        except:
            participant_type = ParticipantType.Invalid
        if inst is not None:
            if inst.interaction_parameters and interaction_parameters:
                interaction_parameters = frozendict(inst.interaction_parameters, interaction_parameters)
            else:
                interaction_parameters = inst.interaction_parameters or interaction_parameters
        inst_or_cls = inst if inst is not None else cls
        if inst_or_cls.simless:
            sim = None
        else:
            sim = inst.sim if sim is DEFAULT else sim
        target = inst.target if target is DEFAULT else target
        if participant_type == ParticipantType.Actor:
            if sim is not None:
                return (sim,)
            return ()
        elif participant_type == ParticipantType.Object:
            if target is not None:
                return (target,)
            return ()
        return ()
        if participant_type == ParticipantType.TargetSim:
            if target is not None and target.is_sim:
                return (target,)
            elif inst is not None and inst.target is not None and inst.target.is_sim:
                return (inst.target,)
            return ()
        object_manager = services.object_manager()
        inventory_manager = services.current_zone().inventory_manager
        definition_manager = services.definition_manager()
        if inst is not None:
            carry_target = inst.carry_target if carry_target is DEFAULT else carry_target
        is_all = participant_type & ParticipantType.All
        all_sims = participant_type & ParticipantType.AllSims
        if target_type is None:
            target_type = inst_or_cls.target_type
        result = set()
        if is_all:
            all_sims = True
        target_is_sim = target is not DEFAULT and (target is not None and (hasattr(target, 'is_sim') and target.is_sim))
        if all_sims or participant_type & ParticipantType.Actor:
            result.add(sim)
        if is_all or participant_type & ParticipantType.Object:
            result.add(target)
        if (is_all or participant_type & ParticipantType.ObjectParent) and target is not None and not target.is_sim:
            result.add(target.parent)
        if participant_type & ParticipantType.ObjectChildren and target is not None and not isinstance(target, PostureSpecVariable):
            if target.is_part:
                result.update(target.part_owner.children_recursive_gen())
            else:
                result.update(target.children_recursive_gen())
        if is_all or participant_type & ParticipantType.CarriedObject:
            if carry_target is not None and carry_target is not DEFAULT:
                result.add(carry_target)
            elif 'carry_target' in interaction_parameters and target is not None:
                result.add(target)
        if all_sims or participant_type & ParticipantType.CarriedSim:
            if carry_target is not None and carry_target is not DEFAULT and carry_target.is_sim:
                result.add(carry_target)
            elif 'carry_target' in interaction_parameters and target is not None and target.is_sim:
                result.add(target)
        if target.posture_state is not None:
            for (hand, _, obj) in get_carried_objects_gen(target):
                if not hand == Hand.LEFT:
                    if hand == Hand.RIGHT:
                        result.add(obj)
                        break
                result.add(obj)
                break
        if target.posture_state is not None:
            for (hand, _, obj) in get_carried_objects_gen(target):
                if obj.is_sim and hand == Hand.BACK:
                    result.add(obj)
                    break
        if (((all_sims or participant_type & ParticipantType.TargetSimFrontCarriedSim) and target_is_sim and all_sims or participant_type & ParticipantType.TargetSimBackCarriedSim) and target_is_sim and all_sims or participant_type & ParticipantType.TargetSim) and target_is_sim:
            result.add(target)
        if all_sims or participant_type & ParticipantType.JoinTarget:
            join_target_ref = interaction_parameters.get('join_target_ref')
            if join_target_ref is not None:
                result.add(join_target_ref())
        if participant_type & ParticipantType.LinkedPostureSim:
            posture = sim.posture
            if posture.multi_sim:
                result.add(posture.linked_posture.sim)
        if all_sims or participant_type & ParticipantType.Listeners:
            social_group = inst.social_group if inst is not None else None
            if not (target_type & TargetType.TARGET and (target is not None and (target_is_sim and target.ignore_group_socials(excluded_group=social_group))) and listener_filtering_enabled):
                result.add(target)
            if social_group is not None:
                for other_sim in social_group:
                    if other_sim is sim:
                        pass
                    else:
                        for si in social_group.get_sis_registered_for_sim(other_sim):
                            if si.pipeline_progress >= PipelineProgress.RUNNING and not si.is_finishing:
                                break
                        if inst.acquire_listeners_as_resource or listener_filtering_enabled and other_sim.ignore_group_socials(excluded_group=social_group):
                            pass
                        elif inst._required_sims is not None and listener_filtering_enabled and other_sim not in inst._required_sims:
                            pass
                        else:
                            result.add(other_sim)
        if participant_type & ParticipantType.SocialGroup and inst is not None and inst.is_social:
            result.add(inst.social_group)
        if participant_type & ParticipantType.SocialGroupSims and inst is not None and inst.is_social:
            social_group = inst.social_group
            if social_group is not None:
                result.update(social_group)
        if participant_type & ParticipantType.InventoryObjectStack and target is not None:
            result.add(target)
            if sim is not None and target.inventoryitem_component is not None:
                stack_id = target.inventoryitem_component.get_stack_id()
                result.update(sim.inventory_component.get_stack_items(stack_id))
        if participant_type & ParticipantType.ObjectInventoryOwner and target is not None and target.is_in_inventory():
            owner = target.inventoryitem_component.last_inventory_owner
            if owner is not None:
                result.add(owner)
        if participant_type & ParticipantType.ObjectIngredients and target is not None and (target.is_sim or target.crafting_component):
            target_crafting_process = target.get_crafting_process()
            if target_crafting_process is not None:
                result.extend([ingredient_definition_tuple.definition for ingredient_definition_tuple in target_crafting_process.get_ingredients_object_definitions()])
        if (participant_type & ParticipantType.ObjectTrendiOutfitTrend or participant_type & ParticipantType.ObjectTrendiOutfitTrendTag) and target is not None:
            fashion_trend_service = services.fashion_trend_service()
            if fashion_trend_service is not None:
                if participant_type == ParticipantType.ObjectTrendiOutfitTrend:
                    outfit_trend = fashion_trend_service.get_outfit_prevalent_trend(target)
                    if outfit_trend is not None:
                        result.add(outfit_trend)
                if participant_type == ParticipantType.ObjectTrendiOutfitTrendTag:
                    outfit_trend_tag = fashion_trend_service.get_outfit_prevalent_trend_tag(target)
                    if outfit_trend_tag is not None:
                        result.add(outfit_trend_tag)
        if participant_type & ParticipantType.GraduatesCurrent:
            graduation_service = services.get_graduation_service()
            if graduation_service is not None:
                result.extend(graduation_service.current_graduating_sims)
        if participant_type & ParticipantType.GraduatesWaiting:
            graduation_service = services.get_graduation_service()
            if graduation_service is not None:
                result.extend(graduation_service.waiting_to_graduate_sims())
        if sim.posture_state is not None:
            if is_all or participant_type & ParticipantType.ActorSurface:
                result.add(sim.posture_state.surface_target)
            if participant_type & ParticipantType.ActorPostureTarget:
                result.add(sim.posture_state.body.target)
        if sim is not None and hasattr(sim, 'posture_state') and target is not None and target_is_sim and hasattr(target, 'posture_state'):
            target_posture_state = target.posture_state
        else:
            target_posture_state = None
        if participant_type == ParticipantType.TargetSimPostureTarget:
            if target_posture_state is not None:
                result.add(target_posture_state.body.target)
            elif inst.target is not None and (inst.target.is_sim and hasattr(inst.target, 'posture_state')) and inst.target.posture_state is not None:
                result.add(inst.target.posture_state.body.target)
        if target_posture_state and (is_all or participant_type & ParticipantType.TargetSurface):
            result.add(target_posture_state.surface_target)
        if participant_type & ParticipantType.CreatedObject or participant_type & ParticipantType.CreatedObjectIngredients:
            if inst is not None and inst.created_target is not None:
                if participant_type & ParticipantType.CreatedObject:
                    result.add(inst.created_target)
                if inst.created_target.crafting_component:
                    inst_created_target_crafting_process = inst.created_target.get_crafting_process()
                    if inst_created_target_crafting_process is not None:
                        result.extend([ingredient_definition_tuple.definition for ingredient_definition_tuple in inst_created_target_crafting_process.get_ingredients_object_definitions()])
            elif inst.interaction_parameters:
                created_target_id = interaction_parameters.get('created_target_id')
                if created_target_id:
                    obj = object_manager.get(created_target_id) or inventory_manager.get(created_target_id)
                    if obj is not None:
                        if participant_type & ParticipantType.CreatedObject:
                            result.add(obj)
                        if obj.crafting_component:
                            crafting_process = obj.get_crafting_process()
                            if crafting_process is not None:
                                result.extend([ingredient_definition_tuple.definition for ingredient_definition_tuple in crafting_process.get_ingredients_object_definitions()])
                    else:
                        del interaction_parameters['created_target_id']
        if participant_type & ParticipantType.PickedItemId:
            picked_item_ids = interaction_parameters.get('picked_item_ids')
            if picked_item_ids is not None:
                result.update(picked_item_ids)
        if participant_type & ParticipantType.Unlockable:
            result.add(interaction_parameters.get('unlockable_name'))
        if participant_type & ParticipantType.PickedObject or participant_type & ParticipantType.StoredSimOnPickedObject:
            picked_item_ids = interaction_parameters.get('picked_item_ids')
            if picked_item_ids is not None:
                for picked_item_id in picked_item_ids:
                    obj = object_manager.get(picked_item_id)
                    if obj is None:
                        obj = inventory_manager.get(picked_item_id)
                    if obj is None:
                        obj = definition_manager.get(picked_item_id)
                    if participant_type & ParticipantType.StoredSimOnPickedObject:
                        stored_sim_info = obj.get_stored_sim_info()
                        if stored_sim_info is not None:
                            result.add(stored_sim_info.get_sim_instance() or stored_sim_info)
                    if obj is not None and participant_type & ParticipantType.PickedObject:
                        result.add(obj)
            elif cls.is_super or cls.get_picked_object_from_super and inst is not None:
                owning_super = inst.super_interaction
                if owning_super is not None:
                    result.update(owning_super.get_participants(participant_type))
        if participant_type & ParticipantType.PurchasedObject:
            purchased_item_ids = interaction_parameters.get('purchased_item_ids')
            if purchased_item_ids is not None:
                for purchased_item_id in purchased_item_ids:
                    obj = object_manager.get(purchased_item_id)
                    if obj is None:
                        obj = inventory_manager.get(purchased_item_id)
                    if obj is not None:
                        result.add(obj)
        if participant_type & ParticipantType.ObjectProvidingTargetAffordance or participant_type & ParticipantType.StoredSimOnObjectProvidingTargetAffordance:
            object_providing_affordance = interaction_parameters.get('object_providing_target_affordance')
            if object_providing_affordance is not None:
                obj = object_manager.get(object_providing_affordance)
                if obj is None:
                    obj = inventory_manager.get(object_providing_affordance)
                if obj is not None:
                    if participant_type & ParticipantType.StoredSimOnObjectProvidingTargetAffordance:
                        stored_sim_info = obj.get_stored_sim_info()
                        if stored_sim_info is not None:
                            result.add(stored_sim_info.get_sim_instance() or stored_sim_info)
                    if participant_type & ParticipantType.ObjectProvidingTargetAffordance:
                        result.add(obj)
        if participant_type & ParticipantType.PickedSim:
            picked_item_ids = interaction_parameters.get('picked_item_ids')
            if picked_item_ids is not None:
                for picked_item_id in picked_item_ids:
                    sim_info = services.sim_info_manager().get(picked_item_id)
                    if sim_info is not None:
                        result.add(sim_info.get_sim_instance() or sim_info)
        if participant_type & ParticipantType.RoutingMaster:
            master = sim.routing_master
            if sim.get_routing_slave_data():
                master = sim
            if target.is_sim:
                master = target.routing_master
            if master is None and master is None and target is not None and master is not None:
                result.add(master)
        if participant_type & ParticipantType.RoutingSlaves:
            routing_slave_data = sim.get_routing_slave_data()
            if target.is_sim:
                routing_slave_data = target.get_routing_slave_data()
            if routing_slave_data or target is not None and routing_slave_data:
                result.update({data.slave for data in routing_slave_data})
        if participant_type & ParticipantType.StoredSim and target is not None:
            stored_sim_info = target.get_stored_sim_info()
            if stored_sim_info is not None:
                result.add(stored_sim_info.get_sim_instance() or stored_sim_info)
        if participant_type & ParticipantType.StoredSim2 and target is not None:
            stored_sim_info2 = target.get_secondary_stored_sim_info()
            if stored_sim_info2 is not None:
                result.add(stored_sim_info2.get_sim_instance() or stored_sim_info2)
        if participant_type & ParticipantType.StoredSimOrNameData and target is not None:
            stored_sim_info_or_name_data = target.get_stored_sim_info_or_name_data()
            if stored_sim_info_or_name_data is not None:
                result.add(stored_sim_info_or_name_data)
        if participant_type & ParticipantType.StoredSimOrNameDataList and target is not None:
            stored_sim_info_or_name_data_list = target.get_stored_sim_info_or_name_data_set()
            if stored_sim_info_or_name_data_list is not None:
                result.update(stored_sim_info_or_name_data_list)
        if participant_type & ParticipantType.StoredSimOnActor and sim is not None:
            stored_sim_info = sim.get_stored_sim_info()
            if stored_sim_info is not None:
                result.add(stored_sim_info.get_sim_instance() or stored_sim_info)
        if participant_type & ParticipantType.StoredCASPartsOnObject and target is not None:
            stored_cas_parts = target.get_stored_cas_parts()
            if stored_cas_parts is not None:
                result.update(stored_cas_parts)
        if participant_type & ParticipantType.OwnerSim and target is not None:
            owner_sim_info_id = target.get_sim_owner_id()
            owner_sim_info = services.sim_info_manager().get(owner_sim_info_id)
            if owner_sim_info is not None:
                result.add(owner_sim_info.get_sim_instance() or owner_sim_info)
        if participant_type & ParticipantType.SignificantOtherActor and sim is not None:
            spouse = sim.get_significant_other_sim_info()
            if spouse is not None:
                result.add(spouse.get_sim_instance() or spouse)
        if participant_type & ParticipantType.SignificantOtherTargetSim and target is not None and target.is_sim:
            spouse = target.get_significant_other_sim_info()
            if spouse is not None:
                result.add(spouse.get_sim_instance() or spouse)
        if participant_type & ParticipantType.AllSignificantOthersActor and sim is not None:
            significant_others = sim.get_significant_other_sim_info(True)
            result.update(significant_others)
        if participant_type & ParticipantType.AllSignificantOthersTargetSim and target is not None and target.is_sim:
            significant_others = target.get_significant_other_sim_info(True)
            result.update(significant_others)
        if participant_type & ParticipantType.ActorFiance and sim is not None:
            fiance = sim.get_fiance_sim_info()
            if fiance is not None:
                result.add(fiance.get_sim_instance() or fiance)
        if participant_type & ParticipantType.TargetFiance and target is not None and target.is_sim:
            fiance = target.get_fiance_sim_info()
            if fiance is not None:
                result.add(fiance.get_sim_instance() or fiance)
        if participant_type & ParticipantType.PregnancyPartnerActor and sim is not None:
            partner = sim.sim_info.pregnancy_tracker.get_partner()
            if partner is not None:
                result.add(partner.get_sim_instance() or partner)
        if participant_type & ParticipantType.PregnancyPartnerTargetSim and target is not None and target.is_sim:
            partner = target.sim_info.pregnancy_tracker.get_partner()
            if partner is not None:
                result.add(partner.get_sim_instance() or partner)
        if participant_type & ParticipantType.Lot:
            result.update(event_testing.resolver.Resolver.get_particpants_shared(ParticipantType.Lot))
        if participant_type & ParticipantType.ActorLot and sim is not None:
            lot = sim.sim_info.get_home_lot()
            if lot is not None:
                result.add(lot)
        if participant_type & ParticipantType.PickedZoneId:
            picked_zone_ids = interaction_parameters.get('picked_zone_ids')
            if picked_zone_ids is not None:
                result.update(picked_zone_ids)
        if participant_type & ParticipantType.ActorZoneId and sim is not None:
            result.add(sim.sim_info.household.home_zone_id)
        if participant_type & ParticipantType.TargetSimZoneId and (target is not None and target.is_sim) and target.sim_info.household is not None:
            result.add(target.sim_info.household.home_zone_id)
        if participant_type & ParticipantType.OtherSimsInteractingWithTarget and target is not None:
            user_target = target.part_owner if target.is_part else target
            user_targets = user_target.parts if user_target.parts else (user_target,)
            other_sims = set()
            for user_target in user_targets:
                if hasattr(user_target, 'get_users'):
                    other_sims.update(user_target.get_users(sims_only=True))
            all_sims_for_removal = inst_or_cls.get_participants(ParticipantType.AllSims, sim=sim, target=target, carry_target=carry_target, **interaction_parameters)
            result.update(set(other_sims) - set(all_sims_for_removal))
        if participant_type & ParticipantType.ActiveHousehold:
            active_household_sim_infos = event_testing.resolver.Resolver.get_particpants_shared(ParticipantType.ActiveHousehold)
            if active_household_sim_infos:
                result.update(active_household_sim_infos)
        if participant_type & ParticipantType.LotOwners:
            owners = event_testing.resolver.Resolver.get_particpants_shared(ParticipantType.LotOwners)
            if owners is not None:
                result.update(owners)
        if participant_type & ParticipantType.LotOwnersOrRenters:
            owners = event_testing.resolver.Resolver.get_particpants_shared(ParticipantType.LotOwnersOrRenters)
            if owners is not None:
                result.update(owners)
        if participant_type & ParticipantType.LotOwnerSingleAndInstanced:
            owners = event_testing.resolver.Resolver.get_particpants_shared(ParticipantType.LotOwnerSingleAndInstanced)
            if owners is not None:
                result.update(owners)
        if participant_type & ParticipantType.SocialGroupAnchor and inst is not None:
            group = inst.social_group
            if group is not None:
                result.add(group.anchor)
        if participant_type & ParticipantType.AllOtherInstancedSims:
            for instanced_sim in services.sim_info_manager().instanced_sims_gen(allow_hidden_flags=ALL_HIDDEN_REASONS):
                if instanced_sim is not sim and not instanced_sim.is_dying:
                    result.add(instanced_sim)
        if participant_type & ParticipantType.CareerEventSim:
            result.update(event_testing.resolver.Resolver.get_particpants_shared(ParticipantType.CareerEventSim))
        if participant_type & ParticipantType.ActorEnsemble and sim is not None:
            ensemble = services.ensemble_service().get_most_important_ensemble_for_sim(sim)
            if ensemble:
                result.update(ensemble)
        if participant_type & ParticipantType.ActorEnsembleSansActor and sim is not None:
            ensemble = services.ensemble_service().get_most_important_ensemble_for_sim(sim)
            if ensemble:
                ensemble_sims = set(ensemble)
                ensemble_sims.discard(sim)
                result.update(ensemble_sims)
        if participant_type & ParticipantType.TargetEnsemble and target_is_sim:
            ensemble = services.ensemble_service().get_most_important_ensemble_for_sim(target)
            if ensemble:
                result.update(ensemble)
        if participant_type & ParticipantType.MissingPet and sim is not None:
            missing_pet = sim.household.missing_pet_tracker.missing_pet_info
            if missing_pet:
                result.add(missing_pet.get_sim_instance() or missing_pet)
        if participant_type & ParticipantType.ActorDiningGroupMembers and sim is not None:
            restaurant_zone_director = get_restaurant_zone_director()
            if restaurant_zone_director:
                dining_group_sims = restaurant_zone_director.get_group_sims_by_sim(sim)
                result.update(dining_group_sims)
        if participant_type & ParticipantType.TargetDiningGroupMembers:
            restaurant_zone_director = get_restaurant_zone_director()
            if restaurant_zone_director is not None and target_is_sim:
                dining_group_sims = restaurant_zone_director.get_group_sims_by_sim(target)
                result.update(dining_group_sims)
        if participant_type & ParticipantType.TableDiningGroupMembers:
            restaurant_zone_director = get_restaurant_zone_director()
            if restaurant_zone_director is not None and target is not None:
                dining_group_sims = restaurant_zone_director.get_group_sims_by_table(target.id)
                result.update(dining_group_sims)
        if participant_type & ParticipantType.LinkedObjects:
            if target.linked_object_component is None:
                logger.error("Requesting ParticipantType.LinkedObjects on target {} that doesn't have a linked Object component", target)
            result.update(target.get_linked_objects_gen())
        if participant_type & ParticipantType.TargetTeleportPortalObjectDestinations:
            if not target.has_component(objects.components.types.PORTAL_COMPONENT):
                logger.error('Trying to get the TeleportPortalObjectDestinations for a target that does not have a portal component. {}', target)
            else:
                portal_data = target.get_portal_data()
                for data in portal_data:
                    result.update(data.traversal_type.get_destination_objects())
        if participant_type & ParticipantType.ActorFeudTarget and sim is not None:
            feud_target = sim.get_feud_target()
            if feud_target is not None:
                result.add(feud_target)
        if participant_type & ParticipantType.TargetFeudTarget and target is not None and target.is_sim:
            feud_target = target.get_feud_target()
            if feud_target is not None:
                result.add(feud_target)
        if sim is not None:
            sim_info_manager = services.sim_info_manager()
            for squad_member_id in sim.squad_members:
                squad_sim_info = sim_info_manager.get(squad_member_id)
                if squad_sim_info is not None:
                    result.add(squad_sim_info)
        if target.is_sim:
            sim_info_manager = services.sim_info_manager()
            for squad_member_id in target.squad_members:
                squad_sim_info = sim_info_manager.get(squad_member_id)
                if squad_sim_info is not None:
                    result.add(squad_sim_info)
        if participant_type & ParticipantType.ActorSquadMembers and participant_type & ParticipantType.TargetSquadMembers and target is not None and participant_type & ParticipantType.StoredObjectsOnActor and sim is not None:
            actor = sim.sim_info
            c = actor.get_component(objects.components.types.STORED_OBJECT_INFO_COMPONENT)
            if c is not None:
                result.update(c.get_stored_objects())
            else:
                logger.error("Requesting ParticipantType.StoredObjectsOnActor on actor {} that doesn't have a Stored Object Info component", actor)
        if participant_type & ParticipantType.StoredObjectsOnTarget and target is not None:
            _target = target.sim_info if target.is_sim else target
            c = _target.get_component(objects.components.types.STORED_OBJECT_INFO_COMPONENT)
            if c is not None:
                result.update(c.get_stored_objects())
            else:
                logger.error("Requesting ParticipantType.StoredObjectsOnTarget on target {} that doesn't have a Stored Object Info component", _target)
        for p_type in ParticipantTypeSituationSims:
            if participant_type & p_type:
                provider = inst_or_cls.get_situation_participant_provider()
                if provider is not None:
                    return provider.get_participants(participant_type, inst_or_cls.get_resolver())
                logger.error("Requesting {} in {} that doesn't have a SituationSimParticipantProviderLiability", participant_type, provider)
        familiar_tracker_array = set()
        if participant_type & ParticipantType.Familiar and sim is not None:
            familiar_tracker = sim.sim_info.familiar_tracker
            if familiar_tracker is not None:
                familiar_tracker_array.add(familiar_tracker)
        if participant_type & ParticipantType.FamiliarOfTarget and target is not None and target_is_sim:
            familiar_tracker = target.sim_info.familiar_tracker
            if familiar_tracker is not None:
                familiar_tracker_array.add(familiar_tracker)
        for familiar_tracker in familiar_tracker_array:
            familiar = familiar_tracker.get_active_familiar()
            if familiar is not None:
                if familiar.is_sim:
                    result.add(familiar.sim_info)
                else:
                    result.add(familiar)
        if participant_type & ParticipantType.PickedStatistic:
            picked_statistic = interaction_parameters.get('picked_statistic')
            if picked_statistic is not None:
                result.add(picked_statistic)
        if participant_type & ParticipantType.ActorHousehold and sim is not None and sim.household is not None:
            result.add(sim.household)
        if participant_type & ParticipantType.ActorHouseholdMembers and sim is not None and sim.household is not None:
            result.update(sim.household)
        if participant_type & ParticipantType.TargetHousehold and (target is not None and target.is_sim) and target.household is not None:
            result.add(target.household)
        if participant_type & ParticipantType.TargetHouseholdMembers and (target is not None and target.is_sim) and target.household is not None:
            result.update(target.household)
        for (index, p_type) in enumerate(ParticipantTypeSavedActor):
            if participant_type & p_type:
                if inst is None:
                    saved_participants = interaction_parameters.get('saved_participants')
                    if saved_participants:
                        result.add(saved_participants[index])
                else:
                    result.add(inst.get_saved_participant(index))
        if participant_type & ParticipantType.CurrentlyOpenSmallBusinessOwner:
            owner = event_testing.resolver.Resolver.get_particpants_shared(ParticipantType.CurrentlyOpenSmallBusinessOwner)
            if owner is not None:
                result.update(owner)
        if participant_type & ParticipantType.SmallBusinessEmployees:
            owner_sim = services.get_active_sim()
            business_manager = services.business_service().get_business_manager_for_sim(owner_sim.sim_id)
            if business_manager:
                employee_list = business_manager.get_employees_sim_info()
                result.update(employee_list)
        if participant_type & ParticipantType.ActorLotLevel and sim is not None:
            lot = services.active_lot()
            if lot is not None:
                lot_level = lot.get_lot_level_instance(sim.routing_surface.secondary_id)
                if lot_level is not None:
                    result.add(lot_level)
        if participant_type & ParticipantType.ObjectLotLevel and target is not None:
            lot = services.active_lot()
            if lot is not None:
                lot_level = lot.get_lot_level_instance(target.routing_surface.secondary_id)
                if lot_level is not None:
                    result.add(lot_level)
        if participant_type & ParticipantType.ObjectAnimalHome and target is not None:
            animal_service = services.animal_service()
            if animal_service is not None:
                animal_home = animal_service.get_animal_home_obj(target)
                if animal_home is not None:
                    result.add(animal_home)
        if participant_type & ParticipantType.AnimalHomeAssignees and target is not None:
            animal_service = services.animal_service()
            if animal_service is not None:
                assignees = animal_service.get_animal_home_assignee_objs(target)
                if assignees:
                    result.update(assignees)
        if sim is not None:
            situation_manager = services.get_zone_situation_manager()
            for situation in situation_manager.get_situations_sim_is_in(sim):
                crafting_object = situation.get_situation_crafting_object()
                if crafting_object is not None:
                    result.add(crafting_object)
        if participant_type & ParticipantType.SituationCraftingItem and participant_type & ParticipantType.ActorClanLeader and sim is not None:
            clan_service = services.clan_service()
            if clan_service is not None:
                clan_leader = clan_service.get_clan_leader(sim.sim_info)
                if clan_leader is not None:
                    result.add(clan_leader)
        if participant_type & ParticipantType.TargetClanLeader and target is not None and target.is_sim:
            clan_service = services.clan_service()
            if clan_service is not None:
                clan_leader = clan_service.get_clan_leader(target.sim_info)
                if clan_leader is not None:
                    result.add(clan_leader)
        if participant_type & ParticipantType.CarryCancellationOriginatorTarget and inst is not None:
            cancellation_originator = inst.context.cancellation_originator
            if cancellation_originator is not None:
                carry_cancellation_target_ref = cancellation_originator.target
                if carry_cancellation_target_ref is not None:
                    carry_cancellation_target = carry_cancellation_target_ref()
                    if carry_cancellation_target is not None:
                        result.add(carry_cancellation_target)
        if participant_type & ParticipantType.TargetObjectOfJoinedInteraction and (inst is not None and inst.join_interaction is not None) and inst.join_interaction.target is not None:
            result.add(inst.join_interaction.target)
        if sim is not None:
            multi_unit_ownership_service = services.get_multi_unit_ownership_service()
            if sim.household is not None:
                for tenant_hh_id in multi_unit_ownership_service.get_tenants_household_ids(sim.household.id):
                    result.add(tenant_hh_id)
        if participant_type & ParticipantType.ActorTenantHouseholds and participant_type & ParticipantType.AllSimsInCurrentGame and target is not None and (target.is_sim or target.has_component(objects.components.types.GAME_COMPONENT)):
            result.update(target.game_component.get_all_players())
        if participant_type & ParticipantType.OtherSimsInCurrentGame and target is not None and (target.is_sim or target.has_component(objects.components.types.GAME_COMPONENT)):
            players = target.game_component.get_all_players()
            if sim is not None and sim in players:
                players.remove(sim)
            result.update(players)
        if participant_type & ParticipantType.CurrentZoneId:
            zone_id = services.current_zone_id()
            if zone_id:
                result.add(zone_id)
        if participant_type & ParticipantType.HeirloomCreatorSim:
            creator_sim_info = target.get_creator_sim_info()
            if creator_sim_info is not None:
                result.add(creator_sim_info)
        result.discard(None)
        return tuple(result)

    PRIORITY_PARTICIPANT_TYPES = (ParticipantType.Actor, ParticipantType.TargetSim, ParticipantType.Listeners)
    AGGREGATE_PARTICIPANT_TYPES = (ParticipantType.All, ParticipantType.AllSims)

    @flexmethod
    def get_participant_type(cls, inst, participant:'Any', restrict_to_participant_types:'Optional[Set[ParticipantType]]'=None, exclude_participant_types:'Set[ParticipantType]'=(), **kwargs) -> 'Optional[ParticipantType]':
        inst_or_cls = inst if inst is not None else cls
        priority_participant_types = inst_or_cls.PRIORITY_PARTICIPANT_TYPES
        exclude_participant_types = inst_or_cls.AGGREGATE_PARTICIPANT_TYPES + exclude_participant_types
        for participant_type in priority_participant_types:
            if restrict_to_participant_types is not None and participant_type not in restrict_to_participant_types:
                pass
            elif participant_type in exclude_participant_types:
                pass
            elif participant in inst_or_cls.get_participants(participant_type, **kwargs):
                return participant_type
        for (_, participant_type) in ParticipantType.items():
            if participant_type in priority_participant_types:
                pass
            elif restrict_to_participant_types is not None and participant_type not in restrict_to_participant_types:
                pass
            elif participant_type in exclude_participant_types:
                pass
            elif participant in inst_or_cls.get_participants(participant_type, **kwargs):
                return participant_type

    def can_sim_violate_privacy(self, sim:'Sim') -> 'bool':
        if self._sim_can_violate_privacy_callbacks:
            for callback in self._sim_can_violate_privacy_callbacks[DEFAULT]:
                if callback(self, sim):
                    return True
            if self.target is not None:
                target_tuning_id = self.target.guid64
                for callback in self._sim_can_violate_privacy_callbacks[target_tuning_id]:
                    if callback(self, sim):
                        return True
        return False

    @flexmethod
    def get_simoleon_deltas_gen(cls, inst, target:'Optional[ScriptObject]'=DEFAULT, context:'Optional[InteractionContext]'=DEFAULT, **interaction_parameters) -> 'Iterator[int]':
        inst_or_cls = inst if inst is not None else cls
        if inst_or_cls._simoleon_delta_callbacks:
            for callback in inst_or_cls._simoleon_delta_callbacks[DEFAULT]:
                yield callback(inst_or_cls, target, context, **interaction_parameters)
            target_inst = target if target is not DEFAULT else inst.target
            if not target_inst.is_sim:
                target_tuning_id = target_inst.guid64
                if target_tuning_id in inst_or_cls._simoleon_delta_callbacks:
                    for callback in inst_or_cls._simoleon_delta_callbacks[target_tuning_id]:
                        yield callback(inst_or_cls, target_inst, context, **interaction_parameters)

    @flexmethod
    def get_simoleon_cost(cls, inst, target:'Optional[ScriptObject]'=DEFAULT, context:'Optional[InteractionContext]'=DEFAULT) -> 'int':
        inst_or_cls = inst if inst is not None else cls
        return -sum(amount for (amount, _) in inst_or_cls.get_simoleon_deltas_gen(target, context) if amount < 0)

    @flexmethod
    def get_simoleon_cost_for_source(cls, inst, funds_source:'FundsSource', target:'Optional[ScriptObject]'=DEFAULT, context:'Optional[InteractionContext]'=DEFAULT, **interaction_parameters) -> 'int':
        inst_or_cls = inst if inst is not None else cls
        return -sum(amount for (amount, _funds_source) in inst_or_cls.get_simoleon_deltas_gen(target, context, **interaction_parameters) if amount < 0 and _funds_source == funds_source)

    @classmethod
    def get_upper_limit_on_payment(cls, funds_source:'FundsSource', context:'Optional[InteractionContext]'=DEFAULT) -> 'Optional[int]':
        callback = cls._upper_limit_callback
        if callback is None:
            return
        return callback(funds_source=funds_source, context=context)

    @flexmethod
    def get_simoleon_payout(cls, inst, target:'Optional[ScriptObject]'=DEFAULT, context:'Optional[InteractionContext]'=DEFAULT) -> 'int':
        inst_or_cls = inst if inst is not None else cls
        return sum(amount for (amount, _) in inst_or_cls.get_simoleon_deltas_gen(target, context) if amount > 0)

    @flexmethod
    def get_cost_name_factory(cls, inst) -> 'Optional[LocalizedString]':
        inst_or_cls = inst if inst is not None else cls
        if inst_or_cls._cost_string_callback is None or inst_or_cls._cost_string_callback() is None:
            return inst_or_cls.SIMOLEON_COST_NAME_FACTORY
        return inst_or_cls._cost_string_callback()

    @flexmethod
    def get_gain_name_factory(cls, inst) -> 'Optional[LocalizedString]':
        inst_or_cls = inst if inst is not None else cls
        if inst_or_cls._gain_string_callback is None or inst_or_cls._gain_string_callback() is None:
            return inst_or_cls.SIMOLEON_GAIN_NAME_FACTORY
        return inst_or_cls._gain_string_callback()

    @classmethod
    def get_category_tags(cls) -> 'Set[Tag]':
        return cls.interaction_category_tags

    @flexmethod
    def get_pie_menu_category(cls, inst, from_inventory_to_owner:'bool'=False, **interaction_parameters) -> 'Optional[PieMenuCategory]':
        inst_or_cls = inst if inst is not None else cls
        if from_inventory_to_owner:
            return inst_or_cls.category_on_forwarded
        return inst_or_cls.category

    @flexmethod
    def get_name_override_and_test_result(cls, inst, **kwargs) -> 'Tuple[Optional[Any], TestResult]':
        inst_or_cls = inst if inst is not None else cls
        if inst_or_cls.display_name_overrides is not None:
            return inst_or_cls.display_name_overrides.get_display_name_and_result(inst_or_cls, **kwargs)
        return (None, event_testing.results.TestResult.NONE)

    @flexmethod
    def get_display_name_wrapper(cls, inst, **kwargs) -> 'Optional[Any]':
        inst_or_cls = inst if inst is not None else cls
        if inst_or_cls.display_name_wrappers is not None:
            return inst_or_cls.display_name_wrappers.get_first_passing_wrapper(inst_or_cls, **kwargs)

    @flexmethod
    def get_name_override_tunable_and_result(cls, inst, **kwargs) -> 'Tuple[Optional[Any], TestResult]':
        inst_or_cls = inst if inst is not None else cls
        (override_tunable, test_result) = inst_or_cls.get_name_override_and_test_result(**kwargs)
        if override_tunable is not None:
            return (override_tunable, test_result)
        return (None, test_result)

    @flexmethod
    def get_name(cls, inst, target:'Optional[ScriptObject]'=DEFAULT, context:'Optional[InteractionContext]'=DEFAULT, apply_name_modifiers:'bool'=True, **interaction_parameters) -> 'str':
        inst_or_cls = inst if inst is not None else cls
        display_name = inst_or_cls._get_name(target=target, context=context, **interaction_parameters)
        if apply_name_modifiers:
            if inst is None and cls.SIMOLEON_DELTA_MODIFIES_AFFORDANCE_NAME or inst.SIMOLEON_DELTA_MODIFIES_INTERACTION_NAME:
                simoleon_cost = inst_or_cls.get_simoleon_cost(target=target, context=context)
                cost_name_factory = inst_or_cls.get_cost_name_factory()
                gain_name_factory = inst_or_cls.get_gain_name_factory()
                if simoleon_cost > 0 and cost_name_factory is not None:
                    display_name = cost_name_factory(display_name, simoleon_cost)
                elif gain_name_factory is not None:
                    simoleon_payout = inst_or_cls.get_simoleon_payout(target=target, context=context)
                    if simoleon_payout > 0:
                        display_name = gain_name_factory(display_name, simoleon_payout)
            if cls.ITEM_COST_NAME_FACTORY:
                display_name = cls.item_cost.get_interaction_name(cls, display_name)
            wrapper_item = inst_or_cls.get_display_name_wrapper(target=target, context=context)
            if inst is None and wrapper_item is not None:
                display_name = wrapper_item.wrapper(display_name)
            if inst_or_cls.DEBUG_NAME_FACTORY is not None:
                display_name = inst_or_cls.DEBUG_NAME_FACTORY(display_name)
        return display_name

    @flexmethod
    def _get_name(cls, inst, target:'Optional[ScriptObject]'=DEFAULT, context:'Optional[InteractionContext]'=DEFAULT, **interaction_parameters) -> 'str':
        inst_or_cls = inst if inst is not None else cls
        if inst is not None and inst.display_name_in_queue is not None:
            display_name = inst.display_name_in_queue
        else:
            display_name = inst_or_cls.display_name
        (override_tunable, _) = inst_or_cls.get_name_override_and_test_result(target=target, context=context)
        if override_tunable.new_display_name is not None:
            display_name = override_tunable.new_display_name
        display_name = inst_or_cls.create_localized_string(display_name, target=target, context=context, **interaction_parameters)
        if context.sim is not None:
            curfew_service = services.get_curfew_service()
            if curfew_service.sim_breaking_curfew(context.sim, target, inst_or_cls):
                display_name = curfew_service.BREAK_CURFEW_WARNING(display_name)
        return display_name

    @flexmethod
    def create_localized_string(cls, inst, localized_string_factory:'TunableLocalizedStringFactory', *tokens, **kwargs) -> 'LocalizedString':
        inst_or_cls = inst if inst is not None else cls
        interaction_tokens = inst_or_cls.get_localization_tokens(**kwargs)
        return localized_string_factory(*interaction_tokens + tokens)

    @flexmethod
    def get_localization_tokens(cls, inst, **interaction_parameters) -> 'Tuple[Optional[Any], ...]':
        inst_or_cls = inst if inst is not None else cls
        tokens = inst_or_cls.display_name_text_tokens.get_tokens(inst_or_cls.get_resolver(**interaction_parameters))
        return tokens

    @classmethod
    def visual_targets_gen(cls, target:'ScriptObject', context:'InteractionContext', **kwargs) -> 'Iterator[ScriptObject]':
        yield target

    @classmethod
    def has_pie_menu_sub_interactions(cls, target:'ScriptObject', context:'InteractionContext', **kwargs) -> 'bool':
        return False

    @classmethod
    def potential_pie_menu_sub_interactions_gen(cls, target:'ScriptObject', context:'InteractionContext', scoring_gsi_handler:'Optional[Dict[Type, Dict[str, Any]]]'=None, **kwargs):
        pass

    @classmethod
    def get_score_modifier(cls, sim:'Sim', target:'ScriptObject') -> 'int':
        test_gender_preference = cls.content_score.test_gender_preference
        if test_gender_preference:
            gender_pref_test = GenderPreferenceTest(subject=ParticipantType.Actor, target_sim=ParticipantType.TargetSim, gender_preference_type=test_gender_preference.gender_preference_type, consider_exploration=test_gender_preference.consider_exploration, override_target_gender_test=None, tooltip=None, ignore_reciprocal=True, negate=False)
            resolver = DoubleSimResolver(sim.sim_info, target.sim_info)
            result = resolver(gender_pref_test)
            if not result:
                return cls.GENDER_PREF_CONTENT_SCORE_PENALTY
        score_modifier = cls.content_score.mood_preference.get(sim.get_mood(), 0)
        if target is None:
            return score_modifier
        sims = set(itertools.chain.from_iterable(group for group in sim.get_groups_for_sim_gen() if target in group))
        if sims:
            social_context = SocialContextTest.get_overall_short_term_context_bit(*sims)
        else:
            relationship_track = sim.relationship_tracker.get_relationship_prevailing_short_term_context_track(target.id)
            if relationship_track is not None:
                social_context = relationship_track.get_active_bit()
            else:
                social_context = None
        social_context_preference = cls.content_score.social_context_preference.get(social_context, 0)
        relationship_bit_preference = 0
        relationship_bit_collection_preference = 0
        trait_preference = 0
        buff_preference = 0
        cross_age_preference = 0
        if cls.content_score.relationship_bit_preference:
            relationship_bit_preference = sum(cls.content_score.relationship_bit_preference.get(rel_bit, 0) for rel_bit in sim.relationship_tracker.get_all_bits(target_sim_id=target.id))
        if cls.content_score.trait_preference:
            trait_preference = sum(cls.content_score.trait_preference.get(trait, 0) for trait in sim.trait_tracker.equipped_traits)
        if cls.content_score.buff_preference:
            buff_preference = sum(score for (buff, score) in cls.content_score.buff_preference.items() if sim.has_buff(buff))
        if target.is_sim:
            buff_preference += sum(score for (buff, score) in cls.content_score.buff_target_preference.items() if target.has_buff(buff))
        if cls.content_score.buff_target_preference and cls.content_score.relationship_bit_collection_preference:
            for (bit_collection, score) in cls.content_score.relationship_bit_collection_preference.items():
                if target.relationship_tracker.has_bit(sim.id, bit_collection):
                    relationship_bit_collection_preference += score
                    break
        if target.is_sim:
            cross_age_preference = cls.content_score.cross_age_preferences.get_value(sim.age, target.age)
        score_modifier = score_modifier + social_context_preference + relationship_bit_preference + trait_preference + buff_preference + relationship_bit_collection_preference + cross_age_preference
        return score_modifier

    @classproperty
    def is_super(cls) -> 'bool':
        return False

    @constproperty
    def is_proxy() -> 'bool':
        return False

    @classmethod
    def _false_advertisements_gen(cls) -> 'Iterator[StatisticOperation]':
        for false_add in cls._false_advertisements:
            yield false_add

    @classproperty
    def commodity_flags(cls) -> 'FrozenSet[Commodity]':
        return frozenset(cls._commodity_flags)

    @classmethod
    def autonomy_ads_gen(cls, target:'Optional[ScriptObject]'=None, include_hidden_false_ads:'bool'=False) -> 'None':
        for ad in target.get_affordance_false_ads(cls):
            cls._add_autonomy_ad(ad, overwrite=False)
        for ad in cls._hidden_false_advertisements:
            cls._add_autonomy_ad(ad, overwrite=False)
        if target is not None and include_hidden_false_ads and cls._autonomy_ads:
            for ad_list in cls._autonomy_ads.values():
                yield ad_list
        if include_hidden_false_ads:
            for ad in cls._hidden_false_advertisements:
                cls._remove_autonomy_ad(ad)
        if target is not None:
            for ad in target.get_affordance_false_ads(cls):
                cls._remove_autonomy_ad(ad)

    @classproperty
    def static_commodities(cls) -> 'FrozenSet[StaticCommodity]':
        static_commodities_frozen_set = frozenset([data.static_commodity for data in cls.static_commodities_data])
        return static_commodities_frozen_set

    @classproperty
    def static_commodities_data(cls) -> 'FrozenSet[Any]':
        if cls._static_commodities_set is None:
            cls._refresh_static_commodity_cache()
        return cls._static_commodities_set

    @classmethod
    def _refresh_static_commodity_cache(cls) -> 'None':
        if cls._static_commodities:
            static_commodities_set = set(cls._static_commodities)
        else:
            static_commodities_set = set()
        if cls._additional_static_commodities:
            static_commodities_set.update(cls._additional_static_commodities)
        cls._static_commodities_set = frozenset(static_commodities_set)
        cls._update_commodity_flags()

    @classmethod
    def trigger_refresh_static_commodity_cache(cls) -> 'None':
        cls._static_commodities_set = None
        cls._refresh_static_commodity_cache()

    @classproperty
    def provided_posture_type(cls) -> 'Optional[Posture]':
        pass

    @flexmethod
    def get_associated_skill(cls, inst) -> 'Optional[Statistic]':
        skill = None
        if inst is not None:
            skill = inst.stat_from_skill_loot_data
        elif cls.outcome is not None:
            skill = cls.outcome.associated_skill
        return skill

    @flexmethod
    def _get_skill_loot_data(cls, inst) -> 'TunableSkillLootData':
        if inst is not None and inst.target is not None:
            target_skill_loot_data = inst.target.get_affordance_skill_loot_data(inst)
            if target_skill_loot_data is not None:
                return target_skill_loot_data
        return cls.skill_loot_data

    @flexproperty
    def stat_from_skill_loot_data(cls, inst) -> 'Optional[Statistic]':
        inst_or_cls = inst if inst is not None else cls
        skill_loot_data = inst_or_cls._get_skill_loot_data()
        return skill_loot_data.stat or inst_or_cls.skill_loot_data.stat

    @flexproperty
    def skill_effectiveness_from_skill_loot_data(cls, inst) -> 'SkillEffectiveness':
        inst_or_cls = inst if inst is not None else cls
        skill_loot_data = inst_or_cls._get_skill_loot_data()
        return skill_loot_data.effectiveness or inst_or_cls.skill_loot_data.effectiveness

    @flexproperty
    def level_range_from_skill_loot_data(cls, inst) -> 'Optional[TunableInterval]':
        inst_or_cls = inst if inst is not None else cls
        skill_loot_data = inst_or_cls._get_skill_loot_data()
        return skill_loot_data.level_range or inst_or_cls.skill_loot_data.level_range

    @classproperty
    def approximate_duration(cls) -> 'int':
        return cls.time_overhead

    @classmethod
    def _define_supported_postures(cls) -> 'Dict[ParticipantType, FrozenPostureManifest]':
        if not cls._actor_role_asm_info_map:
            return frozendict()
        posture_support_map = {}
        for (actor_role, asm_info) in cls._actor_role_asm_info_map.items():
            supported_postures = PostureManifest()
            for ((asm_key, overrides, target_name, carry_target_name, create_target_name), actor_name) in asm_info:
                posture_manifest_overrides = None
                if overrides is not None:
                    posture_manifest_overrides = overrides.manifests
                asm = animation.asm.create_asm(asm_key, None, posture_manifest_overrides)
                supported_postures_asm = asm.get_supported_postures_for_actor(actor_name)
                supported_postures.update(supported_postures_asm)
            if supported_postures:
                posture_support_map[actor_role] = supported_postures.frozen_copy()
        return frozendict(posture_support_map)

    @classmethod
    def filter_supported_postures(cls, supported_postures_from_asm:'PostureManifestBase', filter_posture_name:'Optional[str]'=None, force_carry_state:'Optional[Tuple[bool, bool, bool]]'=None) -> 'PostureManifestBase':
        if supported_postures_from_asm is ALL_POSTURES:
            return ALL_POSTURES
        if force_carry_state is None:
            force_carry_state = (None, None)
        filter_entry = PostureManifestEntry(None, filter_posture_name, filter_posture_name, MATCH_ANY, force_carry_state[0], force_carry_state[1], MATCH_ANY, None)
        supported_postures = supported_postures_from_asm.intersection_single(filter_entry)
        return supported_postures

    @flexmethod
    def constraint_gen(cls, inst, sim:'Sim', target:'Optional[ScriptObject]', participant_type:'ParticipantType'=ParticipantType.Actor) -> 'Iterator[Constraint]':
        inst_or_cls = cls if inst is None else inst
        if inst_or_cls._require_current_posture:
            yield sim.posture_state.posture_constraint_strict
        allow_multi_surface = inst_or_cls._multi_surface
        if target.override_multi_surface_constraints is not None:
            allow_multi_surface = target.override_multi_surface_constraints
        else:
            allow_multi_surface = target.parent.override_multi_surface_constraints_of_children
        for constraint in inst_or_cls._constraint_gen(sim, target, participant_type=participant_type, interaction=inst):
            constraint = constraint.get_multi_surface_version()
            constraint = constraint.generate_forbid_small_intersections_constraint()
            yield constraint

    @classmethod
    def _constraint_gen(cls, sim:'Sim', target:'Optional[ScriptObject]', participant_type:'ParticipantType'=ParticipantType.Actor, interaction:'Optional[Interaction]'=None) -> 'Iterator[Constraint]':
        for constraints in cls._constraints.get(participant_type, ()):
            for constraint in constraints:
                yield constraint.value.create_constraint(sim, target, interaction=interaction)
        if cls._auto_constraints is not None and participant_type in cls._auto_constraints:
            yield cls._auto_constraints[participant_type]

    @flexmethod
    def get_constraint_target(cls, inst, target:'Optional[ScriptObject]') -> 'Optional[ScriptObject]':
        constraint_target = target
        if inst is not None:
            constraint_target = inst.get_participant(participant_type=cls._constraints_actor)
        return constraint_target

    def get_constraint_for_slave_routing(self, sim:'ScriptObject', target:'ScriptObject', constraint:'Constraint') -> 'Constraint':
        routing_master = sim.routing_master
        if routing_master is None:
            return constraint
        actor_participant = self.get_participant(ParticipantType.Actor, target=target)
        if actor_participant is routing_master:
            return constraint
        else:
            slave_data = routing_master.get_formation_data_for_slave(sim)
            if slave_data is not None and slave_data.required_interaction is self:
                return constraint._copy(_allow_geometry_intersections=False)
        return constraint

    @flexmethod
    def constraint_intersection(cls, inst, sim:'Optional[Sim]'=DEFAULT, target:'Optional[ScriptObject]'=DEFAULT, participant_type:'Optional[ParticipantType]'=DEFAULT, *, posture_state:'Optional[PostureState]'=DEFAULT, force_concrete:'bool'=True, allow_holster:'Optional[bool]'=DEFAULT, invalid_expected:'bool'=False) -> 'Constraint':
        inst_or_cls = inst if inst is not None else cls
        target = inst.target if target is DEFAULT else target
        if participant_type is DEFAULT:
            participant_type = ParticipantType.Actor
        sim = inst_or_cls.get_participant(participant_type, target=target) if sim is DEFAULT and sim is DEFAULT else sim
        if sim is None:
            return ANYWHERE
        if posture_state is DEFAULT:
            posture_state = sim.posture_state
        if inst is not None and posture_state is not None and posture_state.body.source_interaction is inst:
            return posture_state.body_posture_state_constraint
        participant_type = inst_or_cls.get_participant_type(sim, target=target) if participant_type is DEFAULT else participant_type
        if participant_type is None:
            return Nowhere('Interaction({}) cannot find {} as a participant, target: {}', inst_or_cls, sim, target)
        add_slot_constraints_if_possible = not sim.parent_may_move
        sim_constraint_cache_key = (sim.ref, add_slot_constraints_if_possible)
        if inst is None or allow_holster is not DEFAULT:
            intersection = None
        elif False and not caches.use_constraints_cache:
            intersection = None
        else:
            if posture_state is not None:
                cached_constraint = inst._constraint_cache_final.get(posture_state)
                if cached_constraint:
                    return inst.get_constraint_for_slave_routing(sim, target, cached_constraint)
            intersection = inst._constraint_cache.get(sim_constraint_cache_key)
        if not (intersection is None or inst_or_cls.use_constraint_cache):
            intersection = ANYWHERE
            constraints = list(inst_or_cls.constraint_gen(sim, inst_or_cls.get_constraint_target(target), participant_type=participant_type))
            routing_master = sim.routing_master
            if routing_master is not None:
                passed_sim = sim if inst is None else DEFAULT
                actor_participant = inst_or_cls.get_participant(ParticipantType.Actor, target=target, sim=passed_sim)
                if actor_participant is not routing_master:
                    slave_data = routing_master.get_formation_data_for_slave(sim)
                    if slave_data is not None:
                        constraints.append(slave_data.get_routing_slave_constraint())
            for constraint in constraints:
                if inst is not None:
                    if force_concrete:
                        constraint = constraint.create_concrete_version(inst, sim=sim)
                    constraint_resolver = inst.get_constraint_resolver(None, participant_type=participant_type, force_actor=sim)
                    constraint = constraint.apply_posture_state(None, constraint_resolver, affordance=inst_or_cls.affordance)
                    if add_slot_constraints_if_possible:
                        constraint = constraint.add_slot_constraints_if_possible(sim)
                test_intersection = constraint.intersect(intersection)
                intersection = test_intersection
                if not intersection.valid:
                    break
            if allow_holster is DEFAULT:
                inst._constraint_cache[sim_constraint_cache_key] = intersection
        if not intersection.valid:
            return intersection
        final_intersection = inst_or_cls.apply_posture_state_and_interaction_to_constraint(posture_state, intersection, sim=sim, target=target, participant_type=participant_type, allow_holster=allow_holster, invalid_expected=invalid_expected)
        use_multi_surface_constraints = inst_or_cls._multi_surface
        if target is not None:
            if target.override_multi_surface_constraints is not None:
                use_multi_surface_constraints = target.override_multi_surface_constraints
            elif target.parent.override_multi_surface_constraints_of_children is not None:
                use_multi_surface_constraints = target.parent.override_multi_surface_constraints_of_children
        if use_multi_surface_constraints:
            final_intersection = final_intersection.get_multi_surface_version()
        if inst is not None:
            if allow_holster is DEFAULT:
                inst._constraint_cache_final[posture_state] = final_intersection
            final_intersection = inst.get_constraint_for_slave_routing(sim, target, final_intersection)
        return final_intersection

    def is_guaranteed(self) -> 'bool':
        return not self.has_active_cancel_replacement

    @classmethod
    def consumes_object(cls) -> 'bool':
        return cls.outcome.consumes_object

    @classproperty
    def interruptible(cls) -> 'bool':
        return False

    @classproperty
    def compare_max_priority(cls) -> 'bool':
        return True

    def should_cancel_on_si_cancel(self, interaction:'Interaction') -> 'bool':
        return False

    def __init__(self, aop:'AffordanceObjectPair', context:'InteractionContext', aop_id:'Optional[int]'=None, super_affordance:'Optional[Type[Interaction]]'=None, must_run:'bool'=False, posture_target:'Optional[ScriptObject]'=None, liabilities:'Optional[Tuple[Tuple[str, Liability], ...]]'=None, route_fail_on_transition_fail:'bool'=True, name_override:'Optional[str]'=None, load_data:'Optional[InteractionLoadData]'=None, depended_on_si:'Optional[SuperInteraction]'=None, depended_on_until_running:'bool'=False, anim_overrides:'Optional[AnimationOverrides]'=None, set_work_timestamp:'bool'=True, saved_participants:'Optional[List[Any]]'=None, disable_vehicles_override:'bool'=None, **kwargs):
        self._kwargs = kwargs
        self._aop = aop
        self._liability_target_ref = None
        self.context = copy.copy(context)
        self.anim_overrides = anim_overrides
        if name_override is not None:
            self.name_override = name_override
        self._pipeline_progress = PipelineProgress.NONE
        self._constraint_cache = {}
        self._constraint_cache_final = WeakKeyDictionary()
        self._target = None
        self.set_target(aop.target)
        self.carry_track = None
        self.slot_manifest = None
        self.motive_handles = []
        self.aditional_instance_ops = []
        self._super_interaction = None
        self._run_interaction_element = None
        self._asm_states = {}
        self.locked_params = frozendict()
        if context is not None:
            self._priority = context.priority
            self._run_priority = context.run_priority if context.run_priority else context.priority
        else:
            self._priority = context.priority if context else interactions.priority.Priority.Low
            self._run_priority = context.priority
        self._active = False
        self._satisfied = not self.get_start_as_guaranteed()
        self._delay_behavior = None
        conditional_actions = self.get_conditional_actions()
        if conditional_actions:
            self._conditional_action_manager = interactions.utils.exit_condition_manager.ConditionalActionManager()
        else:
            self._conditional_action_manager = None
        self._performing = False
        self._start_time = None
        self._loaded_start_time = None
        if load_data.start_time is not None:
            self._loaded_start_time = load_data.start_time
        self._finisher = InteractionFinisher()
        self.interrupting_interaction_guid64 = None
        self.on_pipeline_change_callbacks = CallableList()
        self._must_run_instance = must_run
        self._global_outcome_result = None
        self.outcome_display_message = None
        self._outcome_result_map = {}
        self._posture_target_ref = None
        self._interaction_event_update_alarm = None
        self._liabilities = OrderedDict()
        if load_data is not None and liabilities is not None:
            for liability in liabilities:
                self.add_liability(*liability)
        if self._situation_participant_provider is not None:
            self.add_liability(SituationSimParticipantProviderLiability.LIABILITY_TOKEN, self._situation_participant_provider(self))
        self.route_fail_on_transition_fail = route_fail_on_transition_fail
        self._required_sims = None
        self._required_sims_threading = None
        self._on_cancelled_callbacks = CallableList()
        self.on_path_planned_callbacks = None
        if self.context.continuation_id:
            parent = self.sim.find_interaction_by_id(self.context.continuation_id)
            if parent is not None:
                depended_on_si = parent.depended_on_si
        if depended_on_si is None and depended_on_si is not None:
            depended_on_si.attach_interaction(self)
        self.depended_on_si = depended_on_si
        self.depended_on_until_running = depended_on_until_running
        self._progress_bar_commodity_callback = None
        self._progress_bar_displayed = False
        self.set_work_timestamp = set_work_timestamp
        self._object_create_helper = None
        self._saved_participants = [None, None, None, None] if saved_participants is None else saved_participants
        self._animation_events = []
        self.additional_destination_validity_tests = []
        self.privacy_test_cache = None
        self._additional_instance_basic_extras = None
        self._disable_vehicles_override = disable_vehicles_override
        self.is_waiting_pickup_putdown = False
        self.putdown_derailment_counter = 0
        self.paired_horse_transition_state = CarryTransitionState.NOT_STARTED
        self.wing_putdown_transition_state = {}
        self._context_responsiveness = self.context.responsiveness
        self.context.responsiveness = 0

    def on_asm_state_changed(self, asm:'Asm', state:'str') -> 'None':
        if state == 'exit':
            state = 'entry'
        self._asm_states[asm] = state

    def prevents_distress(self, stat_type:'Statistic') -> 'bool':
        return stat_type in self.commodity_flags

    @property
    def aop(self) -> 'AffordanceObjectPair':
        return self._aop

    @property
    def aop_id(self) -> 'int':
        return self.aop.aop_id

    @property
    def sim(self) -> 'Optional[Sim]':
        return self.context.sim

    @property
    def source(self) -> 'InteractionSource':
        return self.context.source

    @property
    def is_user_directed(self) -> 'bool':
        return self.source == InteractionContext.SOURCE_PIE_MENU or self.source == InteractionContext.SOURCE_SCRIPT_WITH_USER_INTENT

    @property
    def is_autonomous(self) -> 'bool':
        return self.source == InteractionContext.SOURCE_AUTONOMY

    @property
    def object_with_inventory(self) -> 'Optional[ScriptObject]':
        return self._kwargs.get('object_with_inventory')

    @classproperty
    def staging(cls) -> 'bool':
        if cls.basic_content is not None:
            return cls.basic_content.staging
        return False

    @classproperty
    def looping(cls) -> 'bool':
        if cls.basic_content is not None:
            return cls.basic_content.sleeping
        return False

    @classproperty
    def one_shot(cls) -> 'bool':
        basic_content = cls.basic_content
        if basic_content is not None:
            if basic_content.staging:
                return False
            elif basic_content.sleeping:
                return False
        return True

    @classproperty
    def is_basic_content_one_shot(cls) -> 'bool':
        basic_content = cls.basic_content
        if basic_content is not None and (basic_content.staging or basic_content.sleeping):
            return False
        return True

    @property
    def consecutive_running_time_span(self) -> 'TimeSpan':
        if self._start_time is None:
            return TimeSpan.ZERO
        return services.time_service().sim_now - self._start_time

    @property
    def target(self) -> 'Optional[ScriptObject]':
        return self._target

    @property
    def user_facing_target(self) -> 'Optional[ScriptObject]':
        return self.target

    @classproperty
    def immediate(cls) -> 'bool':
        return False

    @contextmanager
    def override_var_map(self, sim:'Sim', var_map:'Dict[PostureSpecVariable, Any]') -> 'None':
        original_target = self.target
        original_carry_track = self.carry_track
        original_slot_manifest = self.slot_manifest
        self._apply_vars(*self._get_vars_from_var_map(sim, var_map))
        yield None
        self._apply_vars(original_target, original_carry_track, original_slot_manifest)

    def apply_var_map(self, sim:'Sim', var_map:'Dict[PostureSpecVariable, Any]') -> 'None':
        self._apply_vars(*self._get_vars_from_var_map(sim, var_map))

    def _get_vars_from_var_map(self, sim:'Sim', var_map:'Dict[PostureSpecVariable, Any]') -> 'Tuple[ScriptObject, PostureTrack, Optional[SlotManifest]]':
        target = var_map.get(PostureSpecVariable.INTERACTION_TARGET)
        hand = var_map.get(PostureSpecVariable.HAND)
        if hand is None:
            carry_track = None
        else:
            carry_track = hand_to_track(hand)
        slot_manifest = var_map.get(PostureSpecVariable.SLOT)
        return (target, carry_track, slot_manifest)

    def _apply_vars(self, target:'ScriptObject', carry_track:'PostureTrack', slot_manifest:'Optional[SlotManifest]') -> 'None':
        self.set_target(target)
        self.carry_track = carry_track
        self.slot_manifest = slot_manifest

    def set_target(self, target:'Optional[ScriptObject]') -> 'None':
        if self.target is target:
            return
        if self.queued and self.target is not None:
            self.target.remove_interaction_reference(self)
            if self.sim is not None and self.sim.transition_controller is not None:
                self.sim.transition_controller.remove_relevant_object(self.target)
        if self.target_type & TargetType.ACTOR or self.target_type & TargetType.FILTERED_TARGET:
            target = None
        elif target is self.sim and target is not None and not self.immediate:
            logger.error('Setting the target of an {} interaction to the running Sim. This can cause errors if the Sim is reset or deleted.', self)
        if self.queued and target is not None:
            target.add_interaction_reference(self)
            if self.sim is not None and self.sim.transition_controller is not None:
                self.sim.transition_controller.add_relevant_object(target)
        self._target = target
        self.refresh_constraints()

    def get_saved_participant(self, index:'int') -> 'Optional[Any]':
        return self._saved_participants[index]

    def set_saved_participant(self, index:'int', obj:'Any') -> 'None':
        self._saved_participants[index] = obj

    def get_saved_participants(self) -> 'List[Any]':
        return tuple(self._saved_participants)

    def is_saved_participant(self, obj:'Any') -> 'bool':
        return any(obj == saved_obj for saved_obj in self._saved_participants if saved_obj is not None)

    @property
    def interaction_parameters(self) -> 'Dict[str, Any]':
        return self._kwargs

    @property
    def continuation_id(self) -> 'Optional[int]':
        return self.context.continuation_id

    @property
    def visual_continuation_id(self) -> 'Optional[int]':
        return self.context.continuation_id or self.context.visual_continuation_id

    def is_continuation_by_id(self, source_id:'int') -> 'bool':
        return source_id is not None and self.continuation_id == source_id

    @property
    def group_id(self) -> 'Optional[int]':
        return self.context.group_id or self.id

    def is_related_to(self, interaction:'Interaction') -> 'bool':
        return self.group_id == interaction.group_id

    @classproperty
    def affordance(cls) -> 'Type':
        return cls

    @classproperty
    def affordances(cls) -> 'Tuple[Type, ...]':
        return (cls.get_interaction_type(),)

    @property
    def super_affordance(self) -> 'Type[Interaction]':
        return self._aop.super_affordance

    @property
    def si_state(self) -> 'Optional[interactions.si_state.SIState]':
        if self.sim is not None:
            return self.sim.si_state

    @property
    def queue(self) -> 'Optional[InteractionQueue]':
        if self.sim is not None:
            return self.sim.queue

    @property
    def visible_as_interaction(self) -> 'bool':
        return self.visible

    @property
    def transition(self) -> 'Optional[TransitionSequenceController]':
        pass

    @property
    def object_create_helper(self) -> 'Optional[CreateObjectHelper]':
        return self._object_create_helper

    @object_create_helper.setter
    def object_create_helper(self, create_helper:'CreateObjectHelper') -> 'None':
        self._object_create_helper = create_helper

    @property
    def carry_target(self) -> 'Optional[ScriptObject]':
        return self.context.carry_target

    @property
    def create_target(self) -> 'Optional[ScriptObject]':
        pass

    @property
    def created_target(self) -> 'Optional[ScriptObject]':
        if self.context.create_target_override is not None:
            return self.context.create_target_override
        if self.object_create_helper is None or self.object_create_helper.is_object_none:
            return
        return self.object_create_helper.object

    @property
    def disable_carry_interaction_mask(self) -> 'bool':
        return False

    @flexmethod
    def get_icon_info(cls, inst, target:'Optional[ScriptObject]'=DEFAULT, context:'Optional[InteractionContext]'=DEFAULT) -> 'IconInfoData':
        inst_or_cls = inst if inst is not None else cls
        resolver = inst_or_cls.get_resolver(target=target, context=context)
        icon_info = inst_or_cls._get_icon(resolver)
        if icon_info is not None:
            return icon_info
        else:
            target = inst.target if inst is not None else target
            if target is not DEFAULT and target is not None:
                return IconInfoData(icon_resource=target.icon)
        return EMPTY_ICON_INFO_DATA

    @flexmethod
    def get_pie_menu_icon_info(cls, inst, target:'Optional[ScriptObject]'=DEFAULT, context:'Optional[InteractionContext]'=DEFAULT, **interaction_parameters) -> 'Optional[IconInfoData]':
        inst_or_cls = inst if inst is not None else cls
        if inst_or_cls.pie_menu_icon is None:
            return
        resolver = inst_or_cls.get_resolver(target=target, context=context)
        icon_info_data = inst_or_cls.pie_menu_icon(resolver)
        return icon_info_data

    @classmethod
    def _get_icon(cls, interaction:'Interaction') -> 'IconInfoData':
        return cls._icon(interaction)

    @classproperty
    def never_user_cancelable(cls) -> 'bool':
        return cls._cancelable_by_user != CancelablePhase.NEVER or cls._must_run

    @property
    def user_cancelable(self, **kwargs) -> 'bool':
        if self.must_run:
            return False
        return self.never_user_cancelable

    @property
    def must_run(self) -> 'bool':
        if self._must_run or self._must_run_instance:
            return True
        return False

    @property
    def super_interaction(self) -> 'Optional[SuperInteraction]':
        return self._super_interaction

    @super_interaction.setter
    def super_interaction(self, si:'Optional[SuperInteraction]') -> 'None':
        if si is self._super_interaction:
            return
        if self._super_interaction is not None:
            self._super_interaction.detach_interaction(self)
        self._super_interaction = si
        if si is not None:
            si.attach_interaction(self)

    @property
    def queued(self) -> 'bool':
        return self.pipeline_progress >= PipelineProgress.QUEUED

    @property
    def prepared(self) -> 'bool':
        return self.pipeline_progress >= PipelineProgress.PREPARED

    @property
    def running(self) -> 'bool':
        return self._run_interaction_element is not None and self._run_interaction_element._child_handle is not None

    @property
    def performing(self) -> 'bool':
        return self._performing

    @property
    def active(self) -> 'bool':
        return self._active

    def get_wing_putdown_transition_state(self, sim:'Sim') -> 'CarryTransitionState':
        if sim not in self.wing_putdown_transition_state:
            self.wing_putdown_transition_state[sim] = CarryTransitionState.NOT_STARTED
        return self.wing_putdown_transition_state[sim]

    def _set_pipeline_progress(self, value:'PipelineProgress') -> 'None':
        self._pipeline_progress = value

    @property
    def pipeline_progress(self) -> 'PipelineProgress':
        return self._pipeline_progress

    @pipeline_progress.setter
    def pipeline_progress(self, value:'PipelineProgress') -> 'None':
        if value is not self._pipeline_progress:
            self._set_pipeline_progress(value)
            self.on_pipeline_change_callbacks(self)

    @property
    def should_reset_based_on_pipeline_progress(self) -> 'bool':
        return self._pipeline_progress >= PipelineProgress.PRE_TRANSITIONING

    def _set_satisfied(self, value:'bool') -> 'None':
        self._satisfied = value

    @property
    def satisfied(self) -> 'bool':
        return self._satisfied

    @satisfied.setter
    def satisfied(self, value:'bool') -> 'None':
        self._set_satisfied(value)

    def get_animation_context_liability(self) -> 'AnimationContextLiability':
        animation_liability = self.get_liability(ANIMATION_CONTEXT_LIABILITY)
        if animation_liability is None:
            animation_context = animation.AnimationContext()
            animation_liability = AnimationContextLiability(animation_context)
            self.add_liability(ANIMATION_CONTEXT_LIABILITY, animation_liability)
        return animation_liability

    @property
    def animation_context(self) -> 'animation.AnimationContext':
        animation_liability = self.get_animation_context_liability()
        return animation_liability.animation_context

    @property
    def priority(self) -> 'Priority':
        return self._priority

    @property
    def run_priority(self) -> 'Priority':
        return self._run_priority

    @priority.setter
    def priority(self, value:'Priority') -> 'None':
        if self._priority == value:
            return
        self._priority = value
        if self.queue is not None:
            self.queue.on_element_priority_changed(self)

    @run_priority.setter
    def run_priority(self, value:'Priority') -> 'None':
        self._run_priority = value
        if self.running:
            logger.error('Setting the run priority of an interaction that is already running, this will not actually cause the interaction to run at a different priority. {}', self)

    @classproperty
    def is_social(cls) -> 'bool':
        return False

    @property
    def social_group(self) -> 'Optional[SocialGroup]':
        pass

    @property
    def liabilities(self) -> 'Tuple[Liability, ...]':
        return reversed(tuple(self._liabilities.values()))

    @property
    def start_time(self) -> 'TimeSpan':
        if self._loaded_start_time is not None:
            return self._loaded_start_time
        return self._start_time

    def should_link_carried_sims(self) -> 'bool':
        link_carried_sims = self.basic_content is not None and self.basic_content.link_carried_sims is not None
        if not link_carried_sims:
            return False
        resolver = self.get_resolver()
        return bool(self.basic_content.link_carried_sims.run_tests(resolver))

    def disable_displace(self, other:'Interaction') -> 'bool':
        return False

    def add_liability(self, key:'str', liability:'Liability') -> 'None':
        target_continuation = self._liability_target_ref() if self._liability_target_ref is not None else None
        if target_continuation is not None and liability.should_transfer(target_continuation):
            liability_target = target_continuation
        else:
            liability_target = self
        old_liability = liability_target.get_liability(key)
        if old_liability is not None:
            liability = old_liability.merge(self, key, liability)
        liability.on_add(liability_target)
        liability_target._liabilities[key] = liability

    def remove_liability(self, key:'str', release:'bool'=True) -> 'None':
        liability = self.get_liability(key)
        if liability is not None:
            if release:
                liability.release()
            del self._liabilities[key]

    def get_liability(self, key:'str') -> 'Optional[Liability]':
        if key in self._liabilities:
            return self._liabilities[key]

    def _acquire_liabilities(self) -> 'None':
        parent = None
        if self.context.continuation_id:
            parent = self.sim.find_interaction_by_id(self.context.continuation_id)
        elif self.context.source_interaction_id:
            if self.context.source_interaction_sim_id is not None:
                sim = services.object_manager().get(self.context.source_interaction_sim_id)
            else:
                sim = self.sim
            if sim is not None:
                parent = sim.find_interaction_by_id(self.context.source_interaction_id)
        if parent is not None:
            if parent.is_super or self.is_super:
                return
            parent._liability_target_ref = weakref.ref(self)
            parent.release_liabilities(continuation=self)

    def release_liabilities(self, continuation:'Optional[Interaction]'=None, liabilities_to_release:'Tuple[Liability, ...]'=()) -> 'None':
        exception = None
        if continuation is not None:
            source_interaction_id = continuation.context.source_interaction_id
            continuation_id = continuation.context.continuation_id
        is_super_to_mixer_continuation = False if continuation is None else self.is_super and not continuation.is_super
        for (key, liability) in list(self._liabilities.items()):
            if liabilities_to_release:
                if key in liabilities_to_release:
                    if not (key != ANIMATION_CONTEXT_LIABILITY or continuation.ignore_animation_context_liability):
                        if source_interaction_id and isinstance(liability, SharedLiability):
                            if is_super_to_mixer_continuation:
                                if liability.is_super_to_mixer_transfer_allowed:
                                    continuation.request_liability_transfer_to_si(None, key, liability)
                            else:
                                continuation_liability = liability.create_new_liability(continuation)
                                continuation.add_liability(key, continuation_liability)
                        elif continuation_id is not None:
                            if is_super_to_mixer_continuation:
                                if liability.is_super_to_mixer_transfer_allowed:
                                    continuation.request_liability_transfer_to_si(self, key, liability)
                            else:
                                liability.transfer(continuation)
                                continuation.add_liability(key, liability)
                                del self._liabilities[key]
                            try:
                                liability.release()
                                del self._liabilities[key]
                            except BaseException as ex:
                                logger.exception('Liability {} threw exception {}', liability, ex)
                                if exception is None:
                                    exception = ex
                        try:
                            liability.release()
                            del self._liabilities[key]
                        except BaseException as ex:
                            logger.exception('Liability {} threw exception {}', liability, ex)
                            if exception is None:
                                exception = ex
            if not (key != ANIMATION_CONTEXT_LIABILITY or continuation.ignore_animation_context_liability):
                if source_interaction_id and isinstance(liability, SharedLiability):
                    if is_super_to_mixer_continuation:
                        if liability.is_super_to_mixer_transfer_allowed:
                            continuation.request_liability_transfer_to_si(None, key, liability)
                    else:
                        continuation_liability = liability.create_new_liability(continuation)
                        continuation.add_liability(key, continuation_liability)
                elif continuation_id is not None:
                    if is_super_to_mixer_continuation:
                        if liability.is_super_to_mixer_transfer_allowed:
                            continuation.request_liability_transfer_to_si(self, key, liability)
                    else:
                        liability.transfer(continuation)
                        continuation.add_liability(key, liability)
                        del self._liabilities[key]
                    try:
                        liability.release()
                        del self._liabilities[key]
                    except BaseException as ex:
                        logger.exception('Liability {} threw exception {}', liability, ex)
                        if exception is None:
                            exception = ex
                try:
                    liability.release()
                    del self._liabilities[key]
                except BaseException as ex:
                    logger.exception('Liability {} threw exception {}', liability, ex)
                    if exception is None:
                        exception = ex
        if exception is not None:
            raise exception

    @flexmethod
    def get_situation_participant_provider(cls, inst) -> 'Optional[SituationSimParticipantProviderLiability]':
        if inst is not None:
            liability = inst.get_liability(SituationSimParticipantProviderLiability.LIABILITY_TOKEN)
            if liability is not None:
                return liability
            elif cls._situation_participant_provider is not None:
                return cls._situation_participant_provider()
        elif cls._situation_participant_provider is not None:
            return cls._situation_participant_provider()

    @property
    def is_affordance_locked(self) -> 'bool':
        return self.sim.is_affordance_locked(self.affordance)

    @classmethod
    def add_additional_static_commodity_data(cls, static_commodity_data:'Tuple[StaticCommodity, int]') -> 'None':
        if cls._additional_static_commodities is None:
            cls._additional_static_commodities = []
        cls._additional_static_commodities.append(static_commodity_data)

    @classmethod
    def remove_additional_static_commodity_data(cls, static_commodity_data:'Tuple[StaticCommodity, int]') -> 'None':
        cls._additional_static_commodities.remove(static_commodity_data)

    @classproperty
    def can_holster_incompatible_carries(cls) -> 'bool':
        return True

    @classproperty
    def allow_holstering_of_owned_carries(cls) -> 'bool':
        return False

    @classproperty
    def allow_with_unholsterable_carries(cls) -> 'bool':
        if cls.basic_content is not None and cls.basic_content.allow_with_unholsterable_object is not None:
            return cls.basic_content.allow_with_unholsterable_object
        return not cls.is_super

    @property
    def combined_posture_preferences(self) -> 'Any':
        return self.posture_preferences

    @flexmethod
    def get_constraint_resolver(cls, inst, posture_state:'Optional[PostureState]', *args, participant_type:'ParticipantType'=ParticipantType.Actor, force_actor:'Optional[Sim]'=None, **kwargs) -> 'Resolver':
        if posture_state is not None:
            posture_sim = posture_state.sim
            participant_sims = inst.get_participants(participant_type)
        inst_or_cls = inst if inst is not None and inst is not None else cls

        def resolver(constraint_participant, default=None):
            result = default
            if constraint_participant == AnimationParticipant.ACTOR:
                if force_actor is not None:
                    result = force_actor
                else:
                    result = inst_or_cls.get_participant(participant_type, *args, **kwargs)
            elif constraint_participant == AnimationParticipant.TARGET or constraint_participant == PostureSpecVariable.INTERACTION_TARGET:
                if inst_or_cls.target_type == TargetType.FILTERED_TARGET and posture_state is not None:
                    result = posture_state.body_target
                else:
                    if inst_or_cls.is_social and participant_type == ParticipantType.TargetSim:
                        target_participant_type = ParticipantType.Actor
                    else:
                        target_participant_type = ParticipantType.Object
                    result = inst_or_cls.get_participant(target_participant_type, *args, **kwargs)
            elif constraint_participant == AnimationParticipant.CARRY_TARGET or constraint_participant == PostureSpecVariable.CARRY_TARGET:
                result = inst_or_cls.get_participant(ParticipantType.CarriedObject, *args, **kwargs)
            elif constraint_participant == AnimationParticipant.CREATE_TARGET:
                if inst is not None:
                    result = inst.create_target
            elif constraint_participant == AnimationParticipant.BASE_OBJECT:
                if posture_state is not None:
                    result = posture_state.body.target
                else:
                    result = PostureSpecVariable.BODY_TARGET_FILTERED
            elif constraint_participant == AnimationParticipant.CONTAINER or constraint_participant == PostureSpecVariable.CONTAINER_TARGET:
                if posture_state is not None:
                    result = posture_state.body.target
                else:
                    result = PostureSpecVariable.CONTAINER_TARGET
            elif constraint_participant in (AnimationParticipant.SURFACE, PostureSpecVariable.SURFACE_TARGET):
                if posture_state is not None:
                    result = posture_state.surface_target
                    if result is None:
                        result = MATCH_NONE
                elif default == AnimationParticipant.SURFACE:
                    result = PostureSpecVariable.SURFACE_TARGET
            elif inst_or_cls.asm_actor_overrides:
                for override in inst_or_cls.asm_actor_overrides:
                    if override.actor_name == constraint_participant:
                        override_actor_obj = inst_or_cls.get_participant(override.actor_participant, *args, **kwargs)
                        result = override_actor_obj
                        break
            return result

        return resolver

    @flexmethod
    def apply_posture_state_and_interaction_to_constraint(cls, inst, posture_state:'Optional[PostureState]', constraint:'Constraint', *args, participant_type:'ParticipantType'=ParticipantType.Actor, sim:'Optional[Sim]'=DEFAULT, allow_holster:'Optional[bool]'=DEFAULT, invalid_expected:'bool'=False, base_object:'Optional[ScriptObject]'=None, **kwargs) -> 'Constraint':
        inst_or_cls = inst if inst is not None else cls
        sim = sim if sim is not DEFAULT else posture_state.sim
        if posture_state is not None:
            body_target = posture_state.body_target
            if body_target is not None and body_target.is_part and not body_target.supports_posture_spec(posture_state.get_posture_spec({}), inst_or_cls):
                return Nowhere('Body Target({}) does not support PostureSpec({})', body_target, posture_state.get_posture_spec({}))
        allow_holster = inst_or_cls.can_holster_incompatible_carries and sim.is_allowed_to_holster() if allow_holster is DEFAULT else allow_holster
        if allow_holster:
            constraint = constraint.get_holster_version()
        constraint_resolver = inst_or_cls.get_constraint_resolver(posture_state, *args, participant_type=participant_type, sim=sim, **kwargs)
        result = constraint.apply_posture_state(posture_state, constraint_resolver, invalid_expected=invalid_expected, base_object=base_object)
        return result

    def log_participants_to_gsi(self) -> 'None':
        if gsi_handlers.interaction_archive_handlers.is_archive_enabled(self):
            for participant in self.get_participants(ParticipantType.All):
                ptype = self.get_participant_type(participant)
                gsi_handlers.interaction_archive_handlers.add_participant(self, ptype, participant)

    def get_asm(self, asm_key:'sims4.resources.Key', actor_name:'str', target_name:'str', carry_target_name:'str', setup_asm_override:'Optional[Callable[[Asm], None]]'=DEFAULT, animation_context:'Optional[animation.AnimationContext]'=DEFAULT, posture:'Optional[Posture]'=None, use_cache:'bool'=True, posture_manifest_overrides:'Optional[AnimationOverrides]'=None, **kwargs) -> 'Asm':
        if posture is None:
            posture = self.sim.posture
        if setup_asm_override is DEFAULT:
            setup_asm_override = lambda asm: self.setup_asm_default(asm, actor_name, target_name, carry_target_name, posture=posture, **kwargs)
        if animation_context is DEFAULT:
            animation_context = self.animation_context
        cache_key = (self.group_id, animation_context.request_id)
        animation_liability = self.get_animation_context_liability()
        cached_keys = animation_liability.cached_asm_keys[posture]
        cached_keys.add(cache_key)
        asm = posture.get_registered_asm(animation_context, asm_key, setup_asm_override, use_cache=use_cache, cache_key=cache_key, interaction=self, posture_manifest_overrides=posture_manifest_overrides)
        if asm is None:
            return
        current_state = self._asm_states.get(asm)
        if current_state is not None:
            asm.set_current_state(current_state)
        else:
            self._asm_states[asm] = None
        return asm

    def setup_asm_default(self, asm:'Asm', actor_name:'str', target_name:'str', carry_target_name:'str', posture:'Optional[Posture]'=None, create_target_name:'Optional[str]'=None, base_object_name:'Optional[str]'=None, custom_part_owner_actor_name:'Optional[str]'=None) -> 'TestResult':
        if posture is None:
            posture = self.sim.posture
        carry_track = self.carry_track if self.carry_track is not None else DEFAULT
        result = posture.setup_asm_interaction(asm, self.sim, self.target, actor_name, target_name, carry_target=self.carry_target, carry_target_name=carry_target_name, create_target_name=create_target_name, carry_track=carry_track, base_object_name=base_object_name, custom_part_owner_actor_name=custom_part_owner_actor_name)
        if not result:
            return result
        if self.target is not None:
            surface_height = get_surface_height_parameter_for_object(self.target, sim=self.sim)
            asm.set_parameter(PARAM_SURFACE_HEIGHT, surface_height)
            placement_height = get_placement_height_parameter_for_object(self.target, sim=self.sim)
            asm.set_parameter(PARAM_PLACEMENT_HEIGHT, placement_height)
            if self.context.carry_hand is not None:
                asm.set_parameter(PARAM_CONTEXT_CARRY_HAND, self.context.carry_hand.name.lower())
        if self.set_terrain_type_param is not None:
            self.set_terrain_type_asm_parameter(asm)
        self.super_interaction.set_stat_asm_parameter(asm, actor_name, target_name, self.sim, self.target)
        self.super_interaction.set_walkstyle_asm_parameter(asm, actor_name, target_name, self.sim, self.target)
        self.sim.set_mood_asm_parameter(asm, actor_name)
        self.sim.set_trait_asm_parameters(asm, actor_name)
        if target_name is not None and self.target is not None and self.target.is_sim:
            self.target.set_mood_asm_parameter(asm, target_name)
            self.target.set_trait_asm_parameters(asm, target_name)
        if create_target_name is not None and asm.get_actor_definition(create_target_name) is not None and self.created_target is not None:
            result = asm.add_potentially_virtual_actor(actor_name, self.sim, create_target_name, self.created_target, target_participant=AnimationParticipant.CREATE_TARGET)
            if not result:
                return result
            set_carry_track_param_if_needed(asm, self.sim, create_target_name, self.created_target, carry_track)
        if self.locked_params:
            virtual_actor_map = {target_name: self.target} if target_name is not None else None
            locked_params_log = None
            if asm.locked_params_log_enabled:
                param_context = LockedParamsContext(LockedParamCategory.INTERACTION, str(self))
                locked_params_log = create_locked_params_log(self.locked_params, param_context)
            asm.update_locked_params(self.locked_params, virtual_actor_map, locked_params_log=locked_params_log)
        if self.asm_actor_overrides:
            for override in self.asm_actor_overrides:
                override_actor_obj = self.get_participant(override.actor_participant)
                if override_actor_obj is not None:
                    asm.add_potentially_virtual_actor(actor_name, self.sim, override.actor_name, override_actor_obj)
        if self.set_carry_track_param:
            if self.sim.parent is not None and self.sim.parent.is_sim:
                set_carry_track_param_if_needed(asm, self.sim.parent, actor_name, self.sim)
            if target_name is not None and (self.target is not None and self.target.parent is not None) and self.target.parent.is_sim:
                set_carry_track_param_if_needed(asm, self.target.parent, target_name, self.target)
        return event_testing.results.TestResult.TRUE

    def set_terrain_type_asm_parameter(self, asm:'Asm') -> 'None':
        if self.sim.transition_controller is None:
            return
        interaction_constraint = self.sim.transition_controller.get_final_constraint(self.sim)
        if not interaction_constraint:
            return
        if interaction_constraint == ANYWHERE:
            interaction_constraint_position = self.sim.position
            routing_surface_id = self.sim.intended_routing_surface.secondary_id
        else:
            interaction_constraint_position = interaction_constraint.average_position
            routing_surface_id = interaction_constraint.routing_surface.secondary_id
        if interaction_constraint_position is None:
            return
        weather_service = services.weather_service()
        if weather_service is not None:
            time = services.time_service().sim_now
            current_snow_accumulation = weather_service.get_weather_element_value(int(GroundCoverType.SNOW_ACCUMULATION), time)
            if current_snow_accumulation > 0:
                asm.set_parameter(PARAM_TERRAIN_TYPE, TerrainTag.SNOW.name)
                return
        test_floor_tiles = self.set_terrain_type_param.test_floor_tiles
        for terrainTag in TerrainTag:
            if terrainTag == TerrainTag.INVALID:
                pass
            else:
                terrain_tag_test = terrain.is_terrain_tag_at_position(interaction_constraint_position.x, interaction_constraint_position.z, (terrainTag,), level=routing_surface_id, test_floor_tiles=test_floor_tiles)
                if terrain_tag_test:
                    asm.set_parameter(PARAM_TERRAIN_TYPE, terrainTag.name)
                    return
        asm.set_parameter(PARAM_TERRAIN_TYPE, TerrainTag.GRASS.name)

    def with_listeners(self, sims:'List[Sim]', sequence:'Element') -> 'Element':
        listeners = WeakSet(sims)
        if self._add_actor_sim_as_listener and self.sim is not None:
            listeners.add(self.sim)

        def event_handler_reactionlet(event_data):
            asm_name_default = event_data.event_data['reaction_name']
            public_state_name = event_data.event_data['public_state']
            self._trigger_reactionlets(listeners, asm_name_default, public_state_name)

        sequence = with_event_handlers(self.animation_context, event_handler_reactionlet, animation.ClipEventType.Reaction, sequence=sequence, tag='reactionlets')
        for listener in sims:
            if listener is self.sim:
                pass
            else:
                sequence = with_sim_focus(self.sim, listener, self.sim, SimFocus.LAYER_INTERACTION, sequence, score=0.999)
        return sequence

    def _trigger_reactionlets(self, listeners:'List[Sim]', asm_name_default:'str', public_state_name:'str') -> 'None':
        if self.super_interaction is None:
            return
        for listener in listeners:

            def setup_asm_listener(asm):
                listener_actor_name = 'x'
                setup_result = listener.posture.setup_asm_interaction(asm, listener, None, listener_actor_name, None)
                if setup_result:
                    listener.set_trait_asm_parameters(asm, listener_actor_name)
                    if self.set_carry_track_param and listener.parent is not None and listener.parent.is_sim:
                        set_carry_track_param_if_needed(asm, listener.parent, listener_actor_name, listener)
                return setup_result

            reactionlet = self.outcome.get_reactionlet(self, setup_asm_override=setup_asm_listener, sim=listener)
            if reactionlet is not None:
                asm = reactionlet.get_asm()
                if asm is None:
                    logger.error('Reactionlet {} from Interaction {} does not have an animation element tuned', reactionlet, self, owner='rmccord')
                    return
                arb = animation.arb.Arb()
                reactionlet.append_to_arb(asm, arb)
                distribute_arb_element(arb)
            else:
                asm = listener.posture.get_registered_asm(self.animation_context, asm_name_default, setup_asm_listener, interaction=self, use_cache=False)
                if asm is not None:
                    reaction_arb = animation.arb.Arb()
                    if public_state_name is None:
                        asm.request('exit', reaction_arb)
                    else:
                        asm.request(public_state_name, reaction_arb)
                    distribute_arb_element(reaction_arb)

    def refresh_constraints(self) -> 'None':
        self._constraint_cache.clear()
        self._constraint_cache_final.clear()

    def apply_posture_state(self, posture_state:'PostureState', participant_type:'ParticipantType'=ParticipantType.Actor, sim:'Optional[Sim]'=DEFAULT) -> 'None':
        if posture_state in self._constraint_cache_final:
            return
        intersection = self.constraint_intersection(sim=sim, participant_type=participant_type, posture_state=posture_state, force_concrete=True)
        if posture_state is not None:
            posture_state.add_constraint(self, intersection)

    def _setup_gen(self, timeline:'Timeline') -> 'Iterator[bool]':
        if self.super_interaction is None:
            return False
        if not self.super_interaction.can_run_subinteraction(self):
            return False
        yield from self.si_state.process_gen(timeline)
        self._active = True
        return True

    def setup_gen(self, timeline) -> 'Iterator[Tuple[bool, Optional[str]]]':
        interaction_parameters = {}
        interaction_parameters['interaction_starting'] = True
        test_result = self.test(skip_safe_tests=self.skip_test_on_execute(), **interaction_parameters)
        if not test_result:
            return (test_result.result, test_result.reason)
        result = yield from self._setup_gen(timeline)
        return (result, None)

    @property
    def should_rally(self) -> 'bool':
        return False

    def maybe_bring_group_along(self, **kwargs) -> 'None':
        pass

    def pre_process_interaction(self) -> 'None':
        pass

    def post_process_interaction(self) -> 'None':
        pass

    def _validate_posture_state(self) -> 'bool':
        return True

    def perform_gen(self, timeline:'Timeline') -> 'Iterator[Tuple[bool, Optional[str]]]':
        if self.sim.transition_controller is not None:
            constraint = self.sim.transition_controller.get_final_constraint(self.sim)
            constraint.apply_posture_state(self.sim.posture_state, self.get_constraint_resolver(self.sim.posture_state))
            (single_point, _) = constraint.single_point()
            if single_point is None:
                self.remove_liability(STAND_SLOT_LIABILITY)
        if not self.disable_transitions:
            constraint_interaction = self.constraint_intersection()
            if constraint_interaction.tentative:
                raise AssertionError("Interaction's constraints are still tentative in perform(): {}.".format(self))
            if self.is_super or not (self.super_interaction is not None and constraint_interaction.valid):
                self.super_interaction._num_nowhere_mixers_executed_in_perform += 1
                if self.super_interaction._num_nowhere_mixers_executed_in_perform > Interaction.MAX_NOWHERE_MIXERS:
                    logger.error("SI: {} is repeatedly executing mixers with a nowhere constraint, auto completing the interaction so the game won't hang.", self.super_interaction)
                    self.super_interaction._auto_complete()
        (result, reason) = yield from self.setup_gen(timeline)
        if not result:
            self.cancel(FinishingType.FAILED_TESTS, cancel_reason_msg='Interaction failed setup on perform. {}'.format(result))
            return (result, reason)
        if self.is_finishing or not self._active:
            return (False, 'is_finishing or not active')
        if self._run_priority is not None:
            self._priority = self._run_priority
        completed = True
        consumed_exc = None
        try:
            self._performing = True
            self._start_time = services.time_service().sim_now
            if not self._pre_perform():
                return (False, 'pre_perform failed')
            self._trigger_interaction_start_event()
            self._add_club_rewards_liability()
            for liability in list(self._liabilities.values()):
                liability.on_run()
            completed = False
            yield from self._do_perform_trigger_gen(timeline)
            completed = True
            if self.provided_posture_type is None:
                non_played_sims = []
                active_sim_found = False
                for required_sim in self.required_sims():
                    required_sim.last_affordance = self.affordance
                    lod = required_sim.sim_info.lod
                    if lod == SimInfoLODLevel.ACTIVE:
                        active_sim_found = True
                    elif lod < SimInfoLODLevel.FULL:
                        non_played_sims.append(required_sim)
                if non_played_sims:
                    culling_service = services.get_culling_service()
                    for required_sim in non_played_sims:
                        culling_service.sim_interacted_with_active_sim(required_sim)
            self._post_perform()
        except Exception as exc:
            for posture in self.sim.posture_state.aspects:
                if posture.source_interaction is self:
                    raise
            logger.exception('Exception while running interaction {0}', self)
            consumed_exc = exc
        finally:
            if not completed:
                with consume_exceptions('Interactions', 'Exception thrown while tearing down an interaction:'):
                    self.detach_conditional_actions()
                    self.kill()
            self._delay_behavior = None
            self._performing = False
            self.clear_outcome_results()
            if not self.is_super:
                self.remove_liability(AUTONOMY_MODIFIER_LIABILITY)
        return (True, None)

    def _pre_perform(self) -> 'bool':
        if self.basic_content.sleeping:
            self._delay_behavior = element_utils.soft_sleep_forever()
        curfew_service = services.get_curfew_service()
        if self.basic_content is not None and curfew_service.sim_breaking_curfew(self.sim, self.target, self):
            curfew_service.add_broke_curfew_buff(self.sim)
        return True

    def _stop_delay_behavior(self) -> 'None':
        if self._delay_behavior is not None:
            self._delay_behavior.trigger_soft_stop()
            self._delay_behavior = None

    def _conditional_action_satisfied_callback(self, condition_group:'ConditionGroup') -> 'None':
        conditional_action = condition_group.conditional_action
        action = conditional_action.interaction_action
        if action == ConditionalInteractionAction.GO_INERTIAL:
            self.satisfied = True
            self._stop_delay_behavior()
        elif action == ConditionalInteractionAction.EXIT_NATURALLY:
            if not self.is_finishing:
                self._finisher.on_pending_finishing_move(FinishingType.NATURAL, self)
            self.satisfied = True
            if self.staging:
                self.cancel(FinishingType.NATURAL, cancel_reason_msg='Conditional Action: Exit Naturally')
            self._stop_delay_behavior()
        elif action == ConditionalInteractionAction.EXIT_CANCEL:
            self.cancel(FinishingType.CONDITIONAL_EXIT, cancel_reason_msg='Conditional Action: Exit Cancel')
            self._stop_delay_behavior()
        elif action == ConditionalInteractionAction.LOWER_PRIORITY:
            self.priority = interactions.priority.Priority.Low
        if gsi_handlers.interaction_archive_handlers.is_archive_enabled(self):
            gsi_handlers.interaction_archive_handlers.add_exit_reason(self, action, condition_group)
        loot_actions = conditional_action.loot_actions
        if loot_actions:
            resolver = self.get_resolver()
            for actions in loot_actions:
                actions.apply_to_resolver(resolver)

    def refresh_conditional_actions(self) -> 'None':
        if self._conditional_action_manager:
            self.detach_conditional_actions()
            self.attach_conditional_actions()

    def attach_conditional_actions(self) -> 'None':
        if self.staging:
            self._satisfied = not self.get_start_as_guaranteed()
        conditional_actions = self.get_conditional_actions()
        if conditional_actions:
            self._conditional_action_manager.attach_conditions(self, conditional_actions, self._conditional_action_satisfied_callback, interaction=self)

    def detach_conditional_actions(self) -> 'None':
        if self._conditional_action_manager is not None:
            self._conditional_action_manager.detach_conditions(self, exiting=True)

    def _run_gen(self, timeline:'Timeline') -> 'Iterator[bool]':
        result = yield from self._do_perform_gen(timeline)
        return result

    def _do_perform_trigger_gen(self, timeline:'Timeline') -> 'Iterator[bool]':
        result = yield from self._do_perform_gen(timeline)
        return result

    def _get_behavior(self) -> 'Element':
        self._run_interaction_element = self.build_basic_elements(sequence=self._run_interaction_gen)
        return self._run_interaction_element

    def _clean_behavior(self) -> 'None':
        self._run_interaction_element = None

    def _do_perform_gen(self, timeline:'Timeline') -> 'Iterator[bool]':
        interaction_element = self._get_behavior()
        result = yield from element_utils.run_child(timeline, interaction_element)
        return result

    def register_additional_event_handlers(self, animation_context:'AnimationContext') -> 'None':
        if animation_context is not None:
            for (callback, handler_id) in self._animation_events:
                animation_context.register_event_handler(callback, handler_id=handler_id)
        else:
            logger.error('ASM Animation Context is None for interaction: {}. Cannot register additional event handlers.', self, owner='rmccord')
        self._animation_events.clear()

    def store_event_handler(self, callback:'Callable[[ArbEventData], Optional[str]]', handler_id:'Optional[int]'=None) -> 'None':
        self._animation_events.append((callback, handler_id))

    def build_basic_content(self, sequence:'Any'=(), **kwargs) -> 'Any':
        if self.basic_content is not None:
            sequence = self.basic_content(self, sequence=sequence, **kwargs)
            sequence = build_critical_section(sequence, self._set_last_animation_factory)
        if self.target is not None:
            target_basic_content = self.target.get_affordance_basic_content(self)
            if target_basic_content is not None:
                sequence = target_basic_content(self, sequence=sequence, **kwargs)
        return sequence

    def _build_outcome_sequence(self) -> 'Any':
        target_sequence = None
        if self.target is not None:
            target_outcome = self.target.get_affordance_outcome(self)
            if target_outcome.has_content:
                target_sequence = target_outcome.build_elements(self, interaction_outcome=self.outcome)
        sequence = self.outcome.build_elements(self, update_global_outcome_result=True, send_telemetry=target_sequence is None)
        if target_sequence is not None:
            sequence = (sequence, target_sequence)
        return sequence

    def build_outcome(self) -> 'Any':
        return self._build_outcome_sequence()

    def build_basic_extras(self, sequence:'Any'=()) -> 'Any':
        for factory in reversed(self.basic_extras):
            sequence = factory(self, sequence=sequence)
        if self.target is not None:
            target_basic_extras = self.target.get_affordance_basic_extras(self)
            for factory in reversed(target_basic_extras):
                sequence = factory(self, sequence=sequence)
        if self._additional_basic_extras is not None:
            for factory in reversed(self._additional_basic_extras):
                sequence = factory(self, sequence=sequence)
        if self._additional_instance_basic_extras is not None:
            for factory in reversed(self._additional_instance_basic_extras):
                sequence = factory(self, sequence=sequence)
        if self.sim is not None:
            for factory in self.sim.get_actor_basic_extras_reversed_gen(self.affordance, self.get_resolver()):
                sequence = factory(self, sequence=sequence)
        if self.confirmation_dialog is not None:

            def on_response(dialog):
                if not dialog.accepted:
                    if self.confirmation_dialog.continuation_on_cancel:
                        self.push_tunable_continuation(self.confirmation_dialog.continuation_on_cancel)
                    self.cancel(FinishingType.USER_CANCEL, cancel_reason_msg='User did not confirm.')
                return dialog.accepted

            sequence = (self.confirmation_dialog.dialog(self.sim, self.get_resolver(), on_response=on_response), sequence)
        return sequence

    @classmethod
    def add_additional_basic_extra(cls, basic_extra:'Element') -> 'None':
        if cls._additional_basic_extras is None:
            cls._additional_basic_extras = []
        cls._additional_basic_extras.append(basic_extra)

    @classmethod
    def remove_additional_basic_extra(cls, basic_extra:'Element') -> 'None':
        cls._additional_basic_extras.remove(basic_extra)
        if not cls._additional_basic_extras:
            cls._additional_basic_extras = None

    @classmethod
    def add_additional_basic_liability(cls, basic_liability:'Liability') -> 'None':
        if cls._additional_basic_liabilities is None:
            cls._additional_basic_liabilities = []
        cls._additional_basic_liabilities.append(basic_liability)

    def add_additional_instance_basic_extra(self, basic_extra:'Element') -> 'None':
        if self._additional_instance_basic_extras is None:
            self._additional_instance_basic_extras = []
        self._additional_instance_basic_extras.append(basic_extra)

    def build_item_cost_element(self, sequence:'Any') -> 'Element':
        return self.item_cost.build_element_for_interaction(sequence, self)

    def _decay_topics(self, e:'Element') -> 'None':
        if self.sim is not None:
            self.sim.decay_topics()

    def _set_last_animation_factory(self, _:'Any') -> 'None':
        if self.basic_content.animation_ref is not None:
            self.sim.last_animation_factory = self.basic_content.animation_ref.factory
            if self.target.parent is self.sim:
                self.target.last_animation_factory = None

    def get_keys_to_process_events(self) -> 'Set[Any]':
        custom_keys = set(self.get_category_tags())
        custom_keys.add(self.affordance.get_interaction_type())
        associated_skill = self.get_associated_skill()
        if associated_skill is not None:
            custom_keys.update(associated_skill.tags)
        return custom_keys

    def _trigger_interaction_complete_test_event(self) -> 'None':
        custom_keys = self.get_keys_to_process_events()
        for participant in self.get_participants(ParticipantType.AllSims):
            if participant is not None:
                services.get_event_manager().process_event(test_events.TestEvent.InteractionComplete, sim_info=participant.sim_info, interaction=self, custom_keys=custom_keys)
        self.remove_event_auto_update()

    def _trigger_interaction_exited_pipeline_test_event(self) -> 'None':
        custom_keys = self.get_keys_to_process_events()
        actor = self.sim
        if actor is not None:
            services.get_event_manager().process_event(test_events.TestEvent.InteractionExitedPipeline, sim_info=actor.sim_info, interaction=self, custom_keys=custom_keys)

    def _build_pre_elements(self) -> 'Optional[Callable[[], None]]':
        pass

    def build_basic_elements(self, sequence:'Element'=(), **kwargs) -> 'Element':
        sequence = self.build_basic_content(sequence=sequence, **kwargs)
        sequence = (lambda _: self.send_current_progress(), sequence)
        if not self.is_basic_content_one_shot:
            sequence = build_critical_section_with_finally(lambda _: self.attach_conditional_actions(), sequence, lambda _: self.detach_conditional_actions())
        listeners = list(self.get_participants(ParticipantType.Listeners, listener_filtering_enabled=True))
        sequence = self.with_listeners(listeners, sequence)
        sequence = self.build_item_cost_element(sequence)
        sequence = build_critical_section(sequence, self._decay_topics, self.build_outcome())
        sequence = self.build_basic_extras(sequence=sequence)
        if self.sim is not None:
            sequence = (lambda _: self.sim.trait_tracker.update_day_night_tracking_state(), sequence)
            if self.sim.has_component(objects.components.types.WEATHER_AWARE_COMPONENT):
                sequence = (lambda _: self.sim.weather_aware_component.on_location_changed_callback(), sequence)
        if not self.disable_transitions:
            create_target_track = self.carry_track if self.create_target is not None else None
            sequence = interact_with_carried_object(self.sim, self.carry_target or self.target, interaction=self, create_target_track=create_target_track, sequence=sequence)
            sequence = holster_carried_object(self.sim, self, self.should_unholster_carried_object, sequence=sequence)
            sequence = self.sim.maybe_append_reins_animation(self, sequence)
            if self.target.is_sim:
                sequence = self.target.maybe_append_reins_animation(self, sequence)
        if self.provided_posture_type is None:
            if self.basic_focus is False:
                sequence = sim_focus.without_sim_focus(self.sim, self.sim, sequence)
            else:
                sequence = self.basic_focus(self, sequence=sequence)
        if (self.immediate or self.basic_focus is not None) and self.autonomy_preference is not None:
            preference = self.autonomy_preference.preference
            should_set = preference.should_set
            if should_set and self.is_user_directed or should_set.autonomous or preference.should_clear:

                def set_preference(_):
                    self.sim.set_autonomy_preference(preference, self.target, self.context)
                    return True

                sequence = (set_preference, sequence)
        if self.target is not None and (self._provided_posture_type is None and isinstance(self.target, objects.game_object.GameObject)) and self.target.autonomy_modifiers:
            self.add_liability(AUTONOMY_MODIFIER_LIABILITY, AutonomyModifierLiability(self))
        for participant in self.get_participants(ParticipantType.All):
            sequence = participant.add_modifiers_for_interaction(self, sequence=sequence)
        reservation_handler = self.get_interaction_reservation_handler()
        if not self.get_liability(RESERVATION_LIABILITY):
            sequence = reservation_handler.do_reserve(sequence=sequence)
        if not (reservation_handler is not None and self.immediate):
            sequence = build_critical_section(sequence, flush_all_animations)
        communicable_commodities = set()
        if self.sim is not None:
            communicable_commodities |= self.sim.commodity_tracker.get_communicable_statistic_set()
        if self.target != self.sim:
            communicable_commodities |= self.target.commodity_tracker.get_communicable_statistic_set()
        for commodity in communicable_commodities:
            for tag_loot_pair in commodity.communicable_by_interaction_tag:
                if len(self.get_category_tags() & {tag_loot_pair.tag}) > 0:
                    tag_loot_pair.loot.apply_to_resolver(self.get_resolver())
                    break
        animation_liability = self.get_animation_context_liability()
        sequence = build_critical_section_with_finally(lambda _: animation_liability.setup_props(self), sequence, lambda _: animation_liability.unregister_handles(self))
        template_affordance_tracker = self.sim.sim_info.template_affordance_tracker if self.target is not None and self.sim is not None else None
        provided_template_affordances = self.provided_template_affordances
        if provided_template_affordances is not None:
            sequence = build_critical_section_with_finally(lambda _: template_affordance_tracker.on_affordance_template_start(provided_template_affordances), sequence, lambda _: template_affordance_tracker.on_affordance_template_stop(provided_template_affordances))

        def sync_element(_):
            try:
                if self.sim is None or self.immediate:
                    return
                noop = distributor.ops.SetLocation(self.sim)
                added_additional_channel = False
                for additional_sim in self.required_sims():
                    if additional_sim is self.sim:
                        pass
                    else:
                        added_additional_channel = True
                        noop.add_additional_channel(additional_sim.manager.id, additional_sim.id)
                if added_additional_channel:
                    distributor.ops.record(self.sim, noop)
            except Exception:
                logger.exception('Exception when trying to create the Sync Element at the end of {}', self, owner='maxr')

        return build_element((self._build_pre_elements(), sequence, sync_element))

    def _run_interaction_gen(self, timeline:'Timeline') -> 'Iterator[bool]':
        if self._delay_behavior:
            result = yield from element_utils.run_child(timeline, self._delay_behavior)
            return result
        return True

    def get_continuation_aop_and_context(self, continuation:'Interaction', actor:'Sim', insert_strategy:'QueueInsertStrategy'=QueueInsertStrategy.NEXT, affordance_override:'Optional[Type[Interaction]]'=None, **kwargs) -> 'Tuple[AffordanceObjectPair, InteractionContext]':
        if actor is self.sim:
            if self.immediate:
                clone_context_fn = functools.partial(self.context.clone_from_immediate_context, self)
            else:
                context_source_si = self.super_interaction if continuation.affordance.is_super else None
                if context_source_si is not None:
                    source = context_source_si.context.source
                    priority = context_source_si.context.priority
                    if self.context.source == InteractionContext.SOURCE_PIE_MENU:
                        source = InteractionContext.SOURCE_PIE_MENU
                        priority = self.context.priority
                    clone_context_fn = functools.partial(self.super_interaction.context.clone_for_continuation, context_source_si, source=source, priority=priority)
                else:
                    clone_context_fn = functools.partial(self.context.clone_for_continuation, self)
        else:
            group_id = self.super_interaction.group_id if self.super_interaction is not None else None
            clone_context_fn = functools.partial(self.context.clone_for_sim, actor, group_id=group_id)
        source_interaction_id = self.super_interaction.id if self.super_interaction is not None else None
        source_interaction_sim_id = self.sim.sim_id if self.sim is not None else None
        if continuation.preserve_preferred_object:
            pick = self.context.pick
            preferred_objects = self.context.preferred_objects
        else:
            pick = None
            preferred_objects = set()
        continuation_affordance_chain = [self.affordance]
        context = clone_context_fn(insert_strategy=insert_strategy, source_interaction_id=source_interaction_id, source_interaction_sim_id=source_interaction_sim_id, pick=pick, preferred_objects=preferred_objects, continuation_affordance_chain=continuation_affordance_chain)
        if not continuation.affordance.involves_carry:
            context.carry_target = None
        elif continuation.carry_target is not None:
            context.carry_target = self.get_participant(continuation.carry_target)
        elif continuation.inventory_carry_target is not None:
            inventory_carry_target = continuation.inventory_carry_target
            check_type = inventory_carry_target.check_type
            for item in actor.inventory_component:
                if check_type == TunableContinuation.ITEM_DEFINITION:
                    if item.definition is inventory_carry_target.definition:
                        context.carry_target = item
                        break
                elif check_type == TunableContinuation.ITEM_TUNING_ID:
                    if item.definition.tuning_file_id == inventory_carry_target.definition.tuning_file_id:
                        context.carry_target = item
                        break
                elif check_type == TunableContinuation.TAGGED_ITEM and inventory_carry_target(item, None):
                    context.carry_target = item
                    break
        if continuation.target != ParticipantType.Invalid:
            targets = self.get_participants(continuation.target)
            target = next(iter(targets), None)
        else:
            target = None
        if target.is_sim:
            if isinstance(target, sims.sim_info.SimInfo):
                target = target.get_sim_instance()
        elif not continuation.preserve_target_part:
            target = target.part_owner
        affordance = continuation.affordance if target is not None and affordance_override is None else affordance_override
        kwargs_copy = kwargs.copy()
        join_target_ref = self.interaction_parameters.get('join_target_ref')
        if join_target_ref is not None:
            kwargs_copy['join_target_ref'] = join_target_ref
        if 'picked_item_ids' not in kwargs_copy:
            picked_items = self.get_participants(ParticipantType.PickedItemId)
            if picked_items:
                kwargs_copy['picked_item_ids'] = picked_items
        if 'compressed_multiple_inventory_items' not in kwargs_copy:
            compressed_multiple_inventory_items = self.interaction_parameters.get('compressed_multiple_inventory_items')
            if compressed_multiple_inventory_items is not None:
                kwargs_copy['compressed_multiple_inventory_items'] = compressed_multiple_inventory_items
        if 'picked_zone_ids' not in kwargs_copy:
            picked_zones = self.get_participants(ParticipantType.PickedZoneId)
            if picked_zones:
                kwargs_copy['picked_zone_ids'] = picked_zones
        kwargs_copy['saved_participants'] = self._saved_participants
        if affordance.is_super:
            aop = interactions.aop.AffordanceObjectPair(affordance, target, affordance, None, picked_object=self.target, continuation_affordance_chain=continuation_affordance_chain, **kwargs_copy)
        else:
            if continuation.si_affordance_override is not None:
                super_affordance = continuation.si_affordance_override
                super_interaction = None
                push_super_on_prepare = True
            else:
                super_affordance = self.super_affordance
                super_interaction = self.super_interaction
                push_super_on_prepare = False
            aop = interactions.aop.AffordanceObjectPair(affordance, target, super_affordance, super_interaction, picked_object=self.target, push_super_on_prepare=push_super_on_prepare, continuation_affordance_chain=continuation_affordance_chain, **kwargs_copy)
        return (aop, context)

    def get_aops_and_contexts_for_tunable_continuation(self, tunable_continuation:'TunableContinuation', multi_push:'bool'=True, insert_strategy:'QueueInsertStrategy'=QueueInsertStrategy.NEXT, actor:'Optional[Sim]'=DEFAULT, **kwargs) -> 'List[Tuple[AffordanceObjectPair, InteractionContext]]':
        num_success = collections.defaultdict(int)
        num_required = collections.defaultdict(int)
        aops_contexts = []
        continuations = tunable_continuation
        if insert_strategy == QueueInsertStrategy.NEXT:
            continuations = reversed(tunable_continuation)
        for continuation in continuations:
            if actor is DEFAULT:
                local_actors = self.get_participants(continuation.actor)
            else:
                local_actors = (actor,)
            for local_actor in local_actors:
                if isinstance(local_actor, sims.sim_info.SimInfo):
                    pass
                else:
                    if multi_push:
                        num_required[local_actor.id] += 1
                    else:
                        num_required[local_actor.id] = 1
                        if num_success[local_actor.id] > 0:
                            pass
                        else:
                            (aop, context) = self.get_continuation_aop_and_context(continuation, local_actor, insert_strategy=insert_strategy, **kwargs)
                            if aop is not None:
                                result = aop.test(context, **kwargs)
                                if result:
                                    num_success[local_actor.id] += 1
                                    aops_contexts.append((aop, context))
                    (aop, context) = self.get_continuation_aop_and_context(continuation, local_actor, insert_strategy=insert_strategy, **kwargs)
                    if aop is not None:
                        result = aop.test(context, **kwargs)
                        if result:
                            num_success[local_actor.id] += 1
                            aops_contexts.append((aop, context))
        return aops_contexts

    def push_tunable_continuation(self, tunable_continuation:'TunableContinuation', multi_push:'bool'=True, insert_strategy:'QueueInsertStrategy'=QueueInsertStrategy.NEXT, actor:'Optional[Sim]'=DEFAULT, notify_continuation:'bool'=False, override_interaction_source:'Optional[InteractionContext.Source]'=None, **kwargs) -> 'bool':
        num_pushed = collections.defaultdict(int)
        num_required = collections.defaultdict(int)
        continuations = tunable_continuation
        if insert_strategy == QueueInsertStrategy.NEXT:
            continuations = reversed(tunable_continuation)
        for continuation in continuations:
            if actor is DEFAULT:
                local_actors = self.get_participants(continuation.actor)
            else:
                local_actors = (actor,)
            for local_actor in local_actors:
                if isinstance(local_actor, sims.sim_info.SimInfo):
                    pass
                else:
                    if multi_push:
                        num_required[local_actor.id] += 1
                    else:
                        num_required[local_actor.id] = 1
                        if num_pushed[local_actor.id] > 0:
                            pass
                        else:
                            (aop, context) = self.get_continuation_aop_and_context(continuation, local_actor, insert_strategy=insert_strategy, interaction_name=self.get_name(), interaction_icon_info=self.get_icon_info(), notify_continuation=notify_continuation, **kwargs)
                            if override_interaction_source is not None:
                                context.source = override_interaction_source
                            if aop is not None:
                                result = aop.test_and_execute(context)
                                if result:
                                    num_pushed[local_actor.id] += 1
                    (aop, context) = self.get_continuation_aop_and_context(continuation, local_actor, insert_strategy=insert_strategy, interaction_name=self.get_name(), interaction_icon_info=self.get_icon_info(), notify_continuation=notify_continuation, **kwargs)
                    if override_interaction_source is not None:
                        context.source = override_interaction_source
                    if aop is not None:
                        result = aop.test_and_execute(context)
                        if result:
                            num_pushed[local_actor.id] += 1
        return num_pushed == num_required

    def _post_perform(self) -> 'None':
        if not self.is_finishing:
            self._finisher.on_finishing_move(FinishingType.NATURAL, self)
        self._active = False
        curfew_service = services.get_curfew_service()
        if not curfew_service.sim_breaking_curfew(self.sim, self.target, self):
            curfew_service.remove_broke_curfew_buff(self.sim)

    def required_sims(self, *args, for_threading:'bool'=False, **kwargs) -> 'Set[Sim]':
        cached_required_sims = self._required_sims if not for_threading else self._required_sims_threading
        if cached_required_sims is not None:
            return cached_required_sims
        else:
            return self._get_required_sims(*args, for_threading=for_threading, **kwargs)

    def get_mutexed_resources(self) -> 'Set[ScriptObject]':
        mutexed_resources = set()

        def _can_be_mutexed_resource(obj):
            if obj is None:
                return False
            if obj.is_sim:
                return False
            elif obj.objectrouting_component is None:
                return False
            return True

        if self.target_type & TargetType.TARGET:
            obj = self.get_participant(ParticipantType.Object)
            if _can_be_mutexed_resource(obj):
                mutexed_resources.add(obj)
        return mutexed_resources

    def has_sim_in_required_sim_cache(self, sim_in_question:'Sim') -> 'bool':
        if self._required_sims is None:
            return False
        return sim_in_question in self._required_sims

    def required_resources(self) -> 'Set[Any]':
        return set()

    def can_cancel_this_work(self, required_sims:'Optional[Set[Sim]]'=None) -> 'bool':
        return False

    def is_required_sims_locked(self) -> 'bool':
        return isinstance(self._required_sims, frozenset)

    def refresh_and_lock_required_sims(self) -> 'None':
        self._required_sims = frozenset(self._get_required_sims())
        self._required_sims_threading = frozenset(self._get_required_sims(for_threading=True))

    def remove_required_sim(self, sim:'Sim') -> 'None':
        if self._required_sims is None:
            logger.error('Trying to remove a Sim {} even though we have not yet reserved a list of required Sims.', sim)
            return
        if sim in self._required_sims:
            self._required_sims -= {sim}
            sim.queue.transition_controller = None

    def unlock_required_sims(self) -> 'None':
        self._required_sims = None

    def _get_required_sims(self, *args, **kwargs) -> 'Set[Sim]':
        return {self.sim}

    def notify_queue_head(self) -> 'None':
        pass

    def on_incompatible_in_queue(self) -> 'None':
        if self.context.cancel_if_incompatible_in_queue or self.context.carry_target_is_busy:
            self.cancel(FinishingType.INTERACTION_INCOMPATIBILITY, 'Canceled because cancel_if_incompatible_in_queue == True')

    def _trigger_interaction_start_event(self) -> 'None':
        if self.sim is not None:
            services.get_event_manager().process_event(test_events.TestEvent.InteractionStart, sim_info=self.sim.sim_info, interaction=self, custom_keys=self.get_keys_to_process_events())
            self.register_event_auto_update()

    def _add_club_rewards_liability(self) -> 'None':
        club_service = services.get_club_service()
        if club_service is None:
            return
        cr_liability = club_service.create_rewards_buck_liability(self.sim.sim_info, self)
        if cr_liability is not None:
            self.add_liability(cr_liability.LIABILITY_TOKEN, cr_liability)

    def register_event_auto_update(self) -> 'None':
        if self._interaction_event_update_alarm is not None:
            self.remove_event_auto_update()
        self._interaction_event_update_alarm = alarms.add_alarm(self, create_time_span(minutes=15), lambda _, sim_info=self.sim.sim_info, interaction=self, custom_keys=self.get_keys_to_process_events(): services.get_event_manager().process_event(test_events.TestEvent.InteractionUpdate, sim_info=sim_info, interaction=interaction, custom_keys=custom_keys), True)

    def remove_event_auto_update(self) -> 'None':
        if self._interaction_event_update_alarm is not None:
            alarms.cancel_alarm(self._interaction_event_update_alarm)
            self._interaction_event_update_alarm = None

    def _interrupt_active_work(self, kill:'bool'=False, finishing_type:'Optional[FinishingType]'=None) -> 'bool':
        element = self._run_interaction_element
        if element is not None:
            if kill:
                element.trigger_hard_stop()
            else:
                element.trigger_soft_stop()
        return True

    def invalidate(self) -> 'None':
        if self.pipeline_progress == PipelineProgress.NONE:
            if self.transition is not None:
                self.transition.shutdown()
            if self.depended_on_si is not None:
                self.depended_on_si.detach_interaction(self)
            self._finisher.on_finishing_move(FinishingType.KILLED, self)
            self.release_liabilities()

    def kill(self) -> 'bool':
        if self.has_been_killed:
            return False
        self._finisher.on_finishing_move(FinishingType.KILLED, self)
        self._interrupt_active_work(kill=True, finishing_type=FinishingType.KILLED)
        self._active = False
        if self.queue is not None:
            self.queue.on_interaction_canceled(self)
        return True

    def cancel(self, finishing_type:'FinishingType', cancel_reason_msg:'str', ignore_must_run:'bool'=False, **kwargs) -> 'bool':
        user_cancel_liability = self.get_liability(UserCancelableChainLiability.LIABILITY_TOKEN)
        if user_cancel_liability is not None and finishing_type == FinishingType.USER_CANCEL:
            user_cancel_liability.set_user_cancel_requested()
        if self.is_finishing or not (self.must_run and ignore_must_run):
            return False
        self._finisher.on_finishing_move(finishing_type, self)
        self._interrupt_active_work(finishing_type=finishing_type)
        self._active = False
        if self.queue is not None:
            self.queue.on_interaction_canceled(self)
        self._on_cancelled_callbacks(self)
        self._on_cancelled_callbacks.clear()
        return True

    def displace(self, displaced_by:'Interaction', **kwargs) -> 'bool':
        return self.cancel(FinishingType.DISPLACED, **kwargs)

    def on_reset(self) -> 'None':
        if self._finisher.has_been_reset:
            return
        self._finisher.on_finishing_move(FinishingType.RESET, self)
        self._active = False
        for liability in list(self.liabilities):
            liability.on_reset()
        self._liabilities.clear()
        self.remove_event_auto_update()

    def cancel_user(self, cancel_reason_msg:'str', on_response:'Optional[Callable[[bool], None]]'=None) -> 'bool':

        def _send_response(response):
            if on_response is not None:
                on_response(response)

        if self.user_canceled:
            return False
        if self.get_liability(UNCANCELABLE_LIABILITY) is not None:
            return False
        if self._cancelable_by_user == CancelablePhase.NEVER or not (self._cancelable_by_user == CancelablePhase.RUNNING and self.running):
            _send_response(False)
            return False
        if self._cancelable_by_user == CancelablePhase.ALWAYS or self._cancelable_by_user == CancelablePhase.RUNNING and self.running or not self.prepared:
            result = self.cancel(FinishingType.USER_CANCEL, cancel_reason_msg=cancel_reason_msg)
            _send_response(True)
            return result

        def on_cancel_dialog_response(dialog):
            if dialog.accepted:
                self.cancel(FinishingType.USER_CANCEL, cancel_reason_msg=cancel_reason_msg)
            else:
                self.sim.ui_manager.update_interaction_cancel_status(self)
            _send_response(dialog.accepted)

        dialog = self._cancelable_by_user(self.sim, self.get_resolver())
        dialog.show_dialog(on_response=on_cancel_dialog_response)
        return True

    def should_visualize_interaction_for_sim(self, participant_type:'ParticipantType') -> 'bool':
        return participant_type == ParticipantType.Actor

    @classmethod
    def additional_mixers_to_cache(cls) -> 'int':
        return 0

    @classproperty
    def has_visible_content_sets(cls) -> 'bool':
        return False

    def get_interaction_queue_visual_type(self) -> 'Tuple[Sims_pb2.Interaction, Any]':
        if self.visual_type_override is not None:
            return (InteractionQueueVisualType.get_interaction_visual_type(self.visual_type_override), self.visual_type_override_data)
        if not self.is_super:
            return (Sims_pb2.Interaction.MIXER, self.visual_type_override_data)
        if self.has_visible_content_sets:
            return (Sims_pb2.Interaction.PARENT, self.visual_type_override_data)
        sim_posture = self.sim.posture
        if sim_posture is not None and sim_posture.source_interaction is self:
            return (Sims_pb2.Interaction.POSTURE, self.visual_type_override_data)
        return (Sims_pb2.Interaction.SIMPLE, self.visual_type_override_data)

    def _avoid_participants_as_body_target_dest_test(self, body_target:'ScriptObject') -> 'bool':
        for object_participant in self._avoid_participants_as_body_target:
            for object in self.get_participants(object_participant):
                if body_target is object:
                    return False
        return True

    def add_preferred_body_target_participants(self) -> 'None':
        if self._prefer_participants_as_body_target is not None:
            for object_participant in self._prefer_participants_as_body_target:
                participants = self.get_participants(object_participant)
                if not participants:
                    pass
                else:
                    self.add_preferred_objects(participants)

    def add_preferred_carrying_sim(self) -> 'None':
        if self._prefer_participants_as_carrying_sim is not None:
            preferred_carrying_sim = self.get_participant(self._prefer_participants_as_carrying_sim)
            if preferred_carrying_sim.is_sim:
                self.context.preferred_carrying_sim = preferred_carrying_sim

    def on_added_to_queue(self, interaction_id_to_insert_after:'Optional[int]'=None, notify_client:'bool'=True) -> 'None':
        self.pipeline_progress = PipelineProgress.QUEUED
        self._entered_pipeline()
        self.sim.ui_manager.add_queued_interaction(self, interaction_id_to_insert_after=interaction_id_to_insert_after, notify_client=notify_client)
        if self.should_visualize_interaction_for_sim(ParticipantType.TargetSim):
            target_sim = self.get_participant(ParticipantType.TargetSim)
            if target_sim is not None and target_sim is not self.sim:
                target_sim.ui_manager.add_queued_interaction(self, notify_client=notify_client)
        if self._avoid_participants_as_body_target is not None:
            self.additional_destination_validity_tests.append(self._avoid_participants_as_body_target_dest_test)
        additional_basic_liabilities = () if self._additional_basic_liabilities is None else self._additional_basic_liabilities
        for liability in itertools.chain(self.basic_liabilities, additional_basic_liabilities):
            liability = liability(self)
            self.add_liability(liability.LIABILITY_TOKEN, liability)

    def on_removed_from_queue(self) -> 'None':
        if self.pipeline_progress < PipelineProgress.RUNNING or self.is_super or self.pipeline_progress < PipelineProgress.EXITED:
            if self.sim is None:
                logger.error('Removing interaction {} from queue from a None Sim', self, owner='camilogarcia')
            else:
                self.sim.ui_manager.remove_queued_interaction(self)
            if self.should_visualize_interaction_for_sim(ParticipantType.TargetSim):
                target_sim = self.get_participant(ParticipantType.TargetSim)
                if target_sim is not None and target_sim is not self.sim:
                    target_sim.ui_manager.remove_queued_interaction(self)
            if not self.is_finishing:
                self.cancel(FinishingType.INTERACTION_QUEUE, 'Being removed from queue without successfully running.', ignore_must_run=True, immediate=True)
            self._exited_pipeline()

    def _entered_pipeline(self) -> 'None':
        self._acquire_liabilities()
        if self.target is not None:
            self.target.add_interaction_reference(self)

    def _exited_pipeline(self, *args, send_exited_events:'bool'=True, **kwargs) -> 'None':
        if not self.is_finishing:
            logger.callstack('Exiting pipeline without having canceled an interaction: \n   {}\n   Pending Finishing Move: {}\n   PipelineProgress:{}', self, self._finisher.get_pending_finishing_move_debug_string(), self.pipeline_progress, level=sims4.log.LEVEL_WARN, owner='bhill')
            self.cancel(FinishingType.UNKNOWN, 'Exiting pipeline without canceling.', ignore_must_run=True, immediate=True)
        if self.target is not None:
            self.target.remove_interaction_reference(self)
        if gsi_handlers.interaction_archive_handlers.is_archive_enabled(self):
            gsi_handlers.interaction_archive_handlers.archive_interaction(self.sim, self, 'Complete')
        if self.pipeline_progress >= PipelineProgress.EXITED:
            logger.callstack('_exited_pipeline called twice on {}', self, level=sims4.log.LEVEL_ERROR)
            return
        completed = self.pipeline_progress >= PipelineProgress.RUNNING
        self.pipeline_progress = PipelineProgress.EXITED
        if self.is_super:
            animation_liability = self.get_liability(ANIMATION_CONTEXT_LIABILITY)
            if animation_liability is not None:
                for (posture, key_list) in animation_liability.cached_asm_keys.items():
                    for key in key_list:
                        posture.remove_from_cache(key)
                animation_liability.cached_asm_keys.clear()
        self.release_liabilities()
        for asm in self._asm_states:
            if self.on_asm_state_changed in asm.on_state_changed_events:
                asm.on_state_changed_events.remove(self.on_asm_state_changed)
        if completed:
            self._trigger_interaction_complete_test_event()
        self._object_create_helper = None
        if self.sim is not None:
            self.sim.skip_autonomy(self, False)
        if send_exited_events:
            self._trigger_interaction_exited_pipeline_test_event()
        if self._report_running_time or self._report_stored_sims_on_target_count:
            with telemetry_helper.begin_hook(interaction_telemetry_writer, TELEMETRY_HOOK_INTERACTION_END, sim=self.sim) as hook:
                hook.write_int(TELEMETRY_FIELD_INTERACTION_ID, self.guid64)
                next_interaction = None
                if self.has_been_user_canceled:
                    next_interaction = 0
                elif self.interrupting_interaction_guid64 is not None:
                    next_interaction = self.interrupting_interaction_guid64
                elif hasattr(self.queue.running, 'guid64'):
                    next_interaction = self.queue.running.guid64
                if next_interaction is not None:
                    hook.write_int(TELEMETRY_FIELD_NEXT_INTERACTION_ID, next_interaction)
                if self._report_running_time:
                    total_sim_minutes_taken = self.consecutive_running_time_span.in_minutes()
                    hook.write_int(TELEMETRY_FIELD_INTERACTION_RUNNING_TIME, total_sim_minutes_taken)
                if self._report_stored_sims_on_target_count and self.target is not None:
                    stored_sim_info_component = self.target.get_component(types.STORED_SIM_INFO_COMPONENT)
                    if stored_sim_info_component is not None:
                        stored_sim_id_list = stored_sim_info_component.get_stored_sim_id_list()
                        hook.write_int(TELEMETRY_FIELD_NUMBER_OF_SIM_PARTICIPANTS, len(stored_sim_id_list))
                if self.target is not None and self.target.is_sim:
                    hook.write_int(TELEMETRY_FIELD_TARGET_SIM_ID, self.target.sim_id)
                    if self.sim.age is not None:
                        hook.write_int(TELEMETRY_FIELD_SIM_AGE, self.target.age)
        self._saved_participants = [None, None, None, None]
        self._required_sims_threading = None
        self._clean_behavior()
        self.responsiveness_metrics_stop()

    def _do(self, *args) -> 'None':
        raise RuntimeError('Calling _do on interaction is not supported')

    def disallows_full_autonomy(self, disable_full_autonomy:'bool'=DEFAULT) -> 'bool':
        return False

    def _update_autonomy_timer(self, force_user_directed:'bool'=False) -> 'None':
        self.sim.skip_autonomy(self, False)
        if force_user_directed or self.is_user_directed:
            self.sim.set_last_user_directed_action_time()
            if not self.is_social:
                return
            target = self.target
            if target is not None and target.is_sim:
                target_autonomy_component = target.autonomy_component
                if target_autonomy_component is not None:
                    target_autonomy_component.set_last_user_directed_action_time()
                else:
                    logger.exception("Autonomy component doesn't exist for target {} in interaction {}", target, self)
        elif self.source == InteractionContext.SOURCE_AUTONOMY:
            self.sim.set_last_autonomous_action_time()

    def register_on_finishing_callback(self, callback:'Callable[[Interaction], None]') -> 'None':
        self._finisher.register_callback(callback)

    def unregister_on_finishing_callback(self, callback:'Callable[[Interaction], None]') -> 'None':
        self._finisher.unregister_callback(callback)

    def should_show_route_fail(self) -> 'bool':
        if self.show_route_failure == ShowRouteFailBehavior.NEVER:
            return False
        if self.always_show_route_failure or self.show_route_failure == ShowRouteFailBehavior.ALWAYS:
            return True
        return self.visible

    @classproperty
    def is_teleport_style_injection_allowed(cls) -> 'bool':
        return False

    @constproperty
    def should_perform_routing_los_check() -> 'bool':
        return True

    @property
    def allow_outcomes(self) -> 'bool':
        if not self.is_super:
            return True
        if self.immediate:
            return True
        if self.is_basic_content_one_shot:
            return True
        if self.is_finishing or self._finisher.has_pending_natural_finisher:
            return True
        elif self.is_finishing_naturally:
            return True
        return False

    @property
    def is_finishing(self) -> 'bool':
        return self._finisher.is_finishing

    @property
    def user_canceled(self) -> 'bool':
        return self._finisher.has_been_user_canceled

    @property
    def is_finishing_naturally(self) -> 'bool':
        return self._finisher.is_finishing_naturally

    @property
    def transition_failed(self) -> 'bool':
        return self._finisher.transition_failed

    @property
    def will_exit(self) -> 'bool':
        return self.is_finishing

    @property
    def was_initially_displaced(self) -> 'bool':
        return self._finisher.was_initially_displaced

    @property
    def uncanceled(self) -> 'bool':
        if not self._finisher.is_finishing:
            return True
        elif self._finisher.is_finishing_naturally:
            return True
        return False

    @property
    def has_active_cancel_replacement(self) -> 'bool':
        return self.sim.queue.cancel_aop_exists_for_si(self)

    @property
    def is_cancel_aop(self) -> 'bool':
        return self.context.is_cancel_aop

    @property
    def has_been_killed(self) -> 'bool':
        return self._finisher.has_been_killed

    @property
    def has_been_canceled(self) -> 'bool':
        return self._finisher.has_been_canceled

    @property
    def has_been_user_canceled(self) -> 'bool':
        return self._finisher.has_been_user_canceled

    @property
    def can_carry_infant(self) -> 'bool':
        if self.carry_back_compatibility_override is not None:
            return self.carry_back_compatibility_override.infant
        elif self.basic_content is not None and self.basic_content.animation_ref is not None:
            return self.basic_content.animation_ref.factory.get_carry_back_compatibility(self.sim, self).infant
        return True

    @property
    def can_carry_wings(self) -> 'bool':
        if self.carry_back_compatibility_override is not None:
            return self.carry_back_compatibility_override.wings
        elif self.basic_content is not None and self.basic_content.animation_ref is not None:
            return self.basic_content.animation_ref.factory.get_carry_back_compatibility(self.sim, self).wings
        return True

    @property
    def has_been_reset(self) -> 'bool':
        return self._finisher.has_been_reset

    def finisher_repr(self) -> 'str':
        return self._finisher.__repr__()

    @property
    def global_outcome_result(self) -> 'OutcomeResult':
        return self._global_outcome_result

    @global_outcome_result.setter
    def global_outcome_result(self, value:'OutcomeResult') -> 'None':
        self._global_outcome_result = value

    def get_result_for_outcome(self, outcome:'InteractionOutcome') -> 'Optional[OutcomeResult]':
        return self._outcome_result_map.get(outcome, None)

    def store_result_for_outcome(self, outcome:'InteractionOutcome', result:'OutcomeResult') -> 'None':
        if result in self._outcome_result_map:
            logger.error('Overriding an existing outcome result. Outcome {}, Previous Result {}, New Result {}, Interaction {}', outcome, self._outcome_result_map[outcome], result, self, owner='tastle')
            return
        self._outcome_result_map[outcome] = result

    def clear_outcome_results(self) -> 'None':
        self._outcome_result_map.clear()

    def is_equivalent(self, interaction:'Interaction', target:'Optional[ScriptObject]'=DEFAULT) -> 'bool':
        if target is DEFAULT:
            target = interaction.target
        return self.get_interaction_type() is interaction.get_interaction_type() and self.target is target

    def merge(self, other:'Interaction') -> 'None':
        if self.context.priority < other.context.priority:
            self.context.priority = interactions.priority.Priority(other.context.priority)
            self.context.source = other.context.source
        self.refresh_conditional_actions()

    def should_unholster_carried_object(self, obj:'ScriptObject') -> 'bool':
        if obj.should_unholster():
            return obj is self.target or obj is self.carry_target
        return not self.is_super

    def cancel_incompatible_carry_interactions(self, can_defer_putdown:'bool'=True, derail_actors:'bool'=False, needs_putdown:'bool'=False) -> 'bool':
        needs_derail = False
        if isinstance(self, interactions.base.teleport_interaction.TeleportHereInteraction):
            can_defer_putdown = False
        for (owning_interaction, carry_posture) in self.get_uncarriable_objects_gen(posture_state=None):
            if can_defer_putdown and carry_posture.target.carryable_component.defer_putdown:
                pass
            elif is_wing_proxy_object(carry_posture.target):
                pass
            elif not owning_interaction.is_cancel_aop:
                if derail_actors or self.is_super and owning_interaction.sim is not self.sim:
                    needs_derail = True
                originator_target = self.target
                if not originator_target.allow_putdown_nearby_on_carry_cancellation:
                    originator_target = None
                cancellation_originator = CancellationOriginatorInfo(self.affordance, weakref.ref(originator_target) if originator_target is not None and originator_target is not None else None)
                owning_interaction.cancel(FinishingType.INTERACTION_INCOMPATIBILITY, cancel_reason_msg='Carry owning interaction incompatible with {}'.format(self.affordance), cancellation_originator=cancellation_originator)
        if needs_derail:
            services.get_master_controller().set_timestamp_for_sim_to_now(self.sim)
            return False
        return True

    def get_uncarriable_objects_gen(self, posture_state:'Optional[PostureState]'=DEFAULT, allow_holster:'Optional[bool]'=DEFAULT, use_holster_compatibility:'bool'=False) -> 'Generator[Interaction, Posture]':
        if self._ignore_incompatible_carries:
            return
        carry_target = self.carry_target or self.super_interaction.carry_target
        interaction_target = self.target
        for sim in self.required_sims():
            participant_type = self.get_participant_type(sim)
            if participant_type is None:
                pass
            else:
                if not sim.is_allowed_to_holster():
                    allow_holster = False
                else:
                    for (_, carry_posture, carry_object) in get_carried_objects_gen(sim):
                        if self.allow_with_unholsterable_carries:
                            allow_holster = True
                        else:
                            allow_holster = False
                        break
                        if carry_object.is_sim and not carry_object is not carry_target or carry_object is not interaction_target:
                            for owning_interaction in carry_posture.owning_interactions:
                                if not owning_interaction.allow_holstering_of_owned_carries:
                                    allow_holster = False
                                    break
                current_interaction_constraint = None
                current_interaction_hand = None
                current_interaction_constraint = self.constraint_intersection(sim=sim, participant_type=participant_type, posture_state=posture_state, allow_holster=allow_holster)
                posture_specs = current_interaction_constraint.get_posture_specs(interaction=self)
                for (_, var_map, _) in posture_specs:
                    var_map_carry_target = var_map.get(PostureSpecVariable.CARRY_TARGET, None)
                    if var_map_carry_target is None:
                        pass
                    else:
                        hand = var_map[PostureSpecVariable.HAND] if var_map_carry_target is carry_target else None
                        if hand is not None:
                            current_interaction_hand = hand
                            temp_carry_posture = carry_target.get_carry_object_posture(carrying_hand=hand)(sim, carry_target, hand)
                            if temp_carry_posture.is_two_handed_carry:
                                current_interaction_constraint = current_interaction_constraint.generate_body_posture_only_constraint()
                                current_interaction_constraint = current_interaction_constraint.intersect(create_two_handed_carry_constraint(carry_target, hand))
                                break
                forced_tentative_interaction_constraint = UNSET
                for (carry_hand, carry_posture, carry_object) in get_carried_objects_gen(sim):
                    current_interaction_constraint = self.constraint_intersection(sim=sim, participant_type=participant_type, posture_state=posture_state, allow_holster=allow_holster)
                    if posture_state is None:
                        forced_tentative_interaction_constraint = current_interaction_constraint
                    else:
                        forced_tentative_interaction_constraint = None
                    for owning_interaction in list(carry_posture.owning_interactions):
                        if not use_holster_compatibility or not carry_object.carryable_component.holster_compatibility(self.super_affordance):
                            yield (owning_interaction, carry_posture)
                        elif not self.is_super or not interactions.si_state.SIState.test_non_constraint_compatibility(self, owning_interaction, owning_interaction.target):
                            yield (owning_interaction, carry_posture)
                        else:
                            if owning_interaction.running:
                                other_constraint = owning_interaction.constraint_intersection(posture_state=posture_state)
                                other_constraint = other_constraint.generate_body_posture_only_constraint()
                                other_constraint = other_constraint.intersect(create_two_handed_carry_constraint(carry_object, carry_hand))
                                interaction_constraint = current_interaction_constraint
                            else:
                                other_constraint = owning_interaction.constraint_intersection(posture_state=None)
                                forced_tentative_interaction_constraint = self.constraint_intersection(sim=sim, participant_type=participant_type, posture_state=None, allow_holster=allow_holster)
                                interaction_constraint = forced_tentative_interaction_constraint
                            if participant_type == ParticipantType.Listeners:
                                constraint_resolver = self.get_constraint_resolver(None, participant_type=participant_type, force_actor=sim)
                            else:
                                constraint_resolver = self.get_constraint_resolver(None, participant_type=participant_type)
                            other_constraint = other_constraint.apply_posture_state(None, constraint_resolver)
                            intersection = interaction_constraint.intersect(other_constraint)
                            if not intersection.valid:
                                yield (owning_interaction, carry_posture)
                            elif self.can_carry_infant or carry_object.is_sim:
                                yield (owning_interaction, carry_posture)
                            elif self.can_carry_wings or is_wing_proxy_object(carry_object):
                                yield (owning_interaction, carry_posture)
                            else:
                                for sub_constraint in intersection:
                                    break
                                    body_target = sub_constraint.posture_state_spec.body_target
                                    if sub_constraint.posture_state_spec is None and body_target is not None and not isinstance(body_target, PostureSpecVariable):
                                        surface = body_target.parent
                                    else:
                                        surface = None
                                    break
                                    for manifest_entry in sub_constraint.posture_state_spec.posture_manifest:
                                        if manifest_entry.surface_target is MATCH_NONE:
                                            pass
                                        else:
                                            break
                                    break
                                    for runtime_slot in surface.get_runtime_slots_gen():
                                        if not runtime_slot.is_valid_for_placement(obj=carry_object, objects_to_ignore=[carry_object]):
                                            pass
                                        else:
                                            break
                                    break
                                yield (owning_interaction, carry_posture)
                    if not current_interaction_constraint is None or not forced_tentative_interaction_constraint == UNSET or not carry_object is not carry_target or not carry_object is not interaction_target or self.is_putdown or carry_object is carry_target and current_interaction_hand is not None and carry_hand is not current_interaction_hand:
                        for owning_interaction in list(carry_posture.owning_interactions):
                            yield (owning_interaction, carry_posture)

    def register_on_cancelled_callback(self, callback:'Callable[[Interaction], None]') -> 'None':
        self._on_cancelled_callbacks.append(callback)

    def register_on_path_planned_callback(self, callback:'Callable[[Interaction, bool], None]') -> 'None':
        if self.on_path_planned_callbacks is None:
            self.on_path_planned_callbacks = CallableList()
        self.on_path_planned_callbacks.register(callback)

    def unregister_on_path_planned_callback(self, callback:'Callable[[Interaction, bool], None]') -> 'None':
        self.on_path_planned_callbacks.unregister(callback)
        if not self.on_path_planned_callbacks:
            self.on_path_planned_callbacks = None

    def send_current_progress(self, new_interaction:'bool'=True) -> 'None':
        self.send_progress_bar_message(new_interaction=new_interaction)

    def send_progress_bar_message(self, new_interaction:'bool'=True) -> 'None':
        if self.progress_bar_enabled.bar_enabled and (self.display_name and self.sim.is_selectable) and self.sim.valid_for_distribution:
            if self.progress_bar_enabled.force_listen_statistic:
                if self._progress_bar_commodity_callback is not None:
                    return
                progress_tuning = self.progress_bar_enabled.force_listen_statistic
                progress_target = self.get_participant(progress_tuning.subject)
                tracker = progress_target.get_tracker(progress_tuning.statistic)
                if tracker is not None and self._progress_bar_goal is None:
                    _statistic = tracker.get_statistic(progress_tuning.statistic)
                    if _statistic is not None and _statistic.decay_rate != 0:
                        self._progress_bar_commodity_callback = tracker.add_watcher(self._progress_bar_update_statistic_callback)
                        override_value = progress_tuning.target_value.value
                        if override_value != 0:
                            current_value = tracker.get_user_value(progress_tuning.statistic)
                            change_rate = (override_value - current_value)/(override_value*100)*_statistic.get_change_rate()
                            self._send_progress_bar_update_msg(0, change_rate, start_msg=True)
            elif self.progress_bar_enabled.interaction_exceptions.is_music_interaction:
                track_time = clock.interval_in_real_seconds(self._track.length).in_minutes()
                if track_time == 0:
                    logger.error('Progress bar: Tuned track time is 0 for interaction {}.', self, owner='camilogarcia')
                    return
                rate_change = 1/track_time
                self._send_progress_bar_update_msg(0, rate_change, start_msg=True)
            elif self._conditional_action_manager:
                (percent, rate_change) = self._conditional_action_manager.get_percent_rate_for_best_exit_conditions(self)
                if percent is not None:
                    if percent < 1:
                        rate_change = rate_change/(1 - percent)
                        percent = 0
                    self._send_progress_bar_update_msg(percent, rate_change, start_msg=True)

    def _progress_bar_update_statistic_callback(self, stat_type:'Statistic', old_value:'int', new_value:'int') -> 'None':
        if stat_type is not self.progress_bar_enabled.force_listen_statistic.statistic:
            return
        target_value = self.progress_bar_enabled.force_listen_statistic.target_value.value
        current_value = new_value
        if target_value < current_value:
            if self._progress_bar_goal is None:
                self._progress_bar_goal = current_value - target_value
            current_value = self._progress_bar_goal - new_value
        else:
            self._progress_bar_goal = target_value
        if self._progress_bar_goal == 0:
            return
        percent = current_value/self._progress_bar_goal
        self._send_progress_bar_update_msg(percent, 0, start_msg=True)

    def send_end_progress(self) -> 'None':
        if self._progress_bar_commodity_callback is not None:
            progress_tuning = self.progress_bar_enabled.force_listen_statistic
            progress_target = self.get_participant(progress_tuning.subject)
            if progress_target is not None:
                tracker = progress_target.get_tracker(progress_tuning.statistic)
                tracker.remove_watcher(self._progress_bar_commodity_callback)
            self._progress_bar_commodity_callback = None
        if self.user_canceled:
            return
        if self._progress_bar_displayed and self.sim.valid_for_distribution:
            self._send_progress_bar_update_msg(1, 0)

    def _send_progress_bar_update_msg(self, percent:'float', rate_change:'float', start_msg:'bool'=False) -> 'None':
        if start_msg:
            self._progress_bar_displayed = True
        if self.sim is None:
            logger.error('Trying to update the progress of interaction where sim no longer exist: {}', self)
            return
        op = distributor.ops.InteractionProgressUpdate(self.sim.sim_id, percent, rate_change, self.id)
        Distributor.instance().add_op(self.sim, op)

    @property
    def acquire_listeners_as_resource(self) -> 'bool':
        return False

    @flexmethod
    def get_resolver(cls, inst, target:'Optional[ScriptObject]'=DEFAULT, context:'Optional[InteractionContext]'=DEFAULT, super_interaction:'Optional[SuperInteraction]'=None, **interaction_parameters) -> 'Resolver':
        inst_or_cls = inst if inst is not None else cls
        if target == DEFAULT:
            target = inst_or_cls.target
        if context == DEFAULT:
            context = inst_or_cls.context
        return event_testing.resolver.InteractionResolver(cls, inst, target=target, context=context, super_interaction=super_interaction, **interaction_parameters)

    @flexmethod
    def get_interaction_reservation_handler(cls, inst, sim:'Optional[Sim]'=DEFAULT, target:'Optional[ScriptObject]'=DEFAULT, resolver:'Optional[Resolver]'=None) -> '_ReservationHandler':
        inst_or_cls = inst if inst is not None else cls
        if inst_or_cls.basic_reserve_object is None:
            return
        sim = inst_or_cls.sim if sim is DEFAULT else sim
        if target is DEFAULT:
            reserve_object_fn = inst_or_cls.basic_reserve_object
        else:
            reserve_object_fn = functools.partial(inst_or_cls.basic_reserve_object, reserve_target=target)
        reservation_handler = reserve_object_fn(sim, inst, resolver=resolver)
        return reservation_handler

    @caches.cached
    def may_reserve_target(self, sim:'Sim', target:'ScriptObject') -> 'bool':
        reservation_handler = self.get_interaction_reservation_handler(sim=sim, target=target)
        if reservation_handler is not None:
            if not reservation_handler.may_reserve():
                return False
            if self.score_additional_sims_for_in_use is not None:
                for (participant, part_definition) in self.score_additional_sims_for_in_use.items():
                    sim_participant = self.get_participant(participant)
                    if sim is sim_participant:
                        pass
                    else:
                        targets_to_check = {}
                        if target.is_part:
                            targets_to_check = {part for part in target.part_owner.parts if part is not target and part_definition is part.part_definition}
                        found_reservable = False
                        for part_target in targets_to_check:
                            part_reservation_handler = self.get_interaction_reservation_handler(sim=sim_participant, target=part_target)
                            if part_reservation_handler.may_reserve():
                                found_reservable = True
                        if not found_reservable:
                            return False
        return True

    @property
    def finishing_type(self) -> 'Optional[FinishingType]':
        if self._finisher is not None:
            return self._finisher.finishing_type

    @property
    def telemetry_finishing_type(self) -> 'Optional[FinishingType]':
        if self._finisher is None:
            return
        finishing_type = self._finisher.finishing_type
        if self._finisher.has_pending_natural_finisher:
            finishing_type = FinishingType.NATURAL
        return finishing_type

    @classmethod
    def is_affordance_available(cls, context:'Optional[InteractionContext]'=None) -> 'bool':
        shift_held = context.shift_held if context is not None else False
        if shift_held:
            if cls.cheat:
                return True
            if cls.debug and False:
                return True
            if cls.automation and paths.AUTOMATION_MODE:
                return True
        elif cls.debug or not cls.cheat:
            return True
        return False

    @property
    def fame_moment_active(self) -> 'bool':
        return False

    @classmethod
    def get_adventures(cls) -> 'Dict[str, List[Element]]':
        return_dict = defaultdict(list)
        for basic_extra in cls.basic_extras:
            if basic_extra._factory_name is 'Adventure':
                return_dict['Basic Extra'].append(basic_extra)
        if cls.outcome is not None:
            for basic_extra in cls.outcome.get_basic_extras_gen():
                if basic_extra._factory_name is 'Adventure':
                    return_dict['Outcome Basic Extra'].append(basic_extra)
        return return_dict

    @flexmethod
    def get_pie_menu_color(cls, inst, target:'Optional[ScriptObject]'=DEFAULT, context:'Optional[InteractionContext]'=DEFAULT) -> 'Tuple[Optional[Mood], Optional[int]]':
        inst_or_cls = inst if inst is not None else cls
        if inst_or_cls.pie_menu_color is None:
            return (None, None)
        else:
            resolver = inst_or_cls.get_resolver(target=target, context=context)
            result = inst_or_cls.pie_menu_color.tests.run_tests(resolver)
            if result:
                return (inst_or_cls.pie_menu_color.mood, inst_or_cls.pie_menu_color.mood_intensity)
        return (None, None)

    @property
    def should_disable_vehicles(self) -> 'bool':
        if self._disable_vehicles_override is not None:
            return self._disable_vehicles_override
        return self.disable_vehicles

    def get_require_reins_up(self, slave_data:'RoutingFormation') -> 'bool':
        return self.require_reins_for_formation is slave_data.formation_type

    def responsiveness_metrics_start(self) -> 'None':
        sim_responsiveness_service = services.get_sim_responsiveness_service()
        if sim_responsiveness_service is not None:
            sim_responsiveness_service.start_metrics(self, self._context_responsiveness)

    def responsiveness_metrics_stop(self, should_report:'bool'=False) -> 'None':
        sim_responsiveness_service = services.get_sim_responsiveness_service()
        if sim_responsiveness_service is not None:
            sim_responsiveness_service.stop_metrics(self, should_report)

