示例#1
0
class LaundryTuning:
    GENERATE_CLOTHING_PILE = TunableTuple(description='\n        The tunable to generate clothing pile on the lot. This will be called\n        when we find laundry hero objects on the lot and there is no hamper\n        available.\n        ', loot_to_apply=TunableReference(description='\n            Loot to apply for generating clothing pile.\n            ', manager=services.get_instance_manager(sims4.resources.Types.ACTION), class_restrictions=('LootActions',), pack_safe=True), naked_outfit_category=TunableSet(description="\n            Set of outfits categories which is considered naked.\n            When Sim switches FROM these outfits, it won't generate the pile.\n            When Sim switches TO these outfits, it won't apply laundry reward\n            or punishment.\n            ", tunable=TunableEnumEntry(tunable_type=OutfitCategory, default=OutfitCategory.EVERYDAY, invalid_enums=(OutfitCategory.CURRENT_OUTFIT,))), no_pile_outfit_category=TunableSet(description="\n            Set of outfits categories which will never generate the pile.\n            When Sim switches FROM or TO these outfits, it won't generate the\n            pile.\n            \n            Laundry reward or punishment will still be applied to the Sim when \n            switching FROM or TO these outfits.\n            ", tunable=TunableEnumEntry(tunable_type=OutfitCategory, default=OutfitCategory.EVERYDAY, invalid_enums=(OutfitCategory.CURRENT_OUTFIT,))), no_pile_interaction_tag=TunableEnumWithFilter(description='\n            If interaction does spin clothing change and has this tag, it will\n            generate no clothing pile.\n            ', tunable_type=Tag, default=Tag.INVALID, filter_prefixes=('interaction',)))
    HAMPER_OBJECT_TAGS = TunableTags(description='\n        Tags that considered hamper objects.\n        ', filter_prefixes=('func',))
    LAUNDRY_HERO_OBJECT_TAGS = TunableTags(description='\n        Tags of laundry hero objects. Placing any of these objects on the lot\n        will cause the service to generate clothing pile for each Sims on the\n        household after spin clothing change.\n        ', filter_prefixes=('func',))
    NOT_DOING_LAUNDRY_PUNISHMENT = TunableTuple(description='\n        If no Sim in the household unload completed laundry in specific\n        amount of time, the negative loot will be applied to Sim household \n        on spin clothing change to engage them doing laundry.\n        ', timeout=TunableSimMinute(description="\n            The amount of time in Sim minutes, since the last time they're \n            finishing laundry, before applying the loot.\n            ", default=2880, minimum=1), loot_to_apply=TunableReference(description='\n            Loot defined here will be applied to the Sim in the household\n            on spin clothing change if they are not doing laundry for \n            a while.\n            ', manager=services.get_instance_manager(sims4.resources.Types.ACTION), class_restrictions=('LootActions',), pack_safe=True))
    PUT_AWAY_FINISHED_LAUNDRY = TunableTuple(description='\n        The tunable to update laundry service on Put Away finished laundry\n        interaction.\n        ', interaction_tag=TunableEnumWithFilter(description='\n            Tag that represent the put away finished laundry interaction which \n            will update Laundry Service data.\n            ', tunable_type=Tag, default=Tag.INVALID, filter_prefixes=('interaction',)), laundry_condition_states=TunableTuple(description='\n            This is the state type of completed laundry object condition \n            which will aggregate the data to the laundry service.\n            ', condition_states=TunableList(description='\n                A list of state types to be stored on laundry service.\n                ', tunable=TunableStateTypeReference(pack_safe=True), unique_entries=True), excluded_states=TunableList(description='\n                A list of state values of Condition States which will not \n                be added to the laundry service.\n                ', tunable=TunableStateValueReference(pack_safe=True), unique_entries=True)), laundry_condition_timeout=TunableSimMinute(description='\n            The amount of time in Sim minutes that the individual laundry\n            finished conditions will be kept in the laundry conditions \n            aggregate data.\n            ', default=1440, minimum=0), conditions_and_rewards_map=TunableMapping(description='\n            Mapping of laundry conditions and loot rewards.\n            ', key_type=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.OBJECT_STATE), pack_safe=True), value_type=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.ACTION), class_restrictions=('LootActions',), pack_safe=True)))
    PUT_CLOTHING_PILE_ON_HAMPER = TunableTuple(description='\n        The Tunable to directly put generated clothing pile in the hamper.\n        ', chance=TunablePercent(description='\n            The chance that a clothing pile will be put directly in the hamper. \n            Tune the value in case putting clothing pile in hamper every \n            spin-outfit-change feeling excessive.\n            ', default=100), clothing_pile=TunableTuple(description="\n            Clothing pile object that will be created and put into the hamper \n            automatically. \n            \n            You won't see the object on the lot since it will go directly to \n            the hamper. We create it because we need to transfer all of the \n            commodities data and average the values into the hamper precisely.\n            ", definition=TunablePackSafeReference(description='\n                Reference to clothing pile object definition.\n                ', manager=services.definition_manager()), initial_states=TunableList(description='\n                A list of states to apply to the clothing pile as soon as it \n                is created.\n                ', tunable=TunableTuple(description='\n                    The state to apply and optional to decide if the state \n                    should be applied.\n                    ', state=TunableStateValueReference(pack_safe=True), tests=TunableTestSet()))), full_hamper_state=TunableStateValueReference(description='\n            The state of full hamper which make the hamper is unavailable to \n            add new clothing pile in it.\n            ', pack_safe=True), loots_to_apply=TunableList(description='\n            Loots to apply to the hamper when clothing pile is being put.\n            ', tunable=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.ACTION), class_restrictions=('LootActions',), pack_safe=True)), tests=TunableTestSet(description='\n            The test to run on the Sim that must pass in order for putting\n            clothing pile automatically to the hamper. These tests will only \n            be run when we have available hamper on the lot.\n            '))
示例#2
0
class AffordanceTagFactory(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'interaction_tags':
        TunableTags(
            description=
            '\n            Affordances with any of these tags to affect.\n            ',
            filter_prefixes=('Interaction', )),
        'exceptions':
        TunableList(
            description=
            '\n            Affordances that are not affected even if they have the specified\n            tags.\n            ',
            tunable=TunableAffordanceListReference(pack_safe=True))
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        affordance_exceptions = frozenset(affordance
                                          for exception_list in self.exceptions
                                          for affordance in exception_list)
        self.affordance_exceptions = affordance_exceptions or None

    def __call__(self, affordance):
        if affordance.interaction_category_tags & self.interaction_tags and (
                self.affordance_exceptions is None
                or affordance not in self.affordance_exceptions):
            return False
        return True
示例#3
0
class ObjectStateHelper(AutoFactoryInit, HasTunableSingletonFactory):
    FACTORY_TUNABLES = {
        'object_target':
        TunableObjectTargetVariant(
            description=
            '\n            Define the set of objects that this interaction is applied to.\n            '
        ),
        'object_tags':
        TunableTags(
            description=
            '\n            Find all of the objects based on these tags.\n            ',
            filter_prefixes=('func', )),
        'desired_state':
        TunableStateValueReference(
            description=
            '\n            State that will be set to the objects.\n            ',
            pack_safe=True),
        'tests':
        TunableTestSet(
            description=
            "\n            If pass these tests, the object's state will be changed to\n            Desired State.\n            "
        )
    }

    def execute_helper(self, interaction):
        if self.desired_state is not None:
            objects = list(services.object_manager().get_objects_with_tags_gen(
                *self.object_tags))
            for obj in self.object_target.get_object_target_gen(
                    interaction, objects):
                resolver = SingleObjectResolver(obj)
                if self.tests.run_tests(resolver):
                    obj.set_state(self.desired_state.state, self.desired_state)
class OutfitChangeSituation(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {'situation_tags': TunableTags(description='\n            Tags for situations that will be considered for the outfit\n            interactions.\n            ', filter_prefixes=['situation'])}

    def outfit_affordances_gen(self, sim, target, affordance, **kwargs):
        resolver = SingleSimResolver(sim.sim_info)
        for situation in services.get_zone_situation_manager().get_situations_by_tags(self.situation_tags):
            situation_job = situation.get_current_job_for_sim(sim)
            if situation_job is not None:
                if situation_job.job_uniform is not None:
                    outfit_generators = situation_job.job_uniform.situation_outfit_generators
                    if outfit_generators is None:
                        continue
                    for entry in outfit_generators:
                        if entry.tests.run_tests(resolver):
                            yield AffordanceObjectPair(affordance, target, affordance, None, pie_menu_cateogory=affordance.category, outfit_tags=entry.generator.tags, **kwargs)

    def get_outfit_tags(self):
        outfit_tags = set()
        situation_manager = services.get_instance_manager(sims4.resources.Types.SITUATION)
        for situation in situation_manager.types.values():
            if self.situation_tags:
                if any(tag in situation.tags for tag in self.situation_tags):
                    for situation_job in situation.get_tuned_jobs():
                        if situation_job.job_uniform is None:
                            continue
                        outfit_generators = situation_job.job_uniform.situation_outfit_generators
                        if outfit_generators is None:
                            continue
                        for entry in outfit_generators:
                            for tag in entry.generator.tags:
                                outfit_tags.add(tag)
        return outfit_tags

    def get_outfit_for_clothing_change(self, sim_info, outfit_change_category):
        return sim_info.get_outfit_for_clothing_change(None, OutfitChangeReason.DefaultOutfit, resolver=SingleSimResolver(sim_info))
示例#5
0
class ApplyTagsToObject(BaseLootOperation):
    FACTORY_TUNABLES = {
        'apply_unpersisted_tags':
        TunableTags(
            description=
            '\n                A set of unpersisted category tags to apply to the finished product.\n                '
        ),
        'apply_persisted_tags':
        TunableTags(
            description=
            '\n                A set of persisted category tags to apply to the finished product.\n                '
        )
    }

    def __init__(self, apply_unpersisted_tags, apply_persisted_tags, *args,
                 **kwargs):
        super().__init__(*args, **kwargs)
        self._apply_unpersisted_tags = apply_unpersisted_tags
        self._apply_persisted_tags = apply_persisted_tags

    def _apply_to_subject_and_target(self, subject, target, resolver):
        if subject is None:
            return
        if hasattr(subject, 'append_tags'):
            subject.append_tags(self._apply_unpersisted_tags, persist=False)
            subject.append_tags(self._apply_persisted_tags, persist=True)
        else:
            logger.error(
                "ApplyTagsToObject Tuning: Subject {} does not have attribute 'append_tags'",
                subject)

    @TunableFactory.factory_option
    def subject_participant_type_options(description=singletons.DEFAULT,
                                         **kwargs):
        if description is singletons.DEFAULT:
            description = 'The object the tags are applied to.'
        return BaseLootOperation.get_participant_tunable(
            *('subject', ),
            description=description,
            default_participant=interactions.ParticipantType.Object,
            **kwargs)
示例#6
0
class _ObjectsFromTags(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'tags':
        TunableTags(
            description=
            '\n            For each tag, in order, gather objects that match that have that\n            tag. If the placement fails, consider another object, then consider\n            objects for the next tag.\n            '
        )
    }

    def get_objects_gen(self, resolver):
        for tag in self.tags:
            yield from services.object_manager().get_objects_with_tag_gen(tag)
class SituationByTags(_SituationMatchBase, HasTunableSingletonFactory,
                      AutoFactoryInit):
    FACTORY_TUNABLES = {
        'situation_tags':
        TunableTags(
            description=
            '\n            Situation tags to match.\n            \n            A situation that matches ANY of these tags will match.\n            ',
            filter_prefixes=('situation', ),
            minlength=1)
    }

    def match(self, situation):
        return self.situation_tags & situation.tags
class _RouteTargetTypeObject(_RouteTargetType):
    FACTORY_TUNABLES = {
        'tags':
        TunableTags(
            description=
            '\n            Tags used to pre-filter the list of potential targets.\n            If any of the tags match the object will be considered.\n            ',
            filter_prefixes=('Func', ))
    }

    def get_objects(self):
        if self.tags:
            return services.object_manager().get_objects_matching_tags(
                self.tags, match_any=True)
        else:
            return services.object_manager().get_valid_objects_gen()
示例#9
0
class _SicknessTagTest(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'tags':
        TunableTags(
            description=
            '\n            Only sickness that share any of the tags specified pass. \n            ',
            filter_prefixes=('Sickness', ))
    }

    def test_item(self, item):
        if item is None:
            return False
        return self.tags & item.sickness_tags

    def test_collection(self, collection):
        return any(self.test_item(item) for item in collection)
示例#10
0
    class RandomizeCASPart(BaseAppearanceModification):
        FACTORY_TUNABLES = {
            'body_type':
            TunableEnumEntry(
                description=
                '\n                The body type that will have its part randomized.\n                ',
                tunable_type=BodyType,
                default=BodyType.NONE,
                invalid_enums=(BodyType.NONE, )),
            'tag_categories_to_keep':
            TunableSet(
                description=
                '\n                Match tags from the existing CAS part of the specified body \n                type that belong to these tag categories when searching\n                for a new random part.\n                ',
                tunable=TunableEnumEntry(
                    description=
                    '\n                    Tags that belong to this category that are on the existing\n                    CAS part of the specified body type will be used to find\n                    a new random part.\n                    ',
                    tunable_type=TagCategory,
                    default=TagCategory.INVALID,
                    invalid_enums=(TagCategory.INVALID, ))),
            'tags':
            TunableTags(
                description=
                '\n                List of tags to use when randomizing a CAS part for the tuned\n                body type.\n                '
            )
        }

        def modify_sim_info(self, source_sim_info, modified_sim_info,
                            random_seed):
            if randomize_caspart(source_sim_info._base,
                                 modified_sim_info._base, self.body_type,
                                 list(self.tag_categories_to_keep),
                                 random_seed, list(self.tags)):
                return BodyTypeFlag.make_body_type_flag(self.body_type)
            return BodyTypeFlag.NONE

        @property
        def modifier_type(self):
            return AppearanceModifierType.RANDOMIZE_CAS_PART

        @property
        def combinable_sorting_key(self):
            return self.body_type

        def __repr__(self):
            return standard_repr(self, body_type=self.body_type)
class _PortalTypeDataTeleport(_PortalTypeDataLocomotion):
    FACTORY_TUNABLES = {
        'destination_object_tags':
        TunableTags(
            description=
            '\n            A list of tags used to find objects that this object connects with\n            to form two sides of a portal. \n            When the portals are created all of the objects on the lot with at \n            least one of the tags found in this list are found and a portal is \n            created between the originating object and the described object.\n            '
        )
    }

    @property
    def requires_los_between_points(self):
        return False

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

    @cached
    def get_portal_locations(self, obj):
        object_manager = services.object_manager()
        locations = []
        for connected_object in object_manager.get_objects_with_tags_gen(
                *self.destination_object_tags):
            if connected_object is obj:
                continue
            for portal_entry in self.object_portals:
                entry_location = portal_entry.location_entry(obj)
                exit_location = portal_entry.location_exit(connected_object)
                if portal_entry.is_bidirectional:
                    locations.append((entry_location, exit_location,
                                      exit_location, entry_location, 0))
                else:
                    locations.append(
                        (entry_location, exit_location, None, None, 0))
        return locations

    @cached
    def get_destination_objects(self):
        object_manager = services.object_manager()
        destination_objects = tuple(
            object_manager.get_objects_with_tags_gen(
                *self.destination_object_tags))
        return destination_objects
class _DestroyObjectSelectionRuleTags(_DestroyObjectSelectionRule):
    FACTORY_TUNABLES = {
        'tags':
        TunableTags(
            description=
            '\n            Only objects with these tags are considered.\n            ',
            filter_prefixes=('Func', )),
        'radius':
        TunableDistanceSquared(
            description=
            '\n            Only objects within this distance are considered.\n            ',
            default=1)
    }

    def get_objects(self, obj, target):
        objects = tuple(
            o for o in services.object_manager().get_objects_matching_tags(
                self.tags, match_any=True)
            if (o.position - obj.position).magnitude_squared() <= self.radius)
        return objects
示例#13
0
class _RandomFromTags(CreationDataBase, HasTunableSingletonFactory,
                      AutoFactoryInit):
    FACTORY_TUNABLES = {
        'filter_tags':
        TunableTags(
            description=
            '\n            Define tags to try and create the object. Picks randomly from\n            objects with these tags.\n            ',
            minlength=1)
    }

    def get_definition(self, resolver):
        definition_manager = services.definition_manager()
        filtered_defs = list(
            definition_manager.get_definitions_for_tags_gen(self.filter_tags))
        if len(filtered_defs) > 0:
            return random.choice(filtered_defs)
        logger.error(
            '{} create object basic extra tries to find object definitions tagged as {} , but no object definitions were found.',
            resolver,
            self.filter_tags,
            owner='jgiordano')
示例#14
0
class SicknessTuning:
    SICKNESS_TIME_OF_DAY = TunableTimeOfDay(
        description=
        '\n        Hour of day in which sicknesses will be distributed to Sims.\n        ',
        default_hour=3)
    SICKNESS_TESTS = TunableTestSet(
        description=
        '\n        Test sets determining whether or not a given Sim may become sick at all.\n        These tests run before we attempt to roll on whether or not \n        the Sim can avoid becoming sick. \n        (ORs of ANDs)\n        '
    )
    SICKNESS_CHANCE = TestedSum.TunableFactory(
        description=
        '\n        Chance of any given Sim to become sick.  \n        \n        Chance is out of 100.\n        \n        When the sum of the base value and values from passed tests are\n        greater than 100, the Sim is guaranteed to become sick during a \n        sickness distribution pass.\n        \n        When 0 or below, the Sim will not get sick.\n        '
    )
    PREVIOUS_SICKNESSES_TO_TRACK = TunableRange(
        description=
        '\n        Number of previous sicknesses to track.  Can use this to help promote\n        variation of sicknesses a Sim receives over time.',
        tunable_type=int,
        minimum=0,
        default=1)
    EXAM_TYPES_TAGS = TunableTags(
        description=
        '\n        Tags that represent the different types of objects that are used\n        to run exams.\n        ',
        filter_prefixes=('interaction', ))
示例#15
0
class _SicknessMatchingCritera(HasTunableSingletonFactory, AutoFactoryInit):

    @staticmethod
    def _verify_tunable_callback(instance_class, tunable_name, source, value):
        if value.tags is None and value.difficulty_range is None:
            logger.error('_SicknessMatchingCritera: {} has a sickness criteria {} that sets no criteria.', source, tunable_name)

    FACTORY_TUNABLES = {'tags': OptionalTunable(description='\n            Optionally, only sicknesses that share any of the tags specified are considered. \n            ', tunable=TunableTags(filter_prefixes=('Sickness',))), 'difficulty_range': OptionalTunable(description="\n            Optionally define the difficulty rating range that is required\n            for the Sim's sickness.\n            ", tunable=TunableInterval(description="\n                The difficulty rating range, this maps to 'difficulty_rating'\n                values in Sickness tuning.\n                ", tunable_type=float, default_lower=0, default_upper=10, minimum=0, maximum=10)), 'verify_tunable_callback': _verify_tunable_callback}

    def give_sickness(self, subject):
        sickness_criteria = CallableTestList()
        if self.tags is not None:
            sickness_criteria.append(lambda s: self.tags & s.sickness_tags)
        if self.difficulty_range is not None:
            sickness_criteria.append(lambda s: s.difficulty_rating in self.difficulty_range)
        services.get_sickness_service().make_sick(subject, criteria_func=sickness_criteria, only_auto_distributable=False)
示例#16
0
class Sickness(HasTunableReference, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(sims4.resources.Types.SICKNESS)):
    INSTANCE_TUNABLES = {'diagnosis_stat': TunableReference(description='\n            Statistic we are using to track diagnostic progress for this sickness.\n            This is used for the threshold actions checks.\n            ', manager=services.get_instance_manager(Types.STATISTIC)), 'threshold_actions': TunableMapping(description='\n            After passing specific values of the diagnosis stat, perform\n            the appropriate actions.\n            ', key_type=int, value_type=TunableList(description='\n                List of actions to process when this threshold is reached \n                or passed.', tunable=_DiagnosticThresholdActions())), 'display_name': TunableLocalizedStringFactory(description="\n            The sickness's display name. This string is provided with the owning\n            Sim as its only token.\n            ", tuning_group=GroupNames.UI), 'difficulty_rating': TunableRange(description='\n            The difficulty rating for treating this sickness.\n            ', tunable_type=float, tuning_group=GroupNames.UI, default=5, minimum=0, maximum=10), 'symptoms': TunableSet(description='\n            Symptoms associated with this sickness.  When the sickness\n            is applied to a Sim, all symptoms are applied.', tunable=TunableReference(manager=services.get_instance_manager(Types.SICKNESS), class_restrictions=(Symptom,), pack_safe=True)), 'associated_buffs': TunableSet(description='\n            The associated buffs that will be added to the Sim when the sickness\n            is applied, and removed when the sickness is removed.\n            ', tunable=TunableReference(manager=services.get_instance_manager(Types.BUFF), pack_safe=True)), 'associated_statistics': TunableSet(description="\n            The associated stats that will be added to the Sim when the sickness\n            is applied, and removed when the sickness is removed.\n            \n            These are added at the statistic's default value.\n            ", tunable=TunableReference(manager=services.get_instance_manager(Types.STATISTIC), pack_safe=True)), 'restrictions': TunableTestSet(description='\n            Test set specifying whether or not this sickness can be applied.\n            One set of tests must pass in order for the sickness to be valid.\n            (This is an OR of ANDS.)\n            '), 'weight': TestedSum.TunableFactory(description='\n            Weighted value of this sickness versus other valid sicknesses that\n            are possible for the Sim to apply a sickness to.\n            \n            Tests, if defined here, may adjust the weight in addition \n            to the tuned base value.\n            '), 'difficulty_rating': TunableRange(description='\n            The difficulty rating for treating this sickness.\n            ', tunable_type=float, tuning_group=GroupNames.UI, default=5, minimum=0, maximum=10), 'examination_loots': TunableMapping(description='\n            Mapping of examination result types to loots to apply\n            as a result of the interaction.\n            ', key_type=TunableEnumEntry(tunable_type=DiagnosticActionResultType, default=DiagnosticActionResultType.DEFAULT), value_type=_DiagnosticActionLoots.TunableFactory()), 'treatment_loots': TunableMapping(description='\n            Mapping of treatment result types to loots to apply\n            as a result of the interaction.\n            ', key_type=TunableEnumEntry(tunable_type=DiagnosticActionResultType, default=DiagnosticActionResultType.DEFAULT), value_type=_DiagnosticActionLoots.TunableFactory()), 'available_treatments': TunableSet(description='\n            Treatments that are available for this sickness.\n            ', tunable=TunableReference(manager=services.get_instance_manager(Types.INTERACTION), pack_safe=True)), 'available_treatment_lists': TunableSet(description='\n            Treatments that are available for this sickness.\n            ', tunable=snippets.TunableAffordanceListReference()), 'correct_treatments': TunableSet(description='\n            Treatments that can cure this sickness.  These sicknesses\n            will never be ruled out as exams are performed.\n            ', tunable=TunableReference(manager=services.get_instance_manager(Types.INTERACTION), pack_safe=True)), 'sickness_tags': TunableTags(description='\n            Tags that help categorize this sickness.\n            ', filter_prefixes=('Sickness',)), 'track_in_history': Tunable(description='\n            If checked, this is tracked in sickness history.\n            ', tunable_type=bool, default=True), 'considered_sick': Tunable(description='\n            Considered as sickness.  Most sickness should have this tuned.\n            Examinations, which are pseudo-sicknesses will have this tuned false.\n            \n            If this is checked, the sickness will pass is_sick tests.\n            ', tunable_type=bool, default=True), 'distribute_manually': Tunable(description='\n            If checked, this is not distributed by the sickness service,\n            and must be done by a game system or loot.\n            ', tunable_type=bool, default=False)}

    @classmethod
    def _can_be_applied(cls, resolver=None, sim_info=None):
        if not resolver and not sim_info:
            raise ValueError('Must specify a Sim info or a resolver')
        if not resolver:
            resolver = SingleSimResolver(sim_info)
        return cls.restrictions.run_tests(resolver)

    @classmethod
    def get_sickness_weight(cls, resolver=None, sim_info=None):
        if not resolver and not sim_info:
            raise ValueError('Must specify a Sim info or a resolver')
        if not cls._can_be_applied(resolver=resolver):
            return 0
        return max(cls.weight.get_modified_value(resolver), 0)

    @classmethod
    def apply_to_sim_info(cls, sim_info, from_load=False):
        if not cls._can_be_applied(resolver=SingleSimResolver(sim_info)):
            return
        for symptom in cls.symptoms:
            symptom.apply_to_sim_info(sim_info)
        for buff in cls.associated_buffs:
            if buff.can_add(sim_info):
                if not sim_info.has_buff(buff):
                    sim_info.add_buff(buff, buff_reason=cls.display_name)
        for stat in cls.associated_statistics:
            if not sim_info.get_tracker(stat).has_statistic(stat):
                sim_info.add_statistic(stat, stat.default_value)
        if not from_load:
            sim_info.sickness_tracker.add_sickness(cls)

    @classmethod
    def remove_from_sim_info(cls, sim_info):
        if sim_info is None:
            return
        for symptom in cls.symptoms:
            symptom.remove_from_sim_info(sim_info)
        for buff in cls.associated_buffs:
            sim_info.remove_buff_by_type(buff)
        for stat in cls.associated_statistics:
            sim_info.remove_statistic(stat)
        sim_info.remove_statistic(cls.diagnosis_stat)
        if sim_info.has_sickness(cls):
            sim_info.sickness_tracker.remove_sickness()

    @classmethod
    def is_available_treatment(cls, affordance):
        return affordance in itertools.chain(cls.available_treatments, *cls.available_treatment_lists)

    @classmethod
    def is_correct_treatment(cls, affordance):
        return affordance in cls.correct_treatments

    @classmethod
    def apply_loots_for_action(cls, action_type, result_type, interaction):
        loots_to_apply = None
        if action_type == SicknessDiagnosticActionType.EXAM:
            if result_type in cls.examination_loots:
                loots_to_apply = cls.examination_loots[result_type]
        elif action_type == SicknessDiagnosticActionType.TREATMENT:
            if result_type in cls.treatment_loots:
                loots_to_apply = cls.treatment_loots[result_type]
        if loots_to_apply is not None:
            loots_to_apply.apply_loots(interaction.get_resolver())
        cls._handle_threshold_actions(interaction.get_resolver())

    @classmethod
    def _handle_threshold_actions(cls, resolver):
        cls.update_diagnosis(resolver.target.sim_info, interaction=resolver.interaction)

    @classmethod
    def update_diagnosis(cls, sim_info, interaction=None):
        diagnostic_progress = sim_info.get_statistic(cls.diagnosis_stat).get_value()
        last_progress = sim_info.sickness_tracker.last_progress
        if diagnostic_progress == last_progress:
            return
        for (threshold, actions) in cls._get_sorted_threshold_actions():
            if threshold <= last_progress:
                continue
            if diagnostic_progress < threshold:
                break
            for action in actions:
                action.perform(sim_info, interaction=interaction)
        sim_info.sickness_record_last_progress(diagnostic_progress)

    @classmethod
    def _get_sorted_threshold_actions(cls):
        return sorted(cls.threshold_actions.items(), key=operator.itemgetter(0))

    @classmethod
    def on_zone_load(cls, sim_info):
        cls.on_sim_info_loaded(sim_info)

    @classmethod
    def on_sim_info_loaded(cls, sim_info):
        if not sim_info.has_sickness_tracking():
            return
        sim_info.sickness_record_last_progress(sim_info.get_statistic(cls.diagnosis_stat).get_value())
        cls.apply_to_sim_info(sim_info, from_load=True)

    @classmethod
    def get_ordered_symptoms(cls):
        ordered_symptoms = []
        for (_, action_list) in cls._get_sorted_threshold_actions():
            for action in action_list:
                if isinstance(action, _DiscoverSymptomThresholdAction):
                    ordered_symptoms.append(action.symptom)
        return ordered_symptoms
示例#17
0
class ActingEmployeeSituation(SituationComplexCommon):
    INSTANCE_TUNABLES = {
        '_pre_performance_state':
        _ActingEmployeePrePerformanceState.TunableFactory(
            description=
            '\n            The initial state for npc co star sims.\n            ',
            tuning_group=GroupNames.STATE,
            display_name='01_pre_performance_state'),
        '_go_to_marks_state':
        _ActingEmployeeGoToMarksState.TunableFactory(
            description=
            '\n            The employee sim will go to this state once the player says their\n            ready to perform.\n            ',
            tuning_group=GroupNames.STATE,
            display_name='02_go_to_marks_state'),
        '_performance_state':
        _ActingEmployeePerformanceState.TunableFactory(
            description=
            '\n            Once the employee gets to their marks, they will end up in this\n            state. The only interactions that should be valid at this point is\n            some idle interaction and the performance interactions.\n            ',
            tuning_group=GroupNames.STATE,
            display_name='03_performance_state'),
        '_post_performance_state':
        _ActingEmployeePostPerformanceState.TunableFactory(
            description=
            '\n            When the main situation goal is completed by the player, employees will be pushed into\n            this state.\n            ',
            tuning_group=GroupNames.STATE,
            display_name='04_post_performance_state'),
        '_actor_career_event_situation_tags':
        TunableTags(
            description=
            '\n            A set of tags that can identify an actor career event situation.\n            \n            Used to track when the actor completes the performance.\n            ',
            tuning_group=GroupNames.SITUATION,
            filter_prefixes=('Situation', ))
    }

    @classmethod
    def _states(cls):
        return (SituationStateData(1,
                                   _ActingEmployeePrePerformanceState,
                                   factory=cls._pre_performance_state),
                SituationStateData(2,
                                   _ActingEmployeeGoToMarksState,
                                   factory=cls._go_to_marks_state),
                SituationStateData(3,
                                   _ActingEmployeePerformanceState,
                                   factory=cls._performance_state),
                SituationStateData(4,
                                   _ActingEmployeePostPerformanceState,
                                   factory=cls._post_performance_state))

    @classmethod
    def default_job(cls):
        pass

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return list(cls._pre_performance_state._tuned_values.
                    job_and_role_changes.items())

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._register_test_event_for_keys(
            TestEvent.MainSituationGoalComplete,
            self._actor_career_event_situation_tags)
        self._register_test_event_for_keys(
            TestEvent.SituationEnded, self._actor_career_event_situation_tags)

    def start_situation(self):
        super().start_situation()
        self._change_state(self._pre_performance_state())

    def handle_event(self, sim_info, event, resolver):
        super().handle_event(sim_info, event, resolver)
        if event == TestEvent.MainSituationGoalComplete:
            self._change_state(self._post_performance_state())
        elif event == TestEvent.SituationEnded:
            self._change_state(self._post_performance_state())
示例#18
0
class SituationIdentityTest(HasTunableSingletonFactory, AutoFactoryInit, BaseTest):
    FACTORY_TUNABLES = {'situation_list': TunableSet(description='\n            Test will pass if the specified reference is in the given list.\n            ', tunable=TunableReference(manager=services.get_instance_manager(Types.SITUATION), pack_safe=True)), 'situation_tags': TunableTags(description='\n            Test will pass if the tested reference is tagged\n            with one of the tuned tags.\n            ', filter_prefixes=('situation',))}

    @cached_test
    def __call__(self, situation):
        match = situation in self.situation_list or self.situation_tags & situation.tags
        if not match:
            return TestResult(False, 'Failed {}. Items Tested: {}. Tags Tested: {}', situation, self.situation_list, self.situation_tags, tooltip=self.tooltip)
        return TestResult.TRUE
示例#19
0
class HolidayTradition(HasTunableReference,
                       HolidayTraditionDisplayMixin,
                       metaclass=HashedTunedInstanceMetaclass,
                       manager=services.get_instance_manager(
                           sims4.resources.Types.HOLIDAY_TRADITION)):
    INSTANCE_TUNABLES = {
        'situation_goal':
        TunableReference(
            description=
            '\n            This is the situation goal that will be offered when this tradition\n            is active.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.SITUATION_GOAL)),
        'pre_holiday_buffs':
        TunableList(
            description=
            '\n            A list of buffs that will be given out to all of the player Sims\n            during the pre-holiday period of each holiday.\n            ',
            tunable=TunableReference(
                description=
                '\n                A buff that is given to all of the player Sims when it is the\n                pre-holiday period.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.BUFF)),
            unique_entries=True),
        'pre_holiday_buff_reason':
        OptionalTunable(
            description=
            '\n            If set, specify a reason why the buff was added.\n            ',
            tunable=TunableLocalizedString(
                description=
                '\n                The reason the buff was added. This will be displayed in the\n                buff tooltip.\n                '
            )),
        'holiday_buffs':
        TunableList(
            description=
            '\n            A list of buffs that will be given out to all Sims during each\n            holiday.\n            ',
            tunable=TunableReference(
                description=
                '\n                A buff that is given to all Sims during the holiday.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.BUFF)),
            unique_entries=True),
        'holiday_buff_reason':
        OptionalTunable(
            description=
            '\n            If set, specify a reason why the buff was added.\n            ',
            tunable=TunableLocalizedString(
                description=
                '\n                The reason the buff was added. This will be displayed in the\n                buff tooltip.\n                '
            )),
        'drama_nodes_to_score':
        TunableList(
            description=
            '\n            Drama nodes that we will attempt to schedule and score when this\n            tradition becomes active.\n            ',
            tunable=TunableReference(
                description=
                '\n                A drama node that we will put in the scoring pass when this\n                tradition becomes active.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.DRAMA_NODE)),
            unique_entries=True),
        'drama_nodes_to_run':
        TunableList(
            description=
            '\n            Drama nodes that will be run when the tradition is activated.\n            ',
            tunable=TunableReference(
                description=
                '\n                A drama node that we will run when the holiday becomes active.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.DRAMA_NODE)),
            unique_entries=True),
        'additional_walkbys':
        SituationCurve.TunableFactory(
            description=
            '\n            An additional walkby schedule that will be added onto the walkby\n            schedule when the tradition is active.\n            ',
            get_create_params={'user_facing': False}),
        'preference':
        TunableList(
            description=
            '\n            A list of pairs of preference categories and tests.  To determine\n            what a Sim feels about a tradition each set of tests in this list\n            will be run in order.  When one of the test sets passes then we\n            will set that as the preference.  If none of them pass we will\n            default to LIKES.\n            ',
            tunable=TunableTuple(
                description=
                '\n                A pair of preference and test set.\n                ',
                preference=TunableEnumEntry(
                    description=
                    '\n                    The preference that the Sim will have to this tradition if\n                    the test set passes.\n                    ',
                    tunable_type=TraditionPreference,
                    default=TraditionPreference.LIKES),
                tests=TunablePreferenceTestList(
                    description=
                    '\n                    A set of tests that need to pass for the Sim to have the\n                    tuned preference.\n                    '
                ),
                reason=OptionalTunable(
                    description=
                    '\n                    If enabled then we will also give this reason as to why the\n                    preference is the way it is.\n                    ',
                    tunable=TunableLocalizedString(
                        description=
                        '\n                        The reason that the Sim has this preference.\n                        '
                    )))),
        'preference_reward_buff':
        OptionalTunable(
            description=
            '\n            If enabled then if the Sim loves this tradition when the holiday is\n            completed they will get a special buff if they completed the\n            tradition.\n            ',
            tunable=TunableBuffReference(
                description=
                '\n                The buff given if this Sim loves the tradition and has completed\n                it at the end of the holiday.\n                '
            )),
        'selectable':
        Tunable(
            description=
            '\n            If checked then this tradition will appear in the tradition\n            selection.\n            ',
            tunable_type=bool,
            default=True,
            tuning_group=GroupNames.UI,
            export_modes=ExportModes.All),
        'lifecycle_actions':
        TunableList(
            description=
            '\n            Actions that occur as a result of the tradition activation/de-activation.\n            ',
            tunable=TraditionActions.TunableFactory()),
        'events':
        TunableList(
            description=
            '\n            A list of times and things we want to happen at that time.\n            ',
            tunable=TunableTuple(
                description=
                '\n                A pair of a time of day and event of something that we want\n                to occur.\n                ',
                time=TunableTimeOfDay(
                    description=
                    '\n                    The time of day this event will occur.\n                    '
                ),
                event=TunableVariant(
                    description=
                    '\n                    What we want to occur at this time.\n                    ',
                    modify_items=ModifyAllItems(),
                    start_situation=StartSituation(),
                    default='start_situation'))),
        'core_object_tags':
        TunableTags(
            description=
            '\n            Tags of all the core objects used in this tradition.\n            ',
            filter_prefixes=('func', ),
            tuning_group=GroupNames.UI,
            export_modes=ExportModes.All),
        'deco_object_tags':
        TunableTags(
            description=
            '\n            Tags of all the deco objects used in this tradition.\n            ',
            filter_prefixes=('func', ),
            tuning_group=GroupNames.UI,
            export_modes=ExportModes.All),
        'business_cost_multiplier':
        TunableMapping(
            description=
            '\n            A mapping between the business type and the cost multiplier that\n            we want to use if this tradition is active.\n            ',
            key_type=TunableEnumEntry(
                description=
                '\n                The type of business that we want to apply this price modifier\n                on.\n                ',
                tunable_type=BusinessType,
                default=BusinessType.INVALID,
                invalid_enums=(BusinessType.INVALID, )),
            value_type=TunableRange(
                description=
                '\n                The value of the multiplier to use.\n                ',
                tunable_type=float,
                default=1.0,
                minimum=0.0))
    }

    @classmethod
    def _verify_tuning_callback(cls):
        if cls._display_data.instance_display_description is None:
            logger.error('Tradition {} missing display description', cls)
        if cls._display_data.instance_display_icon is None:
            logger.error('Tradition {} missing display icon', cls)
        if cls._display_data.instance_display_name is None:
            logger.error('Tradition {} missing display name', cls)

    def __init__(self):
        self._state = HolidayState.INITIALIZED
        self._buffs_added = defaultdict(list)
        self._event_alarm_handles = {}
        self._drama_node_processor = None

    @property
    def state(self):
        return self._state

    @classmethod
    def get_buiness_multiplier(cls, business_type):
        return cls.business_cost_multiplier.get(business_type, 1.0)

    @classmethod
    def get_sim_preference(cls, sim_info):
        resolver = SingleSimResolver(sim_info)
        for possible_preference in cls.preference:
            if possible_preference.tests.run_tests(resolver):
                return (possible_preference.preference,
                        possible_preference.reason)
        return (TraditionPreference.LIKES, None)

    def on_sim_spawned(self, sim):
        if self._state == HolidayState.PRE_DAY:
            if sim.is_npc:
                return
            for buff in self.pre_holiday_buffs:
                buff_handle = sim.add_buff(
                    buff, buff_reason=self.pre_holiday_buff_reason)
                if buff_handle is not None:
                    self._buffs_added[sim.sim_id].append(buff_handle)
        elif self._state == HolidayState.RUNNING:
            for buff in self.holiday_buffs:
                buff_handle = sim.add_buff(
                    buff, buff_reason=self.holiday_buff_reason)
                if buff_handle is not None:
                    self._buffs_added[sim.sim_id].append(buff_handle)

    def activate_pre_holiday(self):
        if self._state >= HolidayState.PRE_DAY:
            logger.error(
                'Tradition {} is trying to be put into the pre_holiday, but is already in {} which is farther along.',
                self, self._state)
            return
        self._state = HolidayState.PRE_DAY
        if self.pre_holiday_buffs:
            services.sim_spawner_service().register_sim_spawned_callback(
                self.on_sim_spawned)
            for sim_info in services.active_household().instanced_sims_gen():
                for buff in self.pre_holiday_buffs:
                    buff_handle = sim_info.add_buff(
                        buff, buff_reason=self.pre_holiday_buff_reason)
                    if buff_handle is not None:
                        self._buffs_added[sim_info.sim_id].append(buff_handle)

    def _remove_all_buffs(self):
        sim_info_manager = services.sim_info_manager()
        for (sim_id, buff_handles) in self._buffs_added.items():
            sim_info = sim_info_manager.get(sim_id)
            if sim_info is None:
                continue
            if sim_info.Buffs is None:
                continue
            for buff_handle in buff_handles:
                sim_info.remove_buff(buff_handle)
        self._buffs_added.clear()

    def _deactivate_pre_holiday(self):
        if self.pre_holiday_buffs:
            services.sim_spawner_service().unregister_sim_spawned_callback(
                self.on_sim_spawned)
            self._remove_all_buffs()

    def deactivate_pre_holiday(self):
        if self._state != HolidayState.PRE_DAY:
            logger.error(
                'Tradition {} is trying to deactivate the preday, but it is in the {} state, not that one.',
                self, self._state)
        self._state = HolidayState.SHUTDOWN
        self._deactivate_pre_holiday()

    def _create_event_alarm(self, key, event):
        def callback(_):
            event.event.perform(GlobalResolver())
            del self._event_alarm_handles[key]

        now = services.time_service().sim_now
        time_to_event = now.time_till_next_day_time(event.time)
        if key in self._event_alarm_handles:
            alarms.cancel_alarm(self._event_alarm_handles[key])
        self._event_alarm_handles[key] = alarms.add_alarm(
            self, time_to_event, callback)

    def _process_scoring_gen(self, timeline):
        try:
            yield from services.drama_scheduler_service(
            ).score_and_schedule_nodes_gen(self.drama_nodes_to_score,
                                           1,
                                           timeline=timeline)
        except GeneratorExit:
            raise
        except Exception as exception:
            logger.exception('Exception while scoring DramaNodes: ',
                             exc=exception,
                             level=sims4.log.LEVEL_ERROR)
        finally:
            self._drama_node_processor = None

    def activate_holiday(self, from_load=False, from_customization=False):
        if self._state >= HolidayState.RUNNING:
            logger.error(
                'Tradition {} is trying to be put into the Running state, but is already in {} which is farther along.',
                self, self._state)
            return
        self._deactivate_pre_holiday()
        self._state = HolidayState.RUNNING
        if self.holiday_buffs:
            services.sim_spawner_service().register_sim_spawned_callback(
                self.on_sim_spawned)
            for sim_info in services.sim_info_manager().instanced_sims_gen():
                for buff in self.holiday_buffs:
                    buff_handle = sim_info.add_buff(
                        buff, buff_reason=self.holiday_buff_reason)
                    if buff_handle is not None:
                        self._buffs_added[sim_info.sim_id].append(buff_handle)
        for (key, event) in enumerate(self.events):
            self._create_event_alarm(key, event)
        if not from_load:
            resolver = GlobalResolver()
            for actions in self.lifecycle_actions:
                actions.try_perform(
                    resolver, TraditionActivationEvent.TRADITION_ADD
                    if from_customization else
                    TraditionActivationEvent.HOLIDAY_ACTIVATE)
            if self.drama_nodes_to_score:
                sim_timeline = services.time_service().sim_timeline
                self._drama_node_processor = sim_timeline.schedule(
                    elements.GeneratorElement(self._process_scoring_gen))
            drama_scheduler = services.drama_scheduler_service()
            for drama_node in self.drama_nodes_to_run:
                drama_scheduler.run_node(drama_node, resolver)

    def deactivate_holiday(self, from_customization=False):
        if self._state != HolidayState.RUNNING:
            logger.error(
                'Tradition {} is trying to deactivate the tradition, but it is in the {} state, not that one.',
                self, self._state)
        self._state = HolidayState.SHUTDOWN
        if self.holiday_buffs:
            services.sim_spawner_service().unregister_sim_spawned_callback(
                self.on_sim_spawned)
            self._remove_all_buffs()
        for alarm in self._event_alarm_handles.values():
            alarms.cancel_alarm(alarm)
        self._event_alarm_handles.clear()
        resolver = GlobalResolver()
        for actions in self.lifecycle_actions:
            actions.try_perform(
                resolver,
                TraditionActivationEvent.TRADITION_REMOVE if from_customization
                else TraditionActivationEvent.HOLIDAY_DEACTIVATE)

    def get_additional_walkbys(self, predicate=lambda _: True):
        weighted_situations = self.additional_walkbys.get_weighted_situations(
            predicate=predicate)
        if weighted_situations is None:
            return ()
        return weighted_situations
示例#20
0
class BowlingVenueMixin:
    BOWLING_LANE_OBJECT_TAGS = TunableTags(
        description=
        '\n        Tags that considered bowling lane objects.\n        ',
        filter_prefixes=('func', ))
    MAX_SIMS_ON_VENUE = TunableRange(
        description=
        '\n        The maximum number of the Sims can be spawned in all bowling situations\n        combined in the venue.\n        ',
        tunable_type=int,
        default=4,
        minimum=1)
    MOONLIGHT_AFFORDANCES = TunableTuple(
        description=
        '\n        Super affordances to set moonlight bowling lane when they are turned\n        on and turned off.\n        ',
        moonlight_on=TunableReference(
            description=
            '\n            A super affordance when the moonlight is turned on.\n            ',
            manager=services.affordance_manager(),
            class_restrictions=('SwitchLightAndStateImmediateInteraction', ),
            pack_safe=True),
        moonlight_off=TunableReference(
            description=
            '\n            A super affordance when the moonlight is turned off.\n            ',
            manager=services.affordance_manager(),
            class_restrictions=('SwitchLightAndStateImmediateInteraction', ),
            pack_safe=True))
    INSTANCE_TUNABLES = {
        'bowling_venue':
        TunableTuple(
            description=
            '\n            Holds data associated with bowling venue situations.\n            ',
            situation_alarm_interval=TunableRange(
                description=
                '\n                Interval in sim minutes to trigger bowling situation.\n                ',
                tunable_type=int,
                minimum=1,
                default=60),
            bowling_situations_schedule=SituationCurve.TunableFactory(
                description=
                '\n                Bowling situations that are created depending on what time of \n                the day it is.\n                ',
                get_create_params={'user_facing': False}),
            moonlight_on_off_schedule=TunableList(
                description=
                '\n                A list of tuples declaring a schedule to turn on/off moonlight bowling lane.\n                ',
                tunable=TunableTuple(
                    description=
                    '\n                    The first value is the day of the week that maps to a desired\n                    on/off time for moonlight bowling by the time of the day.\n                    \n                    days_of_the_week    on_off_moonlight_by_time_of_day\n                        M,Th,F                    schedule_1\n                        W,Sa                      schedule_2\n                    ',
                    days_of_the_week=TunableDayAvailability(),
                    moonlight_on_off_desire_by_time_of_day=TunableMapping(
                        description=
                        '\n                        Each entry in the map has two columns.\n                        The first column is the hour of the day (0-24)\n                        that maps to is_moonlight_on.\n                        \n                        The entry with starting hour that is closest to, but before\n                        the current hour will be chosen.\n                        \n                        Given this tuning\n                            beginning_hour        is_moonlight_on\n                                6                         true\n                                10                        false\n                                14                        true\n                                20                        false\n                                \n                            if the current hour is 11, hour_of_day will be 10 and moonlight_on is false, so moonlight is disabled.\n                            if the current hour is 19, hour_of_day will be 14 and moonlight_on is true, so moonlight is enabled.\n                            if the current hour is 23, hour_of_day will be 20 and moonlight_on is false, so moonlight is disabled.\n                            if the current hour is 2, hour_of_day will be 20 and moonlight_on is false. (uses 20 tuning because it is not 6 yet)\n                            \n                        The entries will be automatically sorted by time.\n                        ',
                        key_type=Tunable(tunable_type=int, default=0),
                        value_type=Tunable(tunable_type=bool, default=False),
                        key_name='beginning_hour',
                        value_name='is_moonlight_on'))))
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._bowling_lanes = self._get_bowling_lanes()
        self._bowling_lanes_dict = {}
        self._bowling_situation_alarm_handle = None
        self._moonlight_alarm_handle = None

    def create_situations_during_zone_spin_up(self):
        super().create_situations_during_zone_spin_up()
        self._initialize_alarms()

    def on_shutdown(self):
        super().on_shutdown()
        if self._bowling_situation_alarm_handle is not None:
            alarms.cancel_alarm(self._bowling_situation_alarm_handle)
            self._bowling_situation_alarm_handle = None
        if self._moonlight_alarm_handle is not None:
            alarms.cancel_alarm(self._moonlight_alarm_handle)
            self._moonlight_alarm_handle = None

    def on_exit_buildbuy(self):
        super().on_exit_buildbuy()
        self._bowling_lanes = self._get_bowling_lanes()

    def _initialize_alarms(self):
        repeating_time_span = date_and_time.create_time_span(
            minutes=self.bowling_venue.situation_alarm_interval)
        self._bowling_situation_alarm_handle = alarms.add_alarm(
            self,
            date_and_time.create_time_span(minutes=5),
            self._find_available_lane_and_start_situations,
            repeating=True,
            repeating_time_span=repeating_time_span)
        if self.bowling_venue.moonlight_on_off_schedule:
            self._moonlight_change()
            time_of_day = services.time_service().sim_now
            self._moonlight_alarm_handle = alarms.add_alarm(
                self,
                self._get_time_span_to_next_moonlight_schedule(time_of_day),
                self._moonlight_change)

    def get_total_bowling_lanes(self):
        if self._bowling_lanes is None:
            return 0
        return len(self._bowling_lanes)

    def set_situation_bowling_lane(self, situation_id, bowling_lane_obj):
        self._bowling_lanes_dict[situation_id] = bowling_lane_obj

    def get_situation_bowling_lane(self, situation_id):
        return self._bowling_lanes_dict.get(situation_id)

    def remove_situation_bowling_lane(self, situation_id):
        if situation_id in self._bowling_lanes_dict:
            del self._bowling_lanes_dict[situation_id]

    def _get_bowling_lanes(self):
        if self.BOWLING_LANE_OBJECT_TAGS is None:
            return
        else:
            bowling_lanes = list(
                services.object_manager().get_objects_with_tags_gen(
                    *self.BOWLING_LANE_OBJECT_TAGS))
            if len(bowling_lanes) == 0:
                return
        return bowling_lanes

    def _get_number_of_sims_in_all_situations(self):
        situation_manager = services.get_zone_situation_manager()
        if situation_manager is None:
            return 0
        total_sims = 0
        for (situation_id, _) in self._bowling_lanes_dict.items():
            situation = situation_manager.get(situation_id)
            if situation is not None:
                total_sims += len(situation._situation_sims)
        return total_sims

    def _find_available_lane_and_start_situations(self, *_):
        if self._bowling_lanes is None:
            return
        if self.get_total_bowling_lanes() - len(self._bowling_lanes_dict) <= 1:
            return
        if self._get_number_of_sims_in_all_situations(
        ) >= self.MAX_SIMS_ON_VENUE:
            return
        for bowling_lane_obj in self._bowling_lanes:
            if bowling_lane_obj.in_use:
                continue
            if bowling_lane_obj in self._bowling_lanes_dict.values():
                continue
            self._start_bowling_situation(bowling_lane_obj)
            break

    def _start_bowling_situation(self, bowling_lane_obj):
        situation_manager = services.get_zone_situation_manager()
        if not self.bowling_venue.bowling_situations_schedule.entries:
            return
        total_bowling_lanes = self.get_total_bowling_lanes()
        all_weighted_situations = self.bowling_venue.bowling_situations_schedule.get_weighted_situations(
        )
        weighted_situations = [
            (weight, situation[0])
            for (weight, situation) in all_weighted_situations
            if situation[0].situation_meets_starting_requirements(
                total_bowling_lanes)
        ]
        if not weighted_situations:
            return
        situation_to_create = weighted_random_item(weighted_situations)
        guest_list = situation_to_create.get_predefined_guest_list()
        if guest_list is None:
            guest_list = SituationGuestList(invite_only=True)
        situation_id = situation_manager.create_situation(
            situation_to_create,
            guest_list=guest_list,
            spawn_sims_during_zone_spin_up=True,
            user_facing=False)
        self.set_situation_bowling_lane(situation_id, bowling_lane_obj)
        return situation_id

    def _get_sorted_moonlight_schedule(self, day):
        moonlight_schedule = []
        for item in self.bowling_venue.moonlight_on_off_schedule:
            enabled = item.days_of_the_week.get(day, None)
            if enabled:
                for (beginning_hour, is_on
                     ) in item.moonlight_on_off_desire_by_time_of_day.items():
                    moonlight_schedule.append((beginning_hour, is_on))
        moonlight_schedule.sort(key=operator.itemgetter(0))
        return moonlight_schedule

    def _moonlight_change(self, alarm_handle=None):
        if not self.bowling_venue.moonlight_on_off_schedule:
            return
        if self._get_desired_is_moonlight_on():
            self._activate_moonlight_affordance(
                self.MOONLIGHT_AFFORDANCES.moonlight_on)
        else:
            self._activate_moonlight_affordance(
                self.MOONLIGHT_AFFORDANCES.moonlight_off)
        if alarm_handle is not None:
            time_of_day = services.time_service().sim_now
            self._moonlight_alarm_handle = alarms.add_alarm(
                self,
                self._get_time_span_to_next_moonlight_schedule(time_of_day),
                self._moonlight_change)

    def _get_desired_is_moonlight_on(self):
        if not self.bowling_venue.moonlight_on_off_schedule:
            return
        time_of_day = services.time_service().sim_now
        hour_of_day = time_of_day.hour()
        day = time_of_day.day()
        moonlight_schedule = self._get_sorted_moonlight_schedule(day)
        if not moonlight_schedule:
            return
        entry = moonlight_schedule[-1]
        desire = entry[1]
        for entry in moonlight_schedule:
            if entry[0] <= hour_of_day:
                desire = entry[1]
            else:
                break
        return desire

    def _get_time_span_to_next_moonlight_schedule(self, time_of_day):
        if not self.bowling_venue.moonlight_on_off_schedule:
            return
        days_to_schedule_ahead = 1
        current_day = time_of_day.day()
        next_day = (current_day + days_to_schedule_ahead) % 7
        next_day_sorted_times = self._get_sorted_moonlight_schedule(next_day)
        if next_day_sorted_times:
            next_moonlight_hour = next_day_sorted_times[0][0]
        else:
            next_moonlight_hour = 0
        now = services.time_service().sim_now
        sorted_times = self._get_sorted_moonlight_schedule(current_day)
        scheduled_day = int(now.absolute_days())
        now_hour = now.hour()
        for (moonlight_hour, _) in sorted_times:
            if moonlight_hour > now_hour:
                next_moonlight_hour = moonlight_hour
                break
        else:
            scheduled_day += 1
        future = date_and_time.create_date_and_time(days=scheduled_day,
                                                    hours=next_moonlight_hour)
        time_span_until = future - now
        return time_span_until

    def _activate_moonlight_affordance(self, affordance):
        if self._bowling_lanes is None:
            return
        moonlight_state = affordance.state_settings.desired_state
        for bowling_lane in self._bowling_lanes:
            if moonlight_state == bowling_lane.get_state(
                    moonlight_state.state):
                continue
            affordance.target = bowling_lane
            affordance.state_settings.execute_helper(affordance)
            affordance.lighting_setting_operation.execute(affordance)
示例#21
0
class LightningTuning:
    ACTIVE_LIGHTNING = TunableTuple(
        description='\n        Active Lightning Tuning\n        ',
        weights=TunableTuple(
            description=
            '\n            Weights for striking various objects.\n            ',
            weight_terrain=TunableRange(
                description=
                '\n                Weighted chance of striking terrain versus other locations.\n                ',
                tunable_type=float,
                default=1.0,
                minimum=0.0),
            weight_object=TunableRange(
                description=
                '\n                Weighted chance of striking non-Sim objects versus other locations.\n                ',
                tunable_type=float,
                default=1.0,
                minimum=0.0),
            weight_sim=TunableRange(
                description=
                '\n                Weighted chance of striking Sims versus other locations.\n                ',
                tunable_type=float,
                default=1.0,
                minimum=0.0)))
    STRIKE_TERRAIN_TUNING = TunableTuple(
        description=
        '\n        Tuning for when we want a lightning bolt to strike the ground.\n        ',
        effect_off_lot=PlayEffect.TunableFactory(
            description=
            '\n            The effect we want to spawn at the terrain location if it is off\n            lot.\n            '
        ),
        effect_on_lot=PlayEffect.TunableFactory(
            description=
            "\n            The effect we want to spawn at the object's location if it is on\n            lot. This will also have a scorch mark associated with it.\n            "
        ),
        scorch_mark_delay=TunableRealSecond(
            description=
            '\n            The delay, in real seconds, before we place a scorch mark for on-\n            lot lightning strikes.\n            ',
            default=0),
        create_object_tuning=TunableTuple(
            description=
            '\n            Tuning related to creating objects when lightning strikes the\n            ground.\n            ',
            chance=TunablePercent(
                description=
                '\n                Chance to spawn one of the objects tuned here when lightning\n                strikes the terrain.\n                ',
                default=10),
            definition_weights=TunableList(
                description=
                '\n                List of definitions and their weighted chance of being created\n                at the location of the lightning strike.\n                ',
                tunable=TunableTuple(
                    description=
                    '\n                    The object definition and weighted chance of it being\n                    created.\n                    ',
                    weight=TunableRange(
                        description=
                        '\n                        The weighted chance of creating this object.\n                        ',
                        tunable_type=float,
                        default=1.0,
                        minimum=0.0),
                    definition=TunableReference(
                        description=
                        '\n                        The object we want to create at the strike location.\n                        ',
                        manager=services.definition_manager())))),
        broadcaster=BroadcasterRequest.TunableFactory(
            description=
            '\n            The broadcaster we want to fire when a lightning bolt strikes the\n            terrain.\n            '
        ))
    STRIKE_OBJECT_TUNING = TunableTuple(
        description=
        "\n        Tuning for when we want a lightning bolt to strike an object.\n        \n        For an object to be considered for a lightning strike, it must have one\n        of the tags tuned here. We will increase its chance based on lightning\n        multiplier tuning on it's Weather Aware Component if it has one, and\n        apply both the generic loot tuned here, as well as any loot that is\n        registered for Struck By Lightning.\n        ",
        effect=PlayEffect.TunableFactory(
            description=
            "\n            The effect we want to spawn at the object's location.\n            "
        ),
        scorch_mark_delay=TunableRealSecond(
            description=
            '\n            The delay, in real seconds, before we place a scorch mark for on-\n            lot lightning strikes.\n            ',
            default=0),
        generic_loot_on_strike=TunableList(
            description=
            '\n            Loot to apply to all objects when struck by lightning.\n            \n            Objects that have a weather aware component can tune loot when\n            listening for Struck By Lightning.\n            ',
            tunable=TunableReference(
                description=
                '\n                A loot action to apply to the object struck by lightning.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.ACTION))),
        tags=TunableTags(
            description=
            '\n            A set of tags that determine if an object can be struck by\n            lightning. Each object has a weight of 1 to be struck by lightning,\n            but can be multiplied in the weather aware component to give\n            preference to electronics, etc.\n            '
        ),
        broadcaster=BroadcasterRequest.TunableFactory(
            description=
            '\n            The broadcaster we want to fire when a lightning bolt strikes an\n            object.\n            '
        ))
    STRIKE_SIM_TUNING = TunableTuple(
        description=
        '\n        Tuning for when we want a lightning bolt to strike a Sim.\n        ',
        affordance=TunablePackSafeReference(
            description=
            '\n            The interaction to push on a Sim that is struck by lightning.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.INTERACTION)))
示例#22
0
class UniversityCourseData(HasTunableReference, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(Types.UNIVERSITY_COURSE_DATA)):
    INSTANCE_TUNABLES = {'spawn_point_tag': TunableMapping(description='\n            University specific spawn point tags.\n            Used by course related interactions to determine which spawn\n            point to use for the constraint. (i.e. the one in front of the\n            appropriate building)\n            ', key_type=TunableReference(manager=services.get_instance_manager(Types.UNIVERSITY)), value_type=TunableSet(tunable=TunableEnumWithFilter(tunable_type=Tag, default=Tag.INVALID, filter_prefixes=('Spawn',)), minlength=1)), 'classroom_tag': TunableMapping(description='\n            University specific classroom tags.\n            Used by university interactions on shells to determine which building\n            shell should have the interaction(s) available.\n            ', key_type=TunableReference(manager=services.get_instance_manager(Types.UNIVERSITY)), value_type=TunableSet(tunable=TunableEnumEntry(tunable_type=Tag, default=Tag.INVALID), minlength=1)), 'university_course_mapping': TunableMapping(description='\n            University specific course name and description.\n            Each university can have its own course name and description\n            defined.\n            ', key_type=TunableReference(manager=services.get_instance_manager(Types.UNIVERSITY)), value_type=TunableTuple(course_name=TunableLocalizedStringFactory(description='\n                    The name of this course.\n                    '), course_description=TunableLocalizedString(description='\n                    A description for this course.\n                    ', allow_none=True), export_class_name='UniversityCourseDisplayData'), tuple_name='UniversityCourseDataMapping', export_modes=ExportModes.All), 'course_skill_data': TunableTuple(description='\n            The related skill data for this specific course.  Whenever a Sim \n            does something that increases their course grade performance (like\n            attending lecture or studying), this skill will also increase by\n            the tunable amount.  Likewise, whenever this related skill \n            increases, the course grade will also increase.\n            ', related_skill=OptionalTunable(description='\n                The related skill associated with this course.\n                ', tunable=TunablePackSafeReference(manager=services.get_instance_manager(Types.STATISTIC), class_restrictions=('Skill',)))), 'icon': TunableIcon(description='\n            Icon for this university course.\n            ', export_modes=ExportModes.All, allow_none=True), 'cost': TunableRange(description='\n            The cost of this course.\n            ', tunable_type=int, default=200, minimum=0, export_modes=ExportModes.All), 'course_tags': TunableTags(description='\n            The tag for this course.  Used for objects that may be shared \n            between courses.\n            ', filter_prefixes=['course']), 'final_requirement_type': TunableEnumEntry(description='\n            The final requirement for this course.  This requirement must be \n            completed before the course can be considered complete.\n            ', tunable_type=FinalCourseRequirement, default=FinalCourseRequirement.NONE), 'final_requirement_aspiration': TunableReference(description='\n            An aspiration to use for tracking the final course requirement. \n            ', manager=services.get_instance_manager(sims4.resources.Types.ASPIRATION), class_restrictions='AspirationAssignment', allow_none=True), 'professor_assignment_trait': TunableMapping(description='\n            A mapping of University -> professor assignment trait.\n            \n            This is needed because each of the universities shipped with EP08\n            use the exact same classes but we want different teachers for each\n            university.\n            ', key_type=TunableReference(description='\n                A reference to the University that the professor will belong to.\n                ', manager=services.get_instance_manager(sims4.resources.Types.UNIVERSITY)), value_type=TunableReference(description='\n                The trait used to identify the professor for this course.\n                ', manager=services.get_instance_manager(sims4.resources.Types.TRAIT)))}

    @classproperty
    def is_elective(cls):
        return any(cls is e.elective for e in University.COURSE_ELECTIVES.electives)
class ObstacleCourseSituation(SituationComplexCommon):
    INSTANCE_TUNABLES = {
        'coach_job_and_role_state':
        TunableSituationJobAndRoleState(
            description=
            '\n            Job and Role State for the coach Sim. Pre-populated as\n            the actor of the Situation.\n            ',
            tuning_group=GroupNames.ROLES),
        'athlete_job_and_role_state':
        TunableSituationJobAndRoleState(
            description=
            '\n            Job and Role State for the athlete. Pre-populated as the\n            target of the Situation.\n            ',
            tuning_group=GroupNames.ROLES),
        'run_course_state':
        RunCourseState.TunableFactory(tuning_group=GroupNames.STATE),
        'obstacle_tags':
        TunableTags(
            description=
            '\n            Tags to use when searching for obstacle course objects.\n            ',
            filter_prefixes=('Func_PetObstacleCourse', ),
            minlength=1),
        'setup_obstacle_state_value':
        ObjectStateValue.TunableReference(
            description=
            '\n            The state to setup obstacles before we run the course.\n            '
        ),
        'teardown_obstacle_state_value':
        ObjectStateValue.TunableReference(
            description=
            '\n            The state to teardown obstacles after we run the course or when the\n            situation ends.\n            '
        ),
        'failure_commodity':
        Commodity.TunableReference(
            description=
            '\n            The commodity we use to track how many times the athlete has failed\n            to overcome an obstacle.\n            '
        ),
        'obstacles_required':
        TunableRange(
            description=
            '\n            The number of obstacles required for the situation to be available. \n            If the obstacles that the pet can route to drops below this number,\n            the situation is destroyed.\n            ',
            tunable_type=int,
            default=4,
            minimum=1),
        'unfinished_notification':
        UiDialogNotification.TunableFactory(
            description=
            '\n            The dialog for when the situation ends prematurely or the dog never\n            finishes the course.\n            Token 0: Athlete\n            Token 1: Coach\n            Token 2: Time\n            ',
            tuning_group=GroupNames.UI),
        'finish_notifications':
        TunableList(
            description=
            '\n            A list of thresholds and notifications to play given the outcome of\n            the course. We run through the thresholds until one passes, and\n            play the corresponding notification.\n            ',
            tuning_group=GroupNames.UI,
            tunable=TunableTuple(
                description=
                '\n                A threshold and notification to play if the threshold passes.\n                ',
                threshold=TunableThreshold(
                    description=
                    '\n                    A threshold to compare the number of failures from the\n                    failure commodity when the course is finished.\n                    '
                ),
                notification=UiDialogNotification.TunableFactory(
                    description=
                    '\n                    Notification to play when the situation ends.\n                    Token 0: Athlete\n                    Token 1: Coach\n                    Token 2: Failure Count\n                    Token 3: Time\n                    '
                )))
    }

    @classmethod
    def _states(cls):
        return (SituationStateData(0, WaitForSimJobsState),
                SituationStateData(1,
                                   RunCourseState,
                                   factory=cls.run_course_state))

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return [(cls.coach_job_and_role_state.job,
                 cls.coach_job_and_role_state.role_state),
                (cls.athlete_job_and_role_state.job,
                 cls.athlete_job_and_role_state.role_state)]

    @classmethod
    def default_job(cls):
        pass

    @classmethod
    def get_prepopulated_job_for_sims(cls, sim, target_sim_id=None):
        prepopulate = [(sim.id, cls.coach_job_and_role_state.job.guid64)]
        if target_sim_id is not None:
            prepopulate.append(
                (target_sim_id, cls.athlete_job_and_role_state.job.guid64))
        return prepopulate

    @classmethod
    def get_obstacles(cls):
        object_manager = services.object_manager()
        found_objects = set()
        for tag in cls.obstacle_tags:
            found_objects.update(
                object_manager.get_objects_matching_tags({tag}))
        return found_objects

    @classmethod
    def is_situation_available(cls, *args, **kwargs):
        obstacles = cls.get_obstacles()
        if len(obstacles) < cls.obstacles_required:
            return TestResult(False, 'Not enough obstacles.')
        return super().is_situation_available(*args, **kwargs)

    @classproperty
    def situation_serialization_option(cls):
        return situations.situation_types.SituationSerializationOption.LOT

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        reader = self._seed.custom_init_params_reader
        if reader is not None:
            obstacles = self.get_obstacles()
            if not obstacles:
                self._self_destruct()
            self._obstacle_ids = {obstacle.id for obstacle in obstacles}
            self._course_start_time = DateAndTime(
                reader.read_uint64(OBSTACLE_COURSE_START_TIME_TOKEN,
                                   services.time_service().sim_now))
            self._course_end_time = DateAndTime(
                reader.read_uint64(OBSTACLE_COURSE_END_TIME_TOKEN,
                                   services.time_service().sim_now))
        else:
            self._obstacle_ids = set()
            self._course_start_time = None
            self._course_end_time = None
        self._course_progress = ObstacleCourseProgress.NOT_STARTED

    @property
    def course_progress(self):
        return self._course_progress

    @property
    def obstacle_ids(self):
        return self._obstacle_ids

    def _save_custom_situation(self, writer):
        super()._save_custom_situation(writer)
        if self._course_start_time is not None:
            writer.write_uint64(OBSTACLE_COURSE_START_TIME_TOKEN,
                                int(self._course_start_time))
        if self._course_end_time is not None:
            writer.write_uint64(OBSTACLE_COURSE_END_TIME_TOKEN,
                                int(self._course_end_time))

    def start_situation(self):
        super().start_situation()
        self._register_obstacle_course_events()
        self._change_state(WaitForSimJobsState())

    def _on_remove_sim_from_situation(self, sim):
        super()._on_remove_sim_from_situation(sim)
        self._self_destruct()

    def _on_add_sim_to_situation(self, sim, job_type, *args, **kwargs):
        super()._on_add_sim_to_situation(sim, job_type, *args, **kwargs)
        if self.get_coach() is not None and self.get_athlete() is not None:
            object_manager = services.object_manager()
            obstacles = {
                object_manager.get(obstacle_id)
                for obstacle_id in self._obstacle_ids
            }
            sim_info_manager = services.sim_info_manager()
            users = sim_info_manager.instanced_sims_gen()
            for user in users:
                if user in self._situation_sims:
                    continue
                for interaction in user.get_all_running_and_queued_interactions(
                ):
                    target = interaction.target
                    target = target.part_owner if target is not None and target.is_part else target
                    if target is not None and target in obstacles:
                        interaction.cancel(
                            FinishingType.SITUATIONS,
                            cancel_reason_msg='Obstacle Course Starting')
            self._change_state(self.run_course_state())

    def _register_obstacle_course_events(self):
        services.get_event_manager().register_single_event(
            self, TestEvent.ObjectDestroyed)
        services.get_event_manager().register_single_event(
            self, TestEvent.OnExitBuildBuy)

    def _unregister_obstacle_course_events(self):
        services.get_event_manager().unregister_single_event(
            self, TestEvent.ObjectDestroyed)
        services.get_event_manager().unregister_single_event(
            self, TestEvent.OnExitBuildBuy)

    def handle_event(self, sim_info, event, resolver):
        super().handle_event(sim_info, event, resolver)
        if event == TestEvent.ObjectDestroyed:
            destroyed_object = resolver.get_resolved_arg('obj')
            if destroyed_object.id in self._obstacle_ids:
                self._obstacle_ids.remove(destroyed_object.id)
                if len(self._obstacle_ids) < self.obstacles_required:
                    self._self_destruct()
        elif event == TestEvent.OnExitBuildBuy:
            self.validate_obstacle_course()

    def on_remove(self):
        coach = self.get_coach()
        athlete = self.get_athlete()
        if coach is not None and athlete is not None:
            if self.course_progress > ObstacleCourseProgress.NOT_STARTED and self.course_progress < ObstacleCourseProgress.FINISHED:
                course_end_time = services.time_service().sim_now
                course_time_span = course_end_time - self._course_start_time
                unfinished_dialog = self.unfinished_notification(coach)
                unfinished_dialog.show_dialog(
                    additional_tokens=(athlete, coach, course_time_span))
            athlete.commodity_tracker.remove_statistic(self.failure_commodity)
        self.teardown_obstacle_course()
        self._unregister_obstacle_course_events()
        super().on_remove()

    def start_course(self):
        self._course_progress = ObstacleCourseProgress.RUNNING
        self._course_start_time = services.time_service(
        ).sim_now if self._course_start_time is None else self._course_start_time

    def continue_course(self):
        self._change_state(self.run_course_state())

    def finish_course(self):
        self._course_end_time = services.time_service().sim_now
        self._course_progress = ObstacleCourseProgress.FINISHED
        self._change_state(self.run_course_state())

    def finish_situation(self):
        course_time_span = self._course_end_time - self._course_start_time
        athlete = self.get_athlete()
        coach = self.get_coach()
        failures = athlete.commodity_tracker.get_value(self.failure_commodity)
        for threshold_notification in self.finish_notifications:
            if threshold_notification.threshold.compare(failures):
                dialog = threshold_notification.notification(coach)
                dialog.show_dialog(additional_tokens=(athlete, coach, failures,
                                                      course_time_span))
                break
        else:
            logger.error(
                "Obstacle Course Situation doesn't have a threshold, notification for failure count of {}",
                failures)
        self._self_destruct()

    def setup_obstacle_course(self):
        obstacles = self.get_obstacles()
        if len(obstacles) < self.obstacles_required:
            self._self_destruct()
        self._obstacle_ids = {obstacle.id for obstacle in obstacles}

    def validate_obstacle_course(self):
        athlete = self.get_athlete()
        if athlete is None:
            self._self_destruct()
            return
        all_obstacles = self.get_obstacles()
        if len(all_obstacles) < self.obstacles_required:
            self._self_destruct()
            return
        valid_obstacles = set()
        for obstacle in all_obstacles:
            currentState = obstacle.get_state(
                self.setup_obstacle_state_value.state)
            if obstacle.is_connected(athlete):
                valid_obstacles.add(obstacle)
                if currentState == self.teardown_obstacle_state_value:
                    obstacle.set_state(self.setup_obstacle_state_value.state,
                                       self.setup_obstacle_state_value,
                                       immediate=True)
                    if currentState == self.setup_obstacle_state_value:
                        obstacle.set_state(
                            self.setup_obstacle_state_value.state,
                            self.teardown_obstacle_state_value,
                            immediate=True)
            elif currentState == self.setup_obstacle_state_value:
                obstacle.set_state(self.setup_obstacle_state_value.state,
                                   self.teardown_obstacle_state_value,
                                   immediate=True)
        if len(valid_obstacles) < self.obstacles_required:
            self._self_destruct()
        else:
            self._obstacle_ids = {obstacle.id for obstacle in valid_obstacles}

    def teardown_obstacle_course(self):
        obstacles = self.get_obstacles()
        for obstacle in obstacles:
            obstacle.set_state(self.teardown_obstacle_state_value.state,
                               self.teardown_obstacle_state_value,
                               immediate=True)

    def get_coach(self):
        return next(
            iter(self.all_sims_in_job_gen(self.coach_job_and_role_state.job)),
            None)

    def get_athlete(self):
        return next(
            iter(self.all_sims_in_job_gen(
                self.athlete_job_and_role_state.job)), None)
示例#24
0
class _RuleOutTreatmentThresholdAction(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {'rule_out_reason': OptionalTunable(description='\n            The reason based on which treatments are ruled out.\n            \n            By default, it will rule out treatments that contain any of the\n            interaction category tags of the exam that was performed. This can\n            be overridden to rule out treatments with specific tags.\n            ', tunable=TunableTags(description='\n                Only rule out treatments with one of the specified tags.\n                '), disabled_name='interaction_tags', enabled_name='specified_tags')}

    def perform(self, patient_sim, interaction=None):
        sickness = patient_sim.current_sickness
        if sickness is None:
            return
        applicable = set(itertools.chain(sickness.available_treatments, *sickness.available_treatment_lists))
        ruled_out = set(itertools.chain(patient_sim.sickness_tracker.treatments_performed, patient_sim.sickness_tracker.ruled_out_treatments))
        correct = set(sickness.correct_treatments)
        available_for_ruling_out = tuple(applicable - ruled_out - correct)
        if self.rule_out_reason is None and interaction is not None:
            interaction_tags = SicknessTuning.EXAM_TYPES_TAGS & interaction.interaction_category_tags
            available_for_ruling_out = tuple(treatment for treatment in available_for_ruling_out if interaction_tags & treatment.interaction_category_tags)
        elif self.rule_out_reason is not None:
            available_for_ruling_out = tuple(treatment for treatment in available_for_ruling_out if self.rule_out_reason & treatment.interaction_category_tags)
        if not available_for_ruling_out:
            return
        to_rule_out = random.choice(available_for_ruling_out)
        patient_sim.rule_out_treatment(to_rule_out)
        services.get_sickness_service().add_sickness_event(patient_sim.sim_info, patient_sim.current_sickness, 'Ruled out {}'.format(to_rule_out.__name__))
示例#25
0
class WalkDogSituation(SituationComplexCommon):
    INSTANCE_TUNABLES = {
        'walker_job_and_role_state':
        TunableSituationJobAndRoleState(
            description=
            '\n            Job and Role State for the Sim walking the dog. Pre-populated as\n            the actor of the Situation.\n            '
        ),
        'dog_job_and_role_state':
        TunableSituationJobAndRoleState(
            description=
            '\n            Job and Role State for the dog being walked. Pre-populated as the\n            target of the Situation.\n            '
        ),
        'walk_nodes':
        TunableInterval(
            description=
            '\n            How many nodes in the world we want to traverse for our walk.\n            Currently this will only affect fallback attractor points. We will\n            try to use ALL of the attractor points returned by search tags.\n            ',
            tunable_type=int,
            default_lower=5,
            default_upper=6,
            minimum=1),
        'finish_walk_state':
        FinishWalkState.TunableFactory(tuning_group=GroupNames.STATE),
        'walk_state':
        WalkState.TunableFactory(tuning_group=GroupNames.STATE),
        'wait_around_state':
        WaitAroundState.TunableFactory(tuning_group=GroupNames.STATE),
        'attractor_point_tags':
        TunableTuple(
            description=
            '\n            Tags that are used to select objects and attractor points for our\n            path.\n            ',
            fallback_tags=TunableTags(
                description=
                "\n                Tags to use if we don't find any objects with the search tags.\n                This is primarily so we can have a separate list for pre-\n                patched worlds where there are no hand-placed attractor points.\n                ",
                filter_prefixes=('AtPo', ),
                minlength=1),
            search_tags=TunableList(
                description=
                "\n                A list of path tags to look for in order. This will search for\n                objects with each tag, find the closest object, and use it's\n                matching tag to find others for a full path. \n                \n                Example: Short_1, Short_2 are in the list. We would search for \n                all objects with either of those tags, and grab the closest \n                one. If the object has Short_1 tag on it, we find all objects \n                with Short_1 to create our path.\n                ",
                tunable=TunableEnumWithFilter(
                    description=
                    '\n                    A set of attractor point tags we use to pull objects from when\n                    searching for attractor points to create a walking path from.\n                    ',
                    tunable_type=Tag,
                    default=Tag.INVALID,
                    invalid_enums=(Tag.INVALID, ),
                    filter_prefixes=('AtPo', )),
                unique_entries=True))
    }
    REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES

    @classmethod
    def _verify_tuning_callback(cls):
        super()._verify_tuning_callback()
        if cls.attractor_point_tags.fallback_tags.issubset(
                cls.attractor_point_tags.search_tags):
            logger.error(
                'Walk Dog Situation {} fallback tags are a subset of search tags. You need at least one tag to be different in fallback tags.',
                cls)

    @classmethod
    def _states(cls):
        return (SituationStateData(0, WalkState),
                SituationStateData(1, WaitAroundState),
                SituationStateData(2, FinishWalkState))

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return [(cls.walker_job_and_role_state.job,
                 cls.walker_job_and_role_state.role_state),
                (cls.dog_job_and_role_state.job,
                 cls.dog_job_and_role_state.role_state)]

    @classmethod
    def default_job(cls):
        pass

    @classmethod
    def get_prepopulated_job_for_sims(cls, sim, target_sim_id=None):
        prepopulate = [(sim.id, cls.walker_job_and_role_state.job.guid64)]
        if target_sim_id is not None:
            prepopulate.append(
                (target_sim_id, cls.dog_job_and_role_state.job.guid64))
        return prepopulate

    @classmethod
    def has_walk_nodes(cls):
        object_manager = services.object_manager()
        found_objects = object_manager.get_objects_matching_tags(
            set(cls.attractor_point_tags.search_tags)
            | cls.attractor_point_tags.fallback_tags,
            match_any=True)
        if found_objects:
            return True
        return False

    @classmethod
    def get_walk_nodes(cls):
        object_manager = services.object_manager()

        def get_objects(tag_set):
            found_objects = set()
            for tag in tag_set:
                found_objects.update(
                    object_manager.get_objects_matching_tags({tag}))
            return found_objects

        attractor_objects = get_objects(cls.attractor_point_tags.search_tags)
        if not attractor_objects:
            return (get_objects(cls.attractor_point_tags.fallback_tags), True)
        return (attractor_objects, False)

    @classmethod
    def is_situation_available(cls, *args, **kwargs):
        result = cls.has_walk_nodes()
        if not result:
            return TestResult(False,
                              'Not enough attractor points to walk the dog.')
        return super().is_situation_available(*args, **kwargs)

    @classproperty
    def situation_serialization_option(cls):
        return situations.situation_types.SituationSerializationOption.DONT

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._walker = None
        self._dog = None
        self._path_index = 0
        self._path_obj_ids = []
        self.walk_dog_progress = WalkDogProgress.WALK_DOG_NOT_STARTED

    def _on_remove_sim_from_situation(self, sim):
        if sim is self.get_walker() or sim is self.get_pet():
            self._self_destruct()
        super()._on_remove_sim_from_situation

    def _on_add_sim_to_situation(self, *args, **kwargs):
        super()._on_add_sim_to_situation(*args, **kwargs)
        if self.get_walker() is not None and self.get_pet() is not None:
            self._build_walking_path()
            if not self._path_obj_ids:
                self._self_destruct()
                return
            self.walk_onward()

    def get_walker(self):
        if self._walker is None:
            self._walker = next(
                iter(
                    self.all_sims_in_job_gen(
                        self.walker_job_and_role_state.job)), None)
        return self._walker

    def get_pet(self):
        if self._dog is None:
            self._dog = next(
                iter(self.all_sims_in_job_gen(
                    self.dog_job_and_role_state.job)), None)
        return self._dog

    def _build_walking_path(self):
        (attractor_objects, is_fallback) = self.get_walk_nodes()
        if not attractor_objects:
            logger.warn('Could not build a path for {}', self)
            return
        sim = self.get_walker() or self.get_pet()
        sim_position = sim.position
        all_obj_and_pos_list = [(obj, obj.position)
                                for obj in attractor_objects]
        min_dist_obj = min(all_obj_and_pos_list,
                           key=lambda k:
                           (k[1] - sim_position).magnitude_2d_squared())[0]
        obj_and_pos_list = []
        if not is_fallback:
            tags = min_dist_obj.get_tags()
            matching_tags = {
                tag
                for tag in self.attractor_point_tags.search_tags if tag in tags
            }
            for obj_pos in all_obj_and_pos_list:
                if obj_pos[0].has_any_tag(matching_tags):
                    obj_and_pos_list.append(obj_pos)
        else:
            obj_and_pos_list = all_obj_and_pos_list
        positions = [item[1] for item in obj_and_pos_list]
        center = sum(positions, sims4.math.Vector3.ZERO()) / len(positions)
        obj_and_pos_list.sort(key=lambda k: sims4.math.atan2(
            k[1].x - center.x, k[1].z - center.z),
                              reverse=True)
        start_index = 0
        for (obj, _) in obj_and_pos_list:
            if obj is min_dist_obj:
                break
            start_index += 1
        if not is_fallback:
            num_nodes = len(obj_and_pos_list)
        elif self.walk_nodes.lower_bound == self.walk_nodes.upper_bound:
            num_nodes = self.walk_nodes.lower_bound
        else:
            num_nodes = random.randrange(self.walk_nodes.lower_bound,
                                         self.walk_nodes.upper_bound)
        clockwise = 1 if random.randint(2, 4) % 2 else -1
        index = start_index
        for _ in range(num_nodes):
            if index >= len(obj_and_pos_list):
                index = 0
            elif index < 0:
                index = len(obj_and_pos_list) - 1
            (node, _) = obj_and_pos_list[index]
            self._path_obj_ids.append(node.id)
            index += clockwise
        if self._path_obj_ids[-1] != min_dist_obj.id:
            self._path_obj_ids.append(min_dist_obj.id)

    def walk_onward(self):
        if self._path_index < len(self._path_obj_ids):
            self.walk_dog_progress = WalkDogProgress.WALK_DOG_WALKING
            self._change_state(
                self.walk_state(self._path_obj_ids[self._path_index]))
            self._path_index += 1
            return
        if self.walk_dog_progress == WalkDogProgress.WALK_DOG_WALKING:
            self.walk_dog_progress = WalkDogProgress.WALK_DOG_FINISHING
            self._change_state(self.finish_walk_state())
            return
        elif self.walk_dog_progress >= WalkDogProgress.WALK_DOG_FINISHING:
            self.walk_dog_progress = WalkDogProgress.WALK_DOG_DONE
            self._self_destruct()
            return

    def wait_around(self, attractor_point):
        self._change_state(self.wait_around_state())
示例#26
0
class HolidayVisitorNPCSituation(WalkbyLimitingTagsMixin,
                                 SituationComplexCommon):
    INSTANCE_TUNABLES = {
        'holiday_visitor_npc_job':
        sims4.tuning.tunable.TunableTuple(
            situation_job=SituationJob.TunableReference(
                description=
                '\n                A reference to the SituationJob used for the Sim performing the\n                holiday visitor situation.\n                '
            ),
            arrival_state=_ArrivalState.TunableFactory(
                description=
                '\n                The state for pushing the NPC onto the lot.\n                '
            ),
            hang_out_state=_HangOutState.TunableFactory(
                description=
                '\n                State where they hang out using role autonomy (if we want\n                them to eat cookies). The interaction of interest should be them\n                leaving at the fireplace.\n                '
            ),
            push_interaction_state=_PushInteractionState.TunableFactory(
                description=
                '\n                The state for pushing the NPC to do an interaction on\n                one of the primary targets\n                '
            ),
            leave_state=_LeaveState.TunableFactory(
                description=
                '\n                The state for pushing the NPC to leave.\n                '
            ),
            tuning_group=GroupNames.SITUATION),
        'target_filter_tags':
        OptionalTunable(
            description=
            '\n            Choose what kind of targets to grab. If\n            turned on, use tags. Otherwise, use \n            household sims.\n            ',
            tunable=TunableTags(
                description=
                '\n                Define tags we want to filter by.\n                ',
                minlength=1),
            disabled_name='use_household_sims',
            enabled_name='use_tags')
    }
    REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.selected_target = None
        reader = self._seed.custom_init_params_reader
        if reader is not None:
            selected_target_id = reader.read_uint64(INTERACTION_TARGET_TOKEN,
                                                    0)
            object_manager = services.object_manager()
            self.selected_target = object_manager.get(selected_target_id)

    @classmethod
    def _states(cls):
        return (
            SituationStateData(
                1,
                _ArrivalState,
                factory=cls.holiday_visitor_npc_job.arrival_state),
            SituationStateData(
                2,
                _PushInteractionState,
                factory=cls.holiday_visitor_npc_job.push_interaction_state),
            SituationStateData(
                3,
                _HangOutState,
                factory=cls.holiday_visitor_npc_job.hang_out_state),
            SituationStateData(
                4,
                _LeaveState,
                factory=cls.holiday_visitor_npc_job.leave_state))

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return [(cls.holiday_visitor_npc_job.situation_job,
                 cls.holiday_visitor_npc_job.arrival_state)]

    @classmethod
    def default_job(cls):
        pass

    @classmethod
    def get_sims_expected_to_be_in_situation(cls):
        return 1

    def _save_custom_situation(self, writer):
        super()._save_custom_situation(writer)
        if self.selected_target is not None:
            writer.write_uint64(INTERACTION_TARGET_TOKEN,
                                int(self.selected_target.id))

    def holiday_visitor_npc(self):
        sim = next(
            self.all_sims_in_job_gen(
                self.holiday_visitor_npc_job.situation_job), None)
        return sim

    def get_random_target(self):
        object_manager = services.object_manager()
        if self.target_filter_tags is not None:
            found_objects = object_manager.get_objects_matching_tags(
                self.target_filter_tags, match_any=True)
            if len(found_objects) > 0:
                random_object = random.choice(list(found_objects))
                return random_object
            return
        else:
            household_sims = services.active_household().instanced_sims_gen()
            random_sim = random.choice(list(household_sims))
            return random_sim

    def start_situation(self):
        super().start_situation()
        if self.selected_target is None:
            self.selected_target = self.get_random_target()
        self._change_state(self.holiday_visitor_npc_job.arrival_state())
示例#27
0
class PortalComponent(Component,
                      HasTunableFactory,
                      AutoFactoryInit,
                      component_name=types.PORTAL_COMPONENT):
    PORTAL_DIRECTION_THERE = 0
    PORTAL_DIRECTION_BACK = 1
    PORTAL_LOCATION_ENTRY = 0
    PORTAL_LOCATION_EXIT = 1
    FACTORY_TUNABLES = {
        '_portal_data':
        TunableList(
            description=
            '\n            The portals that are to be created for this object.\n            ',
            tunable=TunablePortalReference(pack_safe=True)),
        'state_values_which_disable_portals':
        TunableMapping(
            description=
            '\n            A mapping between object state values and portals which should be\n            disabled when those state values are active. Disabling a portal\n            requires a full refresh of the owning objects portals.\n            ',
            key_type=TunableStateValueReference(pack_safe=True),
            value_type=TunableList(tunable=TunablePortalReference(
                pack_safe=True))),
        '_portal_animation_component':
        OptionalTunable(
            description=
            '\n            If enabled, this portal animates in response to agents traversing\n            it. Use Enter/Exit events to control when and for how long an\n            animation plays.\n            ',
            tunable=PortalAnimationComponent.TunableFactory()),
        '_portal_locking_component':
        OptionalTunable(
            description=
            '\n            If enabled then this object will be capable of being locked using\n            the same system as Portal Objects.\n            \n            If not enabled then it will not have a portal locking component\n            and will therefore not be lockable.\n            ',
            tunable=PortalLockingComponent.TunableFactory()),
        '_portal_disallowed_tags':
        TunableTags(
            description=
            '\n            A set of tags used to prevent Sims in particular role states from\n            using this portal.\n            ',
            filter_prefixes=tag.PORTAL_DISALLOWANCE_PREFIX)
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._portals = {}
        self._custom_portals = None
        self._enable_refresh = True

    def get_subcomponents_gen(self):
        yield from super().get_subcomponents_gen()
        if self._portal_locking_component is not None:
            portal_locking_component = self._portal_locking_component(
                self.owner)
            yield from portal_locking_component.get_subcomponents_gen()
        if self._portal_animation_component is not None:
            portal_animation_component = self._portal_animation_component(
                self.owner)
            yield from portal_animation_component.get_subcomponents_gen()

    @property
    def refresh_enabled(self):
        return self._enable_refresh

    @refresh_enabled.setter
    def refresh_enabled(self, value):
        self._enable_refresh = bool(value)

    def on_buildbuy_exit(self, *_, **__):
        self._refresh_portals()

    def on_location_changed(self, *_, **__):
        zone = services.current_zone()
        if zone.is_in_build_buy or zone.is_zone_loading:
            return
        self._refresh_portals()

    def finalize_portals(self):
        self._refresh_portals()

    def _refresh_portals(self):
        if self.refresh_enabled:
            self._remove_portals()
            self._add_portals()
            self.owner.refresh_locks()

    def on_add(self, *_, **__):
        services.object_manager().add_portal_to_cache(self.owner)
        if len(self.state_values_which_disable_portals) > 0:
            self.owner.add_state_changed_callback(
                self._on_state_changed_callback)

    def on_remove(self, *_, **__):
        self._remove_portals()
        services.object_manager().remove_portal_from_cache(self.owner)

    @componentmethod
    @sims4.utils.exception_protected(default_return=0)
    def c_api_get_portal_duration(self, portal_id, walkstyle, age, gender,
                                  species):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.get_portal_duration(portal_id, walkstyle, age,
                                              gender, species)
        return 0

    @componentmethod
    def add_portal_data(self, portal_id, actor, walkstyle):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.add_portal_data(portal_id, actor, walkstyle)

    @componentmethod
    def split_path_on_portal(self, portal_id):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.split_path_on_portal()
        return PathSplitType.PathSplitType_DontSplit

    @componentmethod
    def get_posture_change(self, portal_id, initial_posture):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.get_posture_change(portal_id, initial_posture)
        return (initial_posture, initial_posture)

    @componentmethod
    def provide_route_events(self, portal_id, route_event_context, sim, path,
                             **kwargs):
        if portal_id in self._portals:
            portal = self._portals.get(portal_id)
            return portal.provide_route_events(portal_id, route_event_context,
                                               sim, path, **kwargs)

    @componentmethod
    def add_portal_events(self, portal_id, actor, time, route_pb):
        portal = self._portals.get(portal_id)
        if portal is not None:
            portal.traversal_type.add_portal_events(portal_id, actor,
                                                    self.owner, time, route_pb)
            portal.traversal_type.notify_in_use(actor, portal, self.owner)

    @componentmethod
    def get_portal_asm_params(self, portal_id, sim):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.get_portal_asm_params(portal_id, sim)
        return {}

    @componentmethod
    def get_portal_owner(self, portal_id):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.obj
        return self.owner

    @componentmethod
    def get_target_surface(self, portal_id):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.get_target_surface(portal_id)
        return self.owner.routing_surface

    def _add_portals(self):
        disabled_portals = set()
        if self.state_values_which_disable_portals:
            for (state_value,
                 portals) in self.state_values_which_disable_portals.items():
                if self.owner.state_value_active(state_value):
                    disabled_portals.update(portals)
        for portal_data in self._portal_data:
            if portal_data not in disabled_portals:
                self._add_portal_internal(self.owner, portal_data)
        if self.owner.parts is not None:
            for part in self.owner.parts:
                part_definition = part.part_definition
                for portal_data in part_definition.portal_data:
                    if portal_data not in disabled_portals:
                        self._add_portal_internal(part, portal_data)
        if self._custom_portals is not None:
            for (location_point, portal_data, mask, _) in self._custom_portals:
                self._add_portal_internal(location_point, portal_data, mask)

    def _add_portal_internal(self,
                             obj,
                             portal_data,
                             portal_creation_mask=None):
        portal_instance_ids = []
        for portal in portal_data.get_portal_instances(obj,
                                                       portal_creation_mask):
            if portal.there is not None:
                self._portals[portal.there] = portal
                portal_instance_ids.append(portal.there)
            if portal.back is not None:
                self._portals[portal.back] = portal
                portal_instance_ids.append(portal.back)
        return portal_instance_ids

    def _remove_portal_internal(self, portal_id):
        if portal_id in self._portals:
            remove_portal(portal_id)
            portal = self._portals[portal_id]
            if portal.there is not None and portal.there == portal_id:
                portal.there = None
            elif portal.back is not None:
                if portal.back == portal_id:
                    portal.back = None
            del self._portals[portal_id]

    def _remove_portals(self):
        for portal_id in self._portals:
            remove_portal(portal_id)
        self._portals.clear()
        if self._custom_portals is not None:
            self._custom_portals.clear()
            self._custom_portals = None

    @componentmethod_with_fallback(lambda *_, **__: False)
    def has_portals(self, check_parts=True):
        if self._portal_data or self._custom_portals:
            return True
        elif check_parts and self.owner.parts is not None:
            return any(part.part_definition is not None
                       and part.part_definition.portal_data is not None
                       for part in self.owner.parts)
        return False

    @componentmethod_with_fallback(lambda *_, **__: [])
    def get_portal_pairs(self):
        return set(
            _PortalPair(portal.there, portal.back)
            for portal in self._portals.values())

    @componentmethod_with_fallback(lambda *_, **__: None)
    def get_portal_data(self):
        return self._portal_data

    @componentmethod
    def get_portal_instances(self):
        return frozenset(self._portals.values())

    @componentmethod
    def get_portal_type(self, portal_id):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.portal_type
        return PortalType.PortalType_Animate

    @componentmethod
    def update_portal_cache(self, portal, portal_id):
        self._portals[portal_id] = portal

    @componentmethod_with_fallback(lambda *_, **__: None)
    def get_portal_by_id(self, portal_id):
        return self._portals.get(portal_id, None)

    @componentmethod_with_fallback(lambda *_, **__: ())
    def get_dynamic_portal_locations_gen(self):
        for portal_data in self._portal_data:
            yield from portal_data.get_dynamic_portal_locations_gen(self.owner)

    @componentmethod
    def get_single_portal_locations(self):
        portal_pair = next(iter(self._portals.values()), None)
        if portal_pair is not None:
            portal_there = self.get_portal_by_id(portal_pair.there)
            portal_back = self.get_portal_by_id(portal_pair.back)
            front_location = None
            if portal_there is not None:
                front_location = portal_there.there_entry
            back_location = None
            if portal_back is not None:
                back_location = portal_back.back_entry
            return (front_location, back_location)
        return (None, None)

    @componentmethod
    def set_portal_cost_override(self, portal_id, cost, sim=None):
        portal = self._portals.get(portal_id)
        if portal is not None:
            portal.set_portal_cost_override(cost, sim=sim)

    @componentmethod
    def get_portal_cost(self, portal_id):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.get_portal_cost(portal_id)

    @componentmethod
    def get_portal_cost_override(self, portal_id):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.get_portal_cost_override()

    @componentmethod_with_fallback(lambda *_, **__: True)
    def lock_portal_on_use(self, portal_id):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.lock_portal_on_use
        return True

    @componentmethod
    def clear_portal_cost_override(self, portal_id, sim=None):
        portal = self._portals.get(portal_id)
        if portal is not None:
            portal.clear_portal_cost_override(sim=sim)

    @componentmethod
    def is_ungreeted_sim_disallowed(self):
        return any(p.is_ungreeted_sim_disallowed()
                   for p in self._portals.values())

    @componentmethod
    def get_portal_disallowed_tags(self):
        return self._portal_disallowed_tags

    @componentmethod
    def get_entry_clothing_change(self, interaction, portal_id, **kwargs):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.get_entry_clothing_change(interaction, portal_id,
                                                    **kwargs)

    @componentmethod
    def get_exit_clothing_change(self, interaction, portal_id, **kwargs):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.get_exit_clothing_change(interaction, portal_id,
                                                   **kwargs)

    @componentmethod
    def get_on_entry_outfit(self, interaction, portal_id, **kwargs):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.get_on_entry_outfit(interaction, portal_id, **kwargs)

    @componentmethod
    def get_on_exit_outfit(self, interaction, portal_id, **kwargs):
        portal = self._portals.get(portal_id)
        if portal is not None:
            return portal.get_on_exit_outfit(interaction, portal_id, **kwargs)

    @componentmethod
    def get_gsi_portal_items_list(self, key_name, value_name):
        gsi_portal_items = self.owner.get_gsi_portal_items(
            key_name, value_name)
        return gsi_portal_items

    @componentmethod
    def get_nearest_posture_change(self, sim):
        shortest_dist = sims4.math.MAX_FLOAT
        nearest_portal_id = None
        nearest_portal = None
        sim_position = sim.position
        for (portal_id, portal_instance) in self._portals.items():
            (posture_entry, posture_exit) = portal_instance.get_posture_change(
                portal_id, None)
            if posture_entry is posture_exit:
                continue
            (entry_loc, _) = portal_instance.get_portal_locations(portal_id)
            dist = (entry_loc.position - sim_position).magnitude_squared()
            if not nearest_portal is None:
                if shortest_dist > dist:
                    shortest_dist = dist
                    nearest_portal = portal_instance
                    nearest_portal_id = portal_id
            shortest_dist = dist
            nearest_portal = portal_instance
            nearest_portal_id = portal_id
        if nearest_portal is None:
            return (None, None)
        return nearest_portal.get_posture_change(nearest_portal_id, None)

    @componentmethod_with_fallback(lambda *_, **__: False)
    def has_posture_portals(self):
        for (portal_id, portal_instance) in self._portals.items():
            (posture_entry,
             _) = portal_instance.get_posture_change(portal_id, None)
            if posture_entry is not None:
                return True

    def add_custom_portal(self,
                          location_point,
                          portal_data,
                          portal_creation_mask=None):
        portal_ids = self._add_portal_internal(location_point, portal_data,
                                               portal_creation_mask)
        if portal_ids:
            if self._custom_portals is None:
                self._custom_portals = []
            self._custom_portals.append((location_point, portal_data,
                                         portal_creation_mask, portal_ids))
        return portal_ids

    def remove_custom_portals(self, portal_ids):
        if self._custom_portals is None:
            return
        for custom_portal in list(self._custom_portals):
            (location_point, portal_data, mask,
             custom_portal_ids) = custom_portal
            portal_ids_to_remove = []
            if all(custom_portal_id in portal_ids
                   for custom_portal_id in custom_portal_ids):
                self._custom_portals.remove(custom_portal)
                portal_ids_to_remove = custom_portal_ids
            else:
                portal_ids_to_remove = [
                    custom_portal_id for custom_portal_id in custom_portal_ids
                    if custom_portal_id in portal_ids
                ]
                if portal_ids_to_remove:
                    portal_ids_to_keep = [
                        custom_portal_id
                        for custom_portal_id in custom_portal_ids
                        if custom_portal_id not in portal_ids_to_remove
                    ]
                    self._custom_portals.remove(custom_portal)
                    self._custom_portals.append((location_point, portal_data,
                                                 mask, portal_ids_to_keep))
            for portal_id in portal_ids_to_remove:
                self._remove_portal_internal(portal_id)
        if not self._custom_portals:
            self._custom_portals = None

    def clear_custom_portals(self):
        if self._custom_portals is not None:
            portal_ids_to_remove = [
                portal_id for custom_portal in self._custom_portals
                for portal_id in custom_portal[3]
            ]
            self.remove_custom_portals(portal_ids_to_remove)
            self._custom_portals.clear()
            self._custom_portals = None

    def get_vehicles_nearby_portal_id(self, portal_id):
        object_manager = services.object_manager()
        owner_position = Vector3Immutable(self.owner.position.x, 0,
                                          self.owner.position.z)
        portal_inst = self.get_portal_by_id(portal_id)
        if portal_inst is None:
            return []
        if portal_inst.portal_template.use_vehicle_after_traversal is None:
            return []
        target_surface = portal_inst.get_target_surface(portal_id)
        results = []
        portal_vehicle_tuning = portal_inst.portal_template.use_vehicle_after_traversal
        for vehicle in object_manager.get_objects_with_tags_gen(
                *portal_vehicle_tuning.vehicle_tags):
            if vehicle.routing_surface.type != target_surface.type:
                continue
            vehicle_position = Vector3Immutable(vehicle.position.x, 0,
                                                vehicle.position.z)
            distance = (owner_position - vehicle_position).magnitude_squared()
            if distance > portal_inst.portal_template.use_vehicle_after_traversal.max_distance:
                continue
            results.append(vehicle)
        return results

    def get_portal_location_by_type(self, portal_type, portal_direction,
                                    portal_location):
        portal_pairs = self.get_portal_pairs()
        for (portal_there, portal_back) in portal_pairs:
            if portal_there is None and portal_back is None:
                continue
            there_instance = self.get_portal_by_id(portal_there)
            if there_instance.portal_template is portal_type.value:
                location = self._get_desired_location(portal_there,
                                                      portal_back,
                                                      portal_direction,
                                                      portal_location)
                if location is None:
                    continue
                return location

    def _on_state_changed_callback(self, owner, state, old_value, new_value):
        if old_value == new_value:
            return
        if old_value in self.state_values_which_disable_portals or new_value in self.state_values_which_disable_portals:
            self._refresh_portals()

    def _get_desired_location(self, portal_there_id, portal_back_id,
                              portal_direction, portal_location):
        if portal_direction == PortalComponent.PORTAL_DIRECTION_THERE:
            portal_instance = self.get_portal_by_id(portal_there_id)
        else:
            if portal_back_id is None:
                return
            portal_instance = self.get_portal_by_id(portal_back_id)
            if portal_instance is None:
                return
        location = portal_instance.there_entry if portal_location == PortalComponent.PORTAL_LOCATION_ENTRY else portal_instance.there_exit
        return location
示例#28
0
class ObjectManager(DistributableObjectManager, GameObjectManagerMixin,
                    AttractorManagerMixin):
    FIREMETER_DISPOSABLE_OBJECT_CAP = Tunable(
        int,
        5,
        description=
        'Number of disposable objects a lot can have at any given moment.')
    BED_TAGS = TunableTuple(
        description=
        '\n        Tags to check on an object to determine what type of bed an object is.\n        ',
        beds=TunableSet(
            description=
            '\n            Tags that consider an object as a bed other than double beds.\n            ',
            tunable=TunableEnumWithFilter(tunable_type=tag.Tag,
                                          default=tag.Tag.INVALID,
                                          filter_prefixes=BED_PREFIX_FILTER)),
        double_beds=TunableSet(
            description=
            '\n            Tags that consider an object as a double bed\n            ',
            tunable=TunableEnumWithFilter(tunable_type=tag.Tag,
                                          default=tag.Tag.INVALID,
                                          filter_prefixes=BED_PREFIX_FILTER)),
        kid_beds=TunableSet(
            description=
            '\n            Tags that consider an object as a kid bed\n            ',
            tunable=TunableEnumWithFilter(tunable_type=tag.Tag,
                                          default=tag.Tag.INVALID,
                                          filter_prefixes=BED_PREFIX_FILTER)),
        other_sleeping_spots=TunableSet(
            description=
            '\n            Tags that considered sleeping spots.\n            ',
            tunable=TunableEnumWithFilter(tunable_type=tag.Tag,
                                          default=tag.Tag.INVALID,
                                          filter_prefixes=BED_PREFIX_FILTER)))
    HOUSEHOLD_INVENTORY_OBJECT_TAGS = TunableTags(
        description=
        '\n        List of tags to apply to every household inventory proxy object.\n        '
    )
    INVALID_UNPARENTED_OBJECT_TAGS = TunableTags(
        description=
        '\n        Objects with these tags should not exist without a parent. An obvious\n        case is for transient objects. They should only exist as a carried object,\n        thus parented to a sim, when loading into a save game.\n        '
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._crafting_cache = CraftingObjectCache()
        self._sim_spawn_conditions = collections.defaultdict(set)
        self._water_terrain_object_cache = WaterTerrainObjectCache()
        self._client_connect_callbacks = CallableList()
        self._portal_cache = WeakSet()
        self._portal_added_callbacks = CallableList()
        self._portal_removed_callbacks = CallableList()
        self._front_door_candidates_changed_callback = CallableList()
        self._all_bed_tags = self.BED_TAGS.beds | self.BED_TAGS.double_beds | self.BED_TAGS.kid_beds | self.BED_TAGS.other_sleeping_spots
        self._tag_to_object_list = defaultdict(set)
        self._whim_set_cache = Counter()
        self._posture_providing_object_cache = None
        self._objects_to_ignore_portal_validation_cache = []

    @classproperty
    def save_error_code(cls):
        return persistence_error_types.ErrorCodes.SERVICE_SAVE_FAILED_OBJECT_MANAGER

    @property
    def crafting_cache(self):
        return self._crafting_cache

    @property
    def water_terrain_object_cache(self):
        return self._water_terrain_object_cache

    def portal_cache_gen(self):
        yield from self._portal_cache

    def on_client_connect(self, client):
        all_objects = list(self._objects.values())
        for game_object in all_objects:
            game_object.on_client_connect(client)

    def move_to_inventory(self, obj, inventory_manager):
        logger.assert_raise(
            isinstance(inventory_manager, InventoryManager),
            'Trying to move object to a non-inventory manager: {}',
            inventory_manager,
            owner='tingyul')
        logger.assert_raise(
            obj.id,
            'Attempting to move an object that was never added or has already been removed',
            owner='tingyul')
        logger.assert_raise(
            self._objects.get(obj.id) is obj,
            'Attempting to move an object {} that is not in this manager or not the same object {} in manager',
            obj,
            self._objects.get(obj.id),
            owner='tingyul')
        del self._objects[obj.id]
        obj.manager = inventory_manager
        inventory_manager._objects[obj.id] = obj
        self.remove_object_from_object_tags_cache(obj)
        self.remove_object_from_posture_providing_cache(obj)

    def add(self, obj, *args, **kwargs):
        super().add(obj, *args, **kwargs)
        self.add_object_to_object_tags_cache(obj)
        self.add_object_to_posture_providing_cache(obj)

    def remove(self, obj, *args, **kwargs):
        super().remove(obj, *args, **kwargs)
        current_zone = services.current_zone()
        if not current_zone.is_zone_shutting_down:
            self.remove_object_from_object_tags_cache(obj)
            self.remove_object_from_posture_providing_cache(obj)

    def add_object_to_object_tags_cache(self, obj):
        self.add_tags_and_object_to_cache(obj.get_tags(), obj)

    def add_tags_and_object_to_cache(self, tags, obj):
        if obj.id not in self:
            logger.error(
                "Trying to add object to tag cache when the object isn't in the manager: {}",
                obj,
                owner='tingyul')
            return
        for tag in tags:
            object_list = self._tag_to_object_list[tag]
            object_list.add(obj)

    def remove_object_from_object_tags_cache(self, obj):
        for tag in obj.get_tags():
            if tag not in self._tag_to_object_list:
                continue
            object_list = self._tag_to_object_list[tag]
            if obj not in object_list:
                continue
            object_list.remove(obj)
            if not object_list:
                del self._tag_to_object_list[tag]

    def _should_save_object_on_lot(self, obj):
        parent = obj.parent
        if parent is not None and parent.is_sim:
            inventory = parent.inventory_component
            if inventory.should_save_parented_item_to_inventory(obj):
                return False
            else:
                vehicle_component = obj.vehicle_component
                if vehicle_component is not None:
                    driver = vehicle_component.driver
                    if driver is not None and driver.is_sim:
                        inventory = driver.inventory_component
                        if inventory.should_save_parented_item_to_inventory(
                                obj):
                            return False
        else:
            vehicle_component = obj.vehicle_component
            if vehicle_component is not None:
                driver = vehicle_component.driver
                if driver is not None and driver.is_sim:
                    inventory = driver.inventory_component
                    if inventory.should_save_parented_item_to_inventory(obj):
                        return False
        return True

    def add_object_to_posture_providing_cache(self, obj):
        if not obj.provided_mobile_posture_affordances:
            return
        if self._posture_providing_object_cache is None:
            self._posture_providing_object_cache = set()
        self._posture_providing_object_cache.add(obj)
        posture_graph_service = services.posture_graph_service()
        if not posture_graph_service.has_built_for_zone_spin_up:
            posture_graph_service.on_mobile_posture_object_added_during_zone_spinup(
                obj)

    def remove_object_from_posture_providing_cache(self, obj):
        if not obj.provided_mobile_posture_affordances:
            return
        self._posture_providing_object_cache.remove(obj)
        if not self._posture_providing_object_cache:
            self._posture_providing_object_cache = None

    def get_posture_providing_objects(self):
        return self._posture_providing_object_cache or ()

    def rebuild_objects_to_ignore_portal_validation_cache(self):
        self._objects_to_ignore_portal_validation_cache.clear()
        for obj in self._objects.values():
            if not obj.routing_component is not None:
                if not obj.inventoryitem_component is not None:
                    if obj.live_drag_component is not None:
                        self._objects_to_ignore_portal_validation_cache.append(
                            obj.id)
            self._objects_to_ignore_portal_validation_cache.append(obj.id)

    def clear_objects_to_ignore_portal_validation_cache(self):
        self._objects_to_ignore_portal_validation_cache.clear()

    def get_objects_to_ignore_portal_validation_cache(self):
        return self._objects_to_ignore_portal_validation_cache

    def clear_caches_on_teardown(self):
        self._tag_to_object_list.clear()
        self._water_terrain_object_cache.clear()
        if self._posture_providing_object_cache is not None:
            self._posture_providing_object_cache.clear()
        self.clear_objects_to_ignore_portal_validation_cache()
        build_buy.unregister_build_buy_exit_callback(
            self._water_terrain_object_cache.refresh)

    def pre_save(self):
        all_objects = list(self._objects.values())
        lot = services.current_zone().lot
        for (_, inventory) in lot.get_all_object_inventories_gen(
                shared_only=True):
            for game_object in inventory:
                all_objects.append(game_object)
        for game_object in all_objects:
            game_object.update_all_commodities()

    @staticmethod
    def save_game_object(game_object, object_list, open_street_objects):
        save_result = None
        if game_object.persistence_group == objects.persistence_groups.PersistenceGroups.OBJECT:
            save_result = game_object.save_object(object_list.objects,
                                                  ItemLocation.ON_LOT, 0)
        else:
            if game_object.item_location == ItemLocation.ON_LOT or game_object.item_location == ItemLocation.INVALID_LOCATION:
                item_location = ItemLocation.FROM_OPEN_STREET
            else:
                item_location = game_object.item_location
            save_result = game_object.save_object(open_street_objects.objects,
                                                  item_location, 0)
        return save_result

    def save(self,
             object_list=None,
             zone_data=None,
             open_street_data=None,
             store_travel_group_placed_objects=False,
             **kwargs):
        if object_list is None:
            return
        open_street_objects = file_serialization.ObjectList()
        total_beds = 0
        double_bed_exist = False
        kid_bed_exist = False
        alternative_sleeping_spots = 0
        university_roommate_beds = 0
        if store_travel_group_placed_objects:
            objects_to_save_for_clean_up = []
        roommate_bed_tags = set()
        roommate_service = services.get_roommate_service()
        if roommate_service is not None:
            roommate_bed_tags = roommate_service.BED_TAGS
        for game_object in self._objects.values():
            if self._should_save_object_on_lot(game_object):
                save_result = ObjectManager.save_game_object(
                    game_object, object_list, open_street_objects)
                if not save_result:
                    continue
                if zone_data is None:
                    continue
                if store_travel_group_placed_objects and save_result.owner_id != 0:
                    placement_flags = build_buy.get_object_placement_flags(
                        game_object.definition.id)
                    if build_buy.PlacementFlags.NON_INVENTORYABLE not in placement_flags:
                        objects_to_save_for_clean_up.append(save_result)
                if not game_object.definition.has_build_buy_tag(
                        *self._all_bed_tags):
                    continue
                if game_object.definition.has_build_buy_tag(
                        *self.BED_TAGS.double_beds):
                    double_bed_exist = True
                    total_beds += 1
                elif game_object.definition.has_build_buy_tag(
                        *self.BED_TAGS.kid_beds):
                    total_beds += 1
                    kid_bed_exist = True
                elif game_object.definition.has_build_buy_tag(
                        *self.BED_TAGS.other_sleeping_spots):
                    alternative_sleeping_spots += 1
                elif game_object.definition.has_build_buy_tag(
                        *self.BED_TAGS.beds):
                    total_beds += 1
                if len(roommate_bed_tags) > 0:
                    if game_object.definition.has_build_buy_tag(
                            *roommate_bed_tags):
                        university_roommate_beds += 1
        if open_street_data is not None:
            open_street_data.objects = open_street_objects
        if zone_data is not None:
            bed_info_data = gameplay_serialization.ZoneBedInfoData()
            bed_info_data.num_beds = total_beds
            bed_info_data.double_bed_exist = double_bed_exist
            bed_info_data.kid_bed_exist = kid_bed_exist
            bed_info_data.alternative_sleeping_spots = alternative_sleeping_spots
            if roommate_service is not None:
                household_and_roommate_cap = roommate_service.HOUSEHOLD_AND_ROOMMATE_CAP
                bed_info_data.university_roommate_beds = min(
                    household_and_roommate_cap, university_roommate_beds)
            zone_data.gameplay_zone_data.bed_info_data = bed_info_data
            if store_travel_group_placed_objects:
                current_zone = services.current_zone()
                save_game_protocol_buffer = services.get_persistence_service(
                ).get_save_game_data_proto()
                self._clear_clean_up_data_for_zone(current_zone,
                                                   save_game_protocol_buffer)
                self._save_clean_up_destination_data(
                    current_zone, objects_to_save_for_clean_up,
                    save_game_protocol_buffer)
        lot = services.current_zone().lot
        for (inventory_type, inventory) in lot.get_all_object_inventories_gen(
                shared_only=True):
            for game_object in inventory:
                game_object.save_object(object_list.objects,
                                        ItemLocation.OBJECT_INVENTORY,
                                        inventory_type)

    def _clear_clean_up_data_for_zone(self, current_zone,
                                      save_game_protocol_buffer):
        current_zone_id = current_zone.id
        current_open_street_id = current_zone.open_street_id
        destination_clean_up_data = save_game_protocol_buffer.destination_clean_up_data
        for clean_up_save_data in destination_clean_up_data:
            indexes_to_clean_up = []
            for (index, old_object_clean_up_data) in enumerate(
                    clean_up_save_data.object_clean_up_data_list):
                if not old_object_clean_up_data.zone_id == current_zone_id:
                    if old_object_clean_up_data.world_id == current_open_street_id:
                        indexes_to_clean_up.append(index)
                indexes_to_clean_up.append(index)
            if len(indexes_to_clean_up) == len(
                    clean_up_save_data.object_clean_up_data_list):
                clean_up_save_data.ClearField('object_clean_up_data_list')
            else:
                for index in reversed(indexes_to_clean_up):
                    del clean_up_save_data.object_clean_up_data_list[index]

    def _save_clean_up_destination_data(self, current_zone,
                                        objects_to_save_for_clean_up,
                                        save_game_protocol_buffer):
        household_manager = services.household_manager()
        travel_group_manager = services.travel_group_manager()
        clean_up_save_data = None
        for object_data in sorted(objects_to_save_for_clean_up,
                                  key=lambda x: x.owner_id):
            owner_id = object_data.owner_id
            if clean_up_save_data is None or clean_up_save_data.household_id != owner_id:
                household = household_manager.get(owner_id)
                travel_group = None
                if household is not None:
                    travel_group = household.get_travel_group()
                for clean_up_save_data in save_game_protocol_buffer.destination_clean_up_data:
                    if clean_up_save_data.household_id != owner_id:
                        continue
                    if travel_group is not None:
                        if travel_group.id == clean_up_save_data.travel_group_id:
                            break
                    if clean_up_save_data.travel_group_id in travel_group_manager:
                        continue
                    break
            with ProtocolBufferRollback(
                    clean_up_save_data.object_clean_up_data_list
            ) as object_clean_up_data:
                if object_data.loc_type == ItemLocation.ON_LOT:
                    object_clean_up_data.zone_id = current_zone.id
                else:
                    object_clean_up_data.world_id = current_zone.open_street_id
                object_clean_up_data.object_data = object_data

    def add_sim_spawn_condition(self, sim_id, callback):
        for sim in services.sim_info_manager().instanced_sims_gen():
            if sim.id == sim_id:
                logger.error(
                    'Sim {} is already in the world, cannot add the spawn condition',
                    sim)
                return
        self._sim_spawn_conditions[sim_id].add(callback)

    def remove_sim_spawn_condition(self, sim_id, callback):
        if callback not in self._sim_spawn_conditions.get(sim_id, ()):
            logger.error(
                'Trying to remove sim spawn condition with invalid id-callback pair ({}-{}).',
                sim_id, callback)
            return
        self._sim_spawn_conditions[sim_id].remove(callback)

    def trigger_sim_spawn_condition(self, sim_id):
        if sim_id in self._sim_spawn_conditions:
            for callback in self._sim_spawn_conditions[sim_id]:
                callback()
            del self._sim_spawn_conditions[sim_id]

    def add_portal_lock(self, sim, callback):
        self.register_portal_added_callback(callback)
        for portal in self.portal_cache_gen():
            portal.lock_sim(sim)

    def register_portal_added_callback(self, callback):
        if callback not in self._portal_added_callbacks:
            self._portal_added_callbacks.append(callback)

    def unregister_portal_added_callback(self, callback):
        if callback in self._portal_added_callbacks:
            self._portal_added_callbacks.remove(callback)

    def register_portal_removed_callback(self, callback):
        if callback not in self._portal_removed_callbacks:
            self._portal_removed_callbacks.append(callback)

    def unregister_portal_removed_callback(self, callback):
        if callback in self._portal_removed_callbacks:
            self._portal_removed_callbacks.remove(callback)

    def _is_valid_portal_object(self, portal):
        portal_component = portal.get_component(PORTAL_COMPONENT)
        if portal_component is None:
            return False
        return portal.has_portals()

    def add_portal_to_cache(self, portal):
        if portal not in self._portal_cache and self._is_valid_portal_object(
                portal):
            self._portal_cache.add(portal)
            self._portal_added_callbacks(portal)

    def remove_portal_from_cache(self, portal):
        if portal in self._portal_cache:
            self._portal_cache.remove(portal)
            self._portal_removed_callbacks(portal)

    def register_front_door_candidates_changed_callback(self, callback):
        if callback not in self._front_door_candidates_changed_callback:
            self._front_door_candidates_changed_callback.append(callback)

    def unregister_front_door_candidates_changed_callback(self, callback):
        if callback in self._front_door_candidates_changed_callback:
            self._front_door_candidates_changed_callback.remove(callback)

    def on_front_door_candidates_changed(self):
        self._front_door_candidates_changed_callback()

    def cleanup_build_buy_transient_objects(self):
        household_inventory_proxy_objects = self.get_objects_matching_tags(
            self.HOUSEHOLD_INVENTORY_OBJECT_TAGS)
        for obj in household_inventory_proxy_objects:
            self.remove(obj)

    def get_objects_matching_tags(self, tags: set, match_any=False):
        matching_objects = None
        for tag in tags:
            objs = self._tag_to_object_list[
                tag] if tag in self._tag_to_object_list else set()
            if matching_objects is None:
                matching_objects = objs
            elif match_any:
                matching_objects |= objs
            else:
                matching_objects &= objs
                if not matching_objects:
                    break
        if matching_objects:
            return frozenset(matching_objects)
        return EMPTY_SET

    def get_num_objects_matching_tags(self, tags: set, match_any=False):
        matching_objects = self.get_objects_matching_tags(tags, match_any)
        return len(matching_objects)

    @contextmanager
    def batch_commodity_flags_update(self):
        default_fn = self.clear_commodity_flags_for_objs_with_affordance
        try:
            affordances = set()
            self.clear_commodity_flags_for_objs_with_affordance = affordances.update
            yield None
        finally:
            self.clear_commodity_flags_for_objs_with_affordance = default_fn
            self.clear_commodity_flags_for_objs_with_affordance(affordances)

    def clear_commodity_flags_for_objs_with_affordance(self, affordances):
        for obj in self.valid_objects():
            if not obj.has_updated_commodity_flags():
                continue
            if any(affordance in affordances
                   for affordance in obj.super_affordances()):
                obj.clear_commodity_flags()

    def get_all_objects_with_component_gen(self, component_definition):
        if component_definition is None:
            return
        for obj in self.valid_objects():
            if obj.has_component(component_definition):
                yield obj

    def get_objects_with_tag_gen(self, tag):
        yield from self.get_objects_matching_tags((tag, ))

    def get_objects_with_tags_gen(self, *tags):
        yield from self.get_objects_matching_tags(tags, match_any=True)

    def on_location_changed(self, obj):
        self._registered_callbacks[CallbackTypes.ON_OBJECT_LOCATION_CHANGED](
            obj)

    def process_invalid_unparented_objects(self):
        invalid_objects = self.get_objects_matching_tags(
            self.INVALID_UNPARENTED_OBJECT_TAGS, match_any=True)
        for invalid_object in invalid_objects:
            if invalid_object.parent is None:
                logger.error(
                    'Invalid unparented object {} existed in game. Cleaning up.',
                    invalid_object)
                invalid_object.destroy(
                    source=invalid_object,
                    cause='Invalid unparented object found on zone spin up.')

    @classproperty
    def supports_parenting(self):
        return True

    def add_active_whim_set(self, whim_set):
        self._whim_set_cache[whim_set] += 1

    def remove_active_whim_set(self, whim_set):
        self._whim_set_cache[whim_set] -= 1
        if self._whim_set_cache[whim_set] <= 0:
            del self._whim_set_cache[whim_set]

    @property
    def active_whim_sets(self):
        return set(self._whim_set_cache.keys())
示例#29
0
class MovingObjectsSituation(SituationComplexCommon):
    INSTANCE_TUNABLES = {'_preparation_state': _PreparationState.TunableFactory(tuning_group=GroupNames.STATE), '_waiting_to_move_state': _WaitingToMoveState.TunableFactory(tuning_group=GroupNames.STATE), '_tests_to_continue': TunableTestSet(description='\n            A list of tests that must pass in order to continue the situation\n            after the tuned duration for the waiting state has elapsed.\n            ', tuning_group=GroupNames.STATE), 'starting_requirements': TunableTestSet(description='\n            A list of tests that must pass in order for the situation\n            to start.\n            ', tuning_group=GroupNames.SITUATION), 'object_tags': TunableTags(description='\n            Tags used to find objects which will move about.\n            ', tuning_group=GroupNames.SITUATION), 'placement_strategy_locations': TunableList(description='\n            A list of weighted location strategies.\n            ', tunable=TunableTuple(weight=TunableMultiplier.TunableFactory(description='\n                    The weight of this strategy relative to other locations.\n                    '), placement_strategy=_PlacementStrategyLocation.TunableFactory(description='\n                    The placement strategy for the object.\n                    ')), minlength=1, tuning_group=GroupNames.SITUATION), 'fade': OptionalTunable(description='\n            If enabled, the objects will fade-in/fade-out as opposed to\n            immediately moving to their location.\n            ', tunable=TunableTuple(out_time=TunableSimMinute(description='\n                    Time over which the time will fade out.\n                    ', default=1), in_time=TunableSimMinute(description='\n                    Time over which the time will fade in.\n                    ', default=1)), enabled_by_default=True, tuning_group=GroupNames.SITUATION), 'vfx_on_move': OptionalTunable(description='\n            If tuned, apply this one-shot vfx on the moving object when it\n            is about to move.\n            ', tunable=PlayEffect.TunableFactory(), tuning_group=GroupNames.SITUATION), 'situation_end_loots_to_apply_on_objects': TunableSet(description='\n            The loots to apply on the tagged objects when the situation ends \n            or is destroyed.\n            \n            E.g. use this to reset objects to a specific state after \n            the situation is over.\n            \n            The loot will be processed with the active sim as the actor,\n            and the object as the target.\n            ', tunable=TunableReference(manager=services.get_instance_manager(Types.ACTION), pack_safe=True), tuning_group=GroupNames.SITUATION)}
    REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        reader = self._seed.custom_init_params_reader
        if reader is None:
            self._target_id = self._seed.extra_kwargs.get('default_target_id', None)
        else:
            self._target_id = reader.read_uint64(OBJECT_TOKEN, None)

    @classmethod
    def _states(cls):
        return (SituationStateData(0, _PreparationState, factory=cls._preparation_state), SituationStateData(1, _WaitingToMoveState, factory=cls._waiting_to_move_state))

    @classmethod
    def default_job(cls):
        pass

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return []

    @classmethod
    def situation_meets_starting_requirements(cls, **kwargs):
        if not cls.starting_requirements:
            return True
        else:
            resolver = SingleSimResolver(services.active_sim_info())
            if not cls.starting_requirements.run_tests(resolver):
                return False
        return True

    def _save_custom_situation(self, writer):
        super()._save_custom_situation(writer)
        if self._target_id is not None:
            writer.write_uint64(OBJECT_TOKEN, self._target_id)

    def start_situation(self):
        super().start_situation()
        self._change_state(self._preparation_state())

    def load_situation(self):
        if not self.situation_meets_starting_requirements():
            return False
        return super().load_situation()

    def on_objects_ready(self):
        self._change_state(self._waiting_to_move_state())

    def on_ready_to_move(self):
        if self._tests_to_continue.run_tests(GlobalResolver()):
            self._move_objects()
            self._change_state(self._waiting_to_move_state())
        else:
            self._self_destruct()

    def _get_placement_resolver(self):
        additional_participants = {}
        if self._target_id is not None:
            target = services.object_manager().get(self._target_id)
            additional_participants[ParticipantType.Object] = (target,)
            if target is not None:
                if target.is_sim:
                    additional_participants[ParticipantType.TargetSim] = (target.sim_info,)
        return SingleSimResolver(services.active_sim_info(), additional_participants=additional_participants)

    def _destroy(self):
        objects_of_interest = services.object_manager().get_objects_matching_tags(self.object_tags, match_any=True)
        if not objects_of_interest:
            return
        active_sim_info = services.active_sim_info()
        for obj in objects_of_interest:
            resolver = SingleActorAndObjectResolver(active_sim_info, obj, self)
            for loot in self.situation_end_loots_to_apply_on_objects:
                loot.apply_to_resolver(resolver)
        super()._destroy()

    def _move_objects(self):
        objects_to_move = services.object_manager().get_objects_matching_tags(self.object_tags, match_any=True)
        if not objects_to_move:
            return
        resolver = self._get_placement_resolver()
        choices = [(location.weight.get_multiplier(resolver), location.placement_strategy) for location in self.placement_strategy_locations]
        chosen_strategy = random.weighted_random_item(choices)
        do_fade = self.fade is not None
        out_sequence = []
        moves = []
        in_sequence = []
        for object_to_move in objects_to_move:
            object_to_move.cancel_interactions_running_on_object(FinishingType.OBJECT_CHANGED, cancel_reason_msg='Object changing location.')
            if self.vfx_on_move is not None:
                out_sequence.append(lambda _, object_to_move=object_to_move: self.vfx_on_move(object_to_move).start_one_shot())
            if do_fade:
                out_sequence.append(lambda _, object_to_move=object_to_move: object_to_move.fade_out(self.fade.out_time))
            moves.append(lambda _, object_to_move=object_to_move: chosen_strategy.try_place_object(object_to_move, resolver))
            if do_fade:
                in_sequence.append(lambda _, object_to_move=object_to_move: object_to_move.fade_in(self.fade.in_time))
        sequence = []
        if out_sequence:
            sequence.append(out_sequence)
            sequence.append(SoftSleepElement(clock.interval_in_sim_minutes(self.fade.out_time)))
        sequence.append(moves)
        if in_sequence:
            sequence.append(in_sequence)
        element = build_element(sequence, critical=CleanupType.RunAll)
        services.time_service().sim_timeline.schedule(element)
示例#30
0
class BuffRemovalOp(BaseLootOperation):
    FACTORY_TUNABLES = {
        'remove_all_visible_buffs':
        Tunable(
            description=
            "\n            If checked, all visible buffs on the Sim, excluding those specified in\n            the 'buffs_to_ignore' list will be removed.  If unchecked, buff removal\n            will be handled by the 'buffs_to_remove' list.\n            ",
            tunable_type=bool,
            default=False),
        'buffs_to_remove':
        TunableList(
            description=
            "\n            If 'remove_all_buffs' is not checked, this is the list of buffs that\n            will be removed from the subject.  If 'remove_all_buffs' is checked,\n            this list will be ignored.\n            ",
            tunable=TunableReference(
                description=
                '\n                Buff to be removed.\n                ',
                manager=services.buff_manager(),
                pack_safe=True)),
        'buff_tags_to_remove':
        TunableTags(
            description=
            "\n            If 'remove_all_buffs' is not checked, buffs with any tag in this list\n            will be removed from the subject. If 'remove_all_buffs' is checked, this\n            list will be ignored. You can also specify how many buffs you want to remove\n            by tags in count_to_remove_by_tags\n            ",
            filter_prefixes=('buff', )),
        'count_to_remove_by_tags':
        OptionalTunable(tunable=TunableRange(
            description=
            '\n                If enabled, randomly remove x number of buffs specified in buff_tags_to_remove.\n                If disabled, all buffs specified in buff_tags_to_remove will be removed\n                ',
            tunable_type=int,
            default=1,
            minimum=1)),
        'buffs_to_ignore':
        TunableList(
            description=
            "\n            If 'remove_all_buffs' is checked, no buffs included in this list will\n            be removed.  If 'remove_all_buffs' is unchecked, this list will be\n            ignored.\n            ",
            tunable=TunableReference(
                description=
                '\n                Buff to be removed.\n                ',
                manager=services.buff_manager()))
    }

    def __init__(self, remove_all_visible_buffs, buffs_to_remove,
                 buff_tags_to_remove, count_to_remove_by_tags, buffs_to_ignore,
                 **kwargs):
        super().__init__(**kwargs)
        self._remove_all_visible_buffs = remove_all_visible_buffs
        self._buffs_to_remove = buffs_to_remove
        self._buff_tags_to_remove = buff_tags_to_remove
        self._count_to_remove_by_tags = count_to_remove_by_tags
        self._buffs_to_ignore = buffs_to_ignore

    def _apply_to_subject_and_target(self, subject, target, resolver):
        if self._remove_all_visible_buffs:
            removal_list = []
            removal_list.extend(subject.Buffs)
            for buff in removal_list:
                if type(buff) in self._buffs_to_ignore:
                    continue
                if not buff.visible:
                    continue
                if buff.commodity is not None:
                    if subject.is_statistic_type_added_by_modifier(
                            buff.commodity):
                        continue
                    tracker = subject.get_tracker(buff.commodity)
                    commodity_inst = tracker.get_statistic(buff.commodity)
                    if commodity_inst is not None and commodity_inst.core:
                        continue
                subject.Buffs.remove_buff_entry(buff)
            else:
                subject.Buffs.remove_buffs_by_tags(
                    self._buff_tags_to_remove,
                    count_to_remove=self._count_to_remove_by_tags)
        else:
            for buff_type in self._buffs_to_remove:
                subject.Buffs.remove_buff_by_type(buff_type)
            subject.Buffs.remove_buffs_by_tags(
                self._buff_tags_to_remove,
                count_to_remove=self._count_to_remove_by_tags)