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
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
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')
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
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
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()
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
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
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)
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
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
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()
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()
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)
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)
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)
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)
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
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
def get_constraint(self, sim): return Anywhere()
def make_constraint_default(cls, *args, **kwargs): return Anywhere()
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