示例#1
0
 def _get_constraint(self, sim):
     if self._focus is None:
         logger.error(
             'Attempt to get a constraint for a Sim before the group constraint is initialized: {} for {}',
             self,
             sim,
             owner='camilogarcia')
         return Anywhere()
     geometric_constraint = self._constraint
     if geometric_constraint is None:
         logger.error(
             'Attempt to get the constraint from a Social group before it has been initialized. Social Group is {}, Size of group is {}, and minimum number allowed for group is {}',
             self,
             len(self),
             self.minimum_sim_count,
             owner='camilogarcia')
         return Anywhere()
     scoring_constraints = []
     for other_sim in self:
         if other_sim is sim:
             continue
         facing_anchor = self._anchor_object if self._anchor_object is not None else other_sim
         force_readjustment = sim.id in self._pending_adjustments
         if force_readjustment:
             self._pending_adjustments.remove(sim.id)
         scoring_constraint = self.facing_restriction.create_constraint(
             sim,
             facing_anchor,
             scoring_functions=(self.scoring_function(
                 sim, other_sim, force_readjustment), ))
         scoring_constraints.append(scoring_constraint)
     scoring_constraints = create_constraint_set(scoring_constraints)
     geometric_constraint = geometric_constraint.intersect(
         scoring_constraints)
     return geometric_constraint
 def get_constraint(self, sim, **kwargs):
     carryable = self._obj.get_component(CARRYABLE_COMPONENT)
     if carryable is not None and carryable.constraint_pick_up is not None:
         constraint_total = Anywhere()
         for constraint_factory in carryable.constraint_pick_up:
             constraint = constraint_factory.create_constraint(sim, target=self._obj, routing_surface=self._obj.routing_surface)
             constraint_total = constraint_total.intersect(constraint)
         return constraint_total
     return super().get_constraint(sim)
 def get_constraint(self):
     if self._constraint is None or not self._constraint.valid:
         self._constraint = Anywhere()
         for tuned_constraint in self.constraints:
             self._constraint = self._constraint.intersect(
                 tuned_constraint.create_constraint(
                     None,
                     target=self.broadcasting_object,
                     target_position=self.broadcasting_object.position))
     return self._constraint
 def get_pick_up_constraint(self, sim):
     if self.constraint_pick_up is None:
         return
     final_constraint = Anywhere()
     for constraint in self.constraint_pick_up:
         constraint = self._get_adjusted_circle_constraint(sim, constraint)
         constraint = constraint.create_constraint(sim, target=self.owner)
         final_constraint = final_constraint.intersect(constraint)
     final_constraint = final_constraint._copy(_multi_surface=True)
     return final_constraint
示例#5
0
 def _do_behavior(self):
     self._reaction_constraint = Anywhere()
     for tuned_reaction_constraint in self.reaction_constraints:
         self._reaction_constraint = self._reaction_constraint.intersect(
             tuned_reaction_constraint.create_constraint(
                 None, target=self._reaction_target_sim))
     if self.trigger_on_late_arrivals:
         self._reaction_target_sim.reaction_triggers[
             self.interaction] = self
     for sim in services.sim_info_manager().instanced_sims_gen():
         self.intersect_and_execute(sim)
 def get_constraint(self, sim, **kwargs):
     carryable = self._obj.get_component(CARRYABLE_COMPONENT)
     if carryable is not None and carryable.constraint_pick_up is not None:
         constraint_total = Anywhere()
         for constraint_factory in carryable.constraint_pick_up:
             constraint = constraint_factory.create_constraint(
                 sim,
                 target=self._obj,
                 routing_surface=self._obj.routing_surface)
             constraint_total = constraint_total.intersect(constraint)
         return constraint_total
     return super().get_constraint(sim)
 def constraint_intersection(self):
     if self._constraint_intersection_dirty:
         intersection = Anywhere()
         for constraint in set(self._constraints.values()):
             new_intersection = intersection.intersect(constraint)
             if not self._invalid_expected and not new_intersection.valid:
                 logger.error('Invalid constraint intersection for PostureState:{}', self)
                 intersection = new_intersection
                 break
             intersection = new_intersection
         self._constraint_intersection_dirty = False
         self._constraint_intersection = intersection
     return self._constraint_intersection
 def _find_target(self):
     all_objects = self.target_type.get_objects()
     objects = []
     for o in all_objects:
         dist_sq = (o.position - self._obj.position).magnitude_squared()
         if dist_sq > self.radius:
             continue
         if o == self:
             continue
         if not o.is_sim and not o.may_reserve(self._obj):
             continue
         if self.target_selection_test:
             resolver = DoubleObjectResolver(self._obj, o)
             if not self.target_selection_test.run_tests(resolver):
                 continue
         else:
             objects.append([o, dist_sq])
     if not objects:
         return
     source_handles = [
         routing.connectivity.Handle(self._obj.position,
                                     self._obj.routing_surface)
     ]
     dest_handles = []
     for o in objects:
         obj = o[0]
         parent = obj.parent
         route_to_obj = parent if parent is not None else obj
         constraint = Anywhere()
         for tuned_constraint in self.constraints:
             constraint = constraint.intersect(
                 tuned_constraint.create_constraint(self._obj,
                                                    route_to_obj))
         dests = constraint.get_connectivity_handles(self._obj, target=obj)
         if dests:
             dest_handles.extend(dests)
     if not dest_handles:
         return
     routing_context = self._obj.get_routing_context()
     connections = routing.estimate_path_batch(
         source_handles, dest_handles, routing_context=routing_context)
     if not connections:
         return
     connections.sort(key=lambda connection: connection[2])
     best_connection = connections[0]
     best_dest_handle = best_connection[1]
     best_obj = best_dest_handle.target
     return best_obj
示例#9
0
 def make_constraint_default(cls,
                             actor,
                             target_sim,
                             position,
                             routing_surface,
                             participant_type=ParticipantType.Actor,
                             picked_object=None,
                             participant_slot_overrides=None):
     (actor_transform, target_transform,
      routing_surface) = cls._get_jig_transforms(
          actor,
          target_sim,
          picked_object=picked_object,
          participant_slot_overrides=participant_slot_overrides)
     if actor_transform is None or target_transform is None:
         return Nowhere()
     if participant_type == ParticipantType.Actor:
         constraint_transform = actor_transform
     elif participant_type == ParticipantType.TargetSim:
         constraint_transform = target_transform
     else:
         return Anywhere()
     return interactions.constraints.Transform(
         constraint_transform,
         routing_surface=routing_surface,
         debug_name='JigGroupConstraint')
示例#10
0
 def prepare_gen(self, timeline, *args, **kwargs):
     result = yield super().prepare_gen(timeline, *args, **kwargs)
     if result != InteractionQueuePreparationStatus.SUCCESS:
         return result
     constraint_target = self.get_participant(
         participant_type=self.clear_constraints_actor)
     sim = self.get_participant(ParticipantType.Actor,
                                target=constraint_target)
     if sim is None:
         return result
     if constraint_target is None:
         return result
     intersection = Anywhere()
     for tuned_constraint in self.clear_constraints:
         constraint = tuned_constraint.create_constraint(
             sim, constraint_target)
         constraint = constraint.create_concrete_version(self)
         intersection = constraint.intersect(intersection)
         while not intersection.valid:
             return result
     for constraint_polygon in constraint.polygons:
         if isinstance(constraint_polygon, sims4.geometry.CompoundPolygon):
             for polygon in constraint_polygon:
                 UserFootprintHelper.force_move_sims_in_polygon(
                     polygon,
                     constraint_target.routing_surface,
                     exclude=[sim])
         else:
             UserFootprintHelper.force_move_sims_in_polygon(
                 constraint_polygon,
                 constraint_target.routing_surface,
                 exclude=[sim])
     return result
示例#11
0
 def make_constraint_default(cls,
                             actor,
                             target_sim,
                             position,
                             routing_surface,
                             participant_type=ParticipantType.Actor,
                             picked_object=None,
                             participant_slot_overrides=None):
     if participant_type not in (ParticipantType.Actor,
                                 ParticipantType.TargetSim):
         return Anywhere()
     all_transforms = []
     for (actor_transform, target_transform, routing_surface,
          _) in cls._get_jig_transforms_gen(
              actor,
              target_sim,
              picked_object=picked_object,
              participant_slot_overrides=participant_slot_overrides):
         if participant_type == ParticipantType.Actor:
             transform = actor_transform
         else:
             transform = target_transform
         if transform is None:
             continue
         all_transforms.append(
             interactions.constraints.Transform(
                 transform,
                 routing_surface=routing_surface,
                 debug_name='JigGroupConstraint'))
     if not all_transforms:
         return Nowhere('Unable to get constraints from jig.')
     return create_constraint_set(all_transforms)
def get_total_constraint(sim):
    if sim.queue.running is not None and sim.queue.running.is_super and sim.queue.running.transition is not None:
        constraint = sim.queue.running.transition.get_final_constraint(sim)
    else:
        constraint = Anywhere()
    total_constraint = sim.si_state.get_total_constraint(
        include_inertial_sis=True, existing_constraint=constraint)
    return total_constraint
示例#13
0
 def get_constraint(self, sim):
     transform = self._sim_transform_map.get(sim, None)
     if transform is not None:
         return interactions.constraints.Transform(
             transform, routing_surface=self.routing_surface)
     if sim in self._sim_transform_map:
         return Nowhere()
     return Anywhere()
示例#14
0
 def _make_constraint(self, *args, **kwargs):
     all_constraints = [
         self.get_constraint(sim) for sim in self._sim_transform_map
     ]
     if all_constraints:
         self._constraint = create_constraint_set(all_constraints)
     else:
         self._constraint = Anywhere()
     return self._constraint
示例#15
0
 def _make_constraint(self, *args, **kwargs):
     if self._constraint is None:
         constraints = [
             interactions.constraints.Transform(
                 t, routing_surface=self.routing_surface)
             for t in self._sim_transform_map.values()
         ]
         self._constraint = create_constraint_set(
             constraints) if constraints else Anywhere()
     return self._constraint
 def get_routes_gen(self):
     if self._target is None:
         self.on_no_target()
         return False
         yield
     routing_slot_constraint = Anywhere()
     for tuned_constraint in self.constraints:
         routing_slot_constraint = routing_slot_constraint.intersect(
             tuned_constraint.create_constraint(self._obj, self._target))
     goals = list(
         itertools.chain.from_iterable(
             h.get_goals()
             for h in routing_slot_constraint.get_connectivity_handles(
                 self._obj)))
     routing_context = self._obj.get_routing_context()
     route = routing.Route(self._obj.routing_location,
                           goals,
                           routing_context=routing_context)
     yield route
示例#17
0
 def _constraint_gen(cls, inst, sim, target, *args, **kwargs):
     travel_group = sim.sim_info.travel_group
     if len(travel_group) == 1:
         yield Anywhere()
     else:
         yield from super()._constraint_gen(
             sim,
             target,
             *args,
             to_zone_id=sim.sim_info.household.home_zone_id,
             **kwargs)
示例#18
0
 def constraint_intersection(self):
     if self._constraint_intersection_dirty or self._constraint_intersection is None:
         intersection = Anywhere()
         for constraint in set(self._constraints.values()):
             new_intersection = intersection.intersect(constraint)
             if not self._invalid_expected:
                 if not new_intersection.valid:
                     indent_text = '                '
                     logger.error(
                         'Invalid constraint intersection for PostureState: {}.\n    A: {} \n    A Geometry: {}    B: {} \n    B Geometry: {}',
                         self, intersection,
                         intersection.get_geometry_text(indent_text),
                         constraint,
                         constraint.get_geometry_text(indent_text))
                     intersection = new_intersection
                     break
             intersection = new_intersection
         self._constraint_intersection_dirty = False
         self._constraint_intersection = intersection
     return self._constraint_intersection
 def constraint_intersection(cls,
                             inst,
                             sim=DEFAULT,
                             participant_type=ParticipantType.Actor,
                             **kwargs):
     if inst is None or participant_type != ParticipantType.Actor:
         return Anywhere()
     if inst._constraint_to_satisfy is not DEFAULT:
         return inst._constraint_to_satisfy
     if sim is DEFAULT:
         sim = inst.get_participant(participant_type)
     return sim.si_state.get_total_constraint(to_exclude=inst)
 def get_carry_transition_constraint(self,
                                     sim,
                                     position,
                                     routing_surface,
                                     cost=0,
                                     mobile=True):
     constraints = self.DEFAULT_GEOMETRIC_TRANSITION_CONSTRAINT
     constraints = constraints.constraint_mobile if mobile else constraints.constraint_non_mobile
     final_constraint = Anywhere()
     for constraint in constraints:
         if mobile:
             constraint = self._get_adjusted_circle_constraint(
                 sim, constraint)
         final_constraint = final_constraint.intersect(
             constraint.create_constraint(None,
                                          None,
                                          target_position=position,
                                          routing_surface=routing_surface))
     final_constraint = final_constraint.generate_constraint_with_cost(cost)
     final_constraint = final_constraint._copy(_multi_surface=True)
     return final_constraint
示例#21
0
 def __call__(self, constraint_targets=(), test_actors=()):
     test_actor = test_actors[0] if test_actors else None
     sim_info_manager = services.sim_info_manager()
     instanced_sims = list(sim_info_manager.instanced_sims_gen())
     for target in constraint_targets:
         if target.is_sim:
             target = target.get_sim_instance()
             if target is None:
                 continue
         else:
             total_constraint = Anywhere()
             for tuned_constraint in self.constraints:
                 total_constraint = total_constraint.intersect(tuned_constraint.create_constraint(None, target))
                 if not total_constraint.valid:
                     return TestResult(False, 'Constraint {} relative to {} is invalid.', tuned_constraint, target, tooltip=self.tooltip)
             object_constraint = interactions.constraints.Position(target._get_locations_for_posture_internal_forward_wall_padding(), routing_surface=target.routing_surface)
             for sim in instanced_sims:
                 if not (total_constraint.geometry.test_transform(sim.transform) and (total_constraint.is_routing_surface_valid(sim.routing_surface) and (total_constraint.is_location_water_depth_valid(sim.location) and (total_constraint.is_location_terrain_tags_valid(sim.location) and not self.test_set(DoubleSimResolver(test_actor, sim.sim_info))))) and not (self.must_be_line_of_sight and sim.sim_info is test_actor)):
                     if not object_constraint.intersect(sim.lineofsight_component.constraint).valid:
                         continue
                     return TestResult(False, 'Sims In Constraint Test Failed.', tooltip=self.tooltip)
     return TestResult.TRUE
示例#22
0
 def get_constraint(self, sim):
     transforms = self._sim_transform_map.get(sim, None)
     if transforms is not None:
         all_transforms = [
             interactions.constraints.Transform(
                 transform,
                 routing_surface=self.routing_surface,
                 create_jig_fn=self._set_sim_intended_location)
             for (transform, _) in transforms
         ]
         return create_constraint_set(all_transforms)
     if sim in self._sim_transform_map:
         return Nowhere(
             "JigGroup, Sim is expected to have a transform but we didn't find a good spot for them. Sim: {}",
             sim)
     return Anywhere()
示例#23
0
 def get_constraint(self, participant_type=ParticipantType.Actor):
     from interactions.constraints import Anywhere, create_animation_constraint
     if participant_type == ParticipantType.Actor:
         actor_name = self.actor_name
         target_name = self.target_name
     elif participant_type == ParticipantType.TargetSim:
         actor_name = self.target_name
         target_name = self.actor_name
     else:
         return Anywhere()
     return create_animation_constraint(self.asm_key, actor_name,
                                        target_name, self.carry_target_name,
                                        self.create_target_name,
                                        self.initial_state,
                                        self.begin_states, self.end_states,
                                        self.overrides)
 def _build_constraint(self, context):
     all_objects = list(services.object_manager().values())
     random.shuffle(all_objects)
     for obj in all_objects:
         if not obj.is_sim:
             if not obj.is_on_active_lot():
                 continue
             resolver = SingleObjectResolver(obj)
             if not self.object_tests.run_tests(resolver):
                 continue
             constraint = self.circle_constraint_around_chosen_object.create_constraint(
                 context.sim, obj)
             if constraint.valid:
                 return constraint
     logger.warn(
         'No objects were found for this interaction to route the Sim near. Interaction = {}',
         type(self))
     return Anywhere()
示例#25
0
 def _on_posture_event(self, change, dest_state, track, source_posture,
                       dest_posture):
     if not PostureTrack.is_body(track):
         return
     sim = dest_state.sim
     if change == PostureEvent.TRANSITION_START:
         if sim.queue.running is not None and sim.queue.running.is_super:
             constraint = sim.queue.running.transition.get_final_constraint(
                 sim)
         else:
             constraint = Anywhere()
     elif change in DONE_POSTURE_EVENTS:
         constraint = sim.si_state.get_total_constraint(
             include_inertial_sis=True)
         self._dest_state = dest_state
     else:
         return
     self._register_on_constraint_changed_for_groups()
     if dest_state is not None:
         self._on_rebuild(sim, constraint)
示例#26
0
 def get_combined_constraint(self, existing_constraint=None, priority=None, group_id=None, to_exclude=None, include_inertial_sis=False, force_inertial_sis=False, existing_si=None, posture_state=DEFAULT, allow_posture_providers=True, include_existing_constraint=True, participant_type=ParticipantType.Actor):
     included_sis = set()
     if include_inertial_sis:
         if existing_si is not None and any(si.id == existing_si.continuation_id for si in self):
             sis_must_include = set()
         else:
             sis_must_include = self._get_must_include_sis(priority, group_id, existing_si=existing_si)
         if force_inertial_sis:
             for si in self._super_interactions:
                 if si in sis_must_include:
                     pass
                 if not allow_posture_providers and self.sim.posture_state.is_source_interaction(si):
                     pass
                 if not self._common_included_si_tests(si):
                     pass
                 sis_must_include.add(si)
         to_consider = set()
         for non_guaranteed_si in self._super_interactions:
             if non_guaranteed_si in sis_must_include:
                 pass
             if not allow_posture_providers and self.sim.posture_state.is_source_interaction(non_guaranteed_si):
                 pass
             to_consider.add(non_guaranteed_si)
     else:
         sis_must_include = self._get_must_include_sis(priority, group_id, existing_si=existing_si)
         to_consider = set()
     if allow_posture_providers:
         additional_posture_sis = set()
         for si in sis_must_include:
             owned_posture = self.sim.posture_state.get_source_or_owned_posture_for_si(si)
             if owned_posture is None:
                 pass
             if owned_posture.track != postures.PostureTrack.BODY:
                 pass
             if owned_posture.source_interaction.is_finishing:
                 pass
             additional_posture_sis.add(owned_posture.source_interaction)
         sis_must_include.update(additional_posture_sis)
         additional_posture_sis.clear()
     total_constraint = Anywhere()
     included_carryables = set()
     for si_must_include in sis_must_include:
         if si_must_include.is_finishing:
             pass
         while not si_must_include is to_exclude:
             if si_must_include is existing_si:
                 pass
             if existing_si is not None and existing_si.group_id == si_must_include.group_id:
                 pass
             my_role = si_must_include.get_participant_type(self.sim)
             if existing_si is not None:
                 existing_participant_type = existing_si.get_participant_type(self.sim)
                 if not self.are_sis_compatible(si_must_include, existing_si, my_role, existing_participant_type, ignore_geometry=True):
                     return (Nowhere(), sis_must_include)
             si_constraint = si_must_include.constraint_intersection(participant_type=my_role, posture_state=posture_state)
             if existing_si is not None:
                 if (existing_si.should_rally or existing_si.relocate_main_group) and (si_must_include.is_social and si_must_include.social_group is not None) and si_must_include.social_group is si_must_include.sim.get_main_group():
                     si_constraint = si_constraint.generate_posture_only_constraint()
                 si_constraint = si_constraint.apply_posture_state(None, existing_si.get_constraint_resolver(None, participant_type=participant_type))
             if existing_constraint is not None:
                 si_constraint = si_constraint.apply(existing_constraint)
             test_constraint = total_constraint.intersect(si_constraint)
             if not test_constraint.valid:
                 break
             carry_target = si_must_include.targeted_carryable
             if carry_target is not None:
                 if len(included_carryables) == 2 and carry_target not in included_carryables:
                     pass
                 included_carryables.add(carry_target)
             total_constraint = test_constraint
             included_sis.add(si_must_include)
     if len(included_carryables) == 2 and existing_si is not None:
         existing_carry_target = existing_si.carry_target or existing_si.target
         if existing_carry_target is not None and existing_carry_target.carryable_component is not None:
             if existing_carry_target not in included_carryables:
                 total_constraint = Nowhere()
     if included_sis != sis_must_include:
         total_constraint = Nowhere()
     if not total_constraint.valid:
         return (total_constraint, included_sis)
     if total_constraint.tentative or existing_constraint is not None and existing_constraint.tentative:
         return (total_constraint, included_sis)
     if to_consider:
         for si in self._sis_sorted(to_consider):
             if si is to_exclude:
                 pass
             if existing_si is not None and existing_si.group_id == si.group_id:
                 pass
             if not self._common_included_si_tests(si):
                 pass
             my_role = si.get_participant_type(self.sim)
             if existing_si is not None:
                 existing_participant_type = existing_si.get_participant_type(self.sim)
                 if not self.are_sis_compatible(si, existing_si, my_role, existing_participant_type, ignore_geometry=True):
                     pass
             si_constraint = si.constraint_intersection(participant_type=my_role, posture_state=posture_state)
             if existing_si is not None:
                 si_constraint = si_constraint.apply_posture_state(None, existing_si.get_constraint_resolver(None, participant_type=participant_type))
             if si_constraint.tentative:
                 si_constraint = si.constraint_intersection(participant_type=my_role, posture_state=DEFAULT)
             test_constraint = total_constraint.intersect(si_constraint)
             if existing_constraint is not None:
                 test_constraint_plus_existing = test_constraint.intersect(existing_constraint)
                 while not not test_constraint_plus_existing.valid:
                     if test_constraint_plus_existing.tentative:
                         pass
                     test_constraint = test_constraint.apply(existing_constraint)
                     if test_constraint.valid:
                         total_constraint = test_constraint
                         included_sis.add(si)
                     while total_constraint.tentative:
                         break
             if test_constraint.valid:
                 total_constraint = test_constraint
                 included_sis.add(si)
             while total_constraint.tentative:
                 break
     if allow_posture_providers:
         additional_posture_sis = set()
         for si in included_sis:
             owned_posture = self.sim.posture_state.get_source_or_owned_posture_for_si(si)
             while owned_posture is not None and owned_posture.source_interaction not in included_sis and owned_posture.track == postures.PostureTrack.BODY:
                 additional_posture_sis.add(owned_posture.source_interaction)
         included_sis.update(additional_posture_sis)
     if include_existing_constraint and existing_constraint is not None:
         total_constraint = total_constraint.intersect(existing_constraint)
     return (total_constraint, included_sis)
示例#27
0
def _draw_constraint(layer,
                     constraint,
                     color,
                     altitude_modifier=0,
                     anywhere_position=None):
    if constraint is None:
        return
    if isinstance(constraint, RequiredSlotSingle):
        constraint = constraint._intersect(Anywhere())
    if constraint.IS_CONSTRAINT_SET:
        drawn_geometry = []
        for sub_constraint in constraint._constraints:
            if sub_constraint._geometry is not None:
                if sub_constraint._geometry in drawn_geometry:
                    _draw_constraint(
                        layer,
                        sub_constraint.generate_alternate_geometry_constraint(
                            None), color, altitude_modifier)
                drawn_geometry.append(sub_constraint._geometry)
            _draw_constraint(layer, sub_constraint, color, altitude_modifier)
            altitude_modifier += 0.1
        return
    (r, g, b, a) = sims4.color.to_rgba(color)
    semitransparent = sims4.color.from_rgba(r, g, b, a * 0.5)
    transparent = sims4.color.from_rgba(r, g, b, a * 0.25)
    layer.routing_surface = constraint.routing_surface
    if constraint.geometry is not None:
        if constraint.geometry.polygon is not None:
            drawn_facings = []
            drawn_points = []
            drawn_polys = []
            for poly in constraint.geometry.polygon:
                poly_key = list(poly)
                if poly_key not in drawn_polys:
                    drawn_polys.append(poly_key)
                    layer.add_polygon(poly,
                                      color=color,
                                      altitude=altitude_modifier + 0.1)

                def draw_facing(point, color):
                    altitude = altitude_modifier + 0.1
                    (valid, interval
                     ) = constraint._geometry.get_orientation_range(point)
                    if valid and interval is not None:
                        if interval.a != interval.b:
                            if interval.angle >= sims4.math.TWO_PI:
                                angles = [(interval.ideal, True)]
                            else:
                                angles = [(interval.a, False),
                                          (interval.ideal, True),
                                          (interval.b, False)]
                        else:
                            angles = [(interval.a, True)]
                        for (angle, arrowhead) in angles:
                            facings_key = (point, angle, arrowhead)
                            while facings_key not in drawn_facings:
                                drawn_facings.append(facings_key)
                                layer.add_arrow(point,
                                                angle,
                                                end_arrow=arrowhead,
                                                length=0.2,
                                                color=color,
                                                altitude=altitude)
                    else:
                        point_key = point
                        if point_key not in drawn_points:
                            drawn_points.append(point_key)
                            layer.add_point(point,
                                            color=color,
                                            altitude=altitude)

                if not constraint.geometry.restrictions:
                    pass
                for vertex in poly:
                    draw_facing(vertex, color)
                for i in range(len(poly)):
                    v1 = poly[i]
                    v2 = poly[(i + 1) % len(poly)]
                    draw_facing(v1, transparent)
                    draw_facing(0.5 * (v1 + v2), transparent)
                if _number_of_random_weight_points:
                    num_random_points = _number_of_random_weight_points
                else:
                    num_random_points = math.ceil(poly.area() *
                                                  RANDOM_WEIGHT_DENSITY)
                while num_random_points:
                    while True:
                        for point in random_uniform_points_in_polygon(
                                poly, num_random_points):
                            orientation = sims4.math.Quaternion.IDENTITY()
                            if constraint._geometry is not None:
                                (valid,
                                 quat) = constraint._geometry.get_orientation(
                                     point)
                                if quat is not None:
                                    orientation = quat
                            draw_facing(point, transparent)
                            score = constraint.get_score(point, orientation)
                            color = red_green_lerp(score, a * 0.33)
                            layer.add_point(point, size=0.025, color=color)
        elif constraint.geometry.restrictions:
            for restriction in constraint.geometry.restrictions:
                if isinstance(restriction, RelativeFacingRange):
                    layer.add_point(restriction.target, color=color)
                else:
                    while isinstance(restriction, RelativeFacingWithCircle):
                        layer.add_circle(restriction.target,
                                         radius=restriction.radius,
                                         color=color)
        if isinstance(constraint, RequiredSlotSingle):
            while True:
                for (routing_transform, _) in itertools.chain(
                        constraint._slots_to_params_entry or (),
                        constraint._slots_to_params_exit or ()):
                    layer.add_arrow_for_transform(routing_transform,
                                                  length=0.1,
                                                  color=semitransparent,
                                                  altitude=altitude_modifier)
                    layer.add_segment(
                        routing_transform.translation,
                        constraint.containment_transform.translation,
                        color=transparent,
                        altitude=altitude_modifier)
    elif isinstance(constraint, Anywhere) and anywhere_position is not None:
        layer.add_circle(anywhere_position,
                         radius=0.28,
                         color=transparent,
                         altitude=altitude_modifier)
        layer.add_circle(anywhere_position,
                         radius=0.3,
                         color=semitransparent,
                         altitude=altitude_modifier)
示例#28
0
 def get_combined_constraint(self, existing_constraint=None, priority=None, group_id=None, to_exclude=None, include_inertial_sis=False, force_inertial_sis=False, existing_si=None, posture_state=DEFAULT, allow_posture_providers=True, include_existing_constraint=True, participant_type=ParticipantType.Actor):
     included_sis = set()
     if include_inertial_sis:
         if existing_si is not None and any(si.id == existing_si.continuation_id for si in self):
             sis_must_include = set()
         else:
             sis_must_include = self._get_must_include_sis(priority, group_id, existing_si=existing_si)
         if force_inertial_sis:
             for si in self._super_interactions:
                 if si in sis_must_include:
                     continue
                 if not allow_posture_providers and self.sim.posture_state.is_source_interaction(si):
                     continue
                 if not self._common_included_si_tests(si):
                     continue
                 sis_must_include.add(si)
         to_consider = set()
         is_like_vehicle = self.sim.posture.is_vehicle or self.sim.parent_may_move
         for non_guaranteed_si in self._super_interactions:
             if non_guaranteed_si in sis_must_include:
                 continue
             if not is_like_vehicle:
                 continue
             if not allow_posture_providers and (self.sim.posture_state.is_source_interaction(non_guaranteed_si) and self.sim.posture.is_vehicle) and existing_constraint is not None:
                 final_surfaces = {constraint.routing_surface.type for constraint in existing_constraint if constraint.routing_surface is not None}
                 vehicle = self.sim.posture.target
                 vehicle_surfaces = vehicle.vehicle_component.allowed_surfaces
                 if final_surfaces and not any(final_surface in vehicle_surfaces for final_surface in final_surfaces):
                     continue
             else:
                 to_consider.add(non_guaranteed_si)
     else:
         sis_must_include = self._get_must_include_sis(priority, group_id, existing_si=existing_si)
         to_consider = set()
     if allow_posture_providers:
         additional_posture_sis = set()
         for si in sis_must_include:
             owned_posture = self.sim.posture_state.get_source_or_owned_posture_for_si(si)
             if owned_posture is None:
                 continue
             if owned_posture.track != postures.PostureTrack.BODY:
                 continue
             source_interaction = owned_posture.source_interaction
             if not source_interaction is None:
                 if source_interaction.is_finishing:
                     continue
                 additional_posture_sis.add(source_interaction)
         sis_must_include.update(additional_posture_sis)
         additional_posture_sis.clear()
     total_constraint = Anywhere()
     included_carryables = set()
     for si_must_include in sis_must_include:
         if si_must_include.is_finishing:
             continue
         if not si_must_include is to_exclude:
             if si_must_include is existing_si:
                 continue
             if existing_si is not None and existing_si.group_id == si_must_include.group_id:
                 continue
             my_role = si_must_include.get_participant_type(self.sim)
             if existing_si is not None:
                 existing_participant_type = existing_si.get_participant_type(self.sim)
                 if not self.are_sis_compatible(si_must_include, existing_si, my_role, existing_participant_type, ignore_geometry=True):
                     return (Nowhere('SIState.get_combined_constraint, Two SIs are incompatible: SI_A: {}, SI_B: {}', si_must_include, existing_si), sis_must_include)
             si_constraint = si_must_include.constraint_intersection(participant_type=my_role, posture_state=posture_state)
             if existing_si is not None:
                 if existing_si.should_rally or existing_si.relocate_main_group:
                     if si_must_include.is_social:
                         if si_must_include.social_group is not None:
                             if si_must_include.social_group is si_must_include.sim.get_main_group():
                                 si_constraint = si_constraint.generate_posture_only_constraint()
                 si_constraint = si_constraint.apply_posture_state(None, existing_si.get_constraint_resolver(None, participant_type=participant_type))
             if existing_constraint is not None:
                 si_constraint = si_constraint.apply(existing_constraint)
             test_constraint = total_constraint.intersect(si_constraint)
             if not test_constraint.valid:
                 break
             carry_target = si_must_include.targeted_carryable
             if carry_target is not None:
                 if len(included_carryables) == 2 and carry_target not in included_carryables:
                     continue
                 included_carryables.add(carry_target)
             total_constraint = test_constraint
             included_sis.add(si_must_include)
     if len(included_carryables) == 2:
         if existing_si is not None:
             existing_carry_target = existing_si.carry_target
             if existing_carry_target is not None:
                 if existing_carry_target not in included_carryables:
                     total_constraint = Nowhere('Cannot include more than two interactions that are carrying objects.')
     if included_sis != sis_must_include:
         total_constraint = Nowhere('Unable to combine SIs that are must include.')
     if not total_constraint.valid:
         return (total_constraint, included_sis)
     if total_constraint.tentative or existing_constraint is not None and existing_constraint.tentative:
         return (total_constraint, included_sis)
     if to_consider:
         for si in self._sis_sorted(to_consider):
             if si is to_exclude:
                 continue
             if not self._common_included_si_tests(si):
                 continue
             my_role = si.get_participant_type(self.sim)
             if existing_si is not None:
                 existing_participant_type = existing_si.get_participant_type(self.sim)
                 if not self.are_sis_compatible(si, existing_si, my_role, existing_participant_type, ignore_geometry=True):
                     continue
             else:
                 si_constraint = si.constraint_intersection(participant_type=my_role, posture_state=posture_state)
                 if existing_si is not None:
                     si_constraint = si_constraint.apply_posture_state(None, existing_si.get_constraint_resolver(None, participant_type=participant_type))
                 if si_constraint.tentative:
                     si_constraint = si.constraint_intersection(participant_type=my_role, posture_state=DEFAULT)
                 test_constraint = total_constraint.intersect(si_constraint)
                 if existing_constraint is not None:
                     test_constraint_plus_existing = test_constraint.intersect(existing_constraint)
                     if test_constraint_plus_existing.valid:
                         if test_constraint_plus_existing.tentative:
                             continue
                         test_constraint = test_constraint.apply(existing_constraint)
                         if test_constraint.valid:
                             total_constraint = test_constraint
                             included_sis.add(si)
                         if total_constraint.tentative:
                             break
                 if test_constraint.valid:
                     total_constraint = test_constraint
                     included_sis.add(si)
                 if total_constraint.tentative:
                     break
     if allow_posture_providers:
         additional_posture_sis = set()
         for si in included_sis:
             owned_posture = self.sim.posture_state.get_source_or_owned_posture_for_si(si)
             if owned_posture is not None:
                 if owned_posture.source_interaction not in included_sis:
                     if owned_posture.track == postures.PostureTrack.BODY:
                         additional_posture_sis.add(owned_posture.source_interaction)
         included_sis.update(additional_posture_sis)
     if include_existing_constraint:
         if existing_constraint is not None:
             total_constraint = total_constraint.intersect(existing_constraint)
     return (total_constraint, included_sis)
示例#29
0
class Broadcaster(HasTunableReference, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(sims4.resources.Types.BROADCASTER)):
    __qualname__ = 'Broadcaster'
    FREQUENCY_ENTER = 0
    FREQUENCY_PULSE = 1
    INSTANCE_TUNABLES = {'constraints': TunableList(description='\n            A list of constraints that define the area of influence of this\n            broadcaster. It is required that at least one constraint be defined.\n            ', tunable=TunableGeometricConstraintVariant()), 'effects': TunableList(description='\n            A list of effects that are applied to Sims and objects affected by\n            this broadcaster.\n            ', tunable=TunableBroadcasterEffectVariant()), 'frequency': TunableVariant(description='\n            Define in what instances and how often this broadcaster affects Sims\n            and objects in its area of influence.\n            ', on_enter=TunableTuple(description='\n                Sims and objects are affected by this broadcaster when they\n                enter in its area of influence, or when the broadcaster is\n                created.\n                ', locked_args={'frequency_type': FREQUENCY_ENTER}, allow_multiple=Tunable(description="\n                    If checked, then Sims may react multiple times if they re-\n                    enter the broadcaster's area of influence. If unchecked,\n                    then Sims will only react to the broadcaster once.\n                    ", tunable_type=bool, needs_tuning=True, default=False)), on_pulse=TunableTuple(description='\n                Sims and objects are constantly affected by this broadcaster\n                while they are in its area of influence.\n                ', locked_args={'frequency_type': FREQUENCY_PULSE}, cooldown_time=TunableSimMinute(description='\n                    The time interval between broadcaster pulses. Sims would not\n                    react to the broadcaster for at least this amount of time\n                    while in its area of influence.\n                    ', default=8)), default='on_pulse'), 'clustering': OptionalTunable(description='\n            If set, then similar broadcasters, i.e. broadcasters of the same\n            instance, will be clustered together if their broadcasting objects\n            are close by. This improves performance and avoids having Sims react\n            multiple times to similar broadcasters. When broadcasters are\n            clustered together, there is no guarantee as to what object will be\n            used for testing purposes.\n            \n            e.g. Stinky food reactions are clustered together. A test on the\n            broadcaster should not, for example, differentiate between a stinky\n            lobster and a stinky steak, because the broadcasting object is\n            arbitrary and undefined.\n            \n            e.g. Jealousy reactions are not clustered together. A test on the\n            broadcaster considers the relationship between two Sims. Therefore,\n            it would be unwise to consider an arbitrary Sim if two jealousy\n            broadcasters are close to each other.\n            ', tunable=ObjectClusterRequest.TunableFactory(description='\n                Specify how clusters for this particular broadcaster are formed.\n                ', locked_args={'minimum_size': 1}), enabled_by_default=True), 'allow_objects': Tunable(description='\n            If checked, then in addition to all instantiated Sims, all objects\n            will be affected by this broadcaster. Some tuned effects might still\n            only apply to Sims (e.g. affordance pushing).\n            \n            Checking this tuning field has performance repercussions, as it\n            means we have to process a lot more data during broadcaster pings.\n            Please use this sporadically.\n            ', tunable_type=bool, default=False)}

    @classmethod
    def _verify_tuning_callback(cls):
        if not cls.constraints:
            logger.error('Broadcaster {} does not define any constraints.', cls)

    @classmethod
    def register_static_callbacks(cls, *args, **kwargs):
        for broadcaster_effect in cls.effects:
            broadcaster_effect.register_static_callbacks(*args, **kwargs)

    def __init__(self, *args, broadcasting_object, interaction=None, **kwargs):
        super().__init__(*args, **kwargs)
        self._broadcasting_object_ref = weakref.ref(broadcasting_object, self._on_broadcasting_object_deleted)
        self._interaction = interaction
        self._constraint = None
        self._affected_objects = weakref.WeakKeyDictionary()
        self._current_objects = weakref.WeakSet()
        self._linked_broadcasters = weakref.WeakSet()
        broadcasting_object.register_on_location_changed(self._on_broadcasting_object_moved)
        self._quadtree = None
        self._cluster_request = None

    @property
    def broadcasting_object(self):
        if self._broadcasting_object_ref is not None:
            return self._broadcasting_object_ref()

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

    @property
    def quadtree(self):
        return self._quadtree

    @property
    def cluster_request(self):
        return self._cluster_request

    def _on_broadcasting_object_deleted(self, _):
        current_zone = services.current_zone()
        if current_zone is not None:
            broadcaster_service = current_zone.broadcaster_service
            if broadcaster_service is not None:
                broadcaster_service.remove_broadcaster(self)

    def _on_broadcasting_object_moved(self, *_, **__):
        self.regenerate_constraint()
        current_zone = services.current_zone()
        if current_zone is not None:
            broadcaster_service = current_zone.broadcaster_service
            if broadcaster_service is not None:
                broadcaster_service.update_cluster_request(self)

    def on_processed(self):
        for affected_object in self._affected_objects:
            while affected_object not in self._current_objects:
                self.remove_broadcaster_effect(affected_object)
        self._current_objects.clear()

    def on_removed(self):
        for affected_object in self._affected_objects:
            self.remove_broadcaster_effect(affected_object)
        broadcasting_object = self.broadcasting_object
        if broadcasting_object is not None:
            broadcasting_object.unregister_on_location_changed(self._on_broadcasting_object_moved)

    def on_added_to_quadtree_and_cluster_request(self, quadtree, cluster_request):
        self._quadtree = quadtree
        self._cluster_request = cluster_request

    def can_affect(self, obj):
        if not self.allow_objects and not obj.is_sim:
            return False
        broadcasting_object = self.broadcasting_object
        if broadcasting_object is None:
            return False
        routing_surface = broadcasting_object.routing_surface
        if routing_surface is None or obj.routing_surface != routing_surface:
            return False
        if obj is broadcasting_object:
            return False
        linked_broadcasters = list(self._linked_broadcasters)
        if any(obj is linked_broadcaster.broadcasting_object for linked_broadcaster in linked_broadcasters):
            return False
        return True

    def apply_broadcaster_effect(self, affected_object):
        self._current_objects.add(affected_object)
        if self._should_apply_broadcaster_effect(affected_object):
            self._affected_objects[affected_object] = (services.time_service().sim_now, True)
            for broadcaster_effect in self.effects:
                broadcaster_effect.apply_broadcaster_effect(self, affected_object)
        for linked_broadcaster in self._linked_broadcasters:
            linked_broadcaster._apply_linked_broadcaster_effect(affected_object, self._affected_objects[affected_object])

    def _apply_linked_broadcaster_effect(self, affected_object, data):
        self._apply_linked_broadcaster_data(affected_object, data)
        for broadcaster_effect in self.effects:
            while broadcaster_effect.apply_when_linked:
                broadcaster_effect.apply_broadcaster_effect(self, affected_object)

    def _apply_linked_broadcaster_data(self, affected_object, data):
        if affected_object in self._affected_objects:
            was_in_area = self._affected_objects[affected_object][1]
            is_in_area = data[1]
            if was_in_area and not is_in_area:
                self.remove_broadcaster_effect(affected_object)
        self._affected_objects[affected_object] = data

    def remove_broadcaster_effect(self, affected_object, is_linked=False):
        if not self._affected_objects[affected_object][1]:
            return
        self._affected_objects[affected_object] = (self._affected_objects[affected_object][0], False)
        for broadcaster_effect in self.effects:
            while broadcaster_effect.apply_when_linked or not is_linked:
                broadcaster_effect.remove_broadcaster_effect(self, affected_object)
        if not is_linked:
            for linked_broadcaster in self._linked_broadcasters:
                linked_broadcaster.remove_broadcaster_effect(affected_object, is_linked=True)

    def _should_apply_broadcaster_effect(self, affected_object):
        if self.frequency.frequency_type == self.FREQUENCY_ENTER:
            if affected_object not in self._affected_objects:
                return True
            if self.frequency.allow_multiple and not self._affected_objects[affected_object][1]:
                return True
            return False
        if self.frequency.frequency_type == self.FREQUENCY_PULSE:
            last_reaction = self._affected_objects.get(affected_object, None)
            if last_reaction is None:
                return True
            time_since_last_reaction = services.time_service().sim_now - last_reaction[0]
            if time_since_last_reaction.in_minutes() > self.frequency.cooldown_time:
                return True
            return False

    def clear_linked_broadcasters(self):
        self._linked_broadcasters.clear()

    def set_linked_broadcasters(self, broadcasters):
        self.clear_linked_broadcasters()
        self._linked_broadcasters.update(broadcasters)
        for linked_broadcaster in self._linked_broadcasters:
            linked_broadcaster.clear_linked_broadcasters()
            for (obj, data) in self._affected_objects.items():
                linked_broadcaster._apply_linked_broadcaster_data(obj, data)

    @property
    def has_linked_broadcasters(self):
        if self._linked_broadcasters:
            return True
        return False

    def get_linked_broadcasters_gen(self):
        yield self._linked_broadcasters

    def regenerate_constraint(self, *_, **__):
        self._constraint = None

    def get_constraint(self):
        if self._constraint is None or not self._constraint.valid:
            self._constraint = Anywhere()
            for tuned_constraint in self.constraints:
                self._constraint = self._constraint.intersect(tuned_constraint.create_constraint(None, target=self.broadcasting_object, target_position=self.broadcasting_object.position))
        return self._constraint

    def get_resolver(self, affected_object):
        return DoubleObjectResolver(affected_object, self.broadcasting_object)

    def get_clustering(self):
        broadcasting_object = self.broadcasting_object
        if broadcasting_object is None:
            return
        if broadcasting_object.is_sim:
            return
        if broadcasting_object.is_in_inventory():
            return
        if broadcasting_object.routing_surface is None:
            return
        return self.clustering

    def should_cluster(self):
        return self.get_clustering() is not None

    def get_affected_object_count(self):
        return sum(1 for data in self._affected_objects.values() if data[1])

    @property
    def id(self):
        return self.broadcaster_id

    @property
    def lineofsight_component(self):
        return _BroadcasterLosComponent(self)

    @property
    def position(self):
        return self.broadcasting_object.position

    @property
    def routing_surface(self):
        return self.broadcasting_object.routing_surface

    @property
    def parts(self):
        return self.broadcasting_object.parts
class Broadcaster(HasTunableReference,
                  metaclass=HashedTunedInstanceMetaclass,
                  manager=services.get_instance_manager(
                      sims4.resources.Types.BROADCASTER)):
    __qualname__ = 'Broadcaster'
    FREQUENCY_ENTER = 0
    FREQUENCY_PULSE = 1
    INSTANCE_TUNABLES = {
        'constraints':
        TunableList(
            description=
            '\n            A list of constraints that define the area of influence of this\n            broadcaster. It is required that at least one constraint be defined.\n            ',
            tunable=TunableGeometricConstraintVariant()),
        'effects':
        TunableList(
            description=
            '\n            A list of effects that are applied to Sims and objects affected by\n            this broadcaster.\n            ',
            tunable=TunableBroadcasterEffectVariant()),
        'frequency':
        TunableVariant(
            description=
            '\n            Define in what instances and how often this broadcaster affects Sims\n            and objects in its area of influence.\n            ',
            on_enter=TunableTuple(
                description=
                '\n                Sims and objects are affected by this broadcaster when they\n                enter in its area of influence, or when the broadcaster is\n                created.\n                ',
                locked_args={'frequency_type': FREQUENCY_ENTER},
                allow_multiple=Tunable(
                    description=
                    "\n                    If checked, then Sims may react multiple times if they re-\n                    enter the broadcaster's area of influence. If unchecked,\n                    then Sims will only react to the broadcaster once.\n                    ",
                    tunable_type=bool,
                    needs_tuning=True,
                    default=False)),
            on_pulse=TunableTuple(
                description=
                '\n                Sims and objects are constantly affected by this broadcaster\n                while they are in its area of influence.\n                ',
                locked_args={'frequency_type': FREQUENCY_PULSE},
                cooldown_time=TunableSimMinute(
                    description=
                    '\n                    The time interval between broadcaster pulses. Sims would not\n                    react to the broadcaster for at least this amount of time\n                    while in its area of influence.\n                    ',
                    default=8)),
            default='on_pulse'),
        'clustering':
        OptionalTunable(
            description=
            '\n            If set, then similar broadcasters, i.e. broadcasters of the same\n            instance, will be clustered together if their broadcasting objects\n            are close by. This improves performance and avoids having Sims react\n            multiple times to similar broadcasters. When broadcasters are\n            clustered together, there is no guarantee as to what object will be\n            used for testing purposes.\n            \n            e.g. Stinky food reactions are clustered together. A test on the\n            broadcaster should not, for example, differentiate between a stinky\n            lobster and a stinky steak, because the broadcasting object is\n            arbitrary and undefined.\n            \n            e.g. Jealousy reactions are not clustered together. A test on the\n            broadcaster considers the relationship between two Sims. Therefore,\n            it would be unwise to consider an arbitrary Sim if two jealousy\n            broadcasters are close to each other.\n            ',
            tunable=ObjectClusterRequest.TunableFactory(
                description=
                '\n                Specify how clusters for this particular broadcaster are formed.\n                ',
                locked_args={'minimum_size': 1}),
            enabled_by_default=True),
        'allow_objects':
        Tunable(
            description=
            '\n            If checked, then in addition to all instantiated Sims, all objects\n            will be affected by this broadcaster. Some tuned effects might still\n            only apply to Sims (e.g. affordance pushing).\n            \n            Checking this tuning field has performance repercussions, as it\n            means we have to process a lot more data during broadcaster pings.\n            Please use this sporadically.\n            ',
            tunable_type=bool,
            default=False)
    }

    @classmethod
    def _verify_tuning_callback(cls):
        if not cls.constraints:
            logger.error('Broadcaster {} does not define any constraints.',
                         cls)

    @classmethod
    def register_static_callbacks(cls, *args, **kwargs):
        for broadcaster_effect in cls.effects:
            broadcaster_effect.register_static_callbacks(*args, **kwargs)

    def __init__(self, *args, broadcasting_object, interaction=None, **kwargs):
        super().__init__(*args, **kwargs)
        self._broadcasting_object_ref = weakref.ref(
            broadcasting_object, self._on_broadcasting_object_deleted)
        self._interaction = interaction
        self._constraint = None
        self._affected_objects = weakref.WeakKeyDictionary()
        self._current_objects = weakref.WeakSet()
        self._linked_broadcasters = weakref.WeakSet()
        broadcasting_object.register_on_location_changed(
            self._on_broadcasting_object_moved)
        self._quadtree = None
        self._cluster_request = None

    @property
    def broadcasting_object(self):
        if self._broadcasting_object_ref is not None:
            return self._broadcasting_object_ref()

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

    @property
    def quadtree(self):
        return self._quadtree

    @property
    def cluster_request(self):
        return self._cluster_request

    def _on_broadcasting_object_deleted(self, _):
        current_zone = services.current_zone()
        if current_zone is not None:
            broadcaster_service = current_zone.broadcaster_service
            if broadcaster_service is not None:
                broadcaster_service.remove_broadcaster(self)

    def _on_broadcasting_object_moved(self, *_, **__):
        self.regenerate_constraint()
        current_zone = services.current_zone()
        if current_zone is not None:
            broadcaster_service = current_zone.broadcaster_service
            if broadcaster_service is not None:
                broadcaster_service.update_cluster_request(self)

    def on_processed(self):
        for affected_object in self._affected_objects:
            while affected_object not in self._current_objects:
                self.remove_broadcaster_effect(affected_object)
        self._current_objects.clear()

    def on_removed(self):
        for affected_object in self._affected_objects:
            self.remove_broadcaster_effect(affected_object)
        broadcasting_object = self.broadcasting_object
        if broadcasting_object is not None:
            broadcasting_object.unregister_on_location_changed(
                self._on_broadcasting_object_moved)

    def on_added_to_quadtree_and_cluster_request(self, quadtree,
                                                 cluster_request):
        self._quadtree = quadtree
        self._cluster_request = cluster_request

    def can_affect(self, obj):
        if not self.allow_objects and not obj.is_sim:
            return False
        broadcasting_object = self.broadcasting_object
        if broadcasting_object is None:
            return False
        routing_surface = broadcasting_object.routing_surface
        if routing_surface is None or obj.routing_surface != routing_surface:
            return False
        if obj is broadcasting_object:
            return False
        linked_broadcasters = list(self._linked_broadcasters)
        if any(obj is linked_broadcaster.broadcasting_object
               for linked_broadcaster in linked_broadcasters):
            return False
        return True

    def apply_broadcaster_effect(self, affected_object):
        self._current_objects.add(affected_object)
        if self._should_apply_broadcaster_effect(affected_object):
            self._affected_objects[affected_object] = (
                services.time_service().sim_now, True)
            for broadcaster_effect in self.effects:
                broadcaster_effect.apply_broadcaster_effect(
                    self, affected_object)
        for linked_broadcaster in self._linked_broadcasters:
            linked_broadcaster._apply_linked_broadcaster_effect(
                affected_object, self._affected_objects[affected_object])

    def _apply_linked_broadcaster_effect(self, affected_object, data):
        self._apply_linked_broadcaster_data(affected_object, data)
        for broadcaster_effect in self.effects:
            while broadcaster_effect.apply_when_linked:
                broadcaster_effect.apply_broadcaster_effect(
                    self, affected_object)

    def _apply_linked_broadcaster_data(self, affected_object, data):
        if affected_object in self._affected_objects:
            was_in_area = self._affected_objects[affected_object][1]
            is_in_area = data[1]
            if was_in_area and not is_in_area:
                self.remove_broadcaster_effect(affected_object)
        self._affected_objects[affected_object] = data

    def remove_broadcaster_effect(self, affected_object, is_linked=False):
        if not self._affected_objects[affected_object][1]:
            return
        self._affected_objects[affected_object] = (
            self._affected_objects[affected_object][0], False)
        for broadcaster_effect in self.effects:
            while broadcaster_effect.apply_when_linked or not is_linked:
                broadcaster_effect.remove_broadcaster_effect(
                    self, affected_object)
        if not is_linked:
            for linked_broadcaster in self._linked_broadcasters:
                linked_broadcaster.remove_broadcaster_effect(affected_object,
                                                             is_linked=True)

    def _should_apply_broadcaster_effect(self, affected_object):
        if self.frequency.frequency_type == self.FREQUENCY_ENTER:
            if affected_object not in self._affected_objects:
                return True
            if self.frequency.allow_multiple and not self._affected_objects[
                    affected_object][1]:
                return True
            return False
        if self.frequency.frequency_type == self.FREQUENCY_PULSE:
            last_reaction = self._affected_objects.get(affected_object, None)
            if last_reaction is None:
                return True
            time_since_last_reaction = services.time_service(
            ).sim_now - last_reaction[0]
            if time_since_last_reaction.in_minutes(
            ) > self.frequency.cooldown_time:
                return True
            return False

    def clear_linked_broadcasters(self):
        self._linked_broadcasters.clear()

    def set_linked_broadcasters(self, broadcasters):
        self.clear_linked_broadcasters()
        self._linked_broadcasters.update(broadcasters)
        for linked_broadcaster in self._linked_broadcasters:
            linked_broadcaster.clear_linked_broadcasters()
            for (obj, data) in self._affected_objects.items():
                linked_broadcaster._apply_linked_broadcaster_data(obj, data)

    @property
    def has_linked_broadcasters(self):
        if self._linked_broadcasters:
            return True
        return False

    def get_linked_broadcasters_gen(self):
        yield self._linked_broadcasters

    def regenerate_constraint(self, *_, **__):
        self._constraint = None

    def get_constraint(self):
        if self._constraint is None or not self._constraint.valid:
            self._constraint = Anywhere()
            for tuned_constraint in self.constraints:
                self._constraint = self._constraint.intersect(
                    tuned_constraint.create_constraint(
                        None,
                        target=self.broadcasting_object,
                        target_position=self.broadcasting_object.position))
        return self._constraint

    def get_resolver(self, affected_object):
        return DoubleObjectResolver(affected_object, self.broadcasting_object)

    def get_clustering(self):
        broadcasting_object = self.broadcasting_object
        if broadcasting_object is None:
            return
        if broadcasting_object.is_sim:
            return
        if broadcasting_object.is_in_inventory():
            return
        if broadcasting_object.routing_surface is None:
            return
        return self.clustering

    def should_cluster(self):
        return self.get_clustering() is not None

    def get_affected_object_count(self):
        return sum(1 for data in self._affected_objects.values() if data[1])

    @property
    def id(self):
        return self.broadcaster_id

    @property
    def lineofsight_component(self):
        return _BroadcasterLosComponent(self)

    @property
    def position(self):
        return self.broadcasting_object.position

    @property
    def routing_surface(self):
        return self.broadcasting_object.routing_surface

    @property
    def parts(self):
        return self.broadcasting_object.parts
示例#31
0
class Broadcaster(HasTunableReference,
                  RouteEventProviderMixin,
                  metaclass=HashedTunedInstanceMetaclass,
                  manager=services.get_instance_manager(
                      sims4.resources.Types.BROADCASTER)):
    class _BroadcasterObjectFilter(HasTunableSingletonFactory,
                                   AutoFactoryInit):
        def is_affecting_objects(self):
            raise NotImplementedError

        def can_affect_object(self, obj):
            raise NotImplementedError

    class _BroadcasterObjectFilterNone(HasTunableSingletonFactory):
        def __str__(self):
            return 'Nothing'

        def is_affecting_objects(self):
            return (False, None)

        def can_affect_object(self, obj):
            return False

    class _BroadcasterObjectFilterFlammable(HasTunableSingletonFactory):
        def __str__(self):
            return 'Flammable Objects'

        def is_affecting_objects(self):
            return (True, {FireTuning.FLAMMABLE_TAG})

        def can_affect_object(self, obj):
            target_object_tags = obj.get_tags()
            if FireTuning.FLAMMABLE_TAG in target_object_tags:
                return True
            return False

    class _BroadcasterObjectFilterTags(HasTunableSingletonFactory,
                                       AutoFactoryInit):
        def __str__(self):
            return '{}'.format(', '.join(str(tag) for tag in self.tags))

        FACTORY_TUNABLES = {
            'tags':
            TunableSet(
                description=
                '\n                An object with any tag in this set can be affected by the\n                broadcaster.\n                ',
                tunable=TunableEnumEntry(
                    description=
                    '\n                    A tag.\n                    ',
                    tunable_type=Tag,
                    default=Tag.INVALID,
                    pack_safe=True))
        }

        def is_affecting_objects(self):
            return (True, self.tags)

        def can_affect_object(self, obj):
            target_object_tags = obj.get_tags()
            if self.tags & target_object_tags:
                return True
            return False

    FREQUENCY_ENTER = 0
    FREQUENCY_PULSE = 1
    INSTANCE_TUNABLES = {
        'clock_type':
        TunableEnumEntry(
            description=
            "\n            Denotes whether broadcasters of this type are managed in real time\n            or game time.\n            \n            Most broadcasters should be managed in Game Time because they will\n            update with the speed of the game, including performance dips, and\n            speed changes. However, Real Time broadcasters are more performant\n            because they will only update based on the frequency of real time.\n            You should use real time updates if the broadcaster is enabled for\n            the lifetime of the object, there are a lot of that type, or timing\n            doesn't matter as much.\n            \n            One Shot Interaction broadcasters should always be Game Time.\n            Environment Score broadcasters should be in Real Time. Consult an\n            engineer if you have questions.\n            ",
            tunable_type=BroadcasterClockType,
            default=BroadcasterClockType.GAME_TIME),
        'constraints':
        TunableList(
            description=
            '\n            A list of constraints that define the area of influence of this\n            broadcaster. It is required that at least one constraint be defined.\n            ',
            tunable=TunableGeometricConstraintVariant(
                constraint_locked_args={'multi_surface': True},
                circle_locked_args={'require_los': False},
                disabled_constraints={'spawn_points', 'current_position'}),
            minlength=1),
        'effects':
        TunableList(
            description=
            '\n            A list of effects that are applied to Sims and objects affected by\n            this broadcaster.\n            ',
            tunable=TunableBroadcasterEffectVariant()),
        'route_events':
        TunableList(
            description=
            "\n            Specify any route events that are triggered when the Sim follows a\n            path that has points within this broadcaster's constraints.\n            ",
            tunable=RouteEvent.TunableReference(
                description=
                '\n                A Route Event that is to be played when a Sim plans a route\n                through this broadcaster.\n                ',
                pack_safe=True)),
        'frequency':
        TunableVariant(
            description=
            '\n            Define in what instances and how often this broadcaster affects Sims\n            and objects in its area of influence.\n            ',
            on_enter=TunableTuple(
                description=
                '\n                Sims and objects are affected by this broadcaster when they\n                enter in its area of influence, or when the broadcaster is\n                created.\n                ',
                locked_args={'frequency_type': FREQUENCY_ENTER},
                allow_multiple=Tunable(
                    description=
                    "\n                    If checked, then Sims may react multiple times if they re-\n                    enter the broadcaster's area of influence. If unchecked,\n                    then Sims will only react to the broadcaster once.\n                    ",
                    tunable_type=bool,
                    default=False)),
            on_pulse=TunableTuple(
                description=
                '\n                Sims and objects are constantly affected by this broadcaster\n                while they are in its area of influence.\n                ',
                locked_args={'frequency_type': FREQUENCY_PULSE},
                cooldown_time=TunableSimMinute(
                    description=
                    '\n                    The time interval between broadcaster pulses. Sims would not\n                    react to the broadcaster for at least this amount of time\n                    while in its area of influence.\n                    ',
                    default=8)),
            default='on_pulse'),
        'clustering':
        OptionalTunable(
            description=
            '\n            If set, then similar broadcasters, i.e. broadcasters of the same\n            instance, will be clustered together if their broadcasting objects\n            are close by. This improves performance and avoids having Sims react\n            multiple times to similar broadcasters. When broadcasters are\n            clustered together, there is no guarantee as to what object will be\n            used for testing purposes.\n            \n            e.g. Stinky food reactions are clustered together. A test on the\n            broadcaster should not, for example, differentiate between a stinky\n            lobster and a stinky steak, because the broadcasting object is\n            arbitrary and undefined.\n            \n            e.g. Jealousy reactions are not clustered together. A test on the\n            broadcaster considers the relationship between two Sims. Therefore,\n            it would be unwise to consider an arbitrary Sim if two jealousy\n            broadcasters are close to each other.\n            ',
            tunable=ObjectClusterRequest.TunableFactory(
                description=
                '\n                Specify how clusters for this particular broadcaster are formed.\n                ',
                locked_args={'minimum_size': 1}),
            enabled_by_default=True),
        'allow_objects':
        TunableVariant(
            description=
            '\n            If enabled, then in addition to all instantiated Sims, some objects\n            will be affected by this broadcaster. Some tuned effects might still\n            only apply to Sims (e.g. affordance pushing).\n            \n            Setting this tuning field has serious performance repercussions. Its\n            indiscriminate use could undermine our ability to meet Minspec\n            goals. Please use this sparingly.\n            ',
            disallow=_BroadcasterObjectFilterNone.TunableFactory(),
            from_tags=_BroadcasterObjectFilterTags.TunableFactory(),
            from_flammable=_BroadcasterObjectFilterFlammable.TunableFactory(),
            default='disallow'),
        'allow_sims':
        Tunable(
            description=
            '\n            If checked then this broadcaster will consider Sims. This is on by\n            default. \n            \n            If neither allow_objects or allow_sims is checked that will result\n            in a tuning error.\n            ',
            tunable_type=bool,
            default=True),
        'allow_sim_test':
        OptionalTunable(
            description=
            '\n            If enabled, allows for a top level test set to determine which\n            sims can be affected by the broadcaster at all.\n            ',
            tunable=TunableTestSet()),
        'immediate':
        Tunable(
            description=
            '\n            If checked, this broadcaster will trigger a broadcaster update when\n            added to the service. This adds a performance cost so please use\n            this sparingly. This can be used for one-shot interactions that\n            generate broadcasters because the update interval might be excluded\n            in the interaction duration.\n            ',
            tunable_type=bool,
            default=False)
    }

    @classmethod
    def _verify_tuning_callback(cls):
        (allow_objects, _) = cls.allow_objects.is_affecting_objects()
        if not cls.allow_sims and not allow_objects:
            logger.error(
                'Broadcaster {} is tuned to not allow any objects as targets.',
                cls)

    @classmethod
    def register_static_callbacks(cls, *args, **kwargs):
        for broadcaster_effect in cls.effects:
            broadcaster_effect.register_static_callbacks(*args, **kwargs)

    @classmethod
    def get_broadcaster_service(cls):
        current_zone = services.current_zone()
        if current_zone is not None:
            if cls.clock_type == BroadcasterClockType.GAME_TIME:
                return current_zone.broadcaster_service
            if cls.clock_type == BroadcasterClockType.REAL_TIME:
                return current_zone.broadcaster_real_time_service
            raise NotImplementedError

    def __init__(self, *args, broadcasting_object, interaction=None, **kwargs):
        super().__init__(*args, **kwargs)
        self._broadcasting_object_ref = weakref.ref(
            broadcasting_object, self._on_broadcasting_object_deleted)
        self._interaction = interaction
        self._constraint = None
        self._affected_objects = weakref.WeakKeyDictionary()
        self._current_objects = weakref.WeakSet()
        self._linked_broadcasters = weakref.WeakSet()
        broadcasting_object.register_on_location_changed(
            self._on_broadcasting_object_moved)
        self._quadtree = None
        self._cluster_request = None

    @property
    def broadcasting_object(self):
        if self._broadcasting_object_ref is not None:
            return self._broadcasting_object_ref()

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

    @property
    def quadtree(self):
        return self._quadtree

    @property
    def cluster_request(self):
        return self._cluster_request

    def _on_broadcasting_object_deleted(self, _):
        broadcaster_service = self.get_broadcaster_service()
        if broadcaster_service is not None:
            broadcaster_service.remove_broadcaster(self)

    def _on_broadcasting_object_moved(self, *_, **__):
        self.regenerate_constraint()
        broadcaster_service = self.get_broadcaster_service()
        if broadcaster_service is not None:
            broadcaster_service.update_cluster_request(self)

    def on_processed(self):
        for affected_object in self._affected_objects:
            if affected_object not in self._current_objects:
                self.remove_broadcaster_effect(affected_object)
        self._current_objects.clear()

    def on_removed(self):
        for affected_object in self._affected_objects:
            self.remove_broadcaster_effect(affected_object)
        broadcasting_object = self.broadcasting_object
        if broadcasting_object is not None:
            for broadcaster_effect in self.effects:
                if broadcaster_effect.apply_when_removed:
                    broadcaster_effect.apply_broadcaster_loot(self)
            broadcasting_object.unregister_on_location_changed(
                self._on_broadcasting_object_moved)

    def on_added_to_quadtree_and_cluster_request(self, quadtree,
                                                 cluster_request):
        self._quadtree = quadtree
        self._cluster_request = cluster_request

    def can_affect(self, obj):
        if obj.is_sim:
            if not self.allow_sims:
                return False
        elif not self.allow_objects.can_affect_object(obj):
            return False
        broadcasting_object = self.broadcasting_object
        if broadcasting_object is None:
            return False
        if obj is broadcasting_object:
            return False
        elif any(obj is linked_broadcaster.broadcasting_object
                 for linked_broadcaster in self._linked_broadcasters):
            return False
        return True

    def on_event_executed(self, route_event, sim):
        super().on_event_executed(route_event, sim)
        if self.can_affect(sim):
            self.apply_broadcaster_effect(sim)

    def is_route_event_valid(self, route_event, time, sim, path):
        if not self.can_affect(sim):
            return False
        constraint = self.get_constraint()
        if not constraint.valid or constraint.geometry is None:
            return False
        else:
            (transform, routing_surface) = path.get_location_data_at_time(time)
            if not (constraint.geometry.test_transform(transform)
                    and constraint.is_routing_surface_valid(routing_surface)):
                return False
        return True

    def apply_broadcaster_effect(self, affected_object):
        if affected_object.is_sim and self.allow_sim_test and not self.allow_sim_test.run_tests(
                SingleSimResolver(affected_object.sim_info)):
            return
        self._current_objects.add(affected_object)
        if self._should_apply_broadcaster_effect(affected_object):
            self._affected_objects[affected_object] = (
                services.time_service().sim_now, True)
            for broadcaster_effect in self.effects:
                if affected_object in self._affected_objects:
                    broadcaster_effect.apply_broadcaster_effect(
                        self, affected_object)
        for linked_broadcaster in self._linked_broadcasters:
            if affected_object in self._affected_objects:
                linked_broadcaster._apply_linked_broadcaster_effect(
                    affected_object, self._affected_objects[affected_object])

    def _apply_linked_broadcaster_effect(self, affected_object, data):
        self._apply_linked_broadcaster_data(affected_object, data)
        for broadcaster_effect in self.effects:
            if broadcaster_effect.apply_when_linked:
                broadcaster_effect.apply_broadcaster_effect(
                    self, affected_object)

    def _apply_linked_broadcaster_data(self, affected_object, data):
        if affected_object in self._affected_objects:
            was_in_area = self._affected_objects[affected_object][1]
            is_in_area = data[1]
            if was_in_area and not is_in_area:
                self.remove_broadcaster_effect(affected_object)
        self._affected_objects[affected_object] = data

    def remove_broadcaster_effect(self, affected_object, is_linked=False):
        affected_object_data = self._affected_objects.get(affected_object)
        if affected_object_data is None:
            return
        if not affected_object_data[1]:
            return
        self._affected_objects[affected_object] = (affected_object_data[0],
                                                   False)
        self._current_objects.discard(affected_object)
        for broadcaster_effect in self.effects:
            if not broadcaster_effect.apply_when_linked:
                if not is_linked:
                    broadcaster_effect.remove_broadcaster_effect(
                        self, affected_object)
            broadcaster_effect.remove_broadcaster_effect(self, affected_object)
        if not is_linked:
            for linked_broadcaster in self._linked_broadcasters:
                linked_broadcaster.remove_broadcaster_effect(affected_object,
                                                             is_linked=True)

    def _should_apply_broadcaster_effect(self, affected_object):
        if self.frequency.frequency_type == self.FREQUENCY_ENTER:
            if affected_object not in self._affected_objects:
                return True
            elif self.frequency.allow_multiple and not self._affected_objects[
                    affected_object][1]:
                return True
            return False
        if self.frequency.frequency_type == self.FREQUENCY_PULSE:
            last_reaction = self._affected_objects.get(affected_object, None)
            if last_reaction is None:
                return True
            else:
                time_since_last_reaction = services.time_service(
                ).sim_now - last_reaction[0]
                if time_since_last_reaction.in_minutes(
                ) > self.frequency.cooldown_time:
                    return True
                else:
                    return False
            return False

    def clear_linked_broadcasters(self):
        self._linked_broadcasters.clear()

    def set_linked_broadcasters(self, broadcasters):
        self.clear_linked_broadcasters()
        self._linked_broadcasters.update(broadcasters)
        for linked_broadcaster in self._linked_broadcasters:
            linked_broadcaster.clear_linked_broadcasters()
            for (obj, data) in linked_broadcaster._affected_objects.items():
                if obj not in self._affected_objects:
                    self._affected_objects[obj] = data
        for linked_broadcaster in self._linked_broadcasters:
            for (obj, data) in self._affected_objects.items():
                linked_broadcaster._apply_linked_broadcaster_data(obj, data)

    def get_linked_broadcasters_gen(self):
        yield from self._linked_broadcasters

    def regenerate_constraint(self, *_, **__):
        self._constraint = None

    def get_constraint(self):
        if not (self._constraint is None or not self._constraint.valid):
            self._constraint = Anywhere()
            for tuned_constraint in self.constraints:
                self._constraint = self._constraint.intersect(
                    tuned_constraint.create_constraint(
                        None,
                        target=self.broadcasting_object,
                        target_position=self.broadcasting_object.position))
        return self._constraint

    def get_resolver(self, affected_object):
        return DoubleObjectResolver(affected_object, self.broadcasting_object)

    def get_clustering(self):
        broadcasting_object = self.broadcasting_object
        if broadcasting_object is None:
            return
        if broadcasting_object.is_sim:
            return
        if broadcasting_object.is_in_inventory():
            return
        if broadcasting_object.routing_surface is None:
            return
        return self.clustering

    def should_cluster(self):
        return self.get_clustering() is not None

    def get_affected_object_count(self):
        return sum(1 for data in self._affected_objects.values() if data[1])

    @property
    def id(self):
        return self.broadcaster_id

    @property
    def lineofsight_component(self):
        return _BroadcasterLosComponent(self)

    @property
    def position(self):
        return self.broadcasting_object.position

    @property
    def routing_surface(self):
        return self.broadcasting_object.routing_surface

    @property
    def parts(self):
        return self.broadcasting_object.parts
示例#32
0
 def get_constraint(self, sim):
     return Anywhere()
示例#33
0
 def make_constraint_default(cls, *args, **kwargs):
     return Anywhere()
示例#34
0
 def get_constraint(self):
     if self._constraint is None or not self._constraint.valid:
         self._constraint = Anywhere()
         for tuned_constraint in self.constraints:
             self._constraint = self._constraint.intersect(tuned_constraint.create_constraint(None, target=self.broadcasting_object, target_position=self.broadcasting_object.position))
     return self._constraint