コード例 #1
0
ファイル: shrink.py プロジェクト: npx/pyhkal2
    class Identity(object):
        """
        Identities are abstract persons which persist throughout several sessions.
        They can be associated to several avatars.

        The `link` method adds an avatar to its available representation.

        Avatars are automatically removed when all references are lost due the
        reference's weakness.
        """

        __metaclass__ = MultitonMeta

        def __init__(self, docid):
            self.docid = docid.encode('latin-1')
            self.avatars = WeakSet()

        def link(self, avatar):
            if avatar.identity not in (None, self):
                raise ValueError("double link from %r to %r and %r" %
                        (avatar, self, avatar.identity))
            service.dispatch_event("login", self, avatar)
            self.avatars.add(avatar)
            avatar.identity = self
            return self

        def fetch(self):
            return service.davenport.openDoc(self.docid)
コード例 #2
0
class Subject:
	def __init__(self, parent):
		self.parent = parent
		self._observers_lock = RLock()
		self._observers = WeakSet()

	def addObserver(self, observer):
		with self._observers_lock:
			self._observers.add(observer)

		logger.debug("%s is being observed by %s", stringFor(self.parent), stringFor(observer))

	def removeObserver(self, observer):
		with self._observers_lock:
			try:
				self._observers.remove(observer)
			except KeyError:
				logger.error("Tried to remove observer %s twice from %s", stringFor(observer), stringFor(self.parent))

	def clearObservers(self):
		with self._observers_lock:
			self._observers.clear()

		logger.debug("%s observers were cleaned.", stringFor(self.parent))

	def notify(self, event, *args):
		with self._observers_lock:
			observers = list(self._observers)

		for obs in observers:
			logger.debug("%s is about to notify %s to %s", stringFor(self.parent), event, stringFor(obs))
			obs.onNotify(self.parent, event, args)
コード例 #3
0
class LightingLiability(Liability, HasTunableFactory, AutoFactoryInit):
    LIABILITY_TOKEN = 'LightingLiability'
    FACTORY_TUNABLES = {'radius_squared': TunableDistanceSquared(description='\n            The distance away from the specified participant that lights will\n            be turned off.\n            ', default=1, display_name='Radius'), 'participant': TunableEnumEntry(description='\n            The participant of the interaction that we will be used as the\n            center of the radius to turn lights off.\n            ', tunable_type=ParticipantType, default=ParticipantType.Actor)}

    def __init__(self, interaction, **kwargs):
        super().__init__(**kwargs)
        self._interaction = interaction
        self._lights = WeakSet()
        self._automated_lights = WeakSet()

    def on_run(self):
        if self._lights:
            return
        participant = self._interaction.get_participant(self.participant)
        position = participant.position
        for obj in services.object_manager().get_all_objects_with_component_gen(objects.components.types.LIGHTING_COMPONENT):
            if get_object_has_tag(obj.definition.id, LightingComponent.MANUAL_LIGHT_TAG):
                continue
            distance_from_pos = obj.position - position
            if distance_from_pos.magnitude_squared() > self.radius_squared:
                continue
            if obj.get_light_dimmer_value() == LightingComponent.LIGHT_AUTOMATION_DIMMER_VALUE:
                self._automated_lights.add(obj)
            else:
                self._lights.add(obj)
            obj.set_light_dimmer_value(LightingComponent.LIGHT_DIMMER_VALUE_OFF)

    def release(self):
        for obj in self._lights:
            obj.set_light_dimmer_value(LightingComponent.LIGHT_DIMMER_VALUE_MAX_INTENSITY)
        self._lights.clear()
        for obj in self._automated_lights:
            obj.set_light_dimmer_value(LightingComponent.LIGHT_AUTOMATION_DIMMER_VALUE)
        self._automated_lights.clear()
コード例 #4
0
class Subject(object):
    def __init__(self, parent, loggingLevel=logging.INFO):
        super(Subject, self).__init__()

        self._logger = logging.getLogger("[OBSERVER {} ({})]".format(
            parent.__class__.__name__.upper(), id(parent)))
        self._logger.setLevel(loggingLevel)

        self.parent = parent
        self._observers_lock = RLock()
        self._observers = WeakSet()

    def addObserver(self, observer):
        with self._observers_lock:
            self._observers.add(observer)

        self._logger.debug("%s is being observed by %s",
                           stringFor(self.parent), stringFor(observer))

    def removeObserver(self, observer):
        with self._observers_lock:
            try:
                self._observers.remove(observer)
            except KeyError:
                self._logger.error("Tried to remove observer %s twice from %s",
                                   stringFor(observer), stringFor(self.parent))

    def hasObserver(self, observer):
        with self._observers_lock:
            return observer in self._observers

    def clearObservers(self):
        with self._observers_lock:
            self._observers.clear()

        self._logger.debug("%s observers were cleaned.",
                           stringFor(self.parent))

    def notify(self, event, *args):
        with self._observers_lock:
            observers = list(self._observers)

        for obs in observers:
            self._logger.debug("%s is about to notify %s to %s",
                               stringFor(self.parent), event, stringFor(obs))
            try:
                obs.onNotify(self.parent, event, args)
            except Exception as e:
                self._logger.error(
                    "Catched exception trying to notify %s to %s with arguments: %s",
                    str(event), str(obs), str(args))
                self._logger.exception(e)
コード例 #5
0
class AttractorManagerMixin:
    ATTRACTOR_OBJECT_TAGS = TunableSet(description='\n        One or more tags that indicate an object is a type of attractor point.\n        We use attractor points to push Sims near things and reference specific\n        geography in the world.\n        ', tunable=TunableEnumWithFilter(description='\n            A specific tag.\n            ', tunable_type=tag.Tag, default=tag.Tag.INVALID, invalid_enums=(tag.Tag.INVALID,), filter_prefixes=('AtPo',)))
    SPAWN_POINT_ATTRACTORS = TunableMapping(description='\n        Mapping from spawn point tags to attractor objects so we can create\n        attractor points at spawn points.\n        ', key_type=TunableEnumEntry(description='\n            The tag on the spawn point.\n            ', tunable_type=tag.Tag, default=tag.Tag.INVALID, invalid_enums=(tag.Tag.INVALID,)), key_name='spawn point tag', value_type=TunableReference(description='\n            The object we want to create on the Spawn Point.\n            ', manager=services.definition_manager(), pack_safe=True), value_name='attractor point definition')

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

    def create_dynamic_attractor_object(self, definition_id, location, tags_to_add=None):
        tags_to_add = set() if tags_to_add is None else tags_to_add

        def setup_obj(obj):
            obj.append_tags(tags_to_add)
            obj.location = location
            obj.persistence_group = PersistenceGroups.NONE

        created_obj = objects.system.create_object(definition_id, init=setup_obj)
        self._dynamic_attractor_ids.add(created_obj)
        if not self.ATTRACTOR_OBJECT_TAGS.intersection(created_obj.get_tags()):
            logger.warn('Attractor object does not have any tags in the ATTRACTOR OBJECT TAGS list. We need to be able to locate attractor objects and keep track of them.')
        return created_obj

    def destroy_dynamic_attractor_object(self, object_id):
        obj_to_destroy = self.get(object_id)
        if obj_to_destroy is None:
            logger.error('Object {} is not a dynamic attractor point.', object_id)
            return
        self._dynamic_attractor_ids.discard(obj_to_destroy)
        obj_to_destroy.destroy(obj_to_destroy, cause='Destroying Dynamic Attractor Point')

    def get_attractor_objects(self):
        return self.get_objects_matching_tags(AttractorManagerMixin.ATTRACTOR_OBJECT_TAGS)

    def create_spawn_point_attractor(self, spawn_point):
        obj_ids = set()
        for (spawn_point_tag, attractor_definition) in self.SPAWN_POINT_ATTRACTORS.items():
            tags = spawn_point.get_tags()
            if spawn_point_tag in tags:
                location = sims4.math.Location(transform=spawn_point.get_approximate_transform(), routing_surface=spawn_point.routing_surface)
                obj = self.create_dynamic_attractor_object(attractor_definition, location, tags_to_add={spawn_point_tag})
                obj_ids.add(obj.id)
        return frozenset(obj_ids)
コード例 #6
0
ファイル: liability.py プロジェクト: NeonOcean/Environment
class SharedLiability(Liability):

    def __init__(self, *args, source_liability=None, **kwargs):
        super().__init__(**kwargs)
        self._released = False
        if source_liability is None:
            self._shared_liability_refs = WeakSet()
        else:
            self._shared_liability_refs = source_liability._shared_liability_refs
        self._shared_liability_refs.add(self)

    def shared_release(self):
        raise NotImplementedError('SharedLiability: {} trying to release with no shared_release implementation'.format(self))

    def release(self, *args, **kwargs):
        self._released = True
        if all(cur_liability._released for cur_liability in self._shared_liability_refs):
            self.shared_release(*args, **kwargs)

    def create_new_liability(self, interaction, *args, **kwargs):
        return self.__class__(*args, source_liability=self, **kwargs)
コード例 #7
0
class _MannequinGroupData:

    def __init__(self):
        self._mannequins = WeakSet()
        self._mannequin_data = None

    def __iter__(self):
        return iter(self._mannequins)

    def add_mannequin(self, mannequin):
        self._mannequins.add(mannequin)

    def get_mannequin_data(self):
        return self._mannequin_data

    def set_mannequin_data(self, mannequin_data):
        self._mannequin_data = mannequin_data

    def reconcile_mannequin_data(self):
        for mannequin_component in self._mannequins:
            mannequin_component.reconcile_mannequin_data()
コード例 #8
0
class Player(object):

    VIEW_SIZE = (32, 32)

    def __init__(self, id, pos=None):
        self.pos = pos
        self.id = id
        self.visible_pl = WeakSet()  # ATTENTION! Cyclic links

    def get_field_of_view(self):
        return Point.validate_coord(self.pos -
                                    (16, 16)), Point.validate_coord(self.pos +
                                                                    (16, 16))

    def add_player(self, player):
        self.visible_pl.add(player)

    def add_players_nearby(self, player):
        player.add_player(self)
        self.add_player(player)

    def on_event(self, event):
        pass

    def notify_all(self, event):
        for pl in self.visible_pl:
            pl.on_event(event)

    def add_task(self, conn):
        conn.tasks.insert_one({
            'timeout':
            datetime.datetime.utcnow() +
            datetime.timedelta(seconds=random.randint(10, 601))
        })

    def __hash__(self):
        return self.id
コード例 #9
0
class MasterController(sims4.service_manager.Service):
    get_next_id = UniqueIdGenerator()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._enabled = True
        self._processing = False
        self._reset_in_progress = False
        self._last_work_timestamps = {}
        self._sims = set()
        self._active_work = {}
        self._denied_sims = OrderedDict()
        self._global_required_resources = WeakSet()
        self._gsi_entry = None
        self._gsi_log_entries = None

    def stop(self):
        self._remove_all_sims()
        if self._sims:
            logger.error('Sims {} should be empty.  MC logic error.',
                         self._sims,
                         owner='mduke')
            self._sims.clear()
        if self._active_work:
            logger.error('Active Work {} should be empty.  MC logic error.',
                         self._active_work,
                         owner='mduke')
            self._active_work.clear()
        if self._denied_sims:
            logger.error('Denied Sims {} should be empty.  MC logic error.',
                         self._denied_sims,
                         owner='mduke')
            self._denied_sims.clear()

    @property
    def timeline(self):
        return services.time_service().sim_timeline

    def remove_all_sims_and_disable_on_teardown(self):
        self._enabled = False
        self._remove_all_sims()

    def _remove_all_sims(self):
        for sim in tuple(self._sims):
            self.remove_sim(sim)

    def add_sim(self, sim):
        logger.assert_raise(
            self._enabled == True,
            'Attempting to add a sim to the master controller when it is not enabled.',
            owner='sscholl')
        self._sims.add(sim)
        self.set_timestamp_for_sim_to_now(sim)
        self._process(sim)

    def added_sims(self):
        return list(self._sims)

    def remove_sim(self, sim):
        self._last_work_timestamps.pop(sim, None)
        self._sims.discard(sim)
        del self._denied_sims[sim]
        if sim in self._denied_sims and sim.is_sim:
            sim.queue.on_head_changed.remove(self._process)

    def reset_timestamp_for_sim(self, sim):
        self._last_work_timestamps[sim] = 0

    def set_timestamp_for_sim_to_now(self, sim):
        self._last_work_timestamps[sim] = self.get_next_id()

    def on_reset_sim(self, sim, reset_reason):
        self._active_work.pop(sim, None)

    def on_reset_begin(self):
        self._reset_in_progress = True

    def on_reset_end(self, *sims):
        self._reset_in_progress = False
        self._process(*sims)

    def add_interdependent_reset_records(self, sim, records):
        work_entry = self._active_work.get(sim, None)
        if work_entry is None:
            return records
        for other_sim in work_entry.resources:
            if other_sim is not sim:
                records.append(
                    ResetRecord(other_sim, ResetReason.RESET_EXPECTED, sim,
                                'Work entry resource:{}'.format(work_entry)))

    def add_global_lock(self, resource):
        self._global_required_resources.add(resource)

    def remove_global_lock(self, resource):
        self._global_required_resources.discard(resource)

    def _process_work_entry(self, sim, work_entry, requested_sims,
                            requested_resources):
        all_free = True
        must_run = not work_entry.cancelable
        immediate_cancels = []
        if work_entry.additional_resources:
            for additional_resource in work_entry.additional_resources:
                if additional_resource in requested_resources:
                    all_free = False
                    break
            else:
                requested_resources.update(work_entry.additional_resources)
        for required_sim in work_entry.resources:
            self._gsi_add_log_entry(
                sim, 'PROCESS_WORK_ENTRY',
                'Sim Resource: {}: testing if valid resource', required_sim)
            if required_sim not in self._sims:
                logger.error(
                    'Attempting to require a resource ({}) that is not managed by the MasterController.',
                    required_sim)
                self._gsi_add_log_entry(
                    sim, 'PROCESS_WORK_ENTRY',
                    'Denied because requested Sim not managed by the MC: {}.',
                    required_sim)
                all_free = False
            if required_sim in requested_sims:
                all_free = False
                self._gsi_add_log_entry(sim, 'PROCESS_WORK_ENTRY',
                                        'Already Requested')
            else:
                if required_sim in self._active_work:
                    self._gsi_add_log_entry(
                        sim, 'PROCESS_WORK_ENTRY',
                        'Sim Resource has Active Work: {} - ',
                        str(self._active_work[required_sim]))
                    if not must_run:
                        all_free = False
                        self._gsi_add_log_entry(sim, 'PROCESS_WORK_ENTRY',
                                                'Work Entry is not must run')
                    else:
                        required_work_entry = self._active_work[required_sim]
                        if not required_work_entry.cancelable:
                            all_free = False
                            requested_sims.add(required_sim)
                            self._gsi_add_log_entry(
                                sim, 'PROCESS_WORK_ENTRY',
                                'Sim Resource has work entry and cannot be canceled immediately'
                            )
                            if not required_sim.is_sim:
                                required_sim.on_requested_as_resource(
                                    work_entry)
                                self._gsi_add_log_entry(
                                    sim, 'PROCESS_WORK_ENTRY',
                                    'Sim Resource has work entry that can be canceled added to immediate_cancels'
                                )
                                immediate_cancels.append(
                                    (required_sim, required_work_entry))
                                self._gsi_add_log_entry(
                                    sim, 'PROCESS_WORK_ENTRY',
                                    'Sim Resource is free')
                        else:
                            self._gsi_add_log_entry(
                                sim, 'PROCESS_WORK_ENTRY',
                                'Sim Resource has work entry that can be canceled added to immediate_cancels'
                            )
                            immediate_cancels.append(
                                (required_sim, required_work_entry))
                            self._gsi_add_log_entry(sim, 'PROCESS_WORK_ENTRY',
                                                    'Sim Resource is free')
                self._gsi_add_log_entry(sim, 'PROCESS_WORK_ENTRY',
                                        'Sim Resource is free')
        if all_free:
            for (required_sim, required_work_entry) in immediate_cancels:
                self._gsi_add_log_entry(sim, 'PROCESS_WORK_ENTRY',
                                        '{} work entry canceled called.',
                                        required_sim)
                required_work_entry.cancel()
                if required_sim in self._active_work:
                    del self._active_work[required_sim]
            for required_sim in work_entry.resources:
                self._gsi_add_log_entry(sim, 'PROCESS_WORK_ENTRY',
                                        'work entry added to sim{}.',
                                        required_sim)
                self._active_work[required_sim] = work_entry
                requested_sims.add(required_sim)
            return True
        if sim not in self._denied_sims:
            self._gsi_add_log_entry(sim, 'PROCESS_WORK_ENTRY',
                                    'Entry added to denied sims.')
            if sim.is_sim:
                sim.queue.on_head_changed.append(self._process)
            self._denied_sims[sim] = work_entry
        if must_run:
            requested_sims.update(work_entry.resources)
        self._gsi_add_log_entry(sim, 'PROCESS_WORK_ENTRY',
                                'work entry NOT added to sim.')
        return False

    def _sorted_sims(self, sims):
        return sorted(
            sims,
            key=lambda sim:
            (-sim.get_next_work_priority(), self._last_work_timestamps[sim]))

    def _process(self, *sims):
        if not self._enabled or self._processing or self._reset_in_progress:
            return
        self._processing = True
        sims_filtered = list(sims)
        try:
            requested_sims = set(self._global_required_resources)
            requested_resources = set()
            for work_entry in self._active_work.values():
                if work_entry.additional_resources:
                    requested_resources.update(work_entry.additional_resources)
            new_work_accepted = []
            self._gsi_entry_initialize(*sims)
            self._gsi_add_sim_time_line_for_sims(sims, 'Start',
                                                 'Begin processing')
            sims_filtered = [
                sim for sim in sims if sim not in self._denied_sims
                if sim in self._sims
            ]
            for sim in self._sorted_sims(
                    itertools.chain(self._denied_sims, sims_filtered)):
                self._gsi_add_log_entry(sim, 'PROCESS', '----- START -----')
                if sim not in self._sims:
                    continue
                if sim in requested_sims:
                    continue
                existing_entry = self._active_work.get(sim)
                if existing_entry is not None and not existing_entry.cancelable:
                    continue
                if sim in self._denied_sims and sim.is_sim:
                    sim.queue.on_head_changed.remove(self._process)
                try:
                    work_request = sim.get_next_work()
                finally:
                    if sim in self._denied_sims and sim.is_sim:
                        sim.queue.on_head_changed.append(self._process)
                if work_request.work_element is None:
                    self._gsi_add_log_entry(sim, 'PROCESS', 'No Work Element')
                else:
                    work_entry = WorkEntry(
                        work_element=work_request.work_element,
                        resources=work_request.required_sims,
                        additional_resources=work_request.additional_resources,
                        owner=sim,
                        master_controller=self,
                        on_accept=work_request.on_accept,
                        debug_name=work_request._debug_name)
                    self._gsi_add_sim_time_line_for_sim(
                        sim, 'Create', 'Work Entry Created')
                    self._gsi_add_log_entry(
                        sim, 'PROCESS', 'Work Entry Created: required_sims:{}',
                        str(work_request.required_sims))
                    if self._process_work_entry(sim, work_entry,
                                                requested_sims,
                                                requested_resources):
                        if sim in self._denied_sims:
                            if sim.is_sim:
                                sim.queue.on_head_changed.remove(self._process)
                            del self._denied_sims[sim]
                        new_work_accepted.append((sim, work_entry))
                        if work_request.set_work_timestamp:
                            self.set_timestamp_for_sim_to_now(sim)
            for (sim, work_entry) in new_work_accepted:
                self._gsi_add_log_entry(sim, 'PROCESS',
                                        'Work Entry Start Called: {}',
                                        work_entry)
                self._gsi_add_sim_time_line_for_sim(sim, 'Start',
                                                    'Work Entry Started')
                work_entry.start()
            for sim in self._sims:
                if sim not in self._active_work:
                    (work_element_idle,
                     cancel_callable) = sim.get_idle_element()
                    if work_element_idle is not None:
                        work_entry = WorkEntry(work_element=work_element_idle,
                                               cancel_callable=cancel_callable,
                                               resources=(sim, ),
                                               owner=sim,
                                               master_controller=self)
                        self._active_work[sim] = work_entry
                        self._gsi_add_log_entry(
                            sim, 'PROCESS',
                            'No active work - run idle behavior')
                        if sim not in self._denied_sims and sim.is_sim:
                            sim.queue.on_head_changed.append(self._process)
                        self._denied_sims[sim] = work_entry
                        work_entry.start()
            self._gsi_entry_finalize()
            self._processing = False
        except:
            logger.exception(
                'Exception while processing the Master Controller.')
        finally:
            if self._processing:
                self._processing = False
                services.get_reset_and_delete_service().trigger_batch_reset(
                    sims_filtered, ResetReason.RESET_ON_ERROR, None,
                    'Exception in _process in the MasterController.')

    def _gsi_create_active_work_entry(self):
        gsi_active_work = []
        for (sim, work_entry) in self._active_work.items():
            entry = {'sim': str(sim), 'work_entry': str(work_entry)}
            gsi_active_work.append(entry)
        return gsi_active_work

    def _gsi_entry_initialize(self, *sims_being_processed):
        if gsi_handlers.master_controller_handlers.archiver.enabled:
            self._gsi_entry = {
                'sims_with_active_work':
                str([str(sim) for sim in self._active_work.keys()]),
                'last_time_stamp':
                str(self._last_work_timestamps)
            }
            self._gsi_entry[
                'active_work_start'] = self._gsi_create_active_work_entry()
            self._gsi_log_entries = []

    def _gsi_add_log_entry(self, sim, tag, log_message, *log_message_args):
        if gsi_handlers.master_controller_handlers.archiver.enabled:
            entry = {
                'sim': str(sim) if sim is not None else '',
                'tag': tag,
                'log': log_message.format(*log_message_args)
            }
            self._gsi_log_entries.append(entry)

    def _gsi_add_sim_time_line_for_sim(self, sim, status, log_message):
        if gsi_handlers.sim_timeline_handlers.archiver.enabled:
            gsi_handlers.sim_timeline_handlers.archive_sim_timeline(
                sim, 'MasterController', status, log_message)

    def _gsi_add_sim_time_line_for_sims(self, sims, status, log_message):
        if gsi_handlers.sim_timeline_handlers.archiver.enabled:
            for sim in sims:
                gsi_handlers.sim_timeline_handlers.archive_sim_timeline(
                    sim, 'MasterController', status, log_message)

    def _gsi_add_sim_time_line_entry(self, work_entry, status, log_message):
        if gsi_handlers.sim_timeline_handlers.archiver.enabled:
            for resource in work_entry.resources:
                if not resource.is_sim:
                    continue
                if resource is work_entry.owner:
                    message_to_log = '{}: as owner: {}'.format(
                        log_message, resource)
                else:
                    message_to_log = '{} as resource: {}'.format(
                        log_message, resource, log_message)
                gsi_handlers.sim_timeline_handlers.archive_sim_timeline(
                    resource, 'MasterController', status, message_to_log)

    def _gsi_entry_finalize(self):
        if gsi_handlers.master_controller_handlers.archiver.enabled:
            self._gsi_entry['sims_with_active_work_after'] = str(
                [str(sim) for sim in self._active_work.keys()])
            self._gsi_entry['last_time_stamp_end'] = str(
                self._last_work_timestamps)
            self._gsi_entry[
                'active_work_end'] = self._gsi_create_active_work_entry()
            self._gsi_entry['Log'] = self._gsi_log_entries
            gsi_handlers.master_controller_handlers.archive_master_controller_entry(
                self._gsi_entry)
            self._gsi_entry = None
            self._gsi_log_entries = None
コード例 #10
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())
コード例 #11
0
ファイル: daycare.py プロジェクト: NeonOcean/Environment
class DaycareService(Service):

    def __init__(self):
        self._unavailable_sims = WeakSet()
        self._global_unavailable_sims = WeakSet()
        self._daycare_interactions = WeakKeyDictionary()
        self._excluded_sims = WeakSet()
        self._nanny_dialog_shown = False

    def on_sim_reset(self, sim):
        sim_info = sim.sim_info
        daycare_interaction = self._daycare_interactions.get(sim_info)
        if daycare_interaction is not None:
            del self._daycare_interactions[sim_info]
            self._apply_daycare_effects_to_sim(sim_info)

    def get_available_sims_gen(self):
        for sim in services.sim_info_manager().instanced_sims_gen(allow_hidden_flags=ALL_HIDDEN_REASONS):
            if sim.sim_info in self._global_unavailable_sims:
                continue
            yield sim.sim_info

    def _apply_daycare_effects_to_sim(self, sim_info):
        self._excluded_sims.discard(sim_info)
        sim = services.object_manager().get(sim_info.id)
        if sim_info.is_baby:
            sim.empty_baby_state()
        elif sim_info.is_toddler:
            daycare_interaction = self._daycare_interactions.get(sim_info)
            if daycare_interaction is None:
                aop = AffordanceObjectPair(DaycareTuning.GO_TO_DAYCARE_INTERACTION, None, DaycareTuning.GO_TO_DAYCARE_INTERACTION, None)
                context = InteractionContext(sim, InteractionContext.SOURCE_SCRIPT, Priority.High, insert_strategy=QueueInsertStrategy.FIRST, must_run_next=True)
                execute_result = aop.test_and_execute(context)
                if execute_result:
                    self._daycare_interactions[sim_info] = execute_result.interaction
        return True

    def _remove_daycare_effects_from_sim(self, sim_info):
        sim = services.object_manager().get(sim_info.id)
        if sim_info.is_baby and sim is not None:
            sim.enable_baby_state()
        elif sim_info.is_toddler:
            daycare_interaction = self._daycare_interactions.pop(sim_info, None)
            if daycare_interaction is not None:
                daycare_interaction.cancel(FinishingType.NATURAL, cancel_reason_msg='Daycare no longer necessary.', ignore_must_run=True)
        if sim_info in self._excluded_sims:
            self._excluded_sims.discard(sim_info)
            return False
        return True

    def _is_sim_available(self, sim_info, household, current_zone_id):
        if sim_info.zone_id == household.home_zone_id:
            if sim_info.zone_id != current_zone_id:
                if sim_info.career_tracker is None or sim_info.career_tracker.currently_at_work or services.hidden_sim_service().is_hidden(sim_info.id):
                    return False
                return True
            elif sim_info not in self._unavailable_sims:
                return True
        return False

    def _start_nanny_service(self, household, sim_infos, callback_fn):

        def _can_trigger_nanny_service(sim_info):
            if not sim_info.is_toddler:
                return False
            else:
                daycare_interaction = self._daycare_interactions.get(sim_info)
                if daycare_interaction is not None:
                    return False
            return True

        if not any(_can_trigger_nanny_service(sim_info) for sim_info in sim_infos):
            callback_fn()
            return

        def _on_response(dialog):
            self._nanny_dialog_shown = False
            if dialog.accepted:
                service_npc_service = services.current_zone().service_npc_service
                service_npc_service.request_service(household, DaycareTuning.NANNY_SERVICE_NPC, from_load=True)
            else:
                callback_fn()

        if self._nanny_dialog_shown or services.current_zone().service_npc_service.is_service_already_in_request_list(household, DaycareTuning.NANNY_SERVICE_NPC):
            return
        self._nanny_dialog_shown = True
        hire_nanny_dialog = DaycareTuning.NANNY_SERVICE_NPC_DIALOG(None)
        hire_nanny_dialog.show_dialog(additional_tokens=(DaycareTuning.NANNY_SERVICE_NPC.cost_up_front, DaycareTuning.NANNY_SERVICE_NPC.cost_hourly), on_response=_on_response)

    def _get_running_situation_for_service(self, service_npc):
        situation_manager = services.get_zone_situation_manager()
        if situation_manager is not None:
            for service_npc_situation in situation_manager.get_situations_by_type(service_npc.situation):
                return service_npc_situation

    def is_daycare_service_npc_available(self, sim_info=None, household=None):
        household = services.active_household() if household is None else household
        if household.home_zone_id == services.current_zone_id():
            nanny_situation = self._get_running_situation_for_service(DaycareTuning.NANNY_SERVICE_NPC)
            if nanny_situation is not None:
                if sim_info is None:
                    return True
                service_sim = nanny_situation.service_sim()
                if service_sim is not None and service_sim.sim_info is sim_info:
                    return True
            if DaycareTuning.BUTLER_SERVICE_NPC is not None:
                butler_situation = self._get_running_situation_for_service(DaycareTuning.BUTLER_SERVICE_NPC)
                if butler_situation is not None:
                    if butler_situation.is_in_childcare_state:
                        if sim_info is None:
                            return True
                        service_sim = butler_situation.service_sim()
                        if service_sim is not None:
                            if service_sim.sim_info is sim_info:
                                return True
        elif sim_info is None:
            all_hired_service_npcs = household.get_all_hired_service_npcs()
            for service_npc in (DaycareTuning.NANNY_SERVICE_NPC, DaycareTuning.BUTLER_SERVICE_NPC):
                if service_npc is None:
                    continue
                if service_npc.guid64 in all_hired_service_npcs:
                    return True
        return False

    def _is_any_sim_available(self, household):
        if self.is_daycare_service_npc_available(household=household):
            return True
        current_zone_id = services.current_zone_id()
        return any(self._is_sim_available(sim_info, household, current_zone_id) for sim_info in household.can_live_alone_info_gen())

    def _is_everyone_on_vacation(self, household):
        return all(sim_info.is_in_travel_group() for sim_info in household.can_live_alone_info_gen())

    def _enable_daycare_or_nanny_if_necessary(self, household):
        is_active_household = services.active_household() == household
        nanny_sim_infos = self.get_sim_infos_for_nanny(household)
        sent_sim_infos = []
        for sim_info in nanny_sim_infos:
            if not sim_info.trait_tracker.has_trait(DaycareTuning.NANNY_TRAIT_ON_KIDS):
                sent_sim_infos.append(sim_info)
                sim_info.add_trait(DaycareTuning.NANNY_TRAIT_ON_KIDS)
                if self.is_sim_info_at_daycare(sim_info):
                    self.remove_sim_info_from_daycare(sim_info)
                if sim_info.zone_id != household.home_zone_id:
                    sim_info.inject_into_inactive_zone(household.home_zone_id)
        if is_active_household and sent_sim_infos:
            services.client_manager().get_first_client().send_selectable_sims_update()
            self._show_nanny_notification(household, sent_sim_infos, is_enable=True)
        if nanny_sim_infos:
            return
        if not self._is_any_sim_available(household):
            current_zone_id = services.current_zone_id()
            daycare_sim_infos = self.get_sim_infos_for_daycare(household)
            if not daycare_sim_infos:
                return

            def _on_send_to_daycare():
                sent_sim_infos = []
                for sim_info in daycare_sim_infos:
                    if household.home_zone_id == current_zone_id:
                        self._apply_daycare_effects_to_sim(sim_info)
                    if not self.is_sim_info_at_daycare(sim_info):
                        sent_sim_infos.append(sim_info)
                        sim_info.add_trait(DaycareTuning.DAYCARE_TRAIT_ON_KIDS)
                        if sim_info.zone_id != household.home_zone_id:
                            sim_info.inject_into_inactive_zone(household.home_zone_id)
                    if sim_info.zone_id != current_zone_id:
                        if sim_info.away_action_tracker is not None:
                            sim_info.away_action_tracker.reset_to_default_away_action()
                if is_active_household:
                    services.client_manager().get_first_client().send_selectable_sims_update()
                    self._show_daycare_notification(household, sent_sim_infos, is_enable=True)

            if is_active_household and household.home_zone_id == current_zone_id:
                self._start_nanny_service(household, daycare_sim_infos, _on_send_to_daycare)
            else:
                _on_send_to_daycare()

    def default_away_action(self, sim_info):
        highest_advertising_value = None
        highest_advertising_away_action = None
        for (commodity, away_action) in DaycareTuning.DAYCARE_AWAY_ACTIONS.items():
            commodity_instance = sim_info.get_statistic(commodity, add=False)
            if commodity_instance is None:
                continue
            if not away_action.test(sim_info=sim_info, target=None):
                continue
            advertising_value = commodity_instance.autonomous_desire
            if not highest_advertising_value is None:
                if highest_advertising_value < advertising_value:
                    highest_advertising_value = advertising_value
                    highest_advertising_away_action = away_action
            highest_advertising_value = advertising_value
            highest_advertising_away_action = away_action
        return highest_advertising_away_action

    def _disable_daycare_or_nanny_if_necessary(self, household, returning_sim_infos=()):
        returned_children = []
        eligible_nanny_count = self.get_number_of_eligible_nanny_sims(household)
        if eligible_nanny_count and not self._is_everyone_on_vacation(household):
            sim_infos_for_nanny = self.get_sim_infos_for_nanny(household, check_for_vacation=False)
            for sim_info in sim_infos_for_nanny:
                if sim_info.has_trait(DaycareTuning.NANNY_TRAIT_ON_KIDS):
                    sim_info.remove_trait(DaycareTuning.NANNY_TRAIT_ON_KIDS)
                    if sim_info not in returning_sim_infos:
                        returned_children.append(sim_info)
            self._show_nanny_notification(household, returned_children, is_enable=False)
        eligible_daycare_count = self.get_number_of_eligible_daycare_sims(household)
        if eligible_daycare_count and self._is_any_sim_available(household):
            daycare_sim_infos = list(self.get_sim_infos_for_daycare(household))
            for sim_info in tuple(daycare_sim_infos):
                if not self._remove_daycare_effects_from_sim(sim_info) or sim_info in returning_sim_infos:
                    daycare_sim_infos.remove(sim_info)
                if self.is_sim_info_at_daycare(sim_info):
                    self.remove_sim_info_from_daycare(sim_info)
            if not returned_children:
                self._show_daycare_notification(household, daycare_sim_infos, is_enable=False)
        if services.active_household() == household:
            services.client_manager().get_first_client().send_selectable_sims_update()

    def is_daycare_enabled(self, household):
        return not self._is_any_sim_available(household)

    def get_abandoned_toddlers(self, household, sims_infos_to_ignore=()):
        caretaker_zone_ids = set()
        offlot_toddlers = set()
        abandoned_toddlers = []
        current_zone_id = services.current_zone_id()
        for sim_info in household:
            if sim_info in sims_infos_to_ignore:
                continue
            if not sim_info.is_toddler:
                if sim_info.can_live_alone:
                    caretaker_zone_ids.add(sim_info.zone_id)
                    if sim_info.zone_id == current_zone_id:
                        continue
                    if sim_info.zone_id == sim_info.household.home_zone_id:
                        continue
                    offlot_toddlers.add(sim_info)
            else:
                if sim_info.zone_id == current_zone_id:
                    continue
                if sim_info.zone_id == sim_info.household.home_zone_id:
                    continue
                offlot_toddlers.add(sim_info)
        for toddler in offlot_toddlers:
            if toddler.zone_id not in caretaker_zone_ids:
                abandoned_toddlers.append(toddler)
        return abandoned_toddlers

    def get_sim_infos_for_daycare(self, household):
        sim_infos_for_daycare = []
        for sim_info in household:
            if not sim_info.is_toddler_or_younger:
                continue
            if sim_info.is_pet:
                continue
            if sim_info.zone_id != sim_info.household.home_zone_id:
                continue
            sim_infos_for_daycare.append(sim_info)
        sim_infos_for_daycare.extend(self.get_abandoned_toddlers(household))
        return sim_infos_for_daycare

    def get_sim_infos_for_nanny(self, household, check_for_vacation=True):
        if check_for_vacation and not self._is_everyone_on_vacation(household):
            return []
        sim_infos_for_nanny = []
        for sim_info in household:
            if not sim_info.is_child_or_younger:
                continue
            if sim_info.is_pet:
                continue
            if sim_info.is_in_travel_group():
                continue
            sim_infos_for_nanny.append(sim_info)
        sim_infos_for_nanny.extend(self.get_abandoned_toddlers(household))
        return sim_infos_for_nanny

    def get_number_of_eligible_daycare_sims(self, household):
        return sum(1 for sim_info in household if sim_info.is_toddler_or_younger if not sim_info.is_pet)

    def get_number_of_eligible_nanny_sims(self, household):
        return sum(1 for sim_info in household if sim_info.is_child_or_younger if not sim_info.is_pet)

    def on_sim_spawn(self, sim_info):
        current_zone = services.current_zone()
        if not current_zone.is_zone_running:
            return
        if sim_info.is_child_or_younger:
            return
        household = sim_info.household
        if household is not None:
            if household.home_zone_id == current_zone.id:
                self._unavailable_sims.add(sim_info)
                self.set_sim_available(sim_info)
            else:
                self.set_sim_unavailable(sim_info)

    def on_loading_screen_animation_finished(self):
        household = services.active_household()
        if household is None:
            return
        if household.home_zone_id == services.current_zone_id():
            returning_sim_infos = [sim_info for sim_info in services.sim_info_manager().get_traveled_to_zone_sim_infos() if sim_info not in self._unavailable_sims]
            if returning_sim_infos:
                for sim_info in returning_sim_infos:
                    if sim_info.is_child_or_younger:
                        continue
                    self._unavailable_sims.add(sim_info)
                for sim_info in returning_sim_infos:
                    if sim_info.is_child_or_younger:
                        continue
                    self.set_sim_available(sim_info, returning_sim_infos=returning_sim_infos)
                else:
                    self._enable_daycare_or_nanny_if_necessary(household)
            else:
                self._enable_daycare_or_nanny_if_necessary(household)
        else:
            self._enable_daycare_or_nanny_if_necessary(household)
        services.get_event_manager().process_event(TestEvent.AvailableDaycareSimsChanged, sim_info=services.active_sim_info())

    def refresh_household_daycare_nanny_status(self, sim_info, try_enable_if_selectable_toddler=False):
        household = services.active_household()
        if household is not None:
            try_enable = try_enable_if_selectable_toddler and (sim_info.is_toddler and sim_info.is_selectable)
            if not try_enable and (self.is_anyone_with_nanny(household) or self.is_anyone_at_daycare(household)):
                self._disable_daycare_or_nanny_if_necessary(household)
            else:
                self._enable_daycare_or_nanny_if_necessary(household)
            services.get_event_manager().process_event(TestEvent.AvailableDaycareSimsChanged, sim_info=sim_info)

    def refresh_daycare_status(self, baby):
        household = baby.household
        if household is None:
            return
        if self.is_daycare_enabled(household):
            self._apply_daycare_effects_to_sim(baby)
        else:
            self._remove_daycare_effects_from_sim(baby)

    def exclude_sim_from_daycare(self, sim_info):
        self._excluded_sims.add(sim_info)

    def is_sim_info_at_daycare(self, sim_info):
        return sim_info.has_trait(DaycareTuning.DAYCARE_TRAIT_ON_KIDS)

    def remove_sim_info_from_daycare(self, sim_info):
        sim_info.remove_trait(DaycareTuning.DAYCARE_TRAIT_ON_KIDS)

    def is_anyone_at_daycare(self, household):
        return any(sim_info.has_trait(DaycareTuning.DAYCARE_TRAIT_ON_KIDS) for sim_info in household if sim_info.is_toddler_or_younger)

    def is_anyone_with_nanny(self, household):
        return any(sim_info.has_trait(DaycareTuning.NANNY_TRAIT_ON_KIDS) for sim_info in household if sim_info.is_child_or_younger)

    def set_sim_available(self, sim_info, returning_sim_infos=()):
        household = sim_info.household
        daycare_previously_enabled = self.is_anyone_at_daycare(household)
        nanny_previously_enabled = False if daycare_previously_enabled else self.is_anyone_with_nanny(household)
        self._unavailable_sims.discard(sim_info)
        if daycare_previously_enabled or nanny_previously_enabled:
            self._disable_daycare_or_nanny_if_necessary(household, returning_sim_infos=returning_sim_infos)
        services.get_event_manager().process_event(TestEvent.AvailableDaycareSimsChanged, sim_info=sim_info)

    def set_sim_globally_available(self, sim_info):
        self._global_unavailable_sims.discard(sim_info)

    def set_sim_unavailable(self, sim_info):
        household = sim_info.household
        nanny_previously_disabled = not self.is_anyone_with_nanny(household)
        daycare_previously_disabled = True if not nanny_previously_disabled else not self.is_anyone_at_daycare(household)
        self._unavailable_sims.add(sim_info)
        if nanny_previously_disabled or daycare_previously_disabled:
            self._enable_daycare_or_nanny_if_necessary(household)
        services.get_event_manager().process_event(TestEvent.AvailableDaycareSimsChanged, sim_info=sim_info)

    def set_sim_globally_unavailable(self, sim_info):
        self._global_unavailable_sims.add(sim_info)

    def _show_nanny_notification(self, household, sim_infos, is_enable=False):
        if sim_infos:
            if is_enable:
                if len(sim_infos) == 1:
                    self._show_notification(DaycareTuning.SEND_CHILD_TO_NANNY_NOTIFICATION_SINGLE, sim_infos, household)
                else:
                    self._show_notification(DaycareTuning.SEND_CHILD_TO_NANNY_NOTIFICATION_MULTIPLE, sim_infos, household)
            elif len(sim_infos) == 1:
                self._show_notification(DaycareTuning.BRING_CHILD_BACK_FROM_NANNY_NOTIFICATION_SINGLE, sim_infos, household)
            else:
                self._show_notification(DaycareTuning.BRING_CHILD_BACK_FROM_NANNY_NOTIFICATION_MULTIPLE, sim_infos, household)

    def _show_daycare_notification(self, household, sim_infos, is_enable=False):
        if sim_infos:
            if is_enable:
                if len(sim_infos) == 1:
                    self._show_notification(DaycareTuning.SEND_BABY_TO_DAYCARE_NOTIFICATION_SINGLE_BABY, sim_infos, household)
                else:
                    self._show_notification(DaycareTuning.SEND_BABY_TO_DAYCARE_NOTIFICATION_MULTIPLE_BABIES, sim_infos, household)
            elif len(sim_infos) == 1:
                self._show_notification(DaycareTuning.BRING_BABY_BACK_FROM_DAYCARE_NOTIFICATION_SINGLE_BABY, sim_infos, household)
            else:
                self._show_notification(DaycareTuning.BRING_BABY_BACK_FROM_DAYCARE_NOTIFICATION_MULTIPLE_BABIES, sim_infos, household)

    def _show_notification(self, notification, sim_infos, household, resolver=None):
        if not services.current_zone().is_zone_running:
            return
        if not household.is_active_household:
            return
        additional_token = sim_infos if len(sim_infos) > 1 else sim_infos[0]
        dialog = notification(None, resolver)
        dialog.show_dialog(additional_tokens=(additional_token,))

    def send_active_household_toddlers_home(self):
        active_household = services.active_household()
        if active_household is None:
            return
        instanced_toddlers = [sim for sim in active_household.instanced_sims_gen() if sim.sim_info.is_toddler]
        for toddler in instanced_toddlers:
            interaction_context = InteractionContext(toddler, InteractionContext.SOURCE_SCRIPT, Priority.Critical)
            toddler.push_super_affordance(TravelTuning.GO_HOME_INTERACTION, None, interaction_context)
コード例 #12
0
class ObjectManager(DistributableObjectManager):
    __qualname__ = 'ObjectManager'
    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)))

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._crafting_cache = CraftingObjectCache()
        self._sim_spawn_conditions = collections.defaultdict(set)
        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

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

    def portal_cache_gen(self):
        yield 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):
        del self._objects[obj.id]
        obj.manager = inventory_manager
        inventory_manager._objects[obj.id] = obj
        if hasattr(obj, 'on_added_to_inventory'):
            obj.on_added_to_inventory()

    def add(self, obj, *args, **kwargs):
        super().add(obj, *args, **kwargs)
        if obj.objectage_component is None:
            current_zone = services.current_zone()
            current_zone.increment_object_count(obj)
            current_zone.household_manager.increment_household_object_count(obj.get_household_owner_id())

    def remove(self, obj, *args, **kwargs):
        super().remove(obj, *args, **kwargs)
        if obj.objectage_component is None:
            current_zone = services.current_zone()
            current_zone.decrement_object_count(obj)
            current_zone.household_manager.decrement_household_object_count(obj.get_household_owner_id())

    def _should_save_object_on_lot(self, obj):
        parent = obj.parent
        if parent is not None and parent.is_sim:
            if obj.can_go_in_inventory_type(InventoryType.SIM):
                return False
        return True

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

    def save(self, object_list=None, zone_data=None, open_street_data=None, **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
        for game_object in self._objects.values():
            while self._should_save_object_on_lot(game_object):
                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)
                if not save_result:
                    pass
                if zone_data is None:
                    pass
                def_build_buy_tags = game_object.definition.build_buy_tags
                if not def_build_buy_tags & self._all_bed_tags:
                    pass
                if def_build_buy_tags & self.BED_TAGS.double_beds:
                    double_bed_exist = True
                    total_beds += 1
                elif def_build_buy_tags & self.BED_TAGS.kid_beds:
                    total_beds += 1
                    kid_bed_exist = True
                elif def_build_buy_tags & self.BED_TAGS.other_sleeping_spots:
                    alternative_sleeping_spots += 1
                elif def_build_buy_tags & self.BED_TAGS.beds:
                    total_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
            zone_data.gameplay_zone_data.bed_info_data = bed_info_data
        lot = services.current_zone().lot
        for (inventory_type, inventory) in lot.get_all_object_inventories_gen():
            for game_object in inventory:
                game_object.save_object(object_list.objects, ItemLocation.OBJECT_INVENTORY, inventory_type)

    def valid_objects(self):
        return [obj for obj in self._objects.values() if not obj._hidden_flags]

    def get_objects_of_type_gen(self, *definitions):
        for obj in self._objects.values():
            while any(obj.definition is d for d in definitions):
                yield obj

    def get_objects_with_tag_gen(self, tag):
        for obj in self._objects.values():
            while build_buy.get_object_has_tag(obj.definition.id, tag):
                yield obj

    def add_sim_spawn_condition(self, sim_id, callback):
        for sim in services.sim_info_manager().instanced_sims_gen():
            while 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 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 add_portal_to_cache(self, portal):
        self._portal_cache.add(portal)
        self._portal_added_callbacks(portal)

    def remove_portal_from_cache(self, portal):
        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 advertising_objects_gen(self, motives:set=DEFAULT):
        if not motives:
            return
        if motives is DEFAULT:
            for obj in self.valid_objects():
                while obj.commodity_flags:
                    yield obj
            return
        for obj in self.valid_objects():
            while obj.commodity_flags & motives:
                yield obj

    def get_all_objects_with_component_gen(self, component):
        if component is None:
            return
        for obj in self.valid_objects():
            if obj.has_component(component.instance_attr):
                yield obj
            else:
                while obj.has_component(component.class_attr):
                    yield obj

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

    @classproperty
    def supports_parenting(self):
        return True
コード例 #13
0
class CaregiverSituation(SituationComplexCommon):
    CAREGIVER_EVENTS = (TestEvent.SituationStarted,
                        TestEvent.AvailableDaycareSimsChanged)
    INSTANCE_TUNABLES = {
        'caregiver_data':
        TunableTuple(
            description=
            '\n            The relationship bits to apply to Sims.\n            ',
            caregiver_bit=TunableReference(
                description=
                "\n                The bit that is applied to Sims that are the situation owner's\n                Sim's caregiver. This is, for example, a bit on an adult\n                targeting a toddler.\n                ",
                manager=services.get_instance_manager(
                    sims4.resources.Types.RELATIONSHIP_BIT)),
            caregiver_job=SituationJob.TunableReference(
                description=
                '\n                The situation job that caregivers are assigned when in this situation.\n                '
            ),
            caregiver_rolestate=RoleState.TunableReference(
                description=
                '\n                The role state that caregivers are assigned when in this situation.\n                '
            ),
            care_dependent_bit=TunableReference(
                description=
                '\n                The bit that is applied to Sims that are the situation owner\n                This is, for example, a bit on a toddler targeting an adult.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.RELATIONSHIP_BIT))),
        'caregiver_relationships':
        TunableSet(
            description=
            '\n            A list of bits that make Sims primary caregivers. If any Sim with\n            any of these bits is instantiated and living in the same household \n            as the care dependent, they are considered caregivers.\n            \n            If no primary caregiver exists, and no caregiver service exists,\n            active TYAE Sims are made caregivers.\n            ',
            tunable=TunableReference(manager=services.get_instance_manager(
                sims4.resources.Types.RELATIONSHIP_BIT),
                                     pack_safe=True))
    }
    REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES

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

    @classmethod
    def default_job(cls):
        pass

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return ((cls.caregiver_data.caregiver_job,
                 cls.caregiver_data.caregiver_rolestate), )

    def _is_valid_caregiver(self,
                            care_dependent,
                            caregiver,
                            ignore_zone=False):
        if not ignore_zone and care_dependent.zone_id != caregiver.zone_id:
            return False
        if caregiver.is_toddler_or_younger:
            return False
        if caregiver.is_pet:
            return False
        if care_dependent.household_id == caregiver.household_id and any(
                caregiver.relationship_tracker.has_bit(care_dependent.sim_id,
                                                       rel_bit)
                for rel_bit in self.caregiver_relationships):
            return True
        else:
            daycare_service = services.daycare_service()
            if daycare_service is not None and daycare_service.is_daycare_service_npc_available(
                    sim_info=caregiver, household=care_dependent.household):
                return True
        return False

    def _update_caregiver_status(self):
        care_dependent = self._guest_list.host_sim
        if care_dependent is None:
            return
        if care_dependent.household is None:
            return
        if care_dependent.is_being_destroyed:
            return
        available_sims = tuple(
            sim_info for sim_info in
            services.daycare_service().get_available_sims_gen())
        current_caregivers = set(self._situation_sims)
        for sim in current_caregivers:
            self._pending_caregivers.discard(sim)
        eligible_caregivers = set(
            sim_info for sim_info in available_sims
            if self._is_valid_caregiver(care_dependent, sim_info))
        if not eligible_caregivers:
            eligible_caregivers = set(
                sim_info for sim_info in
                care_dependent.household.can_live_alone_info_gen()
                if sim_info in available_sims)
        for sim in self._pending_caregivers:
            eligible_caregivers.discard(sim.sim_info)
        for potential_caregiver in tuple(eligible_caregivers):
            sim = potential_caregiver.get_sim_instance(
                allow_hidden_flags=ALL_HIDDEN_REASONS)
            if sim is None or sim.is_being_destroyed:
                eligible_caregivers.discard(potential_caregiver)
            else:
                if sim in current_caregivers:
                    continue
                self.invite_sim_to_job(sim,
                                       job=self.caregiver_data.caregiver_job)
                self._pending_caregivers.add(sim)
                care_dependent.relationship_tracker.add_relationship_bit(
                    potential_caregiver.sim_id,
                    self.caregiver_data.care_dependent_bit)
                potential_caregiver.relationship_tracker.add_relationship_bit(
                    care_dependent.sim_id, self.caregiver_data.caregiver_bit)
        for sim in tuple(current_caregivers):
            if sim.sim_info not in eligible_caregivers:
                self._remove_caregiver_rel_bits(care_dependent, sim.sim_info)
                self.remove_sim_from_situation(sim)
                current_caregivers.discard(sim)

    def _remove_caregiver_rel_bits(self, care_dependent, other_sim_info=None):
        if other_sim_info is not None:
            care_dependent.relationship_tracker.remove_relationship_bit(
                other_sim_info.id, self.caregiver_data.care_dependent_bit)
            other_sim_info.relationship_tracker.remove_relationship_bit(
                care_dependent.id, self.caregiver_data.caregiver_bit)
        else:
            for relationship in care_dependent.relationship_tracker:
                other_sim_id = relationship.get_other_sim_id(
                    care_dependent.sim_id)
                relationship.remove_bit(care_dependent.sim_id, other_sim_id,
                                        self.caregiver_data.care_dependent_bit)
                relationship.remove_bit(other_sim_id, care_dependent.sim_id,
                                        self.caregiver_data.caregiver_bit)

    def get_care_dependent_if_last_caregiver(self,
                                             sim_info,
                                             excluding_interaction_types=None):
        care_dependent = self._guest_list.host_sim
        if care_dependent.household.home_zone_id == services.current_zone_id():
            return
        if not care_dependent.relationship_tracker.has_relationship(
                sim_info.id):
            return
        for relationship in care_dependent.relationship_tracker:
            if relationship.get_other_sim_info(
                    care_dependent.sim_id) is sim_info:
                if not relationship.has_bit(
                        care_dependent.sim_id,
                        self.caregiver_data.care_dependent_bit):
                    return
                    if relationship.has_bit(
                            care_dependent.sim_id,
                            self.caregiver_data.care_dependent_bit):
                        if excluding_interaction_types is not None:
                            other_sim = relationship.get_other_sim(
                                care_dependent.sim_id)
                            if other_sim is None:
                                continue
                            if other_sim.has_any_interaction_running_or_queued_of_types(
                                    excluding_interaction_types):
                                continue
                        else:
                            return
            elif relationship.has_bit(care_dependent.sim_id,
                                      self.caregiver_data.care_dependent_bit):
                if excluding_interaction_types is not None:
                    other_sim = relationship.get_other_sim(
                        care_dependent.sim_id)
                    if other_sim is None:
                        continue
                    if other_sim.has_any_interaction_running_or_queued_of_types(
                            excluding_interaction_types):
                        continue
                else:
                    return
        return care_dependent

    def start_situation(self):
        self._update_caregiver_status()
        services.get_event_manager().register(self, self.CAREGIVER_EVENTS)
        return super().start_situation()

    def _destroy(self):
        services.get_event_manager().unregister(self, self.CAREGIVER_EVENTS)
        care_dependent = self._guest_list.host_sim
        self._remove_caregiver_rel_bits(care_dependent)
        super()._destroy()

    def handle_event(self, sim_info, event, resolver):
        super().handle_event(sim_info, event, resolver)
        if event in self.CAREGIVER_EVENTS:
            self._update_caregiver_status()
コード例 #14
0
class InteractionContext:
    __qualname__ = 'InteractionContext'
    SOURCE_PIE_MENU = InteractionSource.PIE_MENU
    SOURCE_AUTONOMY = InteractionSource.AUTONOMY
    SOURCE_BODY_CANCEL_AOP = InteractionSource.BODY_CANCEL_AOP
    SOURCE_CARRY_CANCEL_AOP = InteractionSource.CARRY_CANCEL_AOP
    SOURCE_SCRIPT = InteractionSource.SCRIPT
    SOURCE_UNIT_TEST = InteractionSource.UNIT_TEST
    SOURCE_SOCIAL_ADJUSTMENT = InteractionSource.SOCIAL_ADJUSTMENT
    SOURCE_QUICKTIME = InteractionSource.QUICKTIME
    SOURCE_GET_COMFORTABLE = InteractionSource.GET_COMFORTABLE
    SOURCE_SCRIPT_WITH_USER_INTENT = InteractionSource.SCRIPT_WITH_USER_INTENT
    SOURCE_POSTURE_GRAPH = InteractionSource.POSTURE_GRAPH

    def __init__(self,
                 sim,
                 source,
                 priority,
                 run_priority=None,
                 client=None,
                 pick=None,
                 insert_strategy=QueueInsertStrategy.LAST,
                 must_run_next=False,
                 continuation_id=None,
                 group_id=None,
                 shift_held=False,
                 carry_target=None,
                 target_sim_id=None,
                 bucket=InteractionBucketType.BASED_ON_SOURCE,
                 visual_continuation_id=None,
                 restored_from_load=False,
                 cancel_if_incompatible_in_queue=False,
                 always_check_in_use=False,
                 preferred_objects=()):
        self._sim = sim.ref() if sim else None
        self.source = source
        self.priority = priority
        self.client = client
        self.pick = pick
        self.insert_strategy = insert_strategy
        self.must_run_next = must_run_next
        self.shift_held = shift_held
        self.continuation_id = continuation_id
        self.visual_continuation_id = visual_continuation_id
        self.group_id = group_id
        self.carry_target = carry_target
        self.target_sim_id = target_sim_id
        self.run_priority = run_priority
        self.bucket = bucket
        self.restored_from_load = restored_from_load
        self.cancel_if_incompatible_in_queue = cancel_if_incompatible_in_queue
        self.always_check_in_use = always_check_in_use
        self.preferred_objects = WeakSet(preferred_objects)

    def _clone(self, **overrides):
        result = copy.copy(self)
        for (name, value) in overrides.items():
            if value is DEFAULT:
                pass
            getattr(result, name)
            setattr(result, name, value)
        return result

    @property
    def bucket_type(self):
        return self.bucket

    @property
    def is_cancel_aop(self):
        return self.source == InteractionSource.BODY_CANCEL_AOP or self.source == InteractionSource.CARRY_CANCEL_AOP

    def clone_for_user_directed_choice(self):
        return self._clone(source=InteractionContext.SOURCE_PIE_MENU,
                           priority=self.client.interaction_priority,
                           insert_strategy=QueueInsertStrategy.LAST,
                           continuation_id=None,
                           group_id=None)

    def clone_for_autonomous_choice(self):
        return self._clone(source=InteractionContext.SOURCE_AUTONOMY,
                           priority=interactions.priority.Priority.Low,
                           insert_strategy=QueueInsertStrategy.LAST,
                           continuation_id=None,
                           group_id=None)

    def clone_for_insert_next(self, preferred_objects=DEFAULT, **kwargs):
        if preferred_objects is DEFAULT:
            preferred_objects = self.preferred_objects
        return self._clone(insert_strategy=QueueInsertStrategy.NEXT,
                           preferred_objects=preferred_objects,
                           restored_from_load=False,
                           **kwargs)

    def clone_for_continuation(self,
                               continuation_of_si,
                               insert_strategy=QueueInsertStrategy.NEXT,
                               continuation_id=DEFAULT,
                               group_id=DEFAULT,
                               preferred_objects=DEFAULT,
                               **kwargs):
        if not continuation_of_si.immediate:
            if continuation_id is DEFAULT:
                continuation_id = continuation_of_si.id
            group_id = continuation_of_si.group_id
        else:
            logger.error(
                'clone_for_continuation: attempting to create a continuation of an immediate interaction, support for this is deprecated and will be removed soon: {}',
                continuation_of_si,
                owner='jpollak/tastle')
        if preferred_objects is DEFAULT:
            preferred_objects = self.preferred_objects
        return self._clone(insert_strategy=insert_strategy,
                           continuation_id=continuation_id,
                           group_id=group_id,
                           preferred_objects=preferred_objects,
                           restored_from_load=False,
                           **kwargs)

    def clone_for_parameterized_autonomy(self,
                                         source_si,
                                         group_id=DEFAULT,
                                         continuation_id=DEFAULT,
                                         visual_continuation_id=DEFAULT,
                                         **kwargs):
        if group_id is DEFAULT:
            group_id = source_si.group_id
        if continuation_id is DEFAULT:
            continuation_id = source_si.id
        if visual_continuation_id is DEFAULT:
            visual_continuation_id = source_si.id
        return self._clone(insert_strategy=QueueInsertStrategy.FIRST,
                           group_id=group_id,
                           continuation_id=continuation_id,
                           run_priority=None,
                           visual_continuation_id=source_si.id,
                           **kwargs)

    def clone_from_immediate_context(self, continuation_of_si, **kwargs):
        if not continuation_of_si.immediate:
            logger.error(
                'clone_from_immediate_context: attempting to create a continuation of a non-immediate interaction.',
                owner='tastle/jpollak')
        return self._clone(group_id=continuation_of_si.group_id, **kwargs)

    def clone_for_sim(self, sim, **overrides):
        return self._clone(_sim=sim.ref(), **overrides)

    def clone_for_concurrent_context(self):
        return self._clone(insert_strategy=QueueInsertStrategy.FIRST)

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

    def add_preferred_object(self, cur_obj):
        self.preferred_objects.add(cur_obj)

    def add_preferred_objects(self, obj_list):
        pass

    @property
    def carry_target(self):
        if self._carry_target:
            return self._carry_target()

    @carry_target.setter
    def carry_target(self, value):
        self._carry_target = value.ref() if value else None

    def __repr__(self):
        return '{0}.{1}({2}, {3}, {4})'.format(self.__module__,
                                               self.__class__.__name__,
                                               repr(self.sim), self.source,
                                               repr(self.priority))
コード例 #15
0
class GameObject(ClientObjectMixin, ReservationMixin, ScriptObject, reset.ResettableObjectMixin):
    INSTANCE_TUNABLES = {'_transient_tuning': Tunable(description='\n            If transient the object will always be destroyed and never put down.\n            ', tunable_type=bool, default=False, tuning_filter=FilterTag.EXPERT_MODE, display_name='Transient'), 'additional_interaction_constraints': TunableList(description='\n            A list of constraints that must be fulfilled in order to run the \n            linked affordances. This should only be used when the same \n            affordance uses different constraints based on the object.\n            ', tunable=TunableTuple(constraint=TunableConstraintVariant(description='\n                    A constraint that must be fulfilled in order to interact with this object.\n                    '), affordance_links=TunableAffordanceFilterSnippet()), tuning_filter=FilterTag.EXPERT_MODE), 'autonomy_modifiers': TunableList(description='\n            List of autonomy modifiers that will be applied to the tuned\n            participant type.  These can be used to tune object variations.\n            ', tunable=TunableAutonomyModifier(locked_args={'commodities_to_add': (), 'score_multipliers': frozendict(), 'provided_affordance_compatibility': None, 'super_affordance_suppression_mode': autonomy.autonomy_modifier.SuperAffordanceSuppression.AUTONOMOUS_ONLY, 'suppress_self_affordances': False, 'only_scored_static_commodities': None, 'only_scored_stats': None, 'relationship_multipliers': None})), 'set_ico_as_carry_target': Tunable(description="\n            Whether or not the crafting process should set the carry target\n            to be the ICO.  Example Usage: Sheet Music has this set to false\n            because the sheet music is in the Sim's inventory and the Sim needs\n            to carry the guitar/violin.  This is a tunable on game object\n            because the ICO in the crafting process can be any game object.\n            ", tunable_type=bool, default=True), 'supported_posture_types': TunablePostureTypeListSnippet(description='\n            The postures supported by this part. If empty, assumes all postures \n            are supported.\n            '), '_add_to_posture_graph_if_parented': Tunable(description="\n            Whether or not the object should be added to the posture graph if it\n            is parented to an object that isn't a surface.  i.e. chairs that\n            should be seatable when slotted into a campfire (which isn't a surface)\n            ", tunable_type=bool, default=False), 'allow_preroll_multiple_targets': Tunable(description='\n            When checked allows multiple sims to target this object during \n            preroll autonomy. If not checked then the default preroll behavior\n            will happen.\n            \n            The default setting is to only allow each target to be targeted\n            once during preroll. However it makes sense in certain cases where\n            multiple sims can use the same object at the same time to allow\n            multiple targets.\n            ', tunable_type=bool, default=False), 'icon_override': OptionalTunable(description='\n            If enabled, the icon that will be displayed in the UI for this object.\n            This does not override the build/buy icon, which can be overriden\n            through the catalog.\n            ', tunable=TunableResourceKey(tuning_group=GroupNames.UI, resource_types=sims4.resources.CompoundTypes.IMAGE)), 'flammable_area': TunableFlammableAreaVariant(description='\n            How the object defines its area of flammability. This is used \n            by the fire service to build the quadtree of flammable objects.\n            '), '_provided_mobile_posture_affordances': OptionalTunable(description="\n            If enabled, this object will add these postures to the posture\n            graph. We need to do this for mobile postures that have no body\n            target and we don't intend on them ever being included in searches\n            for getting from one place to another without this object somewhere\n            on the lot.\n            ", tunable=TunableSet(description='\n                The set of mobile posture providing interactions we want this\n                object to provide.\n                ', tunable=TunableReference(description='\n                    The posture providing interaction we want to add to the\n                    posture graph when this object is instanced.\n                    ', manager=services.affordance_manager(), pack_safe=True, class_restrictions='SuperInteraction'), minlength=1)), 'recycling_data': TunableTuple(description='\n            Recycling information for this object.\n            ', recycling_values=TunableMapping(description='\n                Maps a buck type to the recycled value for this object.\n                ', key_type=TunableEnumEntry(tunable_type=BucksType, default=BucksType.INVALID, invalid_enums=BucksType.INVALID, pack_safe=True), key_name='Bucks Type', value_type=TunableRange(description='\n                    Object multiplier for this buck type.\n                    ', tunable_type=float, default=1.0, minimum=0.0), value_name='Value'), recycling_loot=TunableList(description='\n                Loot Actions that will be given when the object is recycled.\n                SingleActorAndObjectResolver will be used where actor is specified\n                by subject, and object is the object being recycled.\n                ', tunable=TunableReference(description='\n                    A loot action applied.\n                    ', manager=services.get_instance_manager(sims4.resources.Types.ACTION), pack_safe=True))), 'tests_to_bypass_utility_requirement': TunableMapping(description='\n            A mapping of utility types to tunable test sets. \n            ', key_type=TunableEnumEntry(tunable_type=Utilities, default=Utilities.POWER), value_type=TunableTestSet(description='\n                A test set to run when the object is the target of a\n                recipe or interaction and the utility required for that recipe or \n                interaction is absent. If at least one test group passes, \n                then any interaction or recipe that requires the utility\n                will be allowed to run despite the absence of the utility. \n                \n                ORs of ANDs.\n                '))}

    @classmethod
    def _verify_tuning_callback(cls):
        if cls._provided_mobile_posture_affordances is not None:
            for affordance in cls._provided_mobile_posture_affordances:
                if affordance.provided_posture_type is None:
                    logger.error("{} provides posture affordance {} but it doesn't provide a posture.", cls, affordance, owner='rmccord')
                elif not affordance.provided_posture_type.unconstrained:
                    logger.error('{} provides posture affordance {} but the provided posture is not unconstrained and therefore requires a body target.', cls, affordance, owner='rmccord')
                elif affordance in PostureGraphService.POSTURE_PROVIDING_AFFORDANCES:
                    logger.error('{} provides posture affordance {} but this is already provided by the posture graph in Posture Providing Affordances.', cls, affordance, owner='rmccord')

    def __init__(self, definition, **kwargs):
        super().__init__(definition, **kwargs)
        self._on_location_changed_callbacks = None
        self._transient = None
        self._created_constraints = None
        self._created_constraints_dirty = True
        self._household_owner_id = None
        self.new_in_inventory = True
        self.is_new_object = False
        self._provided_surface = UNSET
        zone = services.current_zone()
        account_id = build_buy.get_user_in_build_buy(zone.id)
        if account_id is not None:
            self.set_household_owner_id(zone.lot.owner_household_id)
            self.set_post_bb_fixup_needed()
            zone.set_to_fixup_on_build_buy_exit(self)
        self._hidden_flags = 0
        self._local_tags = None
        self._persisted_tags = None
        self._is_surface = None
        self._build_buy_use_flags = 0
        self._scheduled_elements = None
        self._work_locks = WeakSet()
        self._on_hidden_or_shown_callbacks = None

    def add_work_lock(self, handle):
        self._work_locks.add(handle)

    def remove_work_lock(self, handle):
        self._work_locks.discard(handle)

    @property
    def has_work_locks(self):
        if self._work_locks:
            return True
        return False

    @property
    def is_fire_related_object(self):
        if self.is_sim:
            return False
        return self.fire_retardant or self.flammable

    @constproperty
    def is_valid_for_height_checks():
        return True

    def has_tag(self, tag):
        if self._local_tags and tag in self._local_tags:
            return True
        if self._persisted_tags and tag in self._persisted_tags:
            return True
        return self.definition.has_build_buy_tag(tag)

    def has_any_tag(self, tags):
        return any(self.has_tag(tag) for tag in tags)

    def get_tags(self):
        tags = frozenset(self.definition.build_buy_tags)
        if self._local_tags:
            tags |= self._local_tags
        return tags

    def append_tags(self, tag_set, persist=False):
        if self.manager is not None:
            self.manager.add_tags_and_object_to_cache(tag_set, self)
        if self._local_tags:
            self._local_tags = self._local_tags | tag_set
        else:
            self._local_tags = tag_set
        if persist:
            if self._persisted_tags:
                self._persisted_tags = self._persisted_tags | tag_set
            else:
                self._persisted_tags = tag_set

    def get_icon_info_data(self):
        return IconInfoData(obj_instance=self, obj_def_id=self.definition.id, obj_geo_hash=self.geometry_state, obj_material_hash=self.material_hash, obj_name=LocalizationHelperTuning.get_object_name(self))

    @property
    def catalog_name(self):
        return get_object_catalog_name(self.definition.id)

    @property
    def catalog_description(self):
        return get_object_catalog_description(self.definition.id)

    def is_routing_surface_overlapped_at_position(self, position):
        routing_surface = self.provided_routing_surface
        if routing_surface is not None:
            (_, object_id) = services.terrain_service.terrain_object().get_routing_surface_height_and_surface_object_at(position.x, position.z, routing_surface)
            if object_id == self.id:
                return False
        return True

    @property
    def provided_routing_surface(self):
        if self._provided_surface is UNSET:
            self._provided_surface = None
            if self.routing_surface is not None:
                if self.has_component(FOOTPRINT_COMPONENT):
                    if placement.has_object_surface_footprint(self.get_footprint()):
                        self._provided_surface = routing.SurfaceIdentifier(services.current_zone_id(), self.routing_surface.secondary_id, routing.SurfaceType.SURFACETYPE_OBJECT)
        return self._provided_surface

    def get_icon_override(self):
        for icon_override in self._icon_override_gen():
            if icon_override is not None:
                return icon_override

    @forward_to_components_gen
    def _icon_override_gen(self):
        if self.icon_override is not None:
            yield self.icon_override

    @forward_to_components
    def populate_localization_token(self, token):
        self.definition.populate_localization_token(token)

    def is_hidden(self, allow_hidden_flags=0):
        if int(self._hidden_flags) & ~int(allow_hidden_flags):
            return True
        return False

    def has_hidden_flags(self, hidden_flags):
        if int(self._hidden_flags) & int(hidden_flags):
            return True
        return False

    def hide(self, hidden_reasons_to_add):
        self._hidden_flags = self._hidden_flags | hidden_reasons_to_add
        if self._on_hidden_or_shown_callbacks is not None:
            self._on_hidden_or_shown_callbacks(self, hidden_reasons_to_add, added=True)

    def show(self, hidden_reasons_to_remove):
        self._hidden_flags = self._hidden_flags & ~hidden_reasons_to_remove
        if self._on_hidden_or_shown_callbacks is not None:
            self._on_hidden_or_shown_callbacks(self, hidden_reasons_to_remove, added=False)

    @property
    def transient(self):
        if self._transient is not None:
            return self._transient
        return self._transient_tuning

    @transient.setter
    def transient(self, value):
        self._transient = value

    @distributor.fields.Field(op=distributor.ops.SetBuildBuyUseFlags)
    def build_buy_use_flags(self):
        return self._build_buy_use_flags

    @build_buy_use_flags.setter
    def build_buy_use_flags(self, value):
        self._build_buy_use_flags = value

    @distributor.fields.Field(op=distributor.ops.SetOwnerId)
    def household_owner_id(self):
        return self._household_owner_id

    _resend_household_owner_id = household_owner_id.get_resend()

    def get_edges(self):
        (lower_bound, upper_bound) = self.get_fooptrint_polygon_bounds()
        if lower_bound is None or upper_bound is None:
            return ()
        y = self.position.y
        transform = self.transform
        p0 = transform.transform_point(sims4.math.Vector3(lower_bound.x, y, lower_bound.z))
        p1 = transform.transform_point(sims4.math.Vector3(lower_bound.x, y, upper_bound.z))
        p2 = transform.transform_point(sims4.math.Vector3(upper_bound.x, y, upper_bound.z))
        p3 = transform.transform_point(sims4.math.Vector3(upper_bound.x, y, lower_bound.z))
        return ((p0, p1), (p1, p2), (p2, p3), (p3, p0))

    def get_edge_constraint(self, constraint_width=1.0, inward_dir=False, return_constraint_list=False, los_reference_point=DEFAULT, sim=None):
        edges = self.get_edges()
        polygons = []
        for (start, stop) in edges:
            along = sims4.math.vector_normalize(stop - start)
            inward = sims4.math.vector3_rotate_axis_angle(along, sims4.math.PI/2, sims4.math.Vector3.Y_AXIS())
            if inward_dir:
                polygon = sims4.geometry.Polygon([start, start + constraint_width*inward, stop + constraint_width*inward, stop])
            else:
                polygon = sims4.geometry.Polygon([start, stop, stop - constraint_width*inward, start - constraint_width*inward])
            polygons.append(polygon)
        routing_surface = self.routing_surface
        if return_constraint_list:
            constraint_list = []
            for polygon in polygons:
                restricted_polygon = sims4.geometry.RestrictedPolygon(polygon, ())
                constraint = Constraint(routing_surface=routing_surface, geometry=restricted_polygon, los_reference_point=los_reference_point, posture_state_spec=STAND_AT_NONE_POSTURE_STATE_SPEC)
                constraint_list.append(constraint)
            return constraint_list
        else:
            geometry = sims4.geometry.RestrictedPolygon(sims4.geometry.CompoundPolygon(polygons), ())
            constraint = Constraint(routing_surface=routing_surface, geometry=geometry, posture_state_spec=STAND_AT_NONE_POSTURE_STATE_SPEC)
            return constraint

    def get_created_constraint(self, tuned_constraint):
        if not self.additional_interaction_constraints:
            return
        if not self._created_constraints:
            self._created_constraints = {}
        if self._created_constraints_dirty:
            self._created_constraints.clear()
            for tuned_additional_constraint in self.additional_interaction_constraints:
                constraint = tuned_additional_constraint.constraint
                if constraint is not None:
                    self._created_constraints[constraint] = constraint.create_constraint(None, self)
            self._created_constraints_dirty = False
        return self._created_constraints.get(tuned_constraint)

    @forward_to_components
    def register_rebate_tests(self, test_set):
        pass

    @forward_to_components
    def validate_definition(self):
        pass

    def _should_invalidate_location(self):
        parent = self.parent
        if parent is None:
            return True
        return parent._should_invalidate_location()

    def _notify_buildbuy_of_location_change(self, old_location):
        if self.persistence_group == PersistenceGroups.OBJECT and self._should_invalidate_location():
            invalidate_object_location(self.id)

    def set_build_buy_lockout_state(self, lockout_state, lockout_timer=None):
        if self._build_buy_lockout_alarm_handler is not None:
            alarms.cancel_alarm(self._build_buy_lockout_alarm_handler)
            self._build_buy_lockout_alarm_handler = None
        elif self._build_buy_lockout and lockout_state:
            return
        if lockout_state:
            if lockout_timer is not None:
                time_span_real_time = clock.interval_in_real_seconds(lockout_timer)
                self._build_buy_lockout_alarm_handler = alarms.add_alarm_real_time(self, time_span_real_time, lambda *_: self.set_build_buy_lockout_state(False))
        if lockout_state and not self.build_buy_lockout:
            self.reset(ResetReason.RESET_EXPECTED)
        self._build_buy_lockout = lockout_state
        self.resend_interactable()
        self.resend_tint()

    def on_location_changed(self, old_location):
        super().on_location_changed(old_location)
        self.mark_get_locations_for_posture_needs_update()
        self.clear_check_line_of_sight_cache()
        self._provided_surface = UNSET
        if self.id:
            self._update_persistence_group()
            self._notify_buildbuy_of_location_change(old_location)
            self.manager.on_location_changed(self)
            if self._on_location_changed_callbacks is not None:
                self._on_location_changed_callbacks(self, old_location, self.location)
            self._created_constraints_dirty = True

    def set_object_def_state_index(self, state_index):
        if type(self) != self.get_class_for_obj_state(state_index):
            logger.error("Attempting to change object {}'s state to one that would require a different runtime class.  This is not supported.", self, owner='tastle')
        self.apply_definition(self.definition, state_index)
        self.model = self._model
        self.rig = self._rig
        self.resend_state_index()
        self.resend_slot()

    def register_on_location_changed(self, callback):
        if self._on_location_changed_callbacks is None:
            self._on_location_changed_callbacks = CallableListConsumingExceptions()
        self._on_location_changed_callbacks.append(callback)

    def unregister_on_location_changed(self, callback):
        if self._on_location_changed_callbacks is None:
            logger.error('Unregistering location changed callback on {} when there are none registered.', self)
            return
        if callback not in self._on_location_changed_callbacks:
            logger.error('Unregistering location changed callback on {} that is not registered. Callback: {}.', self, callback)
            return
        self._on_location_changed_callbacks.remove(callback)
        if not self._on_location_changed_callbacks:
            self._on_location_changed_callbacks = None

    def is_on_location_changed_callback_registered(self, callback):
        return callback in self._on_location_changed_callbacks

    def register_on_hidden_or_shown(self, callback):
        if self._on_hidden_or_shown_callbacks is None:
            self._on_hidden_or_shown_callbacks = CallableListConsumingExceptions()
        self._on_hidden_or_shown_callbacks.append(callback)

    def unregister_on_hidden_or_shown(self, callback):
        if self._on_hidden_or_shown_callbacks is None:
            logger.error('Unregistering hidden or shown callback on {} when there are none registered.', self)
            return
        if callback not in self._on_hidden_or_shown_callbacks:
            logger.error('Unregistering hidden or shown callback on {} that is not registered. Callback: {}.', self, callback)
            return
        self._on_hidden_or_shown_callbacks.remove(callback)
        if not self._on_hidden_or_shown_callbacks:
            self._on_hidden_or_shown_callbacks = None

    def is_on_hidden_or_shown_callback_registered(self, callback):
        if self._on_hidden_or_shown_callbacks is None:
            return False
        return callback in self._on_hidden_or_shown_callbacks

    def is_on_active_lot(self, tolerance=0):
        return self.persistence_group == PersistenceGroups.OBJECT

    @property
    def is_in_navmesh(self):
        if self._routing_context is not None and self._routing_context.object_footprint_id is not None:
            return True
        else:
            return False

    @property
    def may_move(self):
        return self.vehicle_component is not None or self.routing_component is not None and self.routing_component.object_routing_component is not None

    def get_surface_override_for_posture(self, source_posture_name):
        pass

    @property
    def add_to_posture_graph_if_parented(self):
        return self._add_to_posture_graph_if_parented

    @classproperty
    def provided_mobile_posture_affordances(cls):
        return cls._provided_mobile_posture_affordances or EMPTY_SET

    def get_joint_transform_for_joint(self, joint_name):
        transform = get_joint_transform_from_rig(self.rig, joint_name)
        transform = Transform.concatenate(transform, self.transform)
        return transform

    @property
    def object_radius(self):
        if self._routing_context is None:
            return routing.get_default_agent_radius()
        return self._routing_context.object_radius

    @property
    def persistence_group(self):
        return self._persistence_group

    @persistence_group.setter
    def persistence_group(self, value):
        self._persistence_group = value

    def _update_persistence_group(self):
        if self.is_in_inventory():
            self.persistence_group = objects.persistence_groups.PersistenceGroups.OBJECT
            return
        if self.persistence_group == objects.persistence_groups.PersistenceGroups.OBJECT:
            if not services.current_zone().lot.is_position_on_lot(self.position, 0):
                remove_object_from_buildbuy_system(self.id)
                self.persistence_group = objects.persistence_groups.PersistenceGroups.IN_OPEN_STREET
        elif self.persistence_group == objects.persistence_groups.PersistenceGroups.IN_OPEN_STREET and services.current_zone().lot.is_position_on_lot(self.position, 0):
            self.persistence_group = objects.persistence_groups.PersistenceGroups.OBJECT
            add_object_to_buildbuy_system(self.id)

    def _fixup_pool_surface(self):
        if (self.item_location == ItemLocation.FROM_WORLD_FILE or self.item_location == ItemLocation.FROM_CONDITIONAL_LAYER) and (self.routing_surface.type != SurfaceType.SURFACETYPE_POOL and build_buy.PlacementFlags.REQUIRES_WATER_SURFACE & build_buy.get_object_placement_flags(self.definition.id)) and get_water_depth_at_location(self.location) > 0:
            routing_surface = self.routing_surface
            self.set_location(self.location.clone(routing_surface=SurfaceIdentifier(routing_surface.primary_id, routing_surface.secondary_id, SurfaceType.SURFACETYPE_POOL)))

    def _add_to_world(self):
        if self.persistence_group == PersistenceGroups.OBJECT:
            add_object_to_buildbuy_system(self.id)

    def _remove_from_world(self):
        if self.persistence_group == PersistenceGroups.OBJECT:
            remove_object_from_buildbuy_system(self.id)

    def on_add(self):
        super().on_add()
        self._add_to_world()
        self.register_on_location_changed(self._location_changed)
        if self.is_fire_related_object:
            fire_service = services.get_fire_service()
            self.register_on_location_changed(fire_service.flammable_object_location_changed)
        posture_graph_service = services.posture_graph_service()
        if posture_graph.is_object_mobile_posture_compatible(self):
            self.register_on_location_changed(posture_graph_service.mobile_posture_object_location_changed)
        if self.provided_mobile_posture_affordances:
            posture_graph_service.add_mobile_posture_provider(self)
        services.call_to_action_service().object_created(self)
        self.try_mark_as_new_object()

    def on_remove(self):
        zone = services.current_zone()
        if zone is not None and not zone.is_zone_shutting_down:
            services.get_event_manager().process_event(test_events.TestEvent.ObjectDestroyed, obj=self)
        super().on_remove()
        if not zone.is_zone_shutting_down:
            self._remove_from_world()
            self.unregister_on_location_changed(self._location_changed)
            if self.is_fire_related_object:
                fire_service = services.get_fire_service()
                if fire_service is not None:
                    self.unregister_on_location_changed(fire_service.flammable_object_location_changed)
            posture_graph_service = services.posture_graph_service()
            if self.provided_mobile_posture_affordances:
                posture_graph_service.remove_mobile_posture_provider(self)
            if posture_graph.is_object_mobile_posture_compatible(self):
                posture_graph_service.remove_object_from_mobile_posture_quadtree(self)
                self.unregister_on_location_changed(posture_graph_service.mobile_posture_object_location_changed)
        else:
            self._on_location_changed_callbacks = None

    def on_added_to_inventory(self):
        super().on_added_to_inventory()
        self._remove_from_world()
        self.visibility = VisibilityState(False)

    def on_removed_from_inventory(self):
        super().on_removed_from_inventory()
        self._add_to_world()
        self.visibility = VisibilityState(True)

    @forward_to_components
    def on_buildbuy_exit(self):
        self._update_location_callbacks(update_surface=True)

    def _update_location_callbacks(self, update_surface=False):
        self._inside_status_change()
        self._natural_ground_status_change()
        if update_surface:
            self._surface_type_changed()

    @staticmethod
    def _location_changed(obj, old_loc, new_loc):
        if obj.zone_id:
            obj._update_location_callbacks(update_surface=old_loc.routing_surface != new_loc.routing_surface)
        obj._fixup_pool_surface()

    @forward_to_components
    def _surface_type_changed(self):
        pass

    def _inside_status_change(self, *_, **__):
        if self.is_outside:
            self._set_placed_outside()
        else:
            self._set_placed_inside()

    def _natural_ground_status_change(self, *_, **__):
        if self.routing_surface is not None and self.routing_surface.type == SurfaceType.SURFACETYPE_POOL:
            return
        if self.is_on_natural_ground():
            self._set_placed_on_natural_ground()
        else:
            self._set_placed_off_natural_ground()

    @forward_to_components
    def _set_placed_outside(self):
        pass

    @forward_to_components
    def _set_placed_inside(self):
        pass

    @forward_to_components
    def _set_placed_on_natural_ground(self):
        pass

    @forward_to_components
    def _set_placed_off_natural_ground(self):
        pass

    @ored_forward_to_components
    def on_hovertip_requested(self):
        return False

    @property
    def is_outside(self):
        routing_surface = self.routing_surface
        level = 0 if routing_surface is None else routing_surface.secondary_id
        try:
            return is_location_outside(self.position, level)
        except RuntimeError:
            pass

    def is_on_natural_ground(self):
        if self.parent is not None:
            return False
        routing_surface = self.routing_surface
        level = 0 if routing_surface is None else routing_surface.secondary_id
        try:
            return is_location_natural_ground(self.position, level)
        except RuntimeError:
            pass

    def try_mark_as_new_object(self):
        if not (self.should_mark_as_new and services.current_zone().is_in_build_buy):
            return
        self.add_dynamic_component(objects.components.types.NEW_OBJECT_COMPONENT)

    def on_child_added(self, child, location):
        super().on_child_added(child, location)
        self.get_raycast_root().on_leaf_child_changed()

    def on_child_removed(self, child, new_parent=None):
        super().on_child_removed(child, new_parent=new_parent)
        self.get_raycast_root().on_leaf_child_changed()

    def on_leaf_child_changed(self):
        if self._raycast_context is not None:
            self._create_raycast_context()

    @property
    def forward_direction_for_picking(self):
        return sims4.math.Vector3.Z_AXIS()

    @property
    def route_target(self):
        parts = self.parts
        if parts is None:
            return (RouteTargetType.OBJECT, self)
        else:
            return (RouteTargetType.PARTS, parts)

    @property
    def should_mark_as_new(self):
        return True

    def is_surface(self, include_parts=False, ignore_deco_slots=False):
        if self._is_surface is None:
            self._is_surface = {}
        key = (include_parts, ignore_deco_slots)
        is_surface = self._is_surface.get(key)
        if is_surface is not None:
            return is_surface
        inventory_component = self.inventory_component
        if inventory_component is not None and inventory_component.has_get_put:
            self._is_surface[key] = True
            return True

        def is_valid_surface_slot(slot_type):
            if not (ignore_deco_slots and slot_type.is_deco_slot) and slot_type.is_surface:
                return True
            return False

        for runtime_slot in self.get_runtime_slots_gen():
            if not include_parts and runtime_slot.owner is not self:
                continue
            if not any(is_valid_surface_slot(slot_type) for slot_type in runtime_slot.slot_types):
                continue
            if not runtime_slot.owner.is_same_object_or_part(self):
                continue
            self._is_surface[key] = True
            return True
        else:
            self._is_surface[key] = False
            return False

    def get_save_lot_coords_and_level(self):
        lot_coord_msg = LotCoord()
        parent = self.parent
        if parent is not None and parent.is_sim:
            parent.force_update_routing_location()
            starting_position = parent.position + parent.forward
            starting_location = placement.create_starting_location(position=starting_position, orientation=parent.orientation, routing_surface=self.location.world_routing_surface)
            fgl_context = placement.create_fgl_context_for_object(starting_location, self)
            (trans, orient) = placement.find_good_location(fgl_context)
            if trans is None:
                logger.warn('Unable to find good location to save object{}, which is parented to sim {} and cannot go into an inventory. Defaulting to location of sim.', self, parent)
                transform = parent.transform
            else:
                transform = sims4.math.Transform(trans, orient)
            if self.persistence_group == PersistenceGroups.OBJECT:
                transform = services.current_zone().lot.convert_to_lot_coordinates(transform)
        elif self.persistence_group == PersistenceGroups.OBJECT:
            transform = services.current_zone().lot.convert_to_lot_coordinates(self.transform)
        else:
            transform = self.transform
        lot_coord_msg.x = transform.translation.x
        lot_coord_msg.y = transform.translation.y
        lot_coord_msg.z = transform.translation.z
        lot_coord_msg.rot_x = transform.orientation.x
        lot_coord_msg.rot_y = transform.orientation.y
        lot_coord_msg.rot_z = transform.orientation.z
        lot_coord_msg.rot_w = transform.orientation.w
        if self.location.world_routing_surface is not None:
            level = self.location.level
        else:
            level = 0
        return (lot_coord_msg, level)

    def save_object(self, object_list, *args, **kwargs):
        save_data = super().save_object(object_list, *args, **kwargs)
        if save_data is None:
            return
        save_data.slot_id = self.bone_name_hash
        (save_data.position, save_data.level) = self.get_save_lot_coords_and_level()
        inventory_plex_id = self.get_inventory_plex_id()
        if inventory_plex_id is not None:
            save_data.inventory_plex_id = inventory_plex_id
        save_data.scale = self.scale
        save_data.state_index = self.state_index
        if hasattr(save_data, 'buildbuy_use_flags'):
            save_data.buildbuy_use_flags = self._build_buy_use_flags
        save_data.cost = self.base_value
        save_data.ui_metadata = self.ui_metadata._value
        self.post_tooltip_save_data_stored()
        save_data.is_new = self.new_in_inventory
        save_data.is_new_object = self.is_new_object
        self.populate_icon_canvas_texture_info(save_data)
        if self._household_owner_id is not None:
            save_data.owner_id = self._household_owner_id
        save_data.needs_depreciation = self._needs_depreciation
        save_data.needs_post_bb_fixup = self._needs_post_bb_fixup
        if self._persisted_tags:
            save_data.persisted_tags.extend(self._persisted_tags)
        if self._multicolor is not None:
            for color in self._multicolor:
                color = getattr(color, 'value', color)
                multicolor_info_msg = save_data.multicolor.add()
                (multicolor_info_msg.x, multicolor_info_msg.y, multicolor_info_msg.z, _) = sims4.color.to_rgba(color)
        save_data.created_from_lot_template = False
        save_data.stack_sort_order = self.get_stack_sort_order()
        if self.material_state:
            save_data.material_state = self.material_state.state_name_hash
        if self.geometry_state:
            save_data.geometry_state = self.geometry_state
        if self.model:
            model_key = sims4.resources.get_protobuff_for_key(self.model)
            save_data.model_override_resource_key = model_key
        parent = self.bb_parent
        if parent is not None:
            if not parent.is_sim:
                save_data.parent_id = parent.id
        if not (parent is None or not parent.is_sim):
            save_data.object_parent_type = self._parent_type
            save_data.encoded_parent_location = self._parent_location
        inventory = self.inventory_component
        if inventory is not None:
            if not inventory.is_shared_inventory:
                save_data.unique_inventory = inventory.save_items()
        return save_data

    def load_object(self, object_data, **kwargs):
        if object_data.HasField('owner_id'):
            self._household_owner_id = object_data.owner_id
        if self.is_downloaded:
            self.base_value = self.catalog_value
        else:
            self.base_value = object_data.cost
        self.new_in_inventory = object_data.is_new
        super().load_object(object_data, **kwargs)
        if object_data.HasField('texture_id') and self.canvas_component is not None:
            self.canvas_component.set_painting_texture_id(object_data.texture_id)
        if object_data.HasField('needs_depreciation'):
            self._needs_depreciation = object_data.needs_depreciation
        if object_data.HasField('needs_post_bb_fixup'):
            self._needs_post_bb_fixup = object_data.needs_post_bb_fixup
        else:
            self._needs_post_bb_fixup = self._needs_depreciation
        inventory = self.inventory_component
        if inventory is not None:
            inventory.load_items(object_data.unique_inventory)
        if sims4.protocol_buffer_utils.has_field(object_data, 'buildbuy_use_flags'):
            self._build_buy_use_flags = object_data.buildbuy_use_flags
        self.is_new_object = object_data.is_new_object
        if self.is_new_object:
            self.add_dynamic_component(objects.components.types.NEW_OBJECT_COMPONENT)
        if object_data.persisted_tags is not None:
            self.append_tags(set(object_data.persisted_tags))

    def finalize(self, **kwargs):
        super().finalize(**kwargs)
        self.try_post_bb_fixup(**kwargs)
        if self.is_fire_related_object:
            fire_service = services.get_fire_service()
            if fire_service is not None:
                fire_service.flammable_object_location_changed(self)
        if posture_graph.is_object_mobile_posture_compatible(self):
            posture_graph_service = services.current_zone().posture_graph_service
            posture_graph_service.mobile_posture_object_location_changed(self)

    def set_household_owner_id(self, new_owner_id):
        self._household_owner_id = new_owner_id
        self._resend_household_owner_id()
        if self.live_drag_component is not None:
            self.live_drag_component.resolve_live_drag_household_permission()

    def get_household_owner_id(self):
        return self._household_owner_id

    def get_object_property(self, property_type):
        if property_type == GameObjectProperty.CATALOG_PRICE:
            return self.definition.price
        if property_type == GameObjectProperty.MODIFIED_PRICE:
            return self.current_value
        if property_type == GameObjectProperty.RARITY:
            return self.get_object_rarity_string()
        if property_type == GameObjectProperty.GENRE:
            return Genre.get_genre_localized_string(self)
        if property_type == GameObjectProperty.RECIPE_NAME or property_type == GameObjectProperty.RECIPE_DESCRIPTION:
            return self.get_craftable_property(self, property_type)
        if property_type == GameObjectProperty.OBJ_TYPE_REL_ID:
            return services.relationship_service().get_object_type_rel_id(self)
        logger.error('Requested property_type {} not found on game_object'.format(property_type), owner='camilogarcia')

    def update_ownership(self, sim, make_sim_owner=True):
        household_id = sim.household_id
        if self._household_owner_id != household_id:
            if self.ownable_component is not None:
                self.ownable_component.update_sim_ownership(None)
            self.set_household_owner_id(household_id)
        if make_sim_owner and self.ownable_component is not None:
            self.ownable_component.update_sim_ownership(sim.sim_id)

    @property
    def flammable(self):
        fire_service = services.get_fire_service()
        if fire_service is not None:
            return fire_service.is_object_flammable(self)
        return False

    def object_bounds_for_flammable_object(self, fire_retardant_bonus):
        return self.flammable_area.get_bounds_for_flammable_object(self, fire_retardant_bonus)

    @property
    def is_set_as_head(self):
        parent = self.parent
        if parent is None:
            return False
        if not parent.is_sim:
            return False
        if parent.current_object_set_as_head is None:
            return False
        else:
            parent_head = parent.current_object_set_as_head()
            if not self.is_same_object_or_part(parent_head):
                return False
        return True

    @classmethod
    def register_tuned_animation(cls, *_, **__):
        pass

    @classmethod
    def add_auto_constraint(cls, *_, **__):
        pass

    def may_reserve(self, sim, *args, **kwargs):
        for child in self.children:
            child_targets = child.parts if child.parts else (child,)
            for child_target in child_targets:
                if child_target.is_sim:
                    continue
                reserve_result = child_target.may_reserve(sim, *args, **kwargs)
                if not reserve_result:
                    return reserve_result
        return super().may_reserve(sim, *args, **kwargs)

    def make_transient(self):
        self.transient = True
        self._destroy_if_not_in_use()

    def _destroy_if_not_in_use(self):
        if self.is_part:
            self.part_owner._destroy_if_not_in_use()
            return
        if self.self_or_part_in_use:
            return
        if not self.transient:
            return
        self.schedule_destroy_asap(source=self, cause='Destroying unused transient object.')
        posture_graph_service = services.current_zone().posture_graph_service
        if posture_graph_service.is_object_pending_deletion(self):
            posture_graph_service.finalize_object_deletion(self)

    def remove_reservation_handler(self, *args, **kwargs):
        super().remove_reservation_handler(*args, **kwargs)
        self._destroy_if_not_in_use()

    def schedule_element(self, timeline, element):
        resettable_element = reset.ResettableElement(element, self)
        resettable_element.on_scheduled(timeline)
        timeline.schedule(resettable_element)
        return resettable_element

    def register_reset_element(self, element):
        if self._scheduled_elements is None:
            self._scheduled_elements = set()
        self._scheduled_elements.add(element)

    def unregister_reset_element(self, element):
        if self._scheduled_elements is not None:
            self._scheduled_elements.discard(element)
            if not self._scheduled_elements:
                self._scheduled_elements = None

    def on_reset_element_hard_stop(self):
        self.reset(reset_reason=ResetReason.RESET_EXPECTED)

    def on_reset_get_elements_to_hard_stop(self, reset_reason):
        elements_to_reset = super().on_reset_get_elements_to_hard_stop(reset_reason)
        if self._scheduled_elements is not None:
            scheduled_elements = list(self._scheduled_elements)
            self._scheduled_elements = None
            for element in scheduled_elements:
                elements_to_reset.append(element)
                element.unregister()
        return elements_to_reset

    def get_gsi_portal_items(self, key_name, value_name):
        household_owner_id = self.household_owner_id
        household_owner = services.household_manager().get(household_owner_id)
        name = household_owner.name if household_owner is not None else 'Not Owned'
        return [{key_name: 'Household Owner', value_name: name}]
コード例 #16
0
class BaseStructure(GameElementBase):

    neighbourhoodThreshold = 2
    REACTION_THRESHOLD = util.REACTION_THRESHOLD

    class Veil(object):
        def __init__(self, structure, time, stopCallback=None, fadeOutTime=120.0, fadeInTime=120.0):
            self._structure = structure
            self._timeToVeil = time

            self._maxFadeOutTime = fadeOutTime
            self._fadeOutTime = fadeOutTime

            self._maxFadeInTime = fadeInTime
            self._fadeInTime = fadeInTime

            self._stopCallback = stopCallback

            self._action = self._veiling

        def onTick(self):
            if self._action is not None:
                self._action()

        def _veiling(self):
            if self._fadeInTime > 0:
                self._fadeInTime -= 1
                self._setVeil(self._fadeInTime / self._maxFadeInTime)
            else:
                self._action = self._veilHolding

        def _veilHolding(self):
            if self._timeToVeil > 0:
                self._timeToVeil -= 1
                self._setVeil(0)
            else:
                self._action = self._unveiling

        def _unveiling(self):
            if self._fadeOutTime > 0:
                self._fadeOutTime -= 1
                self._setVeil(1 - (self._fadeOutTime / self._maxFadeOutTime))
            else:
                self._action = self._finished

        def _finished(self):
            self._removeVeil()
            self._stopCallback()
            self._action = None

        def _setVeil(self, value):
            fxNode = avg.HueSatFXNode(0, 100 * value - 100, 100 * value - 100, False)
            self._structure._image.setEffect(fxNode)

        def _removeVeil(self):
            self._structure._image.setEffect(None)

    def __init__(self, crystalManager=None, framesToGrowth=None, startCrystals=20, *args, **kwargs):
        GameElementBase.__init__(self, *args, **kwargs)

        if self.owner is not None:
            self.owner.addStructure(self)

        self._graph = nx.Graph()
        self._elementMapping = {}

        self._fixElements = WeakSet()

        if __debug__:
            print "FRAMESTO GROWTH", framesToGrowth
        self.framesToGrowth = framesToGrowth
        self.growTimer = 0

        self._overDriveCounter = 0
        self._overdrive = False

        self._veil = None

        self._growLock = False
        self._depletedCallback = None
        self._gameOverCallback = None

        self.crystalManager = crystalManager
        if crystalManager is None:
            logging.warn("Structure {!s} has no valid CrystalManager".format(self))
        else:
            self.crystalManager.registerStructure(self)

        self._shadowWidth = util.CRYSTAL_SIZE * StructureElement.shapeOverflowFactor ** 2
        if self.owner is None:
            self._shadowColor = "000000"
        else:
            self._shadowColor = self.owner.color

        player = avg.Player.get()
        self._canvas = player.createCanvas(
            id=str(id(self)),
            size=(max(util.WINDOW_SIZE), max(util.WINDOW_SIZE)),
            handleevents=True,
            multisamplesamples=4,
        )

        self._blackCanvas = player.createCanvas(
            id=str(id(self)) + "Black",
            size=(max(util.WINDOW_SIZE), max(util.WINDOW_SIZE)),
            handleevents=True,
            multisamplesamples=4,
        )

        self._canvasRoot = self._canvas.getRootNode()

        self._blackBackground = self._blackCanvas.getRootNode()
        self._shadowImage = avg.ImageNode(
            href="canvas:{}Black".format(id(self)),
            parent=self._root,
            size=(max(util.WINDOW_SIZE), max(util.WINDOW_SIZE)),
            opacity=0.4,
        )
        util.centerNodeOnPosition(self._shadowImage, (0, 0))
        self._graphVisRoot = avg.DivNode(parent=self._canvasRoot)

        self._image = avg.ImageNode(
            href="canvas:{}".format(id(self)), parent=self._root, size=(max(util.WINDOW_SIZE), max(util.WINDOW_SIZE))
        )
        util.centerNodeOnPosition(self._image, (0, 0))
        self._edgeNodes = dict()
        self._shadowNodes = dict()

        self._initStructureCore()

        assert self.checkSanity()

        while len(self._graph) < startCrystals:
            self.growSimple()

        self.rotationEnabled = True
        self._tickTimer = None
        self._startTickTimer()

    def getOffscreenPosForElement(self, pos):
        return util.vectorAdd(pos, util.vectorMult(self._canvasRoot.size, 0.5))

    def getElementNodeParent(self):
        return self._canvasRoot

    def _startTickTimer(self):
        self._stopTickTimer()
        player = avg.Player.get()
        self._tickTimer = player.setOnFrameHandler(self.onTick)

    def _stopTickTimer(self):
        player = avg.Player.get()
        if self._tickTimer is not None:
            player.clearInterval(self._tickTimer)
            self._tickTimer = None

    def delete(self):

        if not self.alive:
            return

        self._stopTickTimer()

        if self.owner is not None:
            self.owner.removeStructure(self)

        for element in self._elementMapping.values():
            if element.node is not None:
                element.node.unlink(True)
                element.node = None

        self._elementMapping = None

        for node in self._edgeNodes.values():
            node.unlink(True)

        for node in self._shadowNodes.values():
            node.unlink(True)

        self._edgeNodes = None
        self._shadowNodes = None

        self._rootParent = None

        self._canvasRoot.unlink(True)
        self._canvasRoot = None

        self._shadowImage.unlink(True)
        self._shadowImage = None

        self._graphVisRoot.unlink(True)
        self._graphVisRoot = None

        self._image.unlink(True)
        self._image = None

        player = avg.Player.get()
        player.deleteCanvas(self._blackCanvas.getID())
        player.deleteCanvas(self._canvas.getID())

        self._blackCanvas = None
        self._canvas = None

        self._fixElements = None

        self.crystalManager.removeStructure(self)
        self.crystalManager = None

        GameElementBase.delete(self)

    def _initStructureCore(self):
        raise NotImplementedError

    def _updateElement(self, element):
        self._elementMapping[element.shape] = element
        self._elementMapping[element.node] = element
        self._elementMapping[element.id] = element

    def getElement(self, obj):
        return self._elementMapping[obj]

    def startOverdrive(self, duration):
        self._overDriveCounter += duration

    def _overdriveEnd(self):
        pass

    @property
    def size(self):
        return self._size

    def _initPhysic(self, position, angle):
        mass = pymunk.inf
        moment = pymunk.moment_for_circle(mass, 0, 1)
        self._body = physic.BaseBody(self, mass, moment)
        self._space.add(self._body)

    def _createCircleShape(self, absPos, r=util.CRYSTAL_RADIUS):
        circle = pymunk.Circle(self._body, r, self._body.world_to_local(absPos))
        circle.collision_type = physic.StructureCollisionType
        self._addShape(circle)
        return circle

    @property
    def depletedCallback(self):
        return self._depletedCallback

    @depletedCallback.setter
    def depletedCallback(self, fun):
        self._depletedCallback = fun

    def veil(self, time):
        if self._veil is None:
            self._veil = BaseStructure.Veil(self, time, self._veilEnd)

    def _veilEnd(self):
        self._veil = None

    def getAllElements(self):
        return [self.getElement(id) for id in self._graph]

    def onTick(self):

        if self._overDriveCounter > 0:
            self._overdrive = True
            self._overDriveCounter -= 1
            if self._overDriveCounter == 0:
                self._overdrive = False
                self._overdriveEnd()

        self.grow()

        if self._veil is not None:
            self._veil.onTick()

    @property
    def gameOverCallback(self):
        return self._gameOverCallback

    @gameOverCallback.setter
    def gameOverCallback(self, fun):
        self._gameOverCallback = fun

    def onWallCollision(self, other):
        if self._gameOverCallback is not None:
            self._gameOverCallback()
            self.gameOverCallback = None

    def checkSanity(self):
        return True

    def updateNeigbourhoodVisualisation(self):
        if __debug__:
            if not hasattr(self, "debugNodes"):
                self.debugNodes = []

            while len(self.debugNodes) < self._graph.number_of_edges():
                debugNode = avg.LineNode(parent=self._root)
                debugNode.color = "FF0000"
                debugNode.strokewidth = 2
                debugNode.opacity = 0.5
                debugNode.fillopacity = 0.5
                self.debugNodes.append(debugNode)

            while len(self.debugNodes) > self._graph.number_of_edges():
                self.debugNodes.pop().unlink(True)

            for edge, node in zip(self._graph.edges_iter(), self.debugNodes):
                nodeIdA, nodeIdB = edge
                node.unlink(False)
                node.pos1 = tuple(self.getElement(nodeIdA).shape.offset)
                node.pos2 = tuple(self.getElement(nodeIdB).shape.offset)
                self._root.appendChild(node)

    def removeSurplusElements(self, element):
        assert self.checkSanity()
        removeCounter = 0
        sameNeighbors = self.getSameExtendedNeighborhood(element)
        if self.REACTION_THRESHOLD > 0 and len(sameNeighbors) >= self.REACTION_THRESHOLD:
            for neighbor in sameNeighbors:
                self.removeElement(neighbor)
                removeCounter += 1
            assert self.checkSanity()

        removeCounter += self.removeNotReachableElements()
        return removeCounter

    def getGraphNeighbors(self, element):
        return [self.getElement(id) for id in self._graph.neighbors_iter(element.id)]

    def addElement(self, elementToCreate, color, relPos, rotation=0):
        assert self.checkSanity()

        if color is None:
            element = elementToCreate(relPos, rotation, self, self._helpSystem)
        else:
            element = elementToCreate(color, relPos, rotation, self, self._helpSystem)
        self._updateElement(element)
        self._addElementShadow(element)
        self._graph.add_node(element.id)

        assert self.checkSanity()
        self.updateNeigbors(element)
        assert self.checkSanity()

        return element

    def _removeEdgeNodesForElement(self, element):
        neighbors = self.getGraphNeighbors(element)
        for edge in self._graph.edges_iter([element.id] + neighbors):
            if element.id in edge:
                self._removeEdgeNodes(edge)

    def _addEdgeNodes(self, edge):
        edge = tuple(sorted(edge))

        elementA = self.getElement(edge[0])
        elementB = self.getElement(edge[1])

        shadowLine = avg.LineNode(parent=self._blackBackground)
        shadowLine.color = self._shadowColor
        shadowLine.strokewidth = self._shadowWidth
        shadowLine.opacity = 1
        shadowLine.fillopacity = 1
        shadowLine.pos1 = self.getOffscreenPosForElement(elementA.position)
        shadowLine.pos2 = self.getOffscreenPosForElement(elementB.position)
        self._shadowNodes[edge] = shadowLine
        avg.LinearAnim(shadowLine, "opacity", 700, 0, 1).start()

        edgeLine = avg.LineNode(parent=self._graphVisRoot)
        edgeLine.color = "F88017"
        edgeLine.strokewidth = util.CRYSTAL_SIZE * 0.1
        edgeLine.opacity = 1
        edgeLine.fillopacity = 1
        edgeLine.pos1 = self.getOffscreenPosForElement(elementA.position)
        edgeLine.pos2 = self.getOffscreenPosForElement(elementB.position)
        avg.LinearAnim(edgeLine, "opacity", 5000, 0, 1).start()
        self._edgeNodes[edge] = edgeLine

    def _removeEdgeNodes(self, edge):
        edge = tuple(sorted(edge))
        if edge in self._edgeNodes:
            edgeNode = self._edgeNodes[edge]
            avg.LinearAnim(
                edgeNode, "opacity", 100, edgeNode.opacity, 0, False, None, lambda: edgeNode.unlink(True)
            ).start()
            del self._edgeNodes[edge]
        if edge in self._shadowNodes:
            shadowNode = self._shadowNodes[edge]
            avg.LinearAnim(
                shadowNode, "opacity", 700, shadowNode.opacity, 0, False, None, lambda: shadowNode.unlink(True)
            ).start()
            self._shadowNodes[edge].unlink(True)
            del self._shadowNodes[edge]

    def _addElementShadow(self, element):
        roundShadow = avg.CircleNode(
            parent=self._blackBackground,
            pos=self.getOffscreenPosForElement(element.position),
            r=self._shadowWidth / 2,
            fillcolor=self._shadowColor,
            opacity=0,
            fillopacity=1,
        )
        avg.LinearAnim(roundShadow, "fillopacity", 700, 0, 1).start()
        self._shadowNodes[element.shape] = roundShadow

    def _removeElementShadow(self, element):
        self._shadowNodes[element.shape].unlink(True)
        del self._shadowNodes[element.shape]

    def onCrystalCollision(self, other, hitShape):
        try:
            element = self.getElement(hitShape)
        except KeyError:
            return

        element.onCollision(other)

    def getColorCount(self):
        return collections.Counter([e.color for e in self._elementMapping.values()])

    def removeElement(self, element):
        assert self.checkSanity()
        assert isinstance(element, StructureElement)
        assert element.parent == self

        element.onDestroyAction()
        self._removeEdgeNodesForElement(element)
        self._removeElementShadow(element)

        self._graph.remove_node(element.id)

        self._removeShape(element.shape)
        if element in self._fixElements:
            self._fixElements.remove(element)
        #            avg.LinearAnim(element.node, "opacity",1000, 1 , 0, False, None, lambda:element.node.unlink(True)).start()
        #
        if self._fixElements:
            targetPos = random.choice(list(self._fixElements)).node.pos
            avg.LinearAnim(
                element.node, "pos", 1000, element.node.pos, targetPos, False, None, lambda: element.node.unlink(True)
            ).start()
        else:
            avg.LinearAnim(element.node, "opacity", 1000, 1, 0, False, None, lambda: element.node.unlink(True)).start()

        del self._elementMapping[element.shape]
        del self._elementMapping[element.node]
        del self._elementMapping[element.id]

        assert self.checkSanity()

        self._checkDepleted()

        if __debug__:
            self.updateNeigbourhoodVisualisation()

    def updateNeigbors(self, element, reset=False):
        assert self.checkSanity()
        assert isinstance(element, StructureElement)

        if reset:
            self._removeEdgeNodesForElement(element)
            self._graph.remove_node(element.id)
            self._graph.add_node(element.id)
            assert len(self._graph.neighbors(element.id)) == 0

        for shape in self.getPhysicNeigbors(element):
            shapeId = self.getElement(shape).id
            if shapeId in self._graph:
                self._graph.add_edge(element.id, shapeId)
                self._addEdgeNodes((element.id, shapeId))

        assert self.checkSanity()

        if __debug__:
            self.updateNeigbourhoodVisualisation()

    def getPhysicNeigbors(self, element):
        shape = element.shape
        testBody = pymunk.Body()
        testBody.position = self._body.local_to_world(shape.offset)
        toTest = pymunk.Circle(testBody, self.neighbourhoodThreshold * util.CRYSTAL_SIZE / 2)
        result = self._space.shape_query(toTest)
        result = filter(lambda s: s in self._shapes, result)
        return result

    def randomizeNeighbors(self, element):
        spatialInfo = []
        for element in list(self.getGraphNeighbors(element)):
            spatialInfo.append((element.position, element.node.angle))
            self.removeElement(element)
        for pos, angle in spatialInfo:
            color, toCreate = self.crystalManager.getNextStructureElement(self)
            self.addElement(toCreate, color, pos, angle)

    def _checkDepleted(self):
        if len(self._graph) == 0 and self.depletedCallback is not None:
            self._depletedCallback()
            self._depletedCallback = None

    def removeNeighbors(self, element):
        for element in list(self.getGraphNeighbors(element)):
            self.removeElement(element)

    def _swapShapes(self, elementA, elementB):
        assert self.checkSanity()

        if (elementA in self._fixElements) ^ (elementB in self._fixElements):
            if elementA in self._fixElements:
                self._fixElements.remove(elementA)
                self._fixElements.add(elementB)
            elif elementB in self._fixElements:
                self._fixElements.remove(elementB)
                self._fixElements.add(elementA)

        elementA.shape, elementB.shape = elementB.shape, elementA.shape

        self._updateElement(elementA)
        self._updateElement(elementB)

        assert self.checkSanity()

    def getSameExtendedNeighborhood(self, element):
        assert self.checkSanity()
        neighbourFilter = filter(lambda otherId: element.isSameType(self.getElement(otherId)), self._graph)
        subGraph = self._graph.subgraph(neighbourFilter)
        for graph in nx.connected_components(subGraph):
            if element.id in graph:
                assert self.checkSanity()
                return [self.getElement(i) for i in graph]
        else:
            assert self.checkSanity()
            return []

    def removeNotReachableElements(self):
        toRemove = []
        removeCounter = 0
        for graph in nx.connected_component_subgraphs(self._graph):
            if all(element.id not in graph for element in self._fixElements):
                toRemove.append(graph)

        for graph in toRemove:
            for element in [self.getElement(i) for i in graph]:
                self.removeElement(element)
                removeCounter += 1

        return removeCounter

    #    def writeState(self):
    #            assert(self.checkSanity())
    #            nodes = []
    #            for i, node in self._idNodeMapping.items():
    #                if node is not self:
    #                    nodes.append((i,
    #                                  node.itemType,
    #                                  node.pos.x,
    #                                  node.pos.y,
    #                                  node.shape.offset[0],
    #                                  node.shape.offset[1]))
    #                else:
    #                    nodes.append((0,
    #                                  node.itemType,
    #                                  node.pos.x,
    #                                  node.pos.y,
    #                                  0,0))
    #
    #            edges= []
    #            for a,b in self._graph.edges_iter():
    #                if a == id(self): a=0
    #                if b == id(self): b=0
    #                edges.append((a,b))
    #
    #            with open("test", "w") as outFile:
    #                pickle.dump((nodes,edges), outFile)
    #            assert(self.checkSanity())
    #
    #    def loadState(self, filename):
    #        assert(self.checkSanity())
    #        nodes, edges = pickle.load(filename)
    #
    #        nodeMapping = dict()
    #
    #        for i, crystalType, posX, posY, offsetX, offsetY in nodes:
    #            if i == 0:
    #                nodeMapping[i] = self
    #                self._graph.add_node(id(self))
    #            else:
    #                nodeMapping[i]  = self.addElement(crystalType,
    #                                               (posX,posY),
    #                                               (offsetX, offsetY),
    #                                               False)
    #            self._idNodeMapping[id(nodeMapping[i])]= nodeMapping[i]
    #
    #        for a,b in edges:
    #            self._graph.add_edge(id(nodeMapping[a]), id(nodeMapping[b]) )
    #        assert(self.checkSanity())
    #
    def grow(self):
        if self._growLock:
            return

        if self._overdrive:
            pass
        elif self.framesToGrowth is None:
            return
        elif self.growTimer < self.framesToGrowth:
            self.growTimer += 1
            return
        else:
            self.growTimer = 0

        newNode = self.growOutwards()
        # self.growSimple()

    def growOutwards(self):
        newSpot, elementChain = self.getRandomDestinationPath()

        if newSpot is None:
            return

        color, crystalType = self.getElementTypeToGrow()

        newElement = self.addElement(crystalType, color, newSpot)

        for element in reversed(elementChain[1:]):
            self._swapShapes(element, newElement)

        for element in elementChain[1:]:
            self.updateNeigbors(element, True)
        self.updateNeigbors(newElement, True)

        newElement.node.pos = elementChain[0].node.pos
        newPositions = itertools.chain(
            [element.node.pos for element in elementChain[1:]], (self.getOffscreenPosForElement(newSpot),)
        )
        elementChain = itertools.chain((newElement,), elementChain[1:])

        self._growLock = True
        for element, newPosition in zip(elementChain, newPositions):
            avg.LinearAnim(element.node, "pos", 1000, element.node.pos, newPosition).start()

        avg.LinearAnim(newElement.node, "opacity", 1000, 0, 1, False, None, self._resetGrowLock).start()

        assert self.checkSanity()

    def _resetGrowLock(self):
        self._growLock = False

    def getRandomDestinationPath(self):
        newSpot, targetElement = self.searchSpot()
        if targetElement is None:
            return None, None
        assert isinstance(targetElement, StructureElement)
        paths = []
        for fixElement in self._fixElements:
            try:
                path = nx.shortest_path(self._graph, fixElement.id, targetElement.id)
                paths.append(path)
            except nx.exception.NetworkXNoPath:
                pass
        if not paths:
            return None, None
        shortestPath = min(paths, key=len)
        return newSpot, map(lambda x: self.getElement(x), shortestPath)

    def growSimple(self):
        newSpot, adjacentElement = self.searchSpot()
        if newSpot:
            color, crystalType = self.getElementTypeToGrow()
            element = self.addElement(crystalType, color, relPos=newSpot)
            return element

    def getElementTypeToGrow(self):
        return self.crystalManager.getNextStructureElement(self)

    def removeElementsInArea(self, pos, radius):
        deletedelements = 0
        for shape in self._getShapesInCircle(pos, radius):
            if shape in self._elementMapping:
                deletedelements += self.getElement(shape).delete()
        return deletedelements

    def _getShapesInCircle(self, pos, radius):
        testBody = pymunk.Body()
        testBody.position = pos
        shapeToTest = pymunk.Circle(body=testBody, radius=radius)
        intersections = self._space.shape_query(shapeToTest)
        return intersections

    def searchSpot(self):
        if self._graph.nodes():
            spots = []

            if not self._fixElements:
                return None, None

            fixelement = random.choice(list(self._fixElements))
            spots = self.checkForSpace(fixelement)
            spots = self._filterSpots(spots, fixelement)
            if spots:
                return random.choice(spots), fixelement

            node = random.choice(self._graph.nodes())
            element = self.getElement(node)
            spots = self.checkForSpace(element)
            spots = self._filterSpots(spots, element)
            if spots:
                return random.choice(spots), element

        return None, None

    def _filterSpots(self, spots, origin):
        return spots

    def checkForSpace(self, element):
        radius = element.shape.radius + util.CRYSTAL_RADIUS + 1
        availablePositions = []
        stepsize = 360 // 4
        maxNeigbors = 0
        for alpha in range(0, 360, stepsize):
            alpha += random.randint(0, stepsize - 1)
            # alpha+= random.randrange(stepsize)
            angle = (2 * math.pi) * (alpha / 360.0)
            vector = util.getVectotInDirection(angle, radius)
            posToCheck = util.vectorAdd(self.toAbsPos(element.position), vector)
            testBody = pymunk.Body()
            testBody.position = posToCheck
            shapeToTestInner = pymunk.Circle(body=testBody, radius=util.CRYSTAL_SIZE / 2)
            shapeToTestOuter = pymunk.Circle(
                body=testBody, radius=(util.CRYSTAL_SIZE / 2) * self.neighbourhoodThreshold
            )
            intersectionsInner = self._space.shape_query(shapeToTestInner)
            # dnode = avg.CircleNode(parent=self._root.getParent(), pos=posToCheck, r=util.CRYSTAL_SIZE/2,fillcolor="00FFFF", strokewidth=0)
            if len(intersectionsInner) == 0 or (len(intersectionsInner) == 1 and element.shape in intersectionsInner):
                intersectionsOuter = self._space.shape_query(shapeToTestOuter)
                neighborCount = len(intersectionsOuter)
                # dnode.fillopacity=0.5
                if neighborCount > maxNeigbors:
                    maxNeigbors = neighborCount
                    availablePositions = [self.toRelPos(posToCheck)]
                elif neighborCount == maxNeigbors:
                    availablePositions.append(self.toRelPos(posToCheck))

        return availablePositions
コード例 #17
0
class InventoryStorage:
    UI_SORT_TYPES = TunableList(
        description=
        "\n        A list of gameplay-based sort types used in the sim's inventory in the UI.\n        ",
        tunable=TunableTuple(
            description=
            '\n            Data that defines this sort for the inventory UI.\n            ',
            sort_name=TunableLocalizedString(
                description=
                '\n                The name displayed in the UI for this sort type.\n                '
            ),
            object_data=TunableVariant(
                description=
                '\n                The object data that determines the sort order of\n                this sort type.\n                ',
                states=TunableList(
                    description=
                    '\n                    States whose values are used to sort on for this sort type. \n                    ',
                    tunable=TunableReference(
                        description=
                        '\n                        A State to sort on.\n                        ',
                        manager=services.get_instance_manager(
                            sims4.resources.Types.OBJECT_STATE),
                        class_restrictions='ObjectState')),
                default='states'),
            is_ascending=Tunable(
                description=
                '\n                Whether a higher value from object_data will sort first.\n                If a high value means that the object should sort lower \n                (E.G. brokenness), this should be false.\n                ',
                tunable_type=bool,
                default=True),
            debug_name=Tunable(
                description=
                '\n                A unique name used to select this inventory sort type through \n                the console command ui.inventory.set_sort_filter when the inventory\n                ui is open.\n                ',
                tunable_type=str,
                default='NONE'),
            export_class_name='InventoryUISortTypeTuple',
            export_modes=ExportModes.ClientBinary))
    UI_FILTER_TYPES = TunableList(
        description=
        "\n        A list of filter categories containing filter types used to filter the sim's\n        inventory in the UI. The inventory can also be sorted by filter type; \n        filters lower on this list will sort lower when sorted by filter type.\n        ",
        tunable=TunableTuple(
            description=
            '\n            A category of filters in the UI. Contains a name and a list of filters.\n            ',
            filters=TunableList(
                description=
                '\n                The filters used in this category. \n                ',
                tunable=TunableTuple(
                    description=
                    '\n                    Data that defines a filter type in the inventory UI.\n                    ',
                    tags=TunableTags(
                        description=
                        '\n                        Tags that should be considered part of this filter.\n                        ',
                        binary_type=EnumBinaryExportType.EnumUint32),
                    filter_name=TunableLocalizedString(
                        description=
                        '\n                        The name displayed in the UI for this filter type.            \n                        '
                    ),
                    debug_name=Tunable(
                        description=
                        '\n                        A unique name used to select this inventory filter type through \n                        the console command ui.inventory.set_sort_filter when the inventory\n                        ui is open.\n                        ',
                        tunable_type=str,
                        default='NONE'),
                    export_class_name='InventoryUIFilterTypeTuple')),
            category_name=TunableLocalizedString(
                description=
                '\n                The name displayed in the UI for this filter category.\n                '
            ),
            export_class_name='InventoryUIFilterCategoryTuple',
            export_modes=ExportModes.ClientBinary))

    def __init__(self,
                 inventory_type,
                 item_location,
                 max_size=None,
                 allow_compaction=True,
                 allow_ui=True,
                 hidden_storage=False):
        self._objects = {}
        self._owners = WeakSet()
        self._inventory_type = inventory_type
        self._item_location = item_location
        self._max_size = max_size
        self._allow_compaction = allow_compaction
        self._allow_ui = allow_ui
        self._hidden_storage = hidden_storage
        self._stacks_with_options_counter = None

    def __len__(self):
        return len(self._objects)

    def __iter__(self):
        yield from iter(self._objects.values())

    def __contains__(self, obj_id):
        return obj_id in self._objects

    def __getitem__(self, obj_id):
        if obj_id in self._objects:
            return self._objects[obj_id]

    def __repr__(self):
        return 'InventoryStorage<{},{}>'.format(self._inventory_type,
                                                self._get_inventory_id())

    def register(self, owner):
        self._owners.add(owner)

    def unregister(self, owner):
        self._owners.discard(owner)

    def has_owners(self):
        if self._owners:
            return True
        return False

    def get_owners(self):
        return tuple(self._owners)

    @property
    def allow_ui(self):
        return self._allow_ui

    @allow_ui.setter
    def allow_ui(self, value):
        self._allow_ui = value

    def discard_object_id(self, obj_id):
        if obj_id in self._objects:
            del self._objects[obj_id]

    def discard_all_objects(self):
        for obj in self._objects.values():
            self._distribute_inventory_update_message(
                UI_pb2.InventoryItemUpdate.TYPE_REMOVE, obj)
            obj.inventoryitem_component.set_inventory_type(None, None)
        self._objects.clear()

    def can_insert(self, obj):
        if not obj.can_go_in_inventory_type(self._inventory_type):
            return False
        elif self._max_size is not None and sum(
                inventory_obj.stack_count()
                for inventory_obj in self) >= self._max_size:
            return False
        return True

    def insert(self, obj, inventory_object=None, compact=True):
        if not self.can_insert(obj):
            return False
        try:
            obj.on_before_added_to_inventory()
        except:
            logger.exception(
                'Exception invoking on_before_added_to_inventory. obj: {}',
                obj)
        self._insert(obj, inventory_object)
        try:
            obj.on_added_to_inventory()
        except:
            logger.exception(
                'Exception invoking on_added_to_inventory. obj: {}', obj)
        compacted_obj_id = None
        compacted_count = None
        if compact:
            (compacted_obj_id, compacted_count) = self._try_compact(obj)
        if compacted_obj_id is None:
            for owner in self._owners:
                try:
                    owner.on_object_inserted(obj)
                except:
                    logger.exception(
                        'Exception invoking on_object_inserted. obj: {}, owner: {}',
                        obj, owner)
            self._distribute_inventory_update_message(
                UI_pb2.InventoryItemUpdate.TYPE_ADD, obj)
            sent_stack_update = False
            if obj.inventoryitem_component.has_stack_option:
                if self._stacks_with_options_counter is None:
                    self._stacks_with_options_counter = defaultdict(int)
                stack_id = obj.inventoryitem_component.get_stack_id()
                stack_objects = self._stacks_with_options_counter[stack_id]
                if stack_objects == 0:
                    self._distribute_inventory_update_message(
                        UI_pb2.InventoryItemUpdate.TYPE_SET_STACK_OPTION, obj)
                    sent_stack_update = True
                self._stacks_with_options_counter[stack_id] += 1
            if not sent_stack_update:
                obj_owner = obj.inventoryitem_component.get_inventory().owner
                if obj_owner.is_sim and obj_owner.sim_info.favorites_tracker is not None and obj_owner.sim_info.favorites_tracker.is_favorite_stack(
                        obj):
                    self._distribute_inventory_update_message(
                        UI_pb2.InventoryItemUpdate.TYPE_SET_STACK_OPTION, obj)
        else:
            for owner in self._owners:
                try:
                    owner.on_object_id_changed(obj, compacted_obj_id,
                                               compacted_count)
                except:
                    logger.exception(
                        'Exception invoking on_object_id_changed. obj: {}, owner: {}',
                        obj, owner)
            self._distribute_inventory_update_message(
                UI_pb2.InventoryItemUpdate.TYPE_UPDATE,
                obj,
                obj_id=compacted_obj_id)
        return True

    def update_object_stack_by_id(self, obj_id, new_stack_id):
        if obj_id not in self._objects:
            return
        obj = self._objects[obj_id]
        self._distribute_inventory_update_message(
            UI_pb2.InventoryItemUpdate.TYPE_REMOVE, obj)
        obj.set_stack_id(new_stack_id)
        self._distribute_inventory_update_message(
            UI_pb2.InventoryItemUpdate.TYPE_ADD, obj)

    def remove(self, obj, count=1, move_to_object_manager=True):
        if obj.id not in self._objects:
            return False
        old_stack_count = obj.stack_count()
        split_obj = self._try_split(obj, count)
        try:
            obj.on_before_removed_from_inventory()
        except:
            logger.exception(
                'Exception invoking on_before_removed_from_inventory. obj: {}',
                obj)
        self._remove(obj, move_to_object_manager=move_to_object_manager)
        try:
            obj.on_removed_from_inventory()
        except:
            logger.exception(
                'Exception invoking on_removed_from_inventory. obj: {}', obj)
        if split_obj is None:
            for owner in self._owners:
                try:
                    owner.on_object_removed(obj)
                except:
                    logger.exception(
                        'Exception invoking on_object_removed. obj: {}, owner: {}',
                        obj, owner)
            self._distribute_inventory_update_message(
                UI_pb2.InventoryItemUpdate.TYPE_REMOVE, obj)
            if obj.inventoryitem_component.has_stack_option and self._stacks_with_options_counter is not None:
                stack_id = obj.inventoryitem_component.get_stack_id()
                self._stacks_with_options_counter[stack_id] -= 1
                if stack_id in self._stacks_with_options_counter <= 0:
                    if self._stacks_with_options_counter[stack_id] < 0:
                        logger.error(
                            'Counter went negative for stack_id {} with scheme {}',
                            stack_id,
                            obj.inventoryitem_component.stack_scheme,
                            owner='jdimailig')
                    del self._stacks_with_options_counter[stack_id]
        else:
            for owner in self._owners:
                try:
                    owner.on_object_id_changed(split_obj, obj.id,
                                               old_stack_count)
                except:
                    logger.exception(
                        'Exception invoking on_object_id_changed. obj: {}, owner: {}',
                        obj, owner)
            self._distribute_inventory_update_message(
                UI_pb2.InventoryItemUpdate.TYPE_UPDATE,
                split_obj,
                obj_id=obj.id)
        return True

    def _insert(self, obj, inventory_object):
        self._objects[obj.id] = obj
        obj.inventoryitem_component.set_inventory_type(self._inventory_type,
                                                       inventory_object)
        obj.item_location = self._item_location
        if self._inventory_type == InventoryType.SIM:
            obj.inventoryitem_component.is_hidden = self._hidden_storage
        object_manager = services.object_manager()
        if obj.id in object_manager:
            object_manager.move_to_inventory(
                obj,
                services.current_zone().inventory_manager)
            obj.set_parent(None)
            posture_graph_service = services.current_zone(
            ).posture_graph_service
            if posture_graph_service.is_object_pending_deletion(obj):
                posture_graph_service.finalize_object_deletion(obj)

    def _remove(self, obj, move_to_object_manager=False):
        if move_to_object_manager:
            services.current_zone().inventory_manager.move_to_world(
                obj, services.object_manager())
        obj.item_location = ItemLocation.ON_LOT
        obj.inventoryitem_component.set_inventory_type(
            None, None, from_removal=not move_to_object_manager)
        del self._objects[obj.id]

    def _get_compact_data(self, obj):
        try:
            obj.inventoryitem_component.save_for_stack_compaction = True
            return obj.get_attribute_save_data()
        finally:
            obj.inventoryitem_component.save_for_stack_compaction = False
            obj.post_tooltip_save_data_stored()

    def _try_compact(self, obj):
        if not self._allow_compaction:
            return (None, None)
        if len(self._objects) < 2:
            return (None, None)
        if obj.has_component(
                components.types.OBJECT_CLAIM_COMPONENT
        ) and obj.object_claim_component.requires_claiming:
            return (None, None)
        similar = None
        def_id = obj.definition.id
        data = self._get_compact_data(obj)
        stack_id = obj.inventoryitem_component.get_stack_id()
        for other in self._objects.values():
            if def_id != other.definition.id:
                continue
            if other is obj:
                continue
            if stack_id != other.inventoryitem_component.get_stack_id():
                continue
            if not any(interaction.should_reset_based_on_pipeline_progress
                       for interaction in other.interaction_refs):
                other_data = self._get_compact_data(other)
                if data == other_data:
                    similar = other
                    break
        if similar is None:
            return (None, None)
        similar_id = similar.id
        similar_count = similar.stack_count()
        self._remove(similar)
        similar.destroy(source=self, cause='InventoryStorage compaction')
        obj.update_stack_count(similar_count)
        return (similar_id, similar_count)

    def _try_split(self, obj, count):
        if count >= obj.stack_count():
            return
        clone = obj.inventoryitem_component.get_clone_for_stack_split()
        self._insert(clone, obj.inventoryitem_component.last_inventory_owner)
        clone.update_stack_count(-count)
        obj.set_stack_count(count)
        clone.on_added_to_inventory()
        return clone

    def _get_inventory_id(self):
        if InventoryTypeTuning.is_shared_between_objects(self._inventory_type):
            return int(self._inventory_type)
        if self._owners:
            return next(iter(self._owners)).owner.id
        logger.error(
            "Non-shared storage that's missing an owner: InventoryStorage<{},{}>",
            self._inventory_type, 0)
        return 0

    def _get_inventory_ui_type(self):
        if InventoryTypeTuning.is_shared_between_objects(self._inventory_type):
            return UI_pb2.InventoryItemUpdate.TYPE_SHARED
        return UI_pb2.InventoryItemUpdate.TYPE_OBJECT

    def _get_inventory_update_message(self,
                                      update_type,
                                      obj,
                                      obj_id=None,
                                      allow_while_zone_not_running=False):
        if not self._allow_ui:
            return
        if not services.current_zone(
        ).is_zone_running and not allow_while_zone_not_running:
            return
        if services.current_zone().is_zone_shutting_down:
            return
        msg = UI_pb2.InventoryItemUpdate()
        msg.type = update_type
        msg.inventory_id = self._get_inventory_id()
        msg.inventory_type = self._get_inventory_ui_type()
        msg.stack_id = obj.inventoryitem_component.get_stack_id()
        if obj_id is None:
            msg.object_id = obj.id
        else:
            msg.object_id = obj_id
        if update_type == UI_pb2.InventoryItemUpdate.TYPE_ADD:
            add_data = UI_pb2.InventoryItemData()
            add_data.definition_id = obj.definition.id
            msg.add_data = add_data
        if update_type == UI_pb2.InventoryItemUpdate.TYPE_ADD or update_type == UI_pb2.InventoryItemUpdate.TYPE_UPDATE:
            dynamic_data = UI_pb2.DynamicInventoryItemData()
            dynamic_data.value = obj.current_value
            dynamic_data.count = obj.stack_count()
            dynamic_data.new_object_id = obj.id
            dynamic_data.is_new = obj.new_in_inventory
            dynamic_data.sort_order = obj.get_stack_sort_order()
            icon_info = obj.get_icon_info_data()
            build_icon_info_msg(icon_info, None, dynamic_data.icon_info)
            recipe_name = obj.get_tooltip_field(
                TooltipFieldsComplete.recipe_name
            ) or obj.get_craftable_property(GameObjectProperty.RECIPE_NAME)
            if recipe_name is not None:
                dynamic_data.recipe_name = recipe_name
            if obj.custom_name is not None:
                dynamic_data.custom_name = obj.custom_name
            if InventoryStorage.UI_SORT_TYPES:
                sort_type = 0
                for sort_type_data in InventoryStorage.UI_SORT_TYPES:
                    value = None
                    try:
                        abs_value = None
                        state_component = obj.state_component
                        if state_component is None:
                            continue
                        for state in sort_type_data.object_data:
                            if state_component.has_state(state):
                                test_value = float(
                                    state_component.get_state(state).value)
                                abs_test_value = abs(test_value)
                                if value is None:
                                    value = test_value
                                elif abs_value < abs_test_value:
                                    value = test_value
                                    abs_value = abs_test_value
                    except TypeError:
                        pass
                    if value is not None:
                        sort_data_item = UI_pb2.InventoryItemSortData()
                        sort_data_item.type = sort_type
                        sort_data_item.value = value
                        dynamic_data.sort_data.append(sort_data_item)
                    sort_type += 1
            if update_type == UI_pb2.InventoryItemUpdate.TYPE_ADD:
                msg.add_data.dynamic_data = dynamic_data
            else:
                msg.update_data = dynamic_data
        if update_type == UI_pb2.InventoryItemUpdate.TYPE_SET_STACK_OPTION:
            dynamic_data = UI_pb2.DynamicInventoryItemData()
            if obj.inventoryitem_component.has_stack_option:
                obj.inventoryitem_component.populate_stack_icon_info_data(
                    dynamic_data.icon_info)
            obj_owner = obj.inventoryitem_component.get_inventory().owner
            if obj_owner.is_sim:
                favorites_tracker = obj_owner.sim_info.favorites_tracker
                if favorites_tracker is not None:
                    if favorites_tracker.is_favorite_stack(obj):
                        dynamic_data.is_favorite = True
            msg.update_data = dynamic_data
        return msg

    def _distribute_inventory_update_message(self,
                                             update_type,
                                             obj,
                                             obj_id=None):
        msg = self._get_inventory_update_message(update_type,
                                                 obj,
                                                 obj_id=obj_id)
        if msg is not None:
            op = GenericProtocolBufferOp(Operation.INVENTORY_ITEM_UPDATE, msg)
            Distributor.instance().add_op_with_no_owner(op)

    def distribute_inventory_update_message(self, obj):
        if obj.id not in self._objects:
            return False
        msg = self._get_inventory_update_message(
            UI_pb2.InventoryItemUpdate.TYPE_UPDATE, obj)
        if msg is not None:
            op = GenericProtocolBufferOp(Operation.INVENTORY_ITEM_UPDATE, msg)
            Distributor.instance().add_op_with_no_owner(op)

    def distribute_inventory_stack_update_message(self, obj):
        if obj.id not in self._objects:
            return
        msg = self._get_inventory_update_message(
            UI_pb2.InventoryItemUpdate.TYPE_SET_STACK_OPTION, obj)
        if msg is not None:
            op = GenericProtocolBufferOp(Operation.INVENTORY_ITEM_UPDATE, msg)
            Distributor.instance().add_op_with_no_owner(op)

    def distribute_owned_inventory_update_message(self, obj, owner):
        if obj.id not in self._objects:
            return False
        msg = self._get_inventory_update_message(
            UI_pb2.InventoryItemUpdate.TYPE_UPDATE, obj)
        if msg is not None:
            op = GenericProtocolBufferOp(Operation.INVENTORY_ITEM_UPDATE, msg)
            Distributor.instance().add_op(owner, op)

    def get_item_update_ops_gen(self):
        stack_options_set = set()
        for obj in self._objects.values():
            message = self._get_inventory_update_message(
                UI_pb2.InventoryItemUpdate.TYPE_ADD,
                obj,
                allow_while_zone_not_running=True)
            if message is None:
                continue
            yield (obj,
                   GenericProtocolBufferOp(Operation.INVENTORY_ITEM_UPDATE,
                                           message))
            if not obj.inventoryitem_component.has_stack_option:
                obj_owner = obj.inventoryitem_component.get_inventory().owner
                if obj_owner.is_sim:
                    if obj_owner.sim_info.favorites_tracker is None:
                        continue
                    stack_id = obj.inventoryitem_component.get_stack_id()
                    if stack_id in stack_options_set:
                        continue
                    option_msg = self._get_inventory_update_message(
                        UI_pb2.InventoryItemUpdate.TYPE_SET_STACK_OPTION,
                        obj,
                        allow_while_zone_not_running=True)
                    if option_msg is not None:
                        stack_options_set.add(stack_id)
                        yield (obj,
                               GenericProtocolBufferOp(
                                   Operation.INVENTORY_ITEM_UPDATE,
                                   option_msg))
            else:
                stack_id = obj.inventoryitem_component.get_stack_id()
                if stack_id in stack_options_set:
                    continue
                option_msg = self._get_inventory_update_message(
                    UI_pb2.InventoryItemUpdate.TYPE_SET_STACK_OPTION,
                    obj,
                    allow_while_zone_not_running=True)
                if option_msg is not None:
                    stack_options_set.add(stack_id)
                    yield (obj,
                           GenericProtocolBufferOp(
                               Operation.INVENTORY_ITEM_UPDATE, option_msg))

    def open_ui_panel(self, obj):
        if not self._allow_ui:
            return False
        msg = UI_pb2.OpenInventory()
        msg.object_id = obj.id
        msg.inventory_id = self._get_inventory_id()
        msg.inventory_type = self._get_inventory_ui_type()
        op = GenericProtocolBufferOp(Operation.OPEN_INVENTORY, msg)
        Distributor.instance().add_op_with_no_owner(op)
        return True
コード例 #18
0
class StoryProgressionActionCareer(_StoryProgressionFilterAction):
    FACTORY_TUNABLES = {
        'employment_rate':
        TunableInterval(
            description=
            '\n            The ideal employment rates. If the rate of employed Sims fall\n            outside this interval, Sims will be hired/fired as necessary.\n            ',
            tunable_type=float,
            default_lower=0.6,
            default_upper=0.9,
            minimum=0,
            maximum=1)
    }

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._employed = WeakSet()
        self._unemployed = WeakSet()
        self._workforce_count = 0

    def _allow_instanced_sims(self):
        return True

    def _is_valid_candidate(self, sim_info):
        if not sim_info.is_npc:
            return False
        if sim_info.lod == SimInfoLODLevel.MINIMUM:
            return False
        if sim_info.is_instanced(allow_hidden_flags=ALL_HIDDEN_REASONS):
            return False
        elif sim_info.career_tracker.currently_during_work_hours:
            return False
        return True

    def _apply_action(self, sim_info):
        if sim_info.career_tracker.has_quittable_career():
            self._employed.add(sim_info)
        elif not sim_info.career_tracker.has_work_career():
            self._unemployed.add(sim_info)
        self._workforce_count += 1

    def _post_apply_action(self):
        lower_bound = math.floor(self._workforce_count *
                                 self.employment_rate.lower_bound)
        upper_bound = math.ceil(self._workforce_count *
                                self.employment_rate.upper_bound)
        num_employed = len(self._employed)
        if num_employed < lower_bound:
            self._try_employ_sim()
        elif num_employed > upper_bound:
            self._try_unemploy_sim()
        self._employed.clear()
        self._unemployed.clear()
        self._workforce_count = 0

    def _get_ideal_candidate_for_employment(self):
        def _get_weight(candidate, career):
            if not career.is_valid_career(sim_info=candidate):
                return 0
            if candidate.career_tracker.has_career_by_uid(career.guid64):
                return 0
            return career.career_story_progression.joining.get_multiplier(
                SingleSimResolver(candidate))

        career_service = services.get_career_service()
        weights = [
            (_get_weight(candidate, career), candidate, career)
            for (candidate, career) in itertools.product((
                candidate for candidate in self._unemployed
                if self._is_valid_candidate(candidate)), (
                    career for career in career_service.get_career_list()
                    if career.career_story_progression.joining is not None))
        ]
        if not weights:
            return
        selected_candidate_index = weighted_random_index(weights)
        if selected_candidate_index is None:
            return
        selected_candidate = weights[selected_candidate_index]
        return (selected_candidate[1], selected_candidate[2])

    def _get_ideal_candidate_for_unemployment(self,
                                              get_unemployment_multiplier):
        def _get_weight(candidate, career):
            subaction_multiplier = get_unemployment_multiplier(career)
            return subaction_multiplier.get_multiplier(
                SingleSimResolver(candidate))

        weights = list(
            itertools.chain.from_iterable(
                ((_get_weight(candidate, career), candidate, career)
                 for career in candidate.career_tracker if career.can_quit
                 if get_unemployment_multiplier(career) is not None)
                for candidate in self._employed
                if self._is_valid_candidate(candidate)))
        if not weights:
            return
        selected_candidate_index = weighted_random_index(weights)
        if selected_candidate_index is None:
            return
        selected_candidate = weights[selected_candidate_index]
        return (selected_candidate[1], selected_candidate[2])

    def _try_employ_sim(self):
        selected_candidate = self._get_ideal_candidate_for_employment()
        if selected_candidate is None:
            return False
        (sim_info, career_type) = selected_candidate
        max_user_level = career_type.get_max_user_level()
        user_level = random.randint(1, max_user_level)
        if gsi_handlers.story_progression_handlers.story_progression_archiver.enabled:
            gsi_handlers.story_progression_handlers.archive_story_progression(
                self, 'Add Career to {}: {} ({}/{})', sim_info, career_type,
                user_level, max_user_level)
        sim_info.career_tracker.add_career(career_type(sim_info),
                                           user_level_override=user_level,
                                           give_skipped_rewards=False)
        return True

    def _try_retire_sim(self):
        selected_candidate = self._get_ideal_candidate_for_unemployment(
            lambda career: career.career_story_progression.retiring)
        if selected_candidate is None:
            return False
        (sim_info, career_type) = selected_candidate
        if gsi_handlers.story_progression_handlers.story_progression_archiver.enabled:
            gsi_handlers.story_progression_handlers.archive_story_progression(
                self, 'Retiring {} from {}', sim_info, career_type)
        sim_info.career_tracker.retire_career(career_type.guid64)
        return True

    def _try_quit_sim(self):
        selected_candidate = self._get_ideal_candidate_for_unemployment(
            lambda career: career.career_story_progression.quitting)
        if selected_candidate is None:
            return False
        (sim_info, career_type) = selected_candidate
        if gsi_handlers.story_progression_handlers.story_progression_archiver.enabled:
            gsi_handlers.story_progression_handlers.archive_story_progression(
                self, 'Having {} quit from {}', sim_info, career_type)
        sim_info.career_tracker.quit_quittable_careers()
        return True

    def _try_unemploy_sim(self):
        if self._try_retire_sim():
            return True
        elif self._try_quit_sim():
            return True
        return False
コード例 #19
0
class WorkerPool(object):
    def __init__(self, name, poolSize=4, loggingLevel=logging.INFO):
        super(WorkerPool, self).__init__()

        self._logger = logging.getLogger("[WORKER POOL - {}]".format(
            name.upper()))
        self._logger.setLevel(loggingLevel)

        self._name = name
        self._isRunning = False

        self._tasksQueue = FIFOPriorityQueue()
        self._canceledTasks = WeakSet()
        self._workers = [PoolWorker(self) for _ in range(poolSize)]

    def name(self):
        return self._name

    def logger(self):
        return self._logger

    def tasks(self):
        return self._tasksQueue

    def canceledTasks(self):
        return self._canceledTasks

    def start(self):
        self._logger.info("Starting...")

        for worker in self._workers:
            worker.start()

        self._isRunning = True

        self._logger.info("Started.")

    def stop(self):
        self._logger.info("Stopping WorkerPool ...")

        if not self._isRunning:
            self._logger.warning("Attempted to stop a WorkerPool twice.")
            return

        for _ in range(len(self._workers)):
            self._tasksQueue.put((5, PoolWorker.STOP_TASK))

        self._isRunning = False

        self._tasksQueue.join()

        self._logger.info("WorkerPool stopped.")

    def join(self):
        self._logger.info("Waiting for tasks to be done...")
        self._tasksQueue.join()
        self._logger.info("Tasks done!")

    def addTask(self, function, *args):
        if not self._isRunning:
            self._logger.debug("Task not added to (stopped) queue: %s%s.",
                               function.__name__, str(args))
            return

        self._logger.debug("Adding task to queue: %s%s.", function.__name__,
                           str(args))
        task = Task(function, args)
        self._tasksQueue.put((3, task))

        return task

    def addHighPriorityTask(self, function, *args):
        if not self._isRunning:
            self._logger.debug(
                "HIGH PRIORITY task not added to (stopped) queue: %s%s.",
                function.__name__, str(args))
            return

        self._logger.debug("Adding HIGH PRIORITY task to queue: %s%s.",
                           function.__name__, str(args))
        task = Task(function, args)
        self._tasksQueue.put((0, task))

        return task

    def addLowPriorityTask(self, function, *args):
        if not self._isRunning:
            self._logger.debug(
                "LOW PRIRITY task not added to (stopped) queue: %s%s.",
                function.__name__, str(args))
            return

        self._logger.debug("Adding LOW PRIORITY task to queue: %s%s.",
                           function.__name__, str(args))
        task = Task(function, args)

        self._tasksQueue.put((4, task))

        return task

    def cancelTask(self, task):
        self._logger.debug("Adding CANCELLATION for task: %s%s.",
                           task._function.__name__, str(task._argTuple))
        self._canceledTasks.add(task)

    def isRunning(self):
        return self._isRunning
コード例 #20
0
class ReservationMixin:
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._reservation_handlers = ()
        self._on_reservation_handlers_changed = None
        self._reservation_clobberers = None

    @property
    def in_use(self):
        if self._reservation_handlers:
            return True
        return False

    @property
    def self_or_part_in_use(self):
        if self._reservation_handlers:
            return True
        elif self.parts:
            return any(part.in_use for part in self.parts)
        return False

    def in_use_by(self, sim, owner=None):
        for handler in self._reservation_handlers:
            if handler.sim is not sim:
                continue
            if owner is not None and handler.reservation_interaction is not owner:
                continue
            return True
        return False

    def get_users(self, sims_only=False):
        users = set(handler.sim for handler in self._reservation_handlers
                    if not sims_only or handler.sim.is_sim)
        if self.parts:
            for part in self.parts:
                users |= part.get_users(sims_only=sims_only)
        return frozenset(users)

    def get_reservation_handler(self, sim, **kwargs):
        reservation_type = ReservationHandlerBasic if not self.parts else ReservationHandlerAllParts
        return reservation_type(sim, self, **kwargs)

    def get_use_list_handler(self, sim, **kwargs):
        return ReservationHandlerUseList(sim, self, **kwargs)

    def may_reserve(self,
                    reserver,
                    *_,
                    reservation_handler=None,
                    _from_reservation_call=False,
                    **kwargs):
        if reservation_handler is None:
            reservation_handler = self.get_reservation_handler(reserver)
        reserve_result = reservation_handler.may_reserve_internal(**kwargs)
        if gsi_handlers.sim_handlers_log.sim_reservation_archiver.enabled and reserver.is_sim:
            reserve_result_str = '{}: {}'.format(
                'reserve' if not _from_reservation_call else 'may_reserve',
                reserve_result)
            gsi_handlers.sim_handlers_log.archive_sim_reservation(
                reservation_handler, reserve_result_str)
        return reserve_result

    def add_reservation_handler(self, reservation_handler):
        if isinstance(self._reservation_handlers, tuple):
            self._reservation_handlers = WeakSet()
        self._reservation_handlers.add(reservation_handler)
        if self._on_reservation_handlers_changed:
            self._on_reservation_handlers_changed(user=reservation_handler.sim,
                                                  added=True)

    def get_reservation_handlers(self):
        return tuple(self._reservation_handlers)

    def remove_reservation_handler(self, reservation_handler):
        if not self._reservation_handlers:
            return
        self._reservation_handlers.discard(reservation_handler)
        if self._on_reservation_handlers_changed:
            self._on_reservation_handlers_changed(user=reservation_handler.sim,
                                                  added=False)

    def add_reservation_clobberer(self, reservation_holder,
                                  reservation_clobberer):
        if self._reservation_clobberers is None:
            self._reservation_clobberers = defaultdict(WeakSet)
        self._reservation_clobberers[reservation_holder].add(
            reservation_clobberer)

    def is_reservation_clobberer(self, reservation_holder,
                                 reservation_clobberer):
        if self._reservation_clobberers is None:
            return False
        if reservation_holder not in self._reservation_clobberers:
            return False
        return reservation_clobberer in self._reservation_clobberers[
            reservation_holder]

    def remove_reservation_clobberer(self, reservation_holder,
                                     reservation_clobberer):
        if self._reservation_clobberers is None:
            return
        if reservation_holder not in self._reservation_clobberers:
            return
        self._reservation_clobberers[reservation_holder].discard(
            reservation_clobberer)
        if not self._reservation_clobberers[reservation_holder]:
            del self._reservation_clobberers[reservation_holder]
        if not self._reservation_clobberers:
            self._reservation_clobberers = None

    def on_reset_get_interdependent_reset_records(self, reset_reason,
                                                  reset_records):
        super().on_reset_get_interdependent_reset_records(
            reset_reason, reset_records)
        relevant_sims = self.get_users(sims_only=True)
        for sim in relevant_sims:
            if self.reset_reason() == ResetReason.BEING_DESTROYED:
                reset_records.append(
                    ResetRecord(sim, ResetReason.RESET_EXPECTED, self,
                                'In use list of object being destroyed.'))
            else:
                body_target_part_owner = sim.posture_state.body.target
                if body_target_part_owner is not None:
                    if body_target_part_owner.is_part:
                        body_target_part_owner = body_target_part_owner.part_owner
                transition_controller = sim.queue.transition_controller
                if not body_target_part_owner is self:
                    if not transition_controller is None:
                        if not transition_controller.will_derail_if_given_object_is_reset(
                                self):
                            reset_records.append(
                                ResetRecord(sim, ResetReason.RESET_EXPECTED,
                                            self, 'Transitioning To or In.'))
                reset_records.append(
                    ResetRecord(sim, ResetReason.RESET_EXPECTED, self,
                                'Transitioning To or In.'))

    def usable_by_transition_controller(self, transition_controller):
        if transition_controller is None:
            return False
        required_sims = transition_controller.interaction.required_sims()
        targets = (self, ) + tuple(
            self.get_overlapping_parts()) if self.is_part else (self, )
        for reservation_handler in itertools.chain.from_iterable(
                target.get_reservation_handlers() for target in targets):
            if reservation_handler.sim in required_sims:
                continue
            reservation_interaction = reservation_handler.reservation_interaction
            if reservation_interaction is None:
                continue
            if reservation_interaction.priority >= transition_controller.interaction.priority:
                return False
            if transition_controller.interaction.priority <= Priority.Low:
                return False
        return True

    def register_on_use_list_changed(self, callback):
        if self._on_reservation_handlers_changed is None:
            self._on_reservation_handlers_changed = CallableList()
        self._on_reservation_handlers_changed.append(callback)

    def unregister_on_use_list_changed(self, callback):
        if callback in self._on_reservation_handlers_changed:
            self._on_reservation_handlers_changed.remove(callback)
            if not self._on_reservation_handlers_changed:
                self._on_reservation_handlers_changed = None
コード例 #21
0
class ConnectionObject:
    def __init__(self, type, source, destination, save_id=None):
        self.__type = type
        self.__data = type.ufl_type.build_default(None)
        self.__source = ref(source)
        self.__destination = ref(destination)
        self.__visuals = WeakSet()
        self.__cache = ModelTemporaryDataCache(None)
        if save_id is None:
            self.__save_id = uuid4()
        else:
            self.__save_id = save_id

    def add_visual(self, visual):
        if visual.object is not self:
            raise Exception
        self.__visuals.add(visual)

    def remove_visual(self, visual):
        self.__visuals.remove(visual)

    @property
    def visuals(self):
        yield from self.__visuals

    @property
    def cache(self):
        return self.__cache

    @property
    def type(self):
        return self.__type

    @property
    def data(self):
        return self.__data

    @property
    def source(self):
        return self.__source()

    @property
    def destination(self):
        return self.__destination()

    @property
    def project(self):
        return self.__source().project

    def reverse(self):
        self.__source, self.__destination = self.__destination, self.__source
        for visual in self.__visuals:
            visual._reverse()
        self.__cache.invalidate()

    def get_other_end(self, element):
        if self.__source() is element:
            return self.__destination()
        elif self.__destination() is element:
            return self.__source()
        else:
            return None

    def is_connected_with(self, element):
        return self.__source() is element or self.__destination() is element

    @property
    def save_id(self):
        return self.__save_id

    def create_appearance_object(self, ruler):
        return self.__type.create_appearance_object(self, ruler)

    def create_label_object(self, id, ruler):
        return self.__type.get_label(id).create_appearance_object(self, ruler)

    def apply_ufl_patch(self, patch):
        self.__data.apply_patch(patch)
        self.__cache.refresh()

    @property
    def has_ufl_dialog(self):
        return self.__type.ufl_type.has_attributes

    def create_ufl_dialog(self, options=UflDialogOptions.standard):
        if not self.__type.ufl_type.has_attributes:
            raise Exception
        dialog = UflDialog(self.__type.ufl_type, options)
        dialog.associate(self.__data)
        return dialog
コード例 #22
0
class Part(ProxyObject, ReservationMixin):
    _unproxied_attributes = ProxyObject._unproxied_attributes | {
        '_data', '_reservation_handlers', '_joint_transform',
        '_routing_context', '_children_cache', '_is_surface', '_parts',
        '_part_location', '_containment_slot_info_cache', '_disabling_states'
    }

    def __init__(self, owner, data):
        super().__init__(owner)
        self._data = data
        self._reservation_handlers = ()
        self._joint_transform = None
        self._routing_context = None
        self._children_cache = None
        self._containment_slot_info_cache = None
        self._part_location = None
        self._is_surface = {}
        self._disabling_states = None

    def __repr__(self):
        return '<part {0} on {1}>'.format(self.part_group_index,
                                          self.part_owner)

    def __str__(self):
        return '{}[{}]'.format(self.part_owner, self.part_group_index)

    @constproperty
    def is_part():
        return True

    @property
    def parts(self):
        pass

    @property
    def _parts(self):
        raise AttributeError()

    @property
    def part_owner(self):
        return self._proxied_obj

    @property
    def part_group_index(self):
        return self.part_owner.parts.index(self)

    @property
    def part_definition(self):
        return self._data.part_definition

    @property
    def disable_sim_aop_forwarding(self):
        return self._data.disable_sim_aop_forwarding

    @property
    def disable_child_aop_forwarding(self):
        return self._data.disable_child_aop_forwarding

    @property
    def restrict_autonomy_preference(self):
        return self._data.restrict_autonomy_preference

    @property
    def disabling_states(self):
        return self._data.disabling_states

    @property
    def part_name(self):
        return self._data.name

    @property
    def forward_direction_for_picking(self):
        offset = self._data.forward_direction_for_picking
        return sims4.math.Vector3(offset.x, 0, offset.y)

    @property
    def transform(self):
        return self._part_location.world_transform

    @transform.setter
    def transform(self):
        raise AttributeError(
            "A part's Transform should never be set by hand. Only the part owner's transform should be set."
        )

    def add_disabling_state(self, state):
        if not self._disabling_states:
            self._disabling_states = set()
        self._disabling_states.add(state)

    def remove_disabling_state(self, state):
        self._disabling_states.remove(state)

    def get_joint_transform(self):
        if self._joint_transform is None:
            if not self.is_base_part:
                target_root_joint = ArbElement._BASE_SUBROOT_STRING + str(
                    self.subroot_index)
                try:
                    self._joint_transform = get_joint_transform_from_rig(
                        self.rig, target_root_joint)
                except KeyError:
                    raise KeyError('Unable to find joint {} on {}'.format(
                        target_root_joint, self))
                except ValueError:
                    raise ValueError(
                        'Unable to find rig for joint {} on {}'.format(
                            self.rig, self))
            else:
                self._joint_transform = Transform.IDENTITY()
        return self._joint_transform

    def get_joint_transform_for_joint(self, joint_name):
        if isinstance(joint_name, str):
            joint_name = joint_name + str(self.subroot_index)
        else:
            joint_name = hash32(str(self.subroot_index),
                                initial_hash=joint_name)
        transform = get_joint_transform_from_rig(self.rig, joint_name)
        transform = Transform.concatenate(transform, self.part_owner.transform)
        return transform

    @property
    def location(self):
        return self._part_location

    @property
    def routing_surface(self):
        return self._part_location.world_routing_surface

    def is_routing_surface_overlapped_at_position(self, position):
        return self.part_owner.is_routing_surface_overlapped_at_position(
            position)

    @property
    def provided_routing_surface(self):
        return self.part_owner.provided_routing_surface

    def on_children_changed(self):
        self._children_cache = None

    def _add_child(self, child, location):
        self.part_owner._add_child(child, location)
        self.on_children_changed()

    def _remove_child(self, child, new_parent=None):
        self.part_owner._remove_child(child, new_parent=new_parent)
        self.on_children_changed()

    @property
    def children(self):
        if self._children_cache is None:
            self._children_cache = WeakSet()
            for child in self.part_owner.children:
                if self.has_slot(child.location.slot_hash
                                 or child.location.joint_name_hash):
                    self._children_cache.add(child)
                elif self.part_owner.has_slot(
                        child.location.slot_hash
                        or child.location.joint_name_hash):
                    if child.parts is not None:
                        for part in child.parts:
                            if part.attempt_to_remap_parent(
                                    part.location.parent) == self:
                                self._children_cache.add(part)
        return self._children_cache

    @property
    def routing_context(self):
        return self.part_owner.routing_context

    @property
    def supported_posture_types(self):
        return self.part_definition.supported_posture_types

    @property
    def _anim_overrides_internal(self):
        params = {}
        if any(p.in_use for p in self.part_owner.parts if p is not self
               if p.part_definition is self.part_definition):
            params['otherSimPresent'] = True
        overrides = super()._anim_overrides_internal
        if self._data.anim_overrides:
            overrides = overrides(self._data.anim_overrides())
        return AnimationOverrides(overrides=overrides, params=params)

    @property
    def can_reset(self):
        return False

    def reset(self, reset_reason):
        super().reset(reset_reason)
        self.part_owner.reset(reset_reason)

    def adjacent_parts_gen(self):
        if self._data.adjacent_parts is not None:
            parts = self.part_owner.parts
            for adjacent_part_index in self._data.adjacent_parts:
                yield parts[adjacent_part_index]
        else:
            index = self.part_group_index
            parts = self.part_owner.parts
            if index > 0:
                yield parts[index - 1]
            if index + 1 < len(parts):
                yield parts[index + 1]

    def has_adjacent_part(self, sim):
        for part in self.adjacent_parts_gen():
            if part.may_reserve(sim):
                return True
        return False

    def may_reserve(self, sim, *args, check_overlapping_parts=True, **kwargs):
        if check_overlapping_parts:
            for overlapping_part in self.get_overlapping_parts():
                if overlapping_part is self:
                    continue
                reserve_result = overlapping_part.may_reserve(
                    sim, check_overlapping_parts=False)
                if not reserve_result:
                    return reserve_result
        return super().may_reserve(sim, *args, **kwargs)

    def is_mirrored(self, part=None):
        if part is None:
            return self._data.is_mirrored
        offset = part.position - self.position
        return sims4.math.vector_cross_2d(self.forward, offset) < 0

    @property
    def route_target(self):
        return (RouteTargetType.PARTS, (self, ))

    @property
    def is_base_part(self):
        return self.subroot_index is None

    @property
    def subroot_index(self):
        if self._data is None:
            return
        return self._data.subroot_index

    @property
    def part_suffix(self) -> str:
        subroot_index = self.subroot_index
        if subroot_index is not None:
            return str(subroot_index)

    @cached(key=lambda p, a: (p.part_definition, a.affordance))
    def supports_affordance(self, affordance_or_aop):
        affordance = affordance_or_aop.affordance
        supported_affordance_data = self.part_definition.supported_affordance_data
        if not affordance.is_super and not supported_affordance_data.consider_mixers:
            return True
        return supported_affordance_data.compatibility(
            affordance, allow_ignore_exclude_all=True)

    @cached(maxsize=512,
            key=lambda p, posture_type, *_, is_specific=True, **__:
            (p.part_definition, posture_type, is_specific))
    def supports_posture_type(self, posture_type, *_, is_specific=True, **__):
        if posture_type is None:
            return True
        part_supported_posture_types = {
            posture.posture_type
            for posture in self.part_definition.supported_posture_types
        }
        if not part_supported_posture_types:
            return True
        if is_specific:
            return posture_type in part_supported_posture_types
        return any(posture_type.family_name == supported_posture.family_name
                   for supported_posture in part_supported_posture_types)

    def _supports_sim_buffs(self, sim):
        return not any(
            sim.has_buff(blacklisted_buff)
            for blacklisted_buff in self.part_definition.blacklisted_buffs)

    def _meets_trait_requirements(self, sim):
        if self.part_definition.trait_requirements is None:
            return True
        else:
            traits = sim.sim_info.get_traits()
            return self.part_definition.trait_requirements.test_collection(
                traits)

    def supports_posture_spec(self, posture_spec, interaction=None, sim=None):
        if self._disabling_states:
            return False
        if interaction is not None and interaction.is_super:
            affordance = interaction.affordance
            if affordance.requires_target_support and not self.supports_affordance(
                    affordance):
                return False
            is_sim_putdown = interaction.is_putdown and (
                interaction.carry_target is not None
                and interaction.carry_target.is_sim)
            test_sim = sim or interaction.sim
            if (not is_sim_putdown or interaction.carry_target is test_sim
                ) and not self._supports_sim_buffs(test_sim):
                return False
            if not self._meets_trait_requirements(test_sim):
                return False
        part_supported_posture_types = None
        if self.part_definition:
            part_supported_posture_types = self.part_definition.supported_posture_types
        if part_supported_posture_types:
            if self.part_owner.affordancetuning_component is not None:
                if sim is not None:
                    if posture_spec[BODY_INDEX] is not None:
                        for supported_posture_info in part_supported_posture_types:
                            if posture_spec[BODY_INDEX][
                                    BODY_POSTURE_TYPE_INDEX] is supported_posture_info.posture_type:
                                posture_providing_interactions = [
                                    affordance
                                    for affordance in self.super_affordances()
                                    if affordance.provided_posture_type is
                                    posture_spec[BODY_INDEX]
                                    [BODY_POSTURE_TYPE_INDEX]
                                ]
                                for interaction in posture_providing_interactions:
                                    tests = self.affordancetuning_component.get_affordance_tests(
                                        interaction)
                                    if tests is not None:
                                        if not tests.run_tests(
                                                DoubleObjectResolver(
                                                    sim, self.part_owner)):
                                            return False
                                break
        return True

    @property
    def _bone_name_hashes(self):
        result = self.part_definition.get_bone_name_hashes_for_part_suffix(
            self.part_suffix)
        if self.part_owner.slot_component is not None:
            result |= self.get_deco_slot_hashes(
                (self.part_owner.rig, (self.subroot_index,
                                       self.part_definition)))
        return result

    def get_provided_slot_types(self):
        return self.part_owner.get_provided_slot_types(part=self)

    def get_runtime_slots_gen(self,
                              slot_types=None,
                              bone_name_hash=None,
                              owner_only=False):
        for (slot_hash, slot_slot_types) in self.get_containment_slot_infos():
            if not not slot_types is not None and not not slot_types.intersection(
                    slot_slot_types):
                continue
            if not not bone_name_hash is not None and slot_hash != bone_name_hash:
                continue
            if self.has_slot(slot_hash):
                yield RuntimeSlot(self, slot_hash, slot_slot_types)

    def slot_object(self,
                    parent_slot=None,
                    slotting_object=None,
                    objects_to_ignore=None):
        return self.part_owner.slot_object(parent_slot=parent_slot,
                                           slotting_object=slotting_object,
                                           target=self,
                                           objects_to_ignore=objects_to_ignore)

    def get_containment_slot_infos(self):
        if self._containment_slot_info_cache is None:
            owner = self.part_owner
            object_slots = owner.slots_resource
            if object_slots is None:
                self._containment_slot_info_cache = ()
            else:
                result = SlotComponent.get_containment_slot_infos_static(
                    object_slots, owner.rig, owner)
                bone_name_hashes = self._bone_name_hashes
                self._containment_slot_info_cache = tuple(
                    (slot_hash, slot_types)
                    for (slot_hash, slot_types) in result
                    if slot_hash in bone_name_hashes)
        return self._containment_slot_info_cache

    def is_valid_for_placement(self,
                               *,
                               obj=DEFAULT,
                               definition=DEFAULT,
                               objects_to_ignore=DEFAULT):
        result = Result.NO_RUNTIME_SLOTS
        for runtime_slot in self.get_runtime_slots_gen():
            result = runtime_slot.is_valid_for_placement(
                obj=obj,
                definition=definition,
                objects_to_ignore=objects_to_ignore)
            if result:
                break
        return result

    def has_slot(self, slot_hash):
        if slot_hash in self.part_definition.get_bone_name_hashes_for_part_suffix(
                self.part_suffix):
            return True
        elif slot_hash in self.get_deco_slot_hashes(
            (self.part_owner.rig, (self.subroot_index, self.part_definition))):
            return True
        return False

    def get_overlapping_parts(self):
        if self._data.overlapping_parts is None:
            return []
        parts = self.part_owner.parts
        return [
            parts[overlapping_part_index]
            for overlapping_part_index in self._data.overlapping_parts
        ]

    @property
    def footprint(self):
        return self.part_owner.footprint

    @property
    def footprint_polygon(self):
        return self.part_owner.footprint_polygon

    def on_leaf_child_changed(self):
        self.part_owner.on_leaf_child_changed()

    def on_owner_location_changed(self):
        owner = self.part_owner
        if owner.bb_parent is None:
            owner_transform = owner.transform
        else:
            owner_transform = owner.location.transform
        if self.subroot_index is None:
            transform = owner_transform
        else:
            transform = Transform.concatenate(self.get_joint_transform(),
                                              owner_transform)
        routing_surface = None
        surface_type = self.part_definition.part_surface.get_surface_type(
            self, transform=transform)
        if surface_type is not None:
            routing_surface = routing.SurfaceIdentifier(
                owner.zone_id, owner.level, surface_type)
        self._part_location = owner.location.clone(
            transform=transform, routing_surface=routing_surface)
        for child in self.children:
            if child.parts:
                for part in child.parts:
                    part.on_owner_location_changed()
コード例 #23
0
class TravelInteraction(SuperInteraction):
    INSTANCE_TUNABLES = {
        'travel_xevt':
        OptionalTunable(
            description=
            '\n            If enabled, specify an xevent at which the Sim will disappear from\n            the world.\n            ',
            tunable=Tunable(
                description=
                '\n                The xevent at which the Sim will disappear from the world.\n                ',
                tunable_type=int,
                needs_tuning=False,
                default=100)),
        'travel_care_dependents':
        Tunable(
            description=
            "\n            If checked, this interaction detects whether or not the traveling\n            Sim is any other Sim's (e.g. toddler) caregiver. If so, it does two\n            things:\n             * If the caregiver situation specifies it, it creates a constraint\n             for this SI (e.g. carry a toddler).\n             * It automatically despawns care dependents.\n            ",
            tunable_type=bool,
            default=True)
    }

    @classmethod
    def _define_supported_postures(cls):
        return frozendict({
            ParticipantType.Actor:
            STAND_NO_CARRY_NO_SURFACE_POSTURE_MANIFEST
        })

    def __init__(self, aop, context, **kwargs):
        super().__init__(aop, context, **kwargs)
        self.from_zone_id = kwargs['from_zone_id']
        self.to_zone_id = kwargs['to_zone_id']
        self.on_complete_callback = kwargs['on_complete_callback']
        self.on_complete_context = kwargs['on_complete_context']
        self._care_dependents = WeakSet()
        self._care_dependent_required = None
        if self.travel_care_dependents:
            self._find_care_dependents(context)

    def _find_care_dependents(self, context):
        situation_manager = services.get_zone_situation_manager()
        if situation_manager is not None:
            for situation in situation_manager.get_situations_by_type(
                    CaregiverSituation):
                excluding_interaction_types = (
                    TravelInteraction.get_interaction_type(), )
                care_dependent = situation.get_care_dependent_if_last_caregiver(
                    context.sim.sim_info, excluding_interaction_types)
                if care_dependent is not None:
                    self._care_dependents.add(care_dependent)

    def _get_primary_care_dependent(self):
        if self._care_dependent_required is None:
            self._care_dependent_required = next(iter(self._care_dependents),
                                                 None)
        return self._care_dependent_required

    def _get_required_sims(self, *args, **kwargs):
        required_sims = super()._get_required_sims(*args, **kwargs)
        care_dependent = self._get_primary_care_dependent()
        if care_dependent is not None:
            required_sims.add(care_dependent)
        return required_sims

    @flexmethod
    def _constraint_gen(cls,
                        inst,
                        sim,
                        target,
                        *args,
                        to_zone_id=DEFAULT,
                        **kwargs):
        yield from super(__class__,
                         inst if inst is not None else cls)._constraint_gen(
                             sim, target, *args, **kwargs)
        if inst is not None:
            care_dependent = inst._get_primary_care_dependent()
            if care_dependent is not None:
                yield create_carry_constraint(care_dependent)

    def _setup_gen(self, timeline):
        if self.travel_xevt is not None:

            def on_travel_visuals(*_, **__):
                self.sim.remove_from_client()
                care_dependent = self._get_primary_care_dependent()
                if care_dependent is not None and care_dependent.parent is self.sim:
                    care_dependent.remove_from_client()

            self.store_event_handler(on_travel_visuals,
                                     handler_id=self.travel_xevt)
        result = yield from super()._setup_gen(timeline)
        return result
        yield

    def _run_interaction_gen(self, timeline):
        self.save_and_destroy_sim(False, self.sim.sim_info)

    def _exited_pipeline(self, *args, **kwargs):
        self.sim.socials_locked = False
        return super()._exited_pipeline(*args, **kwargs)

    def save_and_destroy_sim(self, on_reset, sim_info):
        if services.current_zone().is_zone_shutting_down:
            return

        def update_selectable_sim():
            if not sim_info.is_npc:
                services.client_manager().get_first_client(
                ).send_selectable_sims_update()

        try:
            logger.debug('Saving sim during TravelInteraction for {}',
                         sim_info)
            sim_info.inject_into_inactive_zone(self.to_zone_id,
                                               skip_instanced_check=True)
            if sim_info.save_sim() is None:
                logger.error('Failure saving during TravelInteraction for {}',
                             sim_info)
        finally:
            logger.debug('Destroying sim {}', sim_info)
            if on_reset:
                if self.sim is not None:
                    services.object_manager().remove(self.sim)
                update_selectable_sim()
            elif self.sim is not None:
                self.sim.schedule_destroy_asap(
                    source=self, cause='Destroying sim on travel.')
コード例 #24
0
ファイル: fragments.py プロジェクト: nessdoor/RVlyzer
class FragmentView(CodeFragment):
    """
    A code fragment-view of an assembler source snippet.

    This kind of code fragment defines a modifiable view of the assembly code contained in another code fragment.
    Multiple views of the same or different parts of an assembler source can be instantiated, and a shared data
    structure ensures that any update performed through the objects of this class is correctly reflected in the overall
    code layout presented through these views.
    Be aware that any structural modification performed directly on the origin leaves the whole view system in an
    inconsistent state. To regain consistency, discard the corrupted views and recreate them.
    """

    # Declare and allocate the shared catalogue of source fragments
    _sources_catalogue: ClassVar[MutableMapping[
        CodeFragment, MutableSet[FragmentView]]] = WeakKeyDictionary()

    # Instance variable containing a reference to the views ensemble a view belongs to
    _views_catalogue: MutableSet[FragmentView]

    # Instance variable containing a reference to the backing fragment
    _origin: CodeFragment

    _offset: int
    _begin: int
    _end: int

    @classmethod
    # Utility method used for updating all the views' metadata after a structural change
    def _grow_shrink_origin(cls, requester: FragmentView, origin: CodeFragment,
                            position: int, length: int) -> None:
        def descendants_and_mother(mother: FragmentView) -> Set[FragmentView]:
            if mother not in cls._sources_catalogue:
                # Base case, no descendants
                return {mother}

            children = cls._sources_catalogue[mother]
            descendants = {mother}
            for child in children:
                # Recursive call
                descendants.update(descendants_and_mother(child))

            return descendants

        # Get requester's siblings
        siblings = {
            s
            for s in cls._sources_catalogue.get(origin) if s is not requester
        }
        views_to_resize = set()
        # Get all the sibling's descendants, so that we can update them appropriately
        for s in siblings:
            views_to_resize.update(descendants_and_mother(s))

        for view in views_to_resize:
            view: FragmentView
            # View is after the target position, or the modification is an extension of the preceding view
            if view.begin > position or (view.begin == position
                                         and requester.end == position):
                view._offset += length
                view._begin += length

            if view.end > position:
                view._end += length

        # Directly resize the requester. Eventual descendants will be resized by the upper calls.
        requester._end += length

    def __init__(self,
                 src: CodeFragment,
                 begin: int = 0,
                 end: int = 0,
                 offset: int = 0) -> None:
        """
        Generates a new code fragment-view backed by another code fragment.

        :param src: the assembly code fragment of which the new fragment will constitute a view
        :param begin: line number of the first line contained in the new fragment
        :param end: line number of the first line following the last contained in the new fragment
        :param offset: offset of the view inside the origin fragment
        :raises ValueError: when the fragment size would not fit inside the origin fragment
        """

        # Delegate consistency checks
        super().__init__(src, begin, end, offset)
        self._origin = src
        self._begin = begin
        self._end = end
        self._offset = offset

        if src not in FragmentView._sources_catalogue:
            # If this source fragment has never been seen before, add it to the shared catalogue and allocate the views
            # catalogue
            self._views_catalogue = WeakSet()
            FragmentView._sources_catalogue[src] = self._views_catalogue
        else:
            # Otherwise, set the local reference to the views catalogue associated with the provided source
            self._views_catalogue = FragmentView._sources_catalogue[src]

        # Add the new view's metadata to the catalogue
        self._views_catalogue.add(self)

    def _line_to_index(self, line_number: int) -> int:
        # Verify that the calculated index falls within this fragment's range
        if not self.begin <= line_number < self.end:
            raise IndexError("Index out of range")

        return line_number - self.begin + self.offset

    @property
    def begin(self) -> int:
        return self._begin

    @property
    def end(self) -> int:
        return self._end

    @property
    def offset(self) -> int:
        return self._offset

    def slice(self, start: int, end: int) -> FragmentView:
        """
        Creates a new subview by slicing this one.

        The returned subview shares the same properties of any other view and is not in any way dependent from the
        parent, being merely a view of a contiguous subset of the statements contained therein.

        :param start: the starting line of the new fragment
        :param end: the end line of the new fragment
        :return: a FragmentView representing a slice of the contained statements
        :raises ValueError: when the specified interval doesn't fit inside the existing fragment
        """

        return self[start:end]

    def append(self, statement: Statement) -> None:
        self._origin.insert(self.offset + len(self), statement)
        # Growth point is at the end of the slice
        self._grow_shrink_origin(self, self._origin, self.end, 1)

    def extend(self, statements: List[Statement]) -> None:
        insertion_point = self.offset + len(self)
        for st in statements:
            self._origin.insert(insertion_point, st)
            insertion_point += 1

        # Growth point is at the end of the slice
        self._grow_shrink_origin(self, self._origin, self.end, len(statements))

    def insert(self, line_number: int, statement: Statement) -> None:
        self._origin.insert(self._line_to_index(line_number), statement)
        self._grow_shrink_origin(self, self._origin, line_number, 1)

    def pop(self, line_number: int = -1) -> Statement:
        # We emulate the signature of the standard pop() method
        if line_number == -1:
            line_number = self.end - 1

        popping_point = self._line_to_index(line_number)
        popped = self._origin.pop(popping_point)
        self._grow_shrink_origin(self, self._origin, line_number, -1)
        return popped

    def copy(self) -> FragmentView:
        return FragmentView(src=self._origin,
                            begin=self.begin,
                            end=self.end,
                            offset=self.offset)

    def clear(self) -> None:
        offset = self.offset
        length = len(self)
        del self._origin[offset:offset + length]
        # View size shrinks to zero, with growth point set to end as not to influence this view's begin
        self._grow_shrink_origin(self, self._origin, self.end, -length)

    def iter(self, starting_line: int) -> Iterator[Statement]:
        """Return an iterator that starts iterating from the specified line."""

        super().iter(starting_line)

        for s in self[starting_line:self.end]:
            yield s

    def __iter__(self) -> Iterator[Statement]:
        curr = self.offset
        stop = curr + len(self)

        while curr < stop:
            yield self._origin[curr]
            curr += 1

    def __len__(self) -> int:
        return self.end - self.begin

    def __getitem__(
            self, line_number: Union[int,
                                     slice]) -> Union[Statement, FragmentView]:
        """
        Access the contained statements through the Sequence interface, by line index.

        Negative indices are not supported.

        Due to the underlying implementation, access by slices only works if the extremes are included between the start
        and the end of the fragment. Moreover, specifying a step different from `None` or 1 is not allowed.

        :param line_number: line number(s) to be targeted
        :return: the selected statement(s), encapsulated in a FragmentView in case of access by slices
        :raise IndexError: when an invalid line index is specified
        :raise ValueError: when a non-unitary step is specified
        :raise TypeError: when line_number is not an integer nor a slice
        """

        # Delegate type check
        super().__getitem__(line_number)

        if type(line_number) is int:
            return self._origin[self._line_to_index(line_number)]
        elif type(line_number) is slice:
            sl = self._slicer(line_number)
            return FragmentView(self._origin, sl.start, sl.stop,
                                self.offset + sl.start - self.begin)

    def __setitem__(self, line_number: Union[int, slice],
                    statement: Union[Statement, Sequence[Statement]]) -> None:
        """
        Modify the contained statements through the Sequence interface, by line index.

        Negative indices are not supported.

        Due to the underlying implementation, access by slices only works if the extremes are included between the start
        and the end of this fragment. Moreover, specifying a step different from `None` or 1 is not allowed.

        :param line_number: line number(s) to be targeted
        :param statement: statement(s) to be set
        :raise IndexError: when an invalid line index is specified
        :raise ValueError: when a non-unitary step is specified
        :raise TypeError: when line_number is not an integer nor a slice
        """

        # Delegate type check
        super().__setitem__(line_number, statement)

        if type(line_number) is int:
            self._origin[self._line_to_index(line_number)] = statement
        elif type(line_number) is slice:
            sl = self._slicer(line_number)
            # Be aware of the ugly workaround used to let _line_to_index() process a slice reaching the end of the
            # fragment. Without the decrement-call-increment, it would report an IndexError
            start = self._line_to_index(sl.start)
            stop = self._line_to_index(
                sl.stop -
                1) + 1 if sl.stop == self.end else self._line_to_index(sl.stop)
            self._origin[start:stop] = statement

            if len(statement) != stop - start:
                # A funky insertion/deletion just took place, so we must treat it appropriately
                self._grow_shrink_origin(self, self._origin, sl.start,
                                         len(statement) - (stop - start))

    def __delitem__(self, line_number: Union[int, slice]) -> None:
        """
        Delete the contained statements through the Sequence interface, by line index.

        Negative indices are not supported.

        Due to the underlying implementation, access by slices only works if the extremes are included between the start
        and the end of this fragment. Moreover, specifying a step different from `None` or 1 is not allowed.

        :param line_number: line number(s) to be targeted
        :raise IndexError: when an invalid line index is specified
        :raise ValueError: when a non-unitary step is specified
        :raise TypeError: when line_number is not an integer nor a slice
        """

        # Delegate type check
        super().__delitem__(line_number)

        if type(line_number) is int:
            deletion_point = self._line_to_index(line_number)
            del self._origin[deletion_point]
            self._grow_shrink_origin(self, self._origin, line_number, -1)
        elif type(line_number) is slice:
            sl = self._slicer(line_number)
            # Be aware of the ugly workaround used to let _line_to_index() process a slice reaching the end of the
            # fragment. Without the decrement-call-increment, it would report an IndexError
            start = self._line_to_index(sl.start)
            stop = self._line_to_index(
                sl.stop -
                1) + 1 if sl.stop == self.end else self._line_to_index(sl.stop)
            del self._origin[start:stop]

            # Decrease the list's size according to the number of elements that got deleted
            self._grow_shrink_origin(self, self._origin, sl.start,
                                     -(stop - start))

    def __hash__(self) -> int:
        # IDs are unique for the entire life of an object, so no collisions should take place inside the shared
        # catalogue with this
        return hash((id(self), id(self._origin)))

    def __str__(self):
        return "".join(str(stm) for stm in iter(self))
コード例 #25
0
class BroadcasterService(Service):
    INTERVAL = TunableRealSecond(description='\n        The time between broadcaster pulses. A lower number will impact\n        performance.\n        ', default=5)
    DEFAULT_QUADTREE_RADIUS = 0.1

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._alarm_handle = None
        self._processing_task = None
        self._on_update_callbacks = CallableList()
        self._pending_broadcasters = []
        self._active_broadcasters = []
        self._cluster_requests = {}
        self._object_cache = None
        self._object_cache_tags = None
        self._pending_update = False
        self._quadtrees = defaultdict(sims4.geometry.QuadTree)

    def create_update_alarm(self):
        self._alarm_handle = add_alarm(self, interval_in_real_seconds(self.INTERVAL), self._on_update, repeating=True, use_sleep_time=False)

    def start(self):
        self.create_update_alarm()
        object_manager = services.object_manager()
        object_manager.register_callback(CallbackTypes.ON_OBJECT_LOCATION_CHANGED, self._update_object_cache)
        object_manager.register_callback(CallbackTypes.ON_OBJECT_ADD, self._update_object_cache)
        services.current_zone().wall_contour_update_callbacks.append(self._on_wall_contours_changed)

    def stop(self):
        if self._alarm_handle is not None:
            cancel_alarm(self._alarm_handle)
            self._alarm_handle = None
        if self._processing_task is not None:
            self._processing_task.stop()
            self._processing_task = None
        object_manager = services.object_manager()
        object_manager.unregister_callback(CallbackTypes.ON_OBJECT_LOCATION_CHANGED, self._update_object_cache)
        object_manager.unregister_callback(CallbackTypes.ON_OBJECT_ADD, self._update_object_cache)
        services.current_zone().wall_contour_update_callbacks.remove(self._on_wall_contours_changed)

    def add_broadcaster(self, broadcaster):
        if broadcaster not in self._pending_broadcasters:
            self._pending_broadcasters.append(broadcaster)
            if broadcaster.immediate:
                self._pending_update = True
            self._on_update_callbacks()

    def remove_broadcaster(self, broadcaster):
        if broadcaster in self._pending_broadcasters:
            self._pending_broadcasters.remove(broadcaster)
        if broadcaster in self._active_broadcasters:
            self._remove_from_cluster_request(broadcaster)
            self._remove_broadcaster_from_quadtree(broadcaster)
            self._active_broadcasters.remove(broadcaster)
        broadcaster.on_removed()
        self._on_update_callbacks()

    def _activate_pending_broadcasters(self):
        for broadcaster in self._pending_broadcasters:
            self._active_broadcasters.append(broadcaster)
            self.update_cluster_request(broadcaster)
            self._update_object_cache()
        self._pending_broadcasters.clear()

    def _add_broadcaster_to_quadtree(self, broadcaster):
        self._remove_broadcaster_from_quadtree(broadcaster)
        broadcaster_quadtree = self._quadtrees[broadcaster.routing_surface.secondary_id]
        broadcaster_bounds = sims4.geometry.QtCircle(sims4.math.Vector2(broadcaster.position.x, broadcaster.position.z), self.DEFAULT_QUADTREE_RADIUS)
        broadcaster_quadtree.insert(broadcaster, broadcaster_bounds)
        return broadcaster_quadtree

    def _remove_broadcaster_from_quadtree(self, broadcaster):
        broadcaster_quadtree = broadcaster.quadtree
        if broadcaster_quadtree is not None:
            broadcaster_quadtree.remove(broadcaster)

    def update_cluster_request(self, broadcaster):
        if broadcaster not in self._active_broadcasters:
            return
        clustering_request = broadcaster.get_clustering()
        if clustering_request is None:
            return
        self._remove_from_cluster_request(broadcaster)
        cluster_request_key = (type(broadcaster), broadcaster.routing_surface.secondary_id)
        if cluster_request_key in self._cluster_requests:
            cluster_request = self._cluster_requests[cluster_request_key]
            cluster_request.set_object_dirty(broadcaster)
        else:
            cluster_quadtree = self._quadtrees[broadcaster.routing_surface.secondary_id]
            cluster_request = clustering_request(lambda : self._get_broadcasters_for_cluster_request_gen(*cluster_request_key), quadtree=cluster_quadtree)
            self._cluster_requests[cluster_request_key] = cluster_request
        quadtree = self._add_broadcaster_to_quadtree(broadcaster)
        broadcaster.on_added_to_quadtree_and_cluster_request(quadtree, cluster_request)

    def _remove_from_cluster_request(self, broadcaster):
        cluster_request = broadcaster.cluster_request
        if cluster_request is not None:
            cluster_request.set_object_dirty(broadcaster)

    def _is_valid_cache_object(self, obj):
        if obj.is_sim:
            return False
        elif self._object_cache_tags:
            object_tags = obj.get_tags()
            if object_tags & self._object_cache_tags:
                return True
            else:
                return False
        return False
        return True

    def get_object_cache_info(self):
        return (self._object_cache, self._object_cache_tags)

    def _generate_object_cache(self):
        self._object_cache = WeakSet(obj for obj in services.object_manager().valid_objects() if self._is_valid_cache_object(obj))

    def _update_object_cache(self, obj=None):
        if obj is None:
            self._object_cache = None
            self._object_cache_tags = None
            return
        if self._object_cache is not None and self._is_valid_cache_object(obj):
            self._object_cache.add(obj)

    def _is_valid_broadcaster(self, broadcaster):
        broadcasting_object = broadcaster.broadcasting_object
        if broadcasting_object is None or not broadcasting_object.visible_to_client:
            return False
        if broadcasting_object.is_in_inventory():
            return False
        elif broadcasting_object.parent is not None and broadcasting_object.parent.is_sim:
            return False
        return True

    def _get_broadcasters_for_cluster_request_gen(self, broadcaster_type, broadcaster_level):
        for broadcaster in self._active_broadcasters:
            if broadcaster.guid == broadcaster_type.guid:
                if broadcaster.should_cluster():
                    if broadcaster.routing_surface.secondary_id == broadcaster_level:
                        yield broadcaster

    def get_broadcasters_debug_gen(self):
        for cluster_request in self._cluster_requests.values():
            for cluster in cluster_request.get_clusters_gen():
                broadcaster_iter = cluster.objects_gen()
                yield next(broadcaster_iter)
            yield from cluster_request.get_rejects()
        for broadcaster in self._active_broadcasters:
            if not broadcaster.should_cluster():
                if self._is_valid_broadcaster(broadcaster):
                    yield broadcaster

    def get_broadcasters_gen(self):
        for (cluster_request_key, cluster_request) in self._cluster_requests.items():
            is_cluster_dirty = cluster_request.is_dirty()
            for broadcaster in self._get_broadcasters_for_cluster_request_gen(*cluster_request_key):
                if self._is_valid_broadcaster(broadcaster):
                    broadcaster.regenerate_constraint()
            for cluster in cluster_request.get_clusters_gen():
                linkable_broadcasters_iter = (b for b in cluster.objects_gen() if self._is_valid_broadcaster(b))
                master_broadcaster = next(linkable_broadcasters_iter, None)
                if master_broadcaster is None:
                    continue
                master_broadcaster.set_linked_broadcasters(linkable_broadcasters_iter)
                yield master_broadcaster
            yield from (b for b in cluster_request.get_rejects() if self._is_valid_broadcaster(b))
        for broadcaster in self._active_broadcasters:
            if not broadcaster.should_cluster():
                if self._is_valid_broadcaster(broadcaster):
                    yield broadcaster

    PathSegmentData = namedtuple('PathSegmentData', ('prev_pos', 'cur_pos', 'segment_vec', 'segment_mag_sq', 'segment_normal'))

    def get_broadcasters_along_route_gen(self, sim, path, start_time=0, end_time=0):
        path_segment_datas = {}
        start_index = max(0, path.node_at_time(start_time).index - 1)
        end_index = min(len(path) - 1, path.node_at_time(end_time).index)
        for broadcaster in self.get_broadcasters_gen():
            if broadcaster.route_events:
                if not broadcaster.can_affect(sim):
                    continue
                constraint = broadcaster.get_constraint()
                geometry = constraint.geometry
                if geometry is None:
                    continue
                polygon = geometry.polygon
                if polygon is None:
                    continue
                if not constraint.valid:
                    continue
                constraint_pos = polygon.centroid()
                constraint_radius_sq = polygon.radius()
                constraint_radius_sq = constraint_radius_sq*constraint_radius_sq
                for index in range(end_index, start_index, -1):
                    prev_index = index - 1
                    prev_node = path.nodes[prev_index]
                    if not constraint.is_routing_surface_valid(prev_node.routing_surface_id):
                        continue
                    segment_key = (prev_index, index)
                    segment_data = path_segment_datas.get(segment_key, None)
                    if segment_data is None:
                        cur_node = path.nodes[index]
                        cur_pos = sims4.math.Vector3(*cur_node.position)
                        prev_pos = sims4.math.Vector3(*prev_node.position)
                        segment_vec = cur_pos - prev_pos
                        segment_vec.y = 0
                        segment_mag_sq = segment_vec.magnitude_2d_squared()
                        if sims4.math.almost_equal_sq(segment_mag_sq, 0):
                            segment_normal = None
                        else:
                            segment_normal = segment_vec/sims4.math.sqrt(segment_mag_sq)
                        segment_data = BroadcasterService.PathSegmentData(prev_pos, cur_pos, segment_vec, segment_mag_sq, segment_normal)
                        path_segment_datas[segment_key] = segment_data
                    else:
                        (prev_pos, cur_pos, segment_vec, segment_mag_sq, segment_normal) = segment_data
                    if segment_normal is None:
                        constraint_vec = constraint_pos - prev_pos
                        constraint_dist_sq = constraint_vec.magnitude_2d_squared()
                        if constraint_radius_sq < constraint_dist_sq:
                            continue
                    else:
                        constraint_vec = constraint_pos - prev_pos
                        constraint_vec.y = 0
                        contraint_proj = constraint_vec - segment_normal*sims4.math.vector_dot_2d(constraint_vec, segment_normal)
                        if constraint_radius_sq < contraint_proj.magnitude_2d_squared():
                            continue
                    for (transform, _, time) in path.get_location_data_along_segment_gen(prev_index, index):
                        if not geometry.test_transform(transform):
                            continue
                        yield (time, broadcaster)
                        break
                    break

    def get_pending_broadcasters_gen(self):
        yield from self._pending_broadcasters

    def _get_all_objects_gen(self):
        is_any_broadcaster_allowing_objects = True if self._object_cache else False
        if not is_any_broadcaster_allowing_objects:
            for broadcaster in self._active_broadcasters:
                (allow_objects, allow_objects_tags) = broadcaster.allow_objects.is_affecting_objects()
                if allow_objects:
                    is_any_broadcaster_allowing_objects = True
                    if allow_objects_tags is None:
                        self._object_cache_tags = None
                        break
                    else:
                        if self._object_cache_tags is None:
                            self._object_cache_tags = set()
                        self._object_cache_tags |= allow_objects_tags
        if is_any_broadcaster_allowing_objects:
            if self._object_cache is None:
                self._generate_object_cache()
            yield from list(self._object_cache)
        else:
            self._object_cache = None
            self._object_cache_tags = None
        yield from services.sim_info_manager().instanced_sims_gen()

    def register_callback(self, callback):
        if callback not in self._on_update_callbacks:
            self._on_update_callbacks.append(callback)

    def unregister_callback(self, callback):
        if callback in self._on_update_callbacks:
            self._on_update_callbacks.remove(callback)

    def _on_update(self, _):
        self._pending_update = True

    def _on_wall_contours_changed(self, *_, **__):
        self._update_object_cache()

    def provide_route_events(self, route_event_context, sim, path, failed_types=None, start_time=0, end_time=0, **kwargs):
        for (time, broadcaster) in self.get_broadcasters_along_route_gen(sim, path, start_time=start_time, end_time=end_time):
            resolver = broadcaster.get_resolver(sim)
            for route_event in broadcaster.route_events:
                if not failed_types is None:
                    pass
                if not route_event_context.route_event_already_scheduled(route_event, provider=broadcaster) and route_event.test(resolver):
                    route_event_context.add_route_event(RouteEventType.BROADCASTER, route_event(time=time, provider=broadcaster, provider_required=True))

    def update(self):
        if self._pending_update:
            self._pending_update = False
            self._update()

    def _is_location_affected(self, constraint, transform, routing_surface):
        if constraint.geometry is not None and not constraint.geometry.test_transform(transform):
            return False
        elif not constraint.is_routing_surface_valid(routing_surface):
            return False
        return True

    def update_broadcasters_one_shot(self, broadcasters):
        for obj in self._get_all_objects_gen():
            object_transform = None
            routing_surface = obj.routing_surface
            for broadcaster in broadcasters:
                if broadcaster.can_affect(obj):
                    constraint = broadcaster.get_constraint()
                    if not constraint.valid:
                        continue
                    if object_transform is None:
                        parent = obj.parent
                        if parent is None:
                            object_transform = obj.transform
                        else:
                            object_transform = parent.transform
                    if self._is_location_affected(constraint, object_transform, routing_surface):
                        broadcaster.apply_broadcaster_effect(obj)
                        broadcaster.remove_broadcaster_effect(obj)
                        if not obj.valid_for_distribution:
                            break

    def _update(self):
        try:
            self._activate_pending_broadcasters()
            current_broadcasters = set(self.get_broadcasters_gen())
            for obj in self._get_all_objects_gen():
                object_transform = None
                is_affected = False
                for broadcaster in current_broadcasters:
                    if broadcaster.can_affect(obj):
                        constraint = broadcaster.get_constraint()
                        if not constraint.valid:
                            continue
                        if object_transform is None:
                            parent = obj.parent
                            if parent is None:
                                object_transform = obj.transform
                            else:
                                object_transform = parent.transform
                        if self._is_location_affected(constraint, object_transform, obj.routing_surface):
                            broadcaster.apply_broadcaster_effect(obj)
                            if not obj.valid_for_distribution:
                                is_affected = False
                                break
                            is_affected = True
                if not is_affected:
                    if self._object_cache is not None:
                        self._object_cache.discard(obj)
            for broadcaster in current_broadcasters:
                broadcaster.on_processed()
        finally:
            self._on_update_callbacks()
コード例 #26
0
ファイル: context.py プロジェクト: johndpope/sims4-ai-engine
class InteractionContext:
    __qualname__ = 'InteractionContext'
    SOURCE_PIE_MENU = InteractionSource.PIE_MENU
    SOURCE_AUTONOMY = InteractionSource.AUTONOMY
    SOURCE_BODY_CANCEL_AOP = InteractionSource.BODY_CANCEL_AOP
    SOURCE_CARRY_CANCEL_AOP = InteractionSource.CARRY_CANCEL_AOP
    SOURCE_SCRIPT = InteractionSource.SCRIPT
    SOURCE_UNIT_TEST = InteractionSource.UNIT_TEST
    SOURCE_SOCIAL_ADJUSTMENT = InteractionSource.SOCIAL_ADJUSTMENT
    SOURCE_QUICKTIME = InteractionSource.QUICKTIME
    SOURCE_GET_COMFORTABLE = InteractionSource.GET_COMFORTABLE
    SOURCE_SCRIPT_WITH_USER_INTENT = InteractionSource.SCRIPT_WITH_USER_INTENT
    SOURCE_POSTURE_GRAPH = InteractionSource.POSTURE_GRAPH

    def __init__(self, sim, source, priority, run_priority=None, client=None, pick=None, insert_strategy=QueueInsertStrategy.LAST, must_run_next=False, continuation_id=None, group_id=None, shift_held=False, carry_target=None, target_sim_id=None, bucket=InteractionBucketType.BASED_ON_SOURCE, visual_continuation_id=None, restored_from_load=False, cancel_if_incompatible_in_queue=False, always_check_in_use=False, preferred_objects=()):
        self._sim = sim.ref() if sim else None
        self.source = source
        self.priority = priority
        self.client = client
        self.pick = pick
        self.insert_strategy = insert_strategy
        self.must_run_next = must_run_next
        self.shift_held = shift_held
        self.continuation_id = continuation_id
        self.visual_continuation_id = visual_continuation_id
        self.group_id = group_id
        self.carry_target = carry_target
        self.target_sim_id = target_sim_id
        self.run_priority = run_priority
        self.bucket = bucket
        self.restored_from_load = restored_from_load
        self.cancel_if_incompatible_in_queue = cancel_if_incompatible_in_queue
        self.always_check_in_use = always_check_in_use
        self.preferred_objects = WeakSet(preferred_objects)

    def _clone(self, **overrides):
        result = copy.copy(self)
        for (name, value) in overrides.items():
            if value is DEFAULT:
                pass
            getattr(result, name)
            setattr(result, name, value)
        return result

    @property
    def bucket_type(self):
        return self.bucket

    @property
    def is_cancel_aop(self):
        return self.source == InteractionSource.BODY_CANCEL_AOP or self.source == InteractionSource.CARRY_CANCEL_AOP

    def clone_for_user_directed_choice(self):
        return self._clone(source=InteractionContext.SOURCE_PIE_MENU, priority=self.client.interaction_priority, insert_strategy=QueueInsertStrategy.LAST, continuation_id=None, group_id=None)

    def clone_for_autonomous_choice(self):
        return self._clone(source=InteractionContext.SOURCE_AUTONOMY, priority=interactions.priority.Priority.Low, insert_strategy=QueueInsertStrategy.LAST, continuation_id=None, group_id=None)

    def clone_for_insert_next(self, preferred_objects=DEFAULT, **kwargs):
        if preferred_objects is DEFAULT:
            preferred_objects = self.preferred_objects
        return self._clone(insert_strategy=QueueInsertStrategy.NEXT, preferred_objects=preferred_objects, restored_from_load=False, **kwargs)

    def clone_for_continuation(self, continuation_of_si, insert_strategy=QueueInsertStrategy.NEXT, continuation_id=DEFAULT, group_id=DEFAULT, preferred_objects=DEFAULT, **kwargs):
        if not continuation_of_si.immediate:
            if continuation_id is DEFAULT:
                continuation_id = continuation_of_si.id
            group_id = continuation_of_si.group_id
        else:
            logger.error('clone_for_continuation: attempting to create a continuation of an immediate interaction, support for this is deprecated and will be removed soon: {}', continuation_of_si, owner='jpollak/tastle')
        if preferred_objects is DEFAULT:
            preferred_objects = self.preferred_objects
        return self._clone(insert_strategy=insert_strategy, continuation_id=continuation_id, group_id=group_id, preferred_objects=preferred_objects, restored_from_load=False, **kwargs)

    def clone_for_parameterized_autonomy(self, source_si, group_id=DEFAULT, continuation_id=DEFAULT, visual_continuation_id=DEFAULT, **kwargs):
        if group_id is DEFAULT:
            group_id = source_si.group_id
        if continuation_id is DEFAULT:
            continuation_id = source_si.id
        if visual_continuation_id is DEFAULT:
            visual_continuation_id = source_si.id
        return self._clone(insert_strategy=QueueInsertStrategy.FIRST, group_id=group_id, continuation_id=continuation_id, run_priority=None, visual_continuation_id=source_si.id, **kwargs)

    def clone_from_immediate_context(self, continuation_of_si, **kwargs):
        if not continuation_of_si.immediate:
            logger.error('clone_from_immediate_context: attempting to create a continuation of a non-immediate interaction.', owner='tastle/jpollak')
        return self._clone(group_id=continuation_of_si.group_id, **kwargs)

    def clone_for_sim(self, sim, **overrides):
        return self._clone(_sim=sim.ref(), **overrides)

    def clone_for_concurrent_context(self):
        return self._clone(insert_strategy=QueueInsertStrategy.FIRST)

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

    def add_preferred_object(self, cur_obj):
        self.preferred_objects.add(cur_obj)

    def add_preferred_objects(self, obj_list):
        pass

    @property
    def carry_target(self):
        if self._carry_target:
            return self._carry_target()

    @carry_target.setter
    def carry_target(self, value):
        self._carry_target = value.ref() if value else None

    def __repr__(self):
        return '{0}.{1}({2}, {3}, {4})'.format(self.__module__, self.__class__.__name__, repr(self.sim), self.source, repr(self.priority))
コード例 #27
0
class Privacy(LineOfSight):
    __qualname__ = 'Privacy'
    _PRIVACY_FOOTPRINT_TYPE = 5
    _PRIVACY_DISCOURAGEMENT_COST = routing.get_default_discouragement_cost()
    _SHOO_CONSTRAINT_RADIUS = Tunable(
        description=
        '\n        The radius of the constraint a Shooed Sim will attempt to route to.\n        ',
        tunable_type=float,
        default=2.5)
    _UNAVAILABLE_TOOLTIP = TunableLocalizedStringFactory(
        description=
        '\n        Tooltip displayed when an object is not accessible due to being inside\n        a privacy region.\n        '
    )
    _EMBARRASSED_AFFORDANCE = TunableReference(
        description=
        '\n        The affordance a Sim will play when getting embarrassed by walking in\n        on a privacy situation.\n        ',
        manager=services.affordance_manager())

    def __init__(self, interaction, tests, max_line_of_sight_radius,
                 map_divisions, simplification_ratio, boundary_epsilon,
                 facing_offset):
        super().__init__(max_line_of_sight_radius, map_divisions,
                         simplification_ratio, boundary_epsilon)
        self._max_line_of_sight_radius = max_line_of_sight_radius
        self._interaction = interaction
        self._tests = tests
        self._privacy_constraints = []
        self._allowed_sims = WeakSet()
        self._disallowed_sims = WeakSet()
        self._violators = WeakSet()
        self._late_violators = WeakSet()
        self.is_active = False
        self.has_shooed = False
        self.central_object = None
        self._pushed_interactions = []
        services.privacy_service().add_instance(self)

    @property
    def unavailable_tooltip(self):
        return self._UNAVAILABLE_TOOLTIP

    @property
    def interaction(self):
        return self._interaction

    @property
    def is_active(self) -> bool:
        return self._is_active

    @is_active.setter
    def is_active(self, value):
        self._is_active = value

    def _is_sim_allowed(self, sim):
        if self._tests:
            resolver = self._interaction.get_resolver(target=sim)
            if self._tests and self._tests.run_tests(resolver):
                return True
        if self._interaction.can_sim_violate_privacy(sim):
            return True
        return False

    def evaluate_sim(self, sim):
        if self._is_sim_allowed(sim):
            self._allowed_sims.add(sim)
            return True
        self._disallowed_sims.add(sim)
        return False

    def build_privacy(self, target=None):
        self.is_active = True
        target_object = self._interaction.get_participant(
            ParticipantType.Object)
        target_object = None if target_object.is_sim else target_object
        self.central_object = target_object or (target
                                                or self._interaction.sim)
        self.generate(self.central_object.position,
                      self.central_object.routing_surface)
        for poly in self.constraint.geometry.polygon:
            self._privacy_constraints.append(
                PolygonFootprint(
                    poly,
                    routing_surface=self._interaction.sim.routing_surface,
                    cost=self._PRIVACY_DISCOURAGEMENT_COST,
                    footprint_type=self._PRIVACY_FOOTPRINT_TYPE,
                    enabled=True))
        self._allowed_sims.update(
            self._interaction.get_participants(ParticipantType.AllSims))
        for sim in services.sim_info_manager().instanced_sims_gen():
            while sim not in self._allowed_sims:
                self.evaluate_sim(sim)
        violating_sims = self.find_violating_sims()
        self._cancel_unavailable_interactions(violating_sims)
        self._add_overrides_and_constraints_if_needed(violating_sims)

    def cleanup_privacy_instance(self):
        if self.is_active:
            self.is_active = False
            for sim in self._allowed_sims:
                self.remove_override_for_sim(sim)
            for sim in self._late_violators:
                self.remove_override_for_sim(sim)
            del self._privacy_constraints[:]
            self._allowed_sims.clear()
            self._disallowed_sims.clear()
            self._violators.clear()
            self._late_violators.clear()
            self._cancel_pushed_interactions()

    def remove_privacy(self):
        self.cleanup_privacy_instance()
        services.privacy_service().remove_instance(self)

    def intersects_with_object(self, obj):
        if obj.routing_surface != self.central_object.routing_surface:
            return False
        delta = obj.position - self.central_object.position
        distance = delta.magnitude_2d_squared()
        if distance > self.max_line_of_sight_radius * self.max_line_of_sight_radius:
            return False
        object_footprint = obj.footprint_polygon
        if object_footprint is None:
            object_footprint = sims4.geometry.Polygon([obj.position])
        for poly in self.constraint.geometry.polygon:
            intersection = poly.intersect(object_footprint)
            while intersection is not None and intersection.has_enough_vertices:
                return True
        return False

    def find_violating_sims(self):
        if not self.is_active:
            return []
        nearby_sims = placement.get_nearby_sims(
            self.central_object.position,
            self.central_object.routing_surface.secondary_id,
            radius=self.max_line_of_sight_radius,
            exclude=self._allowed_sims,
            only_sim_position=True)
        violators = []
        for sim in nearby_sims:
            if any(sim_primitive.is_traversing_portal()
                   for sim_primitive in sim.primitives
                   if isinstance(sim_primitive, FollowPath)):
                pass
            if sim not in self._disallowed_sims and self.evaluate_sim(sim):
                pass
            while sims4.geometry.test_point_in_compound_polygon(
                    sim.position, self.constraint.geometry.polygon):
                violators.append(sim)
        return violators

    def _add_overrides_and_constraints_if_needed(self, violating_sims):
        for sim in self._allowed_sims:
            self.add_override_for_sim(sim)
        for sim in violating_sims:
            self._violators.add(sim)
            liabilities = ((SHOO_LIABILITY, ShooLiability(self, sim)), )
            result = self._route_sim_away(sim, liabilities=liabilities)
            while result:
                self._pushed_interactions.append(result.interaction)

    def _cancel_unavailable_interactions(self, violating_sims):
        for sim in violating_sims:
            interactions_to_cancel = set()
            if sim.queue.running is not None:
                interactions_to_cancel.add(sim.queue.running)
            for interaction in sim.si_state:
                while interaction.is_super and interaction.target is not None and sim.locked_from_obj_by_privacy(
                        interaction.target):
                    interactions_to_cancel.add(interaction)
            for interaction in sim.queue:
                if interaction.target is not None and sim.locked_from_obj_by_privacy(
                        interaction.target):
                    interactions_to_cancel.add(interaction)
                else:
                    while interaction.target is not None:
                        break
            for interaction in interactions_to_cancel:
                interaction.cancel(
                    FinishingType.INTERACTION_INCOMPATIBILITY,
                    cancel_reason_msg=
                    'Canceled due to incompatibility with privacy instance.')

    def _route_sim_away(self, sim, liabilities=()):
        context = InteractionContext(sim,
                                     InteractionContext.SOURCE_SCRIPT,
                                     Priority.High,
                                     insert_strategy=QueueInsertStrategy.NEXT)
        from interactions.utils.satisfy_constraint_interaction import BuildAndForceSatisfyShooConstraintInteraction
        result = sim.push_super_affordance(
            BuildAndForceSatisfyShooConstraintInteraction,
            None,
            context,
            liabilities=liabilities,
            privacy_inst=self,
            name_override='BuildShooFromPrivacy')
        if not result:
            logger.debug(
                'Failed to push BuildAndForceSatisfyShooConstraintInteraction on Sim {} to route them out of a privacy area.  Result: {}',
                sim,
                result,
                owner='tastle')
            self.interaction.cancel(
                FinishingType.TRANSITION_FAILURE,
                cancel_reason_msg='Failed to shoo Sims away.')
        return result

    def _cancel_pushed_interactions(self):
        for interaction in self._pushed_interactions:
            interaction.cancel(
                FinishingType.AUTO_EXIT,
                cancel_reason_msg='Privacy finished and is cleaning up.')
        self._pushed_interactions.clear()

    def handle_late_violator(self, sim):
        self._cancel_unavailable_interactions((sim, ))
        self.add_override_for_sim(sim)
        liabilities = ((LATE_SHOO_LIABILITY, LateShooLiability(self, sim)), )
        result = self._route_sim_away(sim, liabilities=liabilities)
        if not result:
            return
        if not self._violators:
            context = InteractionContext(
                sim,
                InteractionContext.SOURCE_SCRIPT,
                Priority.High,
                insert_strategy=QueueInsertStrategy.NEXT)
            result = sim.push_super_affordance(
                self._EMBARRASSED_AFFORDANCE,
                self.interaction.get_participant(ParticipantType.Actor),
                context)
            if not result:
                logger.error(
                    'Failed to push the embarrassed affordance on Sim {}. Interaction {}. Result {}. Context {} ',
                    sim,
                    self.interaction,
                    result,
                    context,
                    owner='tastle')
                return
        self._late_violators.add(sim)

    def add_override_for_sim(self, sim):
        for footprint in self._privacy_constraints:
            sim.routing_context.ignore_footprint_contour(
                footprint.footprint_id)

    def remove_override_for_sim(self, sim):
        for footprint in self._privacy_constraints:
            sim.routing_context.remove_footprint_contour_override(
                footprint.footprint_id)

    @property
    def allowed_sims(self):
        return self._allowed_sims

    @property
    def disallowed_sims(self):
        return self._disallowed_sims

    @property
    def violators(self):
        return self._violators

    def remove_violator(self, sim):
        self.remove_override_for_sim(sim)
        self._violators.discard(sim)

    @property
    def late_violators(self):
        return self._late_violators

    def remove_late_violator(self, sim):
        self.remove_override_for_sim(sim)
        self._late_violators.discard(sim)
コード例 #28
0
class Ensemble(metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(sims4.resources.Types.ENSEMBLE)):
    ENSEMBLE_PRIORITIES = TunableList(description='\n        A list of ensembles by priority.  Those with higher guids will be\n        considered more important than those with lower guids.\n        \n        IMPORTANT: All ensemble types must be referenced in this list.\n        ', tunable=TunableReference(description='\n            A single ensemble.\n            ', manager=services.get_instance_manager(sims4.resources.Types.ENSEMBLE), pack_safe=True))

    @staticmethod
    def get_ensemble_priority(ensemble_type):
        index = 0
        for ensemble in Ensemble.ENSEMBLE_PRIORITIES:
            if ensemble is ensemble_type:
                return index
            index += 1
        logger.error('Ensemble of type {} not found in Ensemble Priorities.  Please add the ensemble to ensemble.ensemble.', ensemble_type)

    INSTANCE_TUNABLES = {'max_ensemble_radius': TunableDistanceSquared(description="\n            The maximum distance away from the center of mass that Sims will\n            receive an autonomy bonus for.\n            \n            If Sims are beyond this distance from the ensemble's center of mass,\n            then they will autonomously consider to run any interaction from\n            ensemble_autonomous_interactions.\n            \n            Any such interaction will have an additional constraint that is a\n            circle whose radius is this value.\n            ", default=1.0), 'ensemble_autonomy_bonus_multiplier': TunableRange(description='\n            The autonomy multiplier that will be applied for objects within the\n            autonomy center of mass.\n            ', tunable_type=float, default=2.0, minimum=1.0), 'ensemble_autonomous_interactions': TunableSet(description="\n            This is a set of self interactions that are generated for Sims part \n            of this ensemble.\n            \n            The interactions don't target anything and have an additional\n            constraint equivalent to the circle defined by the ensemble's center\n            of mass and radius.\n            ", tunable=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.INTERACTION), pack_safe=True)), 'visible': Tunable(description='\n            If this ensemble is visible and displays to the UI.\n            ', tunable_type=bool, default=True), 'rally': Tunable(description='\n            If this is True then this ensemble will offer rallying behavior.\n            ', tunable_type=bool, default=True), 'center_of_mass_multiplier': TunableMultiplier.TunableFactory(description="\n            Define multipliers that control the weight that a Sim has when\n            determining the ensemble's center of mass.\n            "), 'max_limit': OptionalTunable(description='\n            If enabled this ensemble will have a maximum number of Sims that\n            can be a part of it.\n            ', tunable=TunableRange(description='\n                The maximum number of Sims that can be in this ensemble.\n                ', tunable_type=int, default=8, minimum=2)), 'prohibited_species': TunableSet(description='\n            A set of species that cannot be added to this type of ensemble.\n            ', tunable=TunableEnumEntry(description='\n                A species that cannot be added to this type of ensemble.\n                ', tunable_type=Species, default=Species.HUMAN, invalid_enums=(Species.INVALID,)))}

    def __init__(self):
        self._guid = None
        self._sims = WeakSet()

    def __iter__(self):
        yield from self._sims

    def __len__(self):
        return len(self._sims)

    @property
    def guid(self):
        return self._guid

    @classmethod
    def can_add_sim_to_ensemble(cls, sim):
        if sim.species in cls.prohibited_species:
            return False
        return True

    def add_sim_to_ensemble(self, sim):
        if sim in self._sims:
            return
        self._sims.add(sim)
        if self.ensemble_autonomous_interactions:
            sim_info_utils.apply_super_affordance_commodity_flags(sim, self, self.ensemble_autonomous_interactions)
        if self.visible:
            op = UpdateEnsemble(self._guid, sim.id, True)
            Distributor.instance().add_op_with_no_owner(op)

    def remove_sim_from_ensemble(self, sim):
        self._sims.remove(sim)
        if self.ensemble_autonomous_interactions:
            sim_info_utils.remove_super_affordance_commodity_flags(sim, self)
        if self.visible:
            op = UpdateEnsemble(self._guid, sim.id, False)
            Distributor.instance().add_op_with_no_owner(op)

    def is_sim_in_ensemble(self, sim):
        return sim in self._sims

    def start_ensemble(self):
        self._guid = id_generator.generate_object_id()
        if self.visible:
            op = StartEnsemble(self._guid)
            Distributor.instance().add_op_with_no_owner(op)

    def end_ensemble(self):
        if self.ensemble_autonomous_interactions:
            for sim in self._sims:
                sim_info_utils.remove_super_affordance_commodity_flags(sim, self)
        self._sims.clear()
        if self.visible:
            op = EndEnsemble(self._guid)
            Distributor.instance().add_op_with_no_owner(op)

    @cached
    def _get_sim_weight(self, sim):
        return self.center_of_mass_multiplier.get_multiplier(SingleSimResolver(sim.sim_info))

    @cached
    def calculate_level_and_center_of_mass(self):
        sims_per_level = defaultdict(list)
        for sim in self._sims:
            sims_per_level[sim.level].append(sim)
        best_level = max(sims_per_level, key=lambda level: (len(sims_per_level[level]), -level))
        best_sims = sims_per_level[best_level]
        center_of_mass = sum((sim.position*self._get_sim_weight(sim) for sim in best_sims), sims4.math.Vector3.ZERO())/sum(self._get_sim_weight(sim) for sim in best_sims)
        return (best_level, center_of_mass)

    def is_within_ensemble_radius(self, obj):
        (level, center_of_mass) = self.calculate_level_and_center_of_mass()
        if obj.level != level:
            return False
        else:
            distance = (obj.position - center_of_mass).magnitude_squared()
            if distance > self.max_ensemble_radius:
                return False
        return True

    @cached
    def get_ensemble_multiplier(self, target):
        if self.is_within_ensemble_radius(target):
            return self.ensemble_autonomy_bonus_multiplier
        return 1

    def get_center_of_mass_constraint(self):
        if not self:
            logger.warn('No Sims in ensemble when trying to construct constraint.')
            return ANYWHERE
        (level, position) = self.calculate_level_and_center_of_mass()
        routing_surface = routing.SurfaceIdentifier(services.current_zone_id(), level, routing.SurfaceType.SURFACETYPE_WORLD)
        return Circle(position, sqrt(self.max_ensemble_radius), routing_surface)

    def get_ensemble_autonomous_interactions_gen(self, context, **interaction_parameters):
        if self.is_within_ensemble_radius(context.sim):
            return
        for ensemble_affordance in self.ensemble_autonomous_interactions:
            affordance = EnsembleConstraintProxyInteraction.generate(ensemble_affordance, self)
            yield from affordance.potential_interactions(context.sim, context, **interaction_parameters)
コード例 #29
0
ファイル: client.py プロジェクト: NeonOcean/Environment
class Client:
    _interaction_source = interactions.context.InteractionContext.SOURCE_PIE_MENU
    _interaction_priority = interactions.priority.Priority.High

    def __init__(self, session_id, account, household_id):
        self.id = session_id
        self.manager = None
        self._account = account
        self._household_id = household_id
        self._choice_menu = None
        self._interaction_parameters = {}
        self.active = True
        self.zone_id = services.current_zone_id()
        self._selectable_sims = SelectableSims(self)
        self._active_sim_info = None
        self._active_sim_changed = CallableList()
        self.ui_objects = weakref.WeakSet()
        self.primitives = ()
        self._live_drag_objects = []
        self._live_drag_start_system = LiveDragLocation.INVALID
        self._live_drag_is_stack = False
        self._live_drag_sell_dialog_active = False
        self.objects_moved_via_live_drag = WeakSet()

    def __repr__(self):
        return '<Client {0:#x}>'.format(self.id)

    @property
    def account(self):
        return self._account

    @distributor.fields.Field(op=distributor.ops.UpdateClientActiveSim)
    def active_sim_info(self):
        return self._active_sim_info

    resend_active_sim_info = active_sim_info.get_resend()

    @active_sim_info.setter
    def active_sim_info(self, sim_info):
        self._set_active_sim_without_field_distribution(sim_info)

    @property
    def active_sim(self):
        if self.active_sim_info is not None:
            return self.active_sim_info.get_sim_instance(
                allow_hidden_flags=ALL_HIDDEN_REASONS)

    @active_sim.setter
    def active_sim(self, sim):
        self.active_sim_info = sim.sim_info

    def _set_active_sim_without_field_distribution(self, sim_info):
        if self._active_sim_info is not None and self._active_sim_info is sim_info:
            return
        current_sim = self._active_sim_info.get_sim_instance(
        ) if self._active_sim_info is not None else None
        if sim_info is not None:
            self._active_sim_info = sim_info
            if sim_info.household is not None:
                sim_info.household.on_active_sim_changed(sim_info)
        else:
            self._active_sim_info = None
        self.notify_active_sim_changed(current_sim, new_sim_info=sim_info)

    @property
    def choice_menu(self):
        return self._choice_menu

    @property
    def interaction_source(self):
        return self._interaction_source

    @interaction_source.setter
    def interaction_source(self, value):
        if value is None:
            if self._interaction_source is not Client._interaction_source:
                del self._interaction_source
        else:
            self._interaction_source = value

    @property
    def interaction_priority(self):
        return self._interaction_priority

    @interaction_priority.setter
    def interaction_priority(self, value):
        if value is None:
            if self._interaction_priority is not Client._interaction_priority:
                del self._interaction_priority
        else:
            self._interaction_priority = value

    @property
    def household_id(self):
        return self._household_id

    @property
    def household(self):
        household_manager = services.household_manager()
        if household_manager is not None:
            return household_manager.get(self._household_id)

    @property
    def selectable_sims(self):
        return self._selectable_sims

    def create_interaction_context(self, sim, **kwargs):
        context = interactions.context.InteractionContext(
            sim,
            self.interaction_source,
            self.interaction_priority,
            client=self,
            **kwargs)
        return context

    @property
    def live_drag_objects(self):
        return self._live_drag_objects

    def get_interaction_parameters(self):
        return self._interaction_parameters

    def set_interaction_parameters(self, **kwargs):
        self._interaction_parameters = kwargs

    def set_choices(self, new_choices):
        self._choice_menu = new_choices

    def select_interaction(self, choice_id, revision):
        if self.choice_menu is not None:
            if revision == self.choice_menu.revision:
                choice_menu = self.choice_menu
                self._choice_menu = None
                self.set_interaction_parameters()
                try:
                    return choice_menu.select(choice_id)
                except:
                    if choice_menu.simref is not None:
                        sim = choice_menu.simref()
                        if sim is not None:
                            sim.reset(
                                ResetReason.RESET_ON_ERROR,
                                cause=
                                'Exception while selecting interaction from the pie menu.'
                            )
                    raise

    def get_create_op(self, *args, **kwargs):
        return distributor.ops.ClientCreate(self,
                                            *args,
                                            is_active=True,
                                            **kwargs)

    def get_delete_op(self):
        return distributor.ops.ClientDelete()

    def get_create_after_objs(self):
        active = self.active_sim
        if active is not None:
            yield active
        household = self.household
        if household is not None:
            yield household

    @property
    def valid_for_distribution(self):
        return True

    def refresh_achievement_data(self):
        active_sim_info = None
        if self.active_sim is not None:
            active_sim_info = self.active_sim.sim_info
        self.account.achievement_tracker.refresh_progress(active_sim_info)

    def send_message(self, msg_id, msg):
        if self.active:
            omega.send(self.id, msg_id, msg.SerializeToString())
        else:
            logger.warn(
                'Message sent to client {} after it has already disconnected.',
                self)

    def validate_selectable_sim(self):
        if self._active_sim_info is None or not self._active_sim_info.is_enabled_in_skewer:
            self.set_next_sim()

    def set_next_sim(self):
        sim_info = self._selectable_sims.get_next_selectable(
            self._active_sim_info)
        if sim_info is self.active_sim_info:
            self.resend_active_sim_info()
            return False
        return self.set_active_sim_info(sim_info)

    def set_next_sim_or_none(self, only_if_this_active_sim_info=None):
        if only_if_this_active_sim_info is not None and self._active_sim_info is not only_if_this_active_sim_info:
            return
        sim_info = self._selectable_sims.get_next_selectable(
            self._active_sim_info)
        if sim_info is None:
            return self.set_active_sim_info(None)
        if sim_info is self._active_sim_info:
            return self.set_active_sim_info(None)
        return self.set_active_sim_info(sim_info)

    def set_active_sim_by_id(self, sim_id):
        if self.active_sim_info is not None and self.active_sim_info.id == sim_id:
            return False
        for sim_info in self._selectable_sims:
            if sim_info.sim_id == sim_id:
                if not sim_info.is_enabled_in_skewer:
                    return False
                return self.set_active_sim_info(sim_info)
        return False

    def set_active_sim(self, sim):
        return self.set_active_sim_info(sim.sim_info)

    def set_active_sim_info(self, sim_info):
        with telemetry_helper.begin_hook(writer,
                                         TELEMETRY_HOOK_ACTIVE_SIM_CHANGED,
                                         sim_info=sim_info):
            pass
        self.active_sim_info = sim_info
        return self._active_sim_info is not None

    def add_selectable_sim_info(self, sim_info, send_relationship_update=True):
        self._selectable_sims.add_selectable_sim_info(
            sim_info, send_relationship_update=send_relationship_update)
        if self.active_sim_info is None:
            self.set_next_sim()
        self.household.refresh_aging_updates(sim_info)

    def add_selectable_sim_by_id(self, sim_id):
        sim_info = services.sim_info_manager().get(sim_id)
        if sim_info is not None:
            self.add_selectable_sim_info(sim_info)

    def remove_selectable_sim_info(self, sim_info):
        self._selectable_sims.remove_selectable_sim_info(sim_info)
        if self.active_sim_info is None:
            self.set_next_sim()
        self.household.refresh_aging_updates(sim_info)

    def remove_selectable_sim_by_id(self, sim_id):
        if len(self._selectable_sims) <= 1:
            return False
        sim_info = services.sim_info_manager().get(sim_id)
        if sim_info is not None:
            self.remove_selectable_sim_info(sim_info)
        return True

    def make_all_sims_selectable(self):
        self.clear_selectable_sims()
        for sim_info in services.sim_info_manager().objects:
            self._selectable_sims.add_selectable_sim_info(sim_info)
        self.set_next_sim()

    def clear_selectable_sims(self):
        self.active_sim_info = None
        self._selectable_sims.clear_selectable_sims()

    def register_active_sim_changed(self, callback):
        if callback not in self._active_sim_changed:
            self._active_sim_changed.append(callback)

    def unregister_active_sim_changed(self, callback):
        if callback in self._active_sim_changed:
            self._active_sim_changed.remove(callback)

    def on_sim_added_to_skewer(self, sim_info, send_relationship_update=True):
        if send_relationship_update:
            sim_info.relationship_tracker.send_relationship_info()
        is_zone_running = services.current_zone().is_zone_running
        sim_info.on_sim_added_to_skewer()
        if not is_zone_running:
            sim_info.commodity_tracker.start_low_level_simulation()
        else:
            services.active_household().distribute_household_data()
        sim_info.commodity_tracker.send_commodity_progress_update(
            from_add=True)
        sim_info.career_tracker.on_sim_added_to_skewer()
        if sim_info.degree_tracker is not None:
            sim_info.degree_tracker.on_sim_added_to_skewer()
        sim_info.send_whim_bucks_update(SetWhimBucks.LOAD)
        sim_info.resend_trait_ids()
        sim = sim_info.get_sim_instance(allow_hidden_flags=ALL_HIDDEN_REASONS)
        if sim is not None:
            if is_zone_running:
                sim.inventory_component.visible_storage.allow_ui = True
                sim.inventory_component.publish_inventory_items()
            sim.ui_manager.refresh_ui_data()
            services.autonomy_service().logging_sims.add(sim)
            sim_info.start_aspiration_tracker_on_instantiation(
                force_ui_update=True)
            if sim_info.whim_tracker is not None:
                sim_info.whim_tracker.start_whims_tracker()
        zone_director = services.venue_service().get_zone_director()
        if zone_director is not None:
            zone_director.on_sim_added_to_skewer(sim_info)
        sim_info.trait_tracker.sort_and_send_commodity_list()

    def on_sim_removed_from_skewer(self, sim_info):
        sim = sim_info.get_sim_instance()
        if sim is not None:
            autonomy_service = services.autonomy_service()
            if autonomy_service is not None:
                autonomy_service.logging_sims.discard(sim)

    def clean_and_send_remaining_relationship_info(self):
        relationship_service = services.relationship_service()
        for sim_info in self.selectable_sims:
            sim_info.relationship_tracker.clean_and_send_remaining_relationship_info(
            )
            relationship_service.clean_and_send_remaining_object_relationships(
                sim_info.id)

    def cancel_live_drag_on_objects(self):
        for obj in self._live_drag_objects:
            obj.live_drag_component.cancel_live_dragging()
        self._live_drag_objects = []

    def _get_stack_items_from_drag_object(self,
                                          drag_object,
                                          remove=False,
                                          is_stack=False):
        if drag_object.inventoryitem_component is None:
            return (False, None)
        previous_inventory = drag_object.inventoryitem_component.get_inventory(
        )
        if previous_inventory is None:
            return (False, None)
        stack_id = drag_object.inventoryitem_component.get_stack_id()
        stack_items = previous_inventory.get_stack_items(stack_id)
        if remove:
            if is_stack:
                for item in stack_items:
                    success = previous_inventory.try_remove_object_by_id(
                        item.id, count=item.stack_count())
                else:
                    success = previous_inventory.try_remove_object_by_id(
                        drag_object.id, count=1)
                    stack_items = previous_inventory.get_stack_items(stack_id)
            else:
                success = previous_inventory.try_remove_object_by_id(
                    drag_object.id, count=1)
                stack_items = previous_inventory.get_stack_items(stack_id)
        else:
            success = True
        return (success, stack_items)

    def remove_drag_object_and_get_next_item(self, drag_object):
        next_object_id = None
        (success,
         stack_items) = self._get_stack_items_from_drag_object(drag_object,
                                                               remove=True)
        if success:
            if stack_items:
                next_object_id = stack_items[0].id
        return (success, next_object_id)

    def get_live_drag_object_value(self, drag_object, is_stack=False):
        (_, stack_items) = self._get_stack_items_from_drag_object(
            drag_object, remove=False, is_stack=is_stack)
        value = 0
        if is_stack and stack_items:
            for item in stack_items:
                value += item.current_value * item.stack_count()
        else:
            value = drag_object.current_value
        return value

    def start_live_drag(self,
                        live_drag_object,
                        start_system,
                        is_stack,
                        should_send_start_message: bool = True):
        self._live_drag_start_system = start_system
        success = True
        if is_stack:
            inventoryitem_component = live_drag_object.inventoryitem_component
            stack_id = inventoryitem_component.get_stack_id()
            current_inventory = inventoryitem_component.get_inventory()
            stack_items = current_inventory.get_stack_items(stack_id)
        else:
            stack_items = [live_drag_object]
        for item in stack_items:
            live_drag_component = live_drag_object.live_drag_component
            live_drag_component = item.live_drag_component
            if live_drag_component is None:
                logger_live_drag.error(
                    'Live Drag Start called on an object with no Live Drag Component. Object: {}'
                    .format(item))
                self.send_live_drag_cancel(live_drag_object.id)
                return
            if not ((not item.in_use or item.in_use_by(self))
                    and live_drag_component.can_live_drag):
                logger_live_drag.warn(
                    'Live Drag Start called on an object that is in use. Object: {}'
                    .format(item))
                self.send_live_drag_cancel(item.id)
                return
            success = live_drag_component.start_live_dragging(
                self, start_system)
            if not success:
                break
            self._live_drag_objects.append(item)
        if not success:
            self.cancel_live_drag_on_objects()
            self.send_live_drag_cancel(live_drag_object.id,
                                       LiveDragLocation.INVALID)
        self._live_drag_is_stack = is_stack
        if gsi_handlers.live_drag_handlers.live_drag_archiver.enabled:
            gsi_handlers.live_drag_handlers.archive_live_drag(
                'Start',
                'Operation',
                LiveDragLocation.GAMEPLAY_SCRIPT,
                start_system,
                live_drag_object_id=live_drag_object.id)
        if live_drag_object.live_drag_component.active_household_has_sell_permission:
            sell_value = self.get_live_drag_object_value(
                live_drag_object, self._live_drag_is_stack
            ) if live_drag_object.definition.get_is_deletable() else -1
            for child_object in live_drag_object.get_all_children_gen():
                sell_value += self.get_live_drag_object_value(
                    child_object) if child_object.definition.get_is_deletable(
                    ) else 0
        else:
            sell_value = -1
        (valid_drop_object_ids,
         valid_stack_id) = live_drag_component.get_valid_drop_object_ids()
        icon_info = create_icon_info_msg(live_drag_object.get_icon_info_data())
        if should_send_start_message:
            op = distributor.ops.LiveDragStart(live_drag_object.id,
                                               start_system,
                                               valid_drop_object_ids,
                                               valid_stack_id, sell_value,
                                               icon_info)
            distributor_system = Distributor.instance()
            distributor_system.add_op_with_no_owner(op)

    def end_live_drag(self,
                      source_object,
                      target_object=None,
                      end_system=LiveDragLocation.INVALID,
                      location=None):
        live_drag_component = source_object.live_drag_component
        if live_drag_component is None:
            logger_live_drag.error(
                'Live Drag End called on an object with no Live Drag Component. Object: {}'
                .format(source_object))
            self.send_live_drag_cancel(source_object.id, end_system)
            return
        if source_object not in self._live_drag_objects:
            logger_live_drag.warn(
                'Live Drag End called on an object not being Live Dragged. Object: {}'
                .format(source_object))
            self.send_live_drag_cancel(source_object.id, end_system)
            return
        source_object_id = source_object.id
        if end_system == LiveDragLocation.BUILD_BUY:
            self.objects_moved_via_live_drag.add(source_object)
        else:
            self.objects_moved_via_live_drag.discard(source_object)
        self.cancel_live_drag_on_objects()
        next_object_id = None
        success = False
        inventory_item = source_object.inventoryitem_component
        if target_object is not None:
            live_drag_target_component = target_object.live_drag_target_component
            if live_drag_target_component is not None:
                (success, next_object_id
                 ) = live_drag_target_component.drop_live_drag_object(
                     source_object, self._live_drag_is_stack)
            elif source_object.parent_object() is target_object:
                success = True
            elif source_object.parent_object(
            ) is None and location is not None:
                inventory_item = source_object.inventoryitem_component
                if inventory_item is not None:
                    if inventory_item.is_in_inventory():
                        (success, next_object_id
                         ) = self.remove_drag_object_and_get_next_item(
                             source_object)
                source_object.set_location(location)
            else:
                logger_live_drag.error(
                    'Live Drag Target Component missing on object: {} and {} cannot be slotted into it.'
                    .format(target_object, source_object))
                success = False
        elif inventory_item is not None and inventory_item.is_in_inventory():
            if inventory_item.can_place_in_world or not inventory_item.inventory_only:
                if location is not None:
                    source_object.set_location(location)
                (success, next_object_id
                 ) = self.remove_drag_object_and_get_next_item(source_object)
        else:
            success = True
            if location is not None:
                source_object.set_location(location)
        if success:
            if gsi_handlers.live_drag_handlers.live_drag_archiver.enabled:
                gsi_handlers.live_drag_handlers.archive_live_drag(
                    'End',
                    'Operation',
                    LiveDragLocation.GAMEPLAY_SCRIPT,
                    end_system,
                    live_drag_object_id=source_object_id,
                    live_drag_target=target_object)
            if not self._live_drag_is_stack:
                next_object_id = None
            op = distributor.ops.LiveDragEnd(source_object_id,
                                             self._live_drag_start_system,
                                             end_system, next_object_id)
            distributor_system = Distributor.instance()
            distributor_system.add_op_with_no_owner(op)
            self._live_drag_objects = []
            self._live_drag_start_system = LiveDragLocation.INVALID
            self._live_drag_is_stack = False
        else:
            self.send_live_drag_cancel(source_object_id, end_system)

    def cancel_live_drag(self,
                         live_drag_object,
                         end_system=LiveDragLocation.INVALID):
        live_drag_component = live_drag_object.live_drag_component
        if live_drag_component is None:
            logger_live_drag.warn(
                'Live Drag Cancel called on an object with no Live Drag Component. Object: {}'
                .format(live_drag_object))
            self.send_live_drag_cancel(live_drag_object.id)
            return
        if live_drag_component.live_drag_state == LiveDragState.NOT_LIVE_DRAGGING:
            logger_live_drag.warn(
                'Live Drag Cancel called on an object not being Live Dragged. Object: {}'
                .format(live_drag_object))
        else:
            self.cancel_live_drag_on_objects()
        self.send_live_drag_cancel(live_drag_object.id, end_system)

    def sell_live_drag_object(self,
                              live_drag_object,
                              end_system=LiveDragLocation.INVALID):
        live_drag_component = live_drag_object.live_drag_component
        if live_drag_component is None or not live_drag_object.definition.get_is_deletable(
        ):
            logger_live_drag.error(
                "Live Drag Sell called on object with no Live Drag Component or can't be deleted. Object: {}"
                .format(live_drag_object))
            self.send_live_drag_cancel(live_drag_object.id, end_system)
            return

        def sell_response(dialog):
            op = distributor.ops.LiveDragEnd(live_drag_object.id,
                                             self._live_drag_start_system,
                                             end_system,
                                             next_stack_object_id=None)
            distributor_system = Distributor.instance()
            distributor_system.add_op_with_no_owner(op)
            if not dialog.accepted:
                self.cancel_live_drag_on_objects()
                return
            value = int(
                self.get_live_drag_object_value(live_drag_object,
                                                self._live_drag_is_stack))
            for child_object in live_drag_object.get_all_children_gen():
                value += self.get_live_drag_object_value(
                    child_object) if child_object.definition.get_is_deletable(
                    ) else 0
            object_tags = set()
            if self._live_drag_is_stack:
                (_, stack_items) = self._get_stack_items_from_drag_object(
                    live_drag_object, remove=True, is_stack=True)
                for item in stack_items:
                    live_drag_component = item.live_drag_component
                    live_drag_component.cancel_live_dragging(
                        should_reset=False)
                    item.base_value = 0
                    item.set_stack_count(0)
                    object_tags.update(item.get_tags())
                    item.destroy(source=item,
                                 cause='Selling stack of live drag objects.')
            else:
                live_drag_object.live_drag_component.cancel_live_dragging(
                    should_reset=False)
                object_tags.update(live_drag_object.get_tags())
                if live_drag_object.is_in_inventory():
                    self.remove_drag_object_and_get_next_item(live_drag_object)
                else:
                    live_drag_object.remove_from_client()
                object_tags = frozenset(object_tags)
                live_drag_object.base_value = 0
                live_drag_object.destroy(source=live_drag_object,
                                         cause='Selling live drag object.')
            services.active_household().funds.add(
                value,
                Consts_pb2.TELEMETRY_OBJECT_SELL,
                self.active_sim,
                tags=object_tags)
            self._live_drag_objects = []
            self._live_drag_start_system = LiveDragLocation.INVALID
            self._live_drag_is_stack = False
            self._live_drag_sell_dialog_active = False

        favorites_tracker = self.active_sim_info.favorites_tracker
        if favorites_tracker and favorites_tracker.is_favorite_stack(
                live_drag_object):
            dialog = LiveDragTuning.LIVE_DRAG_SELL_FAVORITE_DIALOG(
                owner=live_drag_object)
        elif self._live_drag_is_stack:
            dialog = LiveDragTuning.LIVE_DRAG_SELL_STACK_DIALOG(
                owner=live_drag_object)
        else:
            dialog = LiveDragTuning.LIVE_DRAG_SELL_DIALOG(
                owner=live_drag_object)
        dialog.show_dialog(on_response=sell_response)
        self._live_drag_sell_dialog_active = True

    def send_live_drag_cancel(self,
                              live_drag_object_id,
                              live_drag_end_system=LiveDragLocation.INVALID):
        if gsi_handlers.live_drag_handlers.live_drag_archiver.enabled:
            gsi_handlers.live_drag_handlers.archive_live_drag(
                'Cancel',
                'Operation',
                LiveDragLocation.GAMEPLAY_SCRIPT,
                live_drag_end_system,
                live_drag_object_id=live_drag_object_id)
        op = distributor.ops.LiveDragCancel(live_drag_object_id,
                                            self._live_drag_start_system,
                                            live_drag_end_system)
        distributor_system = Distributor.instance()
        distributor_system.add_op_with_no_owner(op)
        if not self._live_drag_sell_dialog_active:
            self._live_drag_objects = []
            self._live_drag_start_system = LiveDragLocation.INVALID
            self._live_drag_is_stack = False

    def on_add(self):
        if self._account is not None:
            self._account.register_client(self)
        for sim_info in self._selectable_sims:
            self.on_sim_added_to_skewer(sim_info)
        distributor = Distributor.instance()
        distributor.add_object(self)
        distributor.add_client(self)
        self.send_selectable_sims_update()
        self.selectable_sims.add_watcher(self,
                                         self.send_selectable_sims_update)

    def on_remove(self):
        if self.active_sim is not None:
            self._set_active_sim_without_field_distribution(None)
        if self._account is not None:
            self._account.unregister_client(self)
        for sim_info in self._selectable_sims:
            self.on_sim_removed_from_skewer(sim_info)
        self.selectable_sims.remove_watcher(self)
        distributor = Distributor.instance()
        distributor.remove_client(self)
        self._selectable_sims = None
        self.active = False

    def get_objects_in_view_gen(self):
        for manager in services.client_object_managers():
            for obj in manager.get_all():
                yield obj

    def notify_active_sim_changed(self, old_sim, new_sim_info=None):
        new_sim = new_sim_info.get_sim_instance(
        ) if new_sim_info is not None else None
        self._active_sim_changed(old_sim, new_sim)
        vfx_mask.notify_client_mask_update(new_sim_info)

    def _get_selector_visual_type(self, sim_info):
        if sim_info.is_baby:
            return (Sims_pb2.SimPB.BABY, None)
        if sim_info.is_toddler and services.daycare_service(
        ).is_sim_info_at_daycare(sim_info):
            return (Sims_pb2.SimPB.AT_DAYCARE, None)
        if sim_info.household.missing_pet_tracker.is_pet_missing(sim_info):
            return (Sims_pb2.SimPB.PET_MISSING, None)
        sim = sim_info.get_sim_instance(allow_hidden_flags=ALL_HIDDEN_REASONS)
        for career in sim_info.careers.values():
            if career.currently_at_work:
                if career.is_at_active_event and sim is None:
                    return (Sims_pb2.SimPB.MISSING_ACTIVE_WORK,
                            career.career_category)
                return (Sims_pb2.SimPB.AT_WORK, career.career_category)
            if career.is_late:
                if not career.taking_day_off:
                    return (Sims_pb2.SimPB.LATE_FOR_WORK,
                            career.career_category)
        if services.get_rabbit_hole_service(
        ).should_override_selector_visual_type(sim_info.id):
            return (Sims_pb2.SimPB.OTHER, None)
        if sim is not None and sim.has_hidden_flags(
                HiddenReasonFlag.RABBIT_HOLE):
            return (Sims_pb2.SimPB.OTHER, None)
        if services.hidden_sim_service().is_hidden(sim_info.id):
            return (Sims_pb2.SimPB.OTHER, None)
        tutorial_service = services.get_tutorial_service()
        if tutorial_service is not None and tutorial_service.is_sim_unselectable(
                sim_info):
            return (Sims_pb2.SimPB.OTHER, None)
        return (Sims_pb2.SimPB.NORMAL, None)

    def send_selectable_sims_update(self):
        msg = Sims_pb2.UpdateSelectableSims()
        for sim_info in self._selectable_sims:
            with ProtocolBufferRollback(msg.sims) as new_sim:
                new_sim.id = sim_info.sim_id
                if sim_info.career_tracker is None:
                    logger.error(
                        'CareerTracker is None for selectable Sim {}'.format(
                            sim_info))
                else:
                    career = sim_info.career_tracker.get_currently_at_work_career(
                    )
                    new_sim.at_work = career is not None and not career.is_at_active_event
                new_sim.is_selectable = sim_info.is_enabled_in_skewer
                (selector_visual_type,
                 career_category) = self._get_selector_visual_type(sim_info)
                new_sim.selector_visual_type = selector_visual_type
                if career_category is not None:
                    new_sim.career_category = career_category
                new_sim.can_care_for_toddler_at_home = sim_info.can_care_for_toddler_at_home
                if not sim_info.is_instanced(
                        allow_hidden_flags=ALL_HIDDEN_REASONS):
                    new_sim.instance_info.zone_id = sim_info.zone_id
                    new_sim.instance_info.world_id = sim_info.world_id
                    new_sim.firstname = sim_info.first_name
                    new_sim.lastname = sim_info.last_name
                    zone_data_proto = services.get_persistence_service(
                    ).get_zone_proto_buff(sim_info.zone_id)
                    if zone_data_proto is not None:
                        new_sim.instance_info.zone_name = zone_data_proto.name
        distributor = Distributor.instance()
        distributor.add_op_with_no_owner(
            GenericProtocolBufferOp(Operation.SELECTABLE_SIMS_UPDATE, msg))

    @constproperty
    def is_sim():
        return False
コード例 #30
0
ファイル: privacy.py プロジェクト: johndpope/sims4-ai-engine
class Privacy(LineOfSight):
    __qualname__ = 'Privacy'
    _PRIVACY_FOOTPRINT_TYPE = 5
    _PRIVACY_DISCOURAGEMENT_COST = routing.get_default_discouragement_cost()
    _SHOO_CONSTRAINT_RADIUS = Tunable(description='\n        The radius of the constraint a Shooed Sim will attempt to route to.\n        ', tunable_type=float, default=2.5)
    _UNAVAILABLE_TOOLTIP = TunableLocalizedStringFactory(description='\n        Tooltip displayed when an object is not accessible due to being inside\n        a privacy region.\n        ')
    _EMBARRASSED_AFFORDANCE = TunableReference(description='\n        The affordance a Sim will play when getting embarrassed by walking in\n        on a privacy situation.\n        ', manager=services.affordance_manager())

    def __init__(self, interaction, tests, max_line_of_sight_radius, map_divisions, simplification_ratio, boundary_epsilon, facing_offset):
        super().__init__(max_line_of_sight_radius, map_divisions, simplification_ratio, boundary_epsilon)
        self._max_line_of_sight_radius = max_line_of_sight_radius
        self._interaction = interaction
        self._tests = tests
        self._privacy_constraints = []
        self._allowed_sims = WeakSet()
        self._disallowed_sims = WeakSet()
        self._violators = WeakSet()
        self._late_violators = WeakSet()
        self.is_active = False
        self.has_shooed = False
        self.central_object = None
        self._pushed_interactions = []
        services.privacy_service().add_instance(self)

    @property
    def unavailable_tooltip(self):
        return self._UNAVAILABLE_TOOLTIP

    @property
    def interaction(self):
        return self._interaction

    @property
    def is_active(self) -> bool:
        return self._is_active

    @is_active.setter
    def is_active(self, value):
        self._is_active = value

    def _is_sim_allowed(self, sim):
        if self._tests:
            resolver = self._interaction.get_resolver(target=sim)
            if self._tests and self._tests.run_tests(resolver):
                return True
        if self._interaction.can_sim_violate_privacy(sim):
            return True
        return False

    def evaluate_sim(self, sim):
        if self._is_sim_allowed(sim):
            self._allowed_sims.add(sim)
            return True
        self._disallowed_sims.add(sim)
        return False

    def build_privacy(self, target=None):
        self.is_active = True
        target_object = self._interaction.get_participant(ParticipantType.Object)
        target_object = None if target_object.is_sim else target_object
        self.central_object = target_object or (target or self._interaction.sim)
        self.generate(self.central_object.position, self.central_object.routing_surface)
        for poly in self.constraint.geometry.polygon:
            self._privacy_constraints.append(PolygonFootprint(poly, routing_surface=self._interaction.sim.routing_surface, cost=self._PRIVACY_DISCOURAGEMENT_COST, footprint_type=self._PRIVACY_FOOTPRINT_TYPE, enabled=True))
        self._allowed_sims.update(self._interaction.get_participants(ParticipantType.AllSims))
        for sim in services.sim_info_manager().instanced_sims_gen():
            while sim not in self._allowed_sims:
                self.evaluate_sim(sim)
        violating_sims = self.find_violating_sims()
        self._cancel_unavailable_interactions(violating_sims)
        self._add_overrides_and_constraints_if_needed(violating_sims)

    def cleanup_privacy_instance(self):
        if self.is_active:
            self.is_active = False
            for sim in self._allowed_sims:
                self.remove_override_for_sim(sim)
            for sim in self._late_violators:
                self.remove_override_for_sim(sim)
            del self._privacy_constraints[:]
            self._allowed_sims.clear()
            self._disallowed_sims.clear()
            self._violators.clear()
            self._late_violators.clear()
            self._cancel_pushed_interactions()

    def remove_privacy(self):
        self.cleanup_privacy_instance()
        services.privacy_service().remove_instance(self)

    def intersects_with_object(self, obj):
        if obj.routing_surface != self.central_object.routing_surface:
            return False
        delta = obj.position - self.central_object.position
        distance = delta.magnitude_2d_squared()
        if distance > self.max_line_of_sight_radius*self.max_line_of_sight_radius:
            return False
        object_footprint = obj.footprint_polygon
        if object_footprint is None:
            object_footprint = sims4.geometry.Polygon([obj.position])
        for poly in self.constraint.geometry.polygon:
            intersection = poly.intersect(object_footprint)
            while intersection is not None and intersection.has_enough_vertices:
                return True
        return False

    def find_violating_sims(self):
        if not self.is_active:
            return []
        nearby_sims = placement.get_nearby_sims(self.central_object.position, self.central_object.routing_surface.secondary_id, radius=self.max_line_of_sight_radius, exclude=self._allowed_sims, only_sim_position=True)
        violators = []
        for sim in nearby_sims:
            if any(sim_primitive.is_traversing_portal() for sim_primitive in sim.primitives if isinstance(sim_primitive, FollowPath)):
                pass
            if sim not in self._disallowed_sims and self.evaluate_sim(sim):
                pass
            while sims4.geometry.test_point_in_compound_polygon(sim.position, self.constraint.geometry.polygon):
                violators.append(sim)
        return violators

    def _add_overrides_and_constraints_if_needed(self, violating_sims):
        for sim in self._allowed_sims:
            self.add_override_for_sim(sim)
        for sim in violating_sims:
            self._violators.add(sim)
            liabilities = ((SHOO_LIABILITY, ShooLiability(self, sim)),)
            result = self._route_sim_away(sim, liabilities=liabilities)
            while result:
                self._pushed_interactions.append(result.interaction)

    def _cancel_unavailable_interactions(self, violating_sims):
        for sim in violating_sims:
            interactions_to_cancel = set()
            if sim.queue.running is not None:
                interactions_to_cancel.add(sim.queue.running)
            for interaction in sim.si_state:
                while interaction.is_super and interaction.target is not None and sim.locked_from_obj_by_privacy(interaction.target):
                    interactions_to_cancel.add(interaction)
            for interaction in sim.queue:
                if interaction.target is not None and sim.locked_from_obj_by_privacy(interaction.target):
                    interactions_to_cancel.add(interaction)
                else:
                    while interaction.target is not None:
                        break
            for interaction in interactions_to_cancel:
                interaction.cancel(FinishingType.INTERACTION_INCOMPATIBILITY, cancel_reason_msg='Canceled due to incompatibility with privacy instance.')

    def _route_sim_away(self, sim, liabilities=()):
        context = InteractionContext(sim, InteractionContext.SOURCE_SCRIPT, Priority.High, insert_strategy=QueueInsertStrategy.NEXT)
        from interactions.utils.satisfy_constraint_interaction import BuildAndForceSatisfyShooConstraintInteraction
        result = sim.push_super_affordance(BuildAndForceSatisfyShooConstraintInteraction, None, context, liabilities=liabilities, privacy_inst=self, name_override='BuildShooFromPrivacy')
        if not result:
            logger.debug('Failed to push BuildAndForceSatisfyShooConstraintInteraction on Sim {} to route them out of a privacy area.  Result: {}', sim, result, owner='tastle')
            self.interaction.cancel(FinishingType.TRANSITION_FAILURE, cancel_reason_msg='Failed to shoo Sims away.')
        return result

    def _cancel_pushed_interactions(self):
        for interaction in self._pushed_interactions:
            interaction.cancel(FinishingType.AUTO_EXIT, cancel_reason_msg='Privacy finished and is cleaning up.')
        self._pushed_interactions.clear()

    def handle_late_violator(self, sim):
        self._cancel_unavailable_interactions((sim,))
        self.add_override_for_sim(sim)
        liabilities = ((LATE_SHOO_LIABILITY, LateShooLiability(self, sim)),)
        result = self._route_sim_away(sim, liabilities=liabilities)
        if not result:
            return
        if not self._violators:
            context = InteractionContext(sim, InteractionContext.SOURCE_SCRIPT, Priority.High, insert_strategy=QueueInsertStrategy.NEXT)
            result = sim.push_super_affordance(self._EMBARRASSED_AFFORDANCE, self.interaction.get_participant(ParticipantType.Actor), context)
            if not result:
                logger.error('Failed to push the embarrassed affordance on Sim {}. Interaction {}. Result {}. Context {} ', sim, self.interaction, result, context, owner='tastle')
                return
        self._late_violators.add(sim)

    def add_override_for_sim(self, sim):
        for footprint in self._privacy_constraints:
            sim.routing_context.ignore_footprint_contour(footprint.footprint_id)

    def remove_override_for_sim(self, sim):
        for footprint in self._privacy_constraints:
            sim.routing_context.remove_footprint_contour_override(footprint.footprint_id)

    @property
    def allowed_sims(self):
        return self._allowed_sims

    @property
    def disallowed_sims(self):
        return self._disallowed_sims

    @property
    def violators(self):
        return self._violators

    def remove_violator(self, sim):
        self.remove_override_for_sim(sim)
        self._violators.discard(sim)

    @property
    def late_violators(self):
        return self._late_violators

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

    def test(self):
        return True

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    @classproperty
    def posture_type(cls):
        return cls

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

    @classmethod
    def _tuning_loading_callback(cls):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    def _on_reset(self):
        self._primitive = None

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

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

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

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

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

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

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

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

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

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

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

    @classproperty
    def multi_sim(cls):
        return False

    @property
    def is_puppet(self):
        return False

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

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

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

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

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

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

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

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

    def _setup_asm_carry_parameter(self, asm, target):
        pass

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

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

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

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

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

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

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

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

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

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

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

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

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

        return _do_begin

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

    def end(self):

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

        return _do_end

    def add_transition_extras(self, sequence):
        return sequence

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    @classmethod
    def is_valid_target(cls, sim, target, **kwargs):
        return True
コード例 #32
0
class ReserveObjectHandler:
    __qualname__ = 'ReserveObjectHandler'
    LOCKOUT_TIME = TunableSimMinute(480, description='Number of sim minutes to lockout an in use object from autonomy.')

    def __init__(self, sim, target, reserver, all_parts=False):
        self._sim = sim
        self._target = target.ref()
        self._reserver = reserver
        self._registered = False
        self._all_parts = all_parts
        self._reserved_objects = WeakSet()

    @property
    def is_multi(self):
        return False

    def _is_valid_target(self, target):
        return True

    def get_targets(self):
        if self._target is not None:
            target = self._target()
            if not target.is_sim:
                if not self._all_parts:
                    return (target,)
                if target.is_part:
                    target = target.part_owner
                if target.parts:
                    return target.parts
                return (target,)
        return ()

    def _begin(self, element):
        if self.reserve():
            return True
        return False

    def reserve(self):
        if self._registered:
            return True
        if self.may_reserve():
            for target in self.get_targets():
                target.reserve(self._sim, self._reserver, multi=self.is_multi)
                self._reserved_objects.add(target)
            self._registered = True
            return True
        return False

    def end(self, *_, **__):
        if self._registered:
            for target in self._reserved_objects:
                target.release(self._sim, self._reserver, multi=self.is_multi)
            self._registered = False

    def may_reserve(self, *args, **kwargs):
        targets = self.get_targets()
        for target in targets:
            test_result = self._is_valid_target(target)
            if not test_result:
                return test_result
            reserve_result = target.may_reserve(self._sim, multi=self.is_multi, *args, **kwargs)
            while not reserve_result:
                return reserve_result
        return TestResult.TRUE

    def do_reserve(self, sequence=None):
        return build_critical_section_with_finally(self._begin, sequence, self.end)