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()
示例#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 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)
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)
示例#5
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)
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
class LinkedObjectComponent(
        AutoFactoryInit,
        objects.components.Component,
        HasTunableFactory,
        allow_dynamic=True,
        component_name=objects.components.types.LINKED_OBJECT_COMPONENT):
    FACTORY_TUNABLES = {
        '_parent_state_value':
        OptionalTunable(
            description=
            "\n            When enabled, this state will be applied to the parent when\n            it has children.\n            \n            For example, the default link state for the console is unlinked.\n            If you set this to the linked state, then when it becomes the\n            parent to a T.V. it'll change the console to the linked state.\n            When the T.V. is unlinked, the console will revert back to \n            the unlinked state.\n            ",
            tunable=TunablePackSafeReference(
                description=
                '\n                state value to apply to parent objects.\n                Behaves as disabled if state not in installed data.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.OBJECT_STATE),
                class_restrictions=('ObjectStateValue', ))),
        '_child_state_value':
        OptionalTunable(
            description=
            "\n            When enabled, this state will be applied to the children.\n\n            For example, the default link state for a T.V is unlinked.\n            If you set this to the linked state, then when it becomes the\n            child of a console. it'll change the T.V. to the linked state.\n            When the T.V. is unlinked, the T.V. will revert back to \n            the unlinked state.\n            ",
            tunable=TunablePackSafeReference(
                description=
                '\n                state value to apply to child objects.\n                Behaves as disabled if state not in installed data.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.OBJECT_STATE),
                class_restrictions=('ObjectStateValue', ))),
        '_child_tag':
        TunableEnumWithFilter(
            description=
            '\n            Tag that determines which objects can be linked.\n            ',
            tunable_type=tag.Tag,
            filter_prefixes=['func'],
            default=tag.Tag.INVALID,
            invalid_enums=(tag.Tag.INVALID, )),
        '_distance':
        TunableDistanceSquared(
            description=
            '\n            Max distance from component owner and still be\n            linkable.\n            ',
            default=3),
        '_count':
        TunableRange(
            description=
            '\n            Max number of children to link.\n            ',
            tunable_type=int,
            default=1,
            minimum=1)
    }

    def __init__(self, *args, parent=True, **kwargs):
        super().__init__(*args, **kwargs)
        self._children = WeakSet()
        self._parent = self.owner
        self._return_state_value = None
        self._is_parent = parent

    def on_add(self):
        self._start()

    def on_remove(self):
        self._stop()

    def on_added_to_inventory(self):
        self._stop()

    def on_removed_from_inventory(self):
        self._start()
        if self._is_parent:
            self._add_all()

    def component_reset(self, reset_reason):
        if reset_reason != ResetReason.BEING_DESTROYED:
            self._relink(from_reset=True)
        elif self._is_parent:
            self.unlink_all_children()

    def on_finalize_load(self):
        self._relink()

    def on_post_load(self):
        if self._is_parent and self._children:
            self._return_state_value = None
            self.link(None, self._parent_state_value)

    def on_location_changed(self, old_location):
        if not services.current_zone(
        ).is_zone_loading and self._parent is not self.owner:
            self._relink(
                update_others=not services.current_zone().is_in_build_buy)

    def on_reset_component_get_interdependent_reset_records(
            self, reset_reason, reset_records):
        owner_users = self.owner.get_users()
        for obj in self.get_linked_objects_gen():
            if self._has_active_link(owner_users, obj):
                reset_records.append(
                    ResetRecord(obj, ResetReason.RESET_EXPECTED, self,
                                'Linked object reset'))

    def _start(self):
        self._parent = None
        if self._is_parent:
            build_buy.register_build_buy_exit_callback(self._relink)

    def _add_all(self):
        self._children = self._get_nearby_objects()
        for child in self._children:
            self._link_child(child)
        if self._children:
            self.link(None, self._parent_state_value)

    def _stop(self):
        if self._parent is self.owner:
            return
        old_parent = self._parent
        self._parent = self.owner
        if self._is_parent:
            build_buy.unregister_build_buy_exit_callback(self._relink)
            self.unlink_all_children()
        elif old_parent is not None:
            old_parent.linked_object_component.child_unlinked(self.owner)

    def unlink_all_children(self, update_others=False):
        for child in self._children:
            self._unlink(child)
        if update_others:
            self._update_others(self._children)
        self._children.clear()
        self.unlink_self()

    def _has_active_link(self, owner_users, severed_object):
        return owner_users & severed_object.get_users()

    def _relink(self, update_others=False, from_reset=False):
        if self._is_parent:
            if not self._children:
                self._add_all()
                return
            new_children = self._get_nearby_objects()
            removed_children = self._children - new_children
            if not from_reset:
                owner_users = self.owner.get_users()
                for child in removed_children:
                    if self._has_active_link(owner_users, child):
                        self.owner.reset(ResetReason.RESET_EXPECTED, None,
                                         'Unlinking child')
                        return
            if not new_children:
                self.unlink_all_children(update_others=update_others)
                return
            for child in removed_children:
                self._unlink(child)
            for child in new_children - self._children:
                self._link_child(child)
            self._children = new_children
            if removed_children and update_others:
                self._update_others(removed_children)
        elif self._parent is not None:
            self._parent.linked_object_component.refresh(self.owner)

    def refresh(self, child):
        if self._is_parent:
            if child not in self._children:
                logger.error(
                    "Refreshing linked child: {} that isn't in parent {}",
                    child, self.owner)
                return
            child.linked_object_component.link(self.owner,
                                               self._child_state_value)

    def unlink_self(self):
        if self._parent is not self.owner:
            self._parent = None
        if self._return_state_value is not None:
            self.owner.state_component.reset_state_to_default(
                self._return_state_value)
        self._return_state_value = None

    def _unlink(self, child):
        if child not in self._children:
            logger.error("Removing linked child: {} that isn't in parent {}",
                         child, self.owner)
            return
        child.reset(ResetReason.RESET_EXPECTED, self.owner,
                    'Unlinking from parent')
        child.linked_object_component.unlink_self()
        child.remove_component(
            objects.components.types.LINKED_OBJECT_COMPONENT)

    def child_unlinked(self, child):
        if child not in self._children:
            logger.error("Removing linked child: {} that isn't in parent {}",
                         child, self.owner)
            return
        self._relink()

    def _link_child(self, child):
        if child.linked_object_component is None:
            child.add_dynamic_component(
                objects.components.types.LINKED_OBJECT_COMPONENT,
                parent=False,
                _parent_state_value=None,
                _child_tag=None,
                _child_state_value=None,
                _count=None,
                _distance=None)
        child.linked_object_component.link(self.owner, self._child_state_value)

    def link(self, parent, state_value):
        self._parent = parent
        if state_value is not None:
            state_component = self.owner.state_component
            if state_component is not None:
                state = state_value.state
                if self._return_state_value is None:
                    if state_component.has_state(state):
                        self._return_state_value = state_component.get_state(
                            state)
                state_component.set_state(state_value.state, state_value)

    @componentmethod_with_fallback(lambda: [])
    def get_linked_objects_gen(self):
        if self._is_parent:
            yield from self._children
        elif self._parent is not None:
            if self._parent is not self.owner:
                yield self._parent
                for child in self._parent.linked_object_component.get_linked_objects_gen(
                ):
                    if child is not self.owner:
                        yield child

    def _get_nearby_objects(self):
        if self.owner.is_hidden():
            return ()
        filtered_near_objects = []
        nearby_objects = services.object_manager().get_objects_with_tag_gen(
            self._child_tag)
        for test_object in nearby_objects:
            if self._is_valid_child(test_object):
                dist_square = (self.owner.position -
                               test_object.position).magnitude_2d_squared()
                if dist_square < self._distance:
                    filtered_near_objects.append((dist_square, test_object))
        filtered_near_objects.sort(key=operator.itemgetter(0))
        return_list = set([x[1] for x in filtered_near_objects[:self._count]])
        return return_list

    def _is_valid_child(self, test_object):
        linked_object_component = test_object.linked_object_component
        if linked_object_component is not None:
            if linked_object_component._is_parent:
                return False
            if linked_object_component._parent is not self.owner and test_object.linked_object_component._parent is not None:
                return False
        if test_object.level != self.owner.level:
            return False
        elif test_object.is_hidden():
            return False
        return True

    def _update_others(self, new_children):
        owner = self.owner
        for obj in services.object_manager().get_valid_objects_gen():
            if obj.linked_object_component is not None:
                if obj is not owner:
                    new_children = obj.linked_object_component._try_add_links(
                        new_children)
                    if not new_children:
                        break

    def _try_add_links(self, new_children):
        if self.owner is self._parent:
            return new_children
        if len(self._children) == self._count:
            return new_children
        else:
            filtered_near_objects = []
            for test_object in new_children:
                if test_object.has_tag(self._child_tag):
                    if test_object.level == self.owner.level:
                        dist_square = (
                            self.owner.position -
                            test_object.position).magnitude_2d_squared()
                        if dist_square < self._distance:
                            filtered_near_objects.append(
                                (dist_square, test_object))
            if filtered_near_objects:
                filtered_near_objects.sort(key=operator.itemgetter(0))
                new_set = set([
                    x[1] for x in filtered_near_objects[:self._count -
                                                        len(self._children)]
                ])
                for child in new_set:
                    self._link_child(child)
                if not self._children:
                    self.link(None, self._parent_state_value)
                self._children |= new_set
                return new_children - new_set
        return new_children
示例#8
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)