Ejemplo n.º 1
0
class Subject:
	def __init__(self, parent):
		self.parent = parent
		self._observers_lock = RLock()
		self._observers = WeakSet()

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

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

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

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

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

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

		for obs in observers:
			logger.debug("%s is about to notify %s to %s", stringFor(self.parent), event, stringFor(obs))
			obs.onNotify(self.parent, event, args)
Ejemplo n.º 2
0
class Subject(object):
    def __init__(self, parent, loggingLevel=logging.INFO):
        super(Subject, self).__init__()

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

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

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

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

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

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

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

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

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

        for obs in observers:
            self._logger.debug("%s is about to notify %s to %s",
                               stringFor(self.parent), event, stringFor(obs))
            try:
                obs.onNotify(self.parent, event, args)
            except Exception as e:
                self._logger.error(
                    "Catched exception trying to notify %s to %s with arguments: %s",
                    str(event), str(obs), str(args))
                self._logger.exception(e)
Ejemplo n.º 3
0
class ObjectManager(DistributableObjectManager, GameObjectManagerMixin,
                    AttractorManagerMixin):
    FIREMETER_DISPOSABLE_OBJECT_CAP = Tunable(
        int,
        5,
        description=
        'Number of disposable objects a lot can have at any given moment.')
    BED_TAGS = TunableTuple(
        description=
        '\n        Tags to check on an object to determine what type of bed an object is.\n        ',
        beds=TunableSet(
            description=
            '\n            Tags that consider an object as a bed other than double beds.\n            ',
            tunable=TunableEnumWithFilter(tunable_type=tag.Tag,
                                          default=tag.Tag.INVALID,
                                          filter_prefixes=BED_PREFIX_FILTER)),
        double_beds=TunableSet(
            description=
            '\n            Tags that consider an object as a double bed\n            ',
            tunable=TunableEnumWithFilter(tunable_type=tag.Tag,
                                          default=tag.Tag.INVALID,
                                          filter_prefixes=BED_PREFIX_FILTER)),
        kid_beds=TunableSet(
            description=
            '\n            Tags that consider an object as a kid bed\n            ',
            tunable=TunableEnumWithFilter(tunable_type=tag.Tag,
                                          default=tag.Tag.INVALID,
                                          filter_prefixes=BED_PREFIX_FILTER)),
        other_sleeping_spots=TunableSet(
            description=
            '\n            Tags that considered sleeping spots.\n            ',
            tunable=TunableEnumWithFilter(tunable_type=tag.Tag,
                                          default=tag.Tag.INVALID,
                                          filter_prefixes=BED_PREFIX_FILTER)))
    HOUSEHOLD_INVENTORY_OBJECT_TAGS = TunableTags(
        description=
        '\n        List of tags to apply to every household inventory proxy object.\n        '
    )
    INVALID_UNPARENTED_OBJECT_TAGS = TunableTags(
        description=
        '\n        Objects with these tags should not exist without a parent. An obvious\n        case is for transient objects. They should only exist as a carried object,\n        thus parented to a sim, when loading into a save game.\n        '
    )

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

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

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

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

    def portal_cache_gen(self):
        yield from self._portal_cache

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

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

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

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

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

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

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

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

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

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

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

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

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

    def get_objects_to_ignore_portal_validation_cache(self):
        return self._objects_to_ignore_portal_validation_cache

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    def on_front_door_candidates_changed(self):
        self._front_door_candidates_changed_callback()

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

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

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

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

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

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

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

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

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

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

    @classproperty
    def supports_parenting(self):
        return True

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

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

    @property
    def active_whim_sets(self):
        return set(self._whim_set_cache.keys())
Ejemplo n.º 4
0
class BaseStructure(GameElementBase):

    neighbourhoodThreshold = 2
    REACTION_THRESHOLD = util.REACTION_THRESHOLD

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

            self._maxFadeOutTime = fadeOutTime
            self._fadeOutTime = fadeOutTime

            self._maxFadeInTime = fadeInTime
            self._fadeInTime = fadeInTime

            self._stopCallback = stopCallback

            self._action = self._veiling

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

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

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

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

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

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

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

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

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

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

        self._fixElements = WeakSet()

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

        self._overDriveCounter = 0
        self._overdrive = False

        self._veil = None

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

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

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

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

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

        self._canvasRoot = self._canvas.getRootNode()

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

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

        self._initStructureCore()

        assert self.checkSanity()

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

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

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

    def getElementNodeParent(self):
        return self._canvasRoot

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

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

    def delete(self):

        if not self.alive:
            return

        self._stopTickTimer()

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

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

        self._elementMapping = None

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

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

        self._edgeNodes = None
        self._shadowNodes = None

        self._rootParent = None

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

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

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

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

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

        self._blackCanvas = None
        self._canvas = None

        self._fixElements = None

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

        GameElementBase.delete(self)

    def _initStructureCore(self):
        raise NotImplementedError

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

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

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

    def _overdriveEnd(self):
        pass

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

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

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

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

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

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

    def _veilEnd(self):
        self._veil = None

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

    def onTick(self):

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

        self.grow()

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

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

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

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

    def checkSanity(self):
        return True

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

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

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

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

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

        removeCounter += self.removeNotReachableElements()
        return removeCounter

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

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

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

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

        return element

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

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

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

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

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

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

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

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

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

        element.onCollision(other)

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

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

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

        self._graph.remove_node(element.id)

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

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

        assert self.checkSanity()

        self._checkDepleted()

        if __debug__:
            self.updateNeigbourhoodVisualisation()

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

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

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

        assert self.checkSanity()

        if __debug__:
            self.updateNeigbourhoodVisualisation()

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

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

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

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

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

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

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

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

        assert self.checkSanity()

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

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

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

        return removeCounter

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

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

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

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

        if newSpot is None:
            return

        color, crystalType = self.getElementTypeToGrow()

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

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

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

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

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

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

        assert self.checkSanity()

    def _resetGrowLock(self):
        self._growLock = False

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

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

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

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

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

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

            if not self._fixElements:
                return None, None

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

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

        return None, None

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

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

        return availablePositions
Ejemplo n.º 5
0
class BroadcasterService(Service):
    __qualname__ = 'BroadcasterService'
    INTERVAL = TunableRealSecond(
        description=
        '\n        The time between broadcaster pulses. A lower number will impact\n        performance.\n        ',
        default=5)
    DEFAULT_QUADTREE_RADIUS = 0.1

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

    def start(self):
        self._alarm_handle = add_alarm_real_time(self,
                                                 interval_in_real_seconds(
                                                     self.INTERVAL),
                                                 self._on_update,
                                                 repeating=True,
                                                 use_sleep_time=False)
        object_manager = services.object_manager()
        object_manager.register_callback(
            CallbackTypes.ON_OBJECT_LOCATION_CHANGED,
            self._update_object_cache)
        object_manager.register_callback(CallbackTypes.ON_OBJECT_ADD,
                                         self._update_object_cache)
        services.current_zone().wall_contour_update_callbacks.append(
            self._update_object_cache)

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

    def add_broadcaster(self, broadcaster):
        if broadcaster not in self._pending_broadcasters:
            self._pending_broadcasters.append(broadcaster)
            self._on_update_callbacks()

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

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

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

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

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

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

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

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

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

    def get_broadcasters_gen(self, inspect_only=False):
        for (cluster_request_key,
             cluster_request) in self._cluster_requests.items():
            is_cluster_dirty = cluster_request.is_dirty()
            if is_cluster_dirty:
                for broadcaster in self._get_broadcasters_for_cluster_request_gen(
                        *cluster_request_key):
                    broadcaster.regenerate_constraint()
            while not is_cluster_dirty or not inspect_only:
                while True:
                    for cluster in cluster_request.get_clusters_gen():
                        broadcaster_iter = cluster.objects_gen()
                        master_broadcaster = next(broadcaster_iter)
                        master_broadcaster.set_linked_broadcasters(
                            list(broadcaster_iter))
                        yield master_broadcaster
        for broadcaster in self._active_broadcasters:
            while not broadcaster.should_cluster():
                if self._is_valid_broadcaster(broadcaster):
                    yield broadcaster

    def get_pending_broadcasters_gen(self):
        yield self._pending_broadcasters

    def _get_all_objects_gen(self):
        if any(broadcaster.allow_objects
               for broadcaster in self._active_broadcasters):
            if self._object_cache is None:
                self._object_cache = WeakSet(
                    services.object_manager().valid_objects())
            yield list(self._object_cache)
        else:
            self._object_cache = None
            yield services.sim_info_manager().instanced_sims_gen()

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

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

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

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

    def _update(self):
        try:
            self._activate_pending_broadcasters()
            current_broadcasters = set(self.get_broadcasters_gen())
            for obj in self._get_all_objects_gen():
                is_affected = False
                for broadcaster in current_broadcasters:
                    while broadcaster.can_affect(obj):
                        constraint = broadcaster.get_constraint()
                        if not constraint.valid:
                            pass
                        if constraint.geometry is None or constraint.geometry.contains_point(
                                obj.position
                        ) and constraint.routing_surface == obj.routing_surface:
                            broadcaster.apply_broadcaster_effect(obj)
                            is_affected = True
                while not is_affected:
                    if self._object_cache is not None:
                        self._object_cache.remove(obj)
            for broadcaster in current_broadcasters:
                broadcaster.on_processed()
        finally:
            self._on_update_callbacks()
Ejemplo n.º 6
0
class ConnectionObject:
    def __init__(self, type, source, destination, save_id=None):
        self.__type = type
        self.__data = type.ufl_type.build_default(None)
        self.__source = ref(source)
        self.__destination = ref(destination)
        self.__visuals = WeakSet()
        self.__cache = ModelTemporaryDataCache(None)
        if save_id is None:
            self.__save_id = uuid4()
        else:
            self.__save_id = save_id

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    def create_ufl_dialog(self, options=UflDialogOptions.standard):
        if not self.__type.ufl_type.has_attributes:
            raise Exception
        dialog = UflDialog(self.__type.ufl_type, options)
        dialog.associate(self.__data)
        return dialog
class BroadcasterService(Service):
    __qualname__ = 'BroadcasterService'
    INTERVAL = TunableRealSecond(description='\n        The time between broadcaster pulses. A lower number will impact\n        performance.\n        ', default=5)
    DEFAULT_QUADTREE_RADIUS = 0.1

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

    def start(self):
        self._alarm_handle = add_alarm_real_time(self, interval_in_real_seconds(self.INTERVAL), self._on_update, repeating=True, use_sleep_time=False)
        object_manager = services.object_manager()
        object_manager.register_callback(CallbackTypes.ON_OBJECT_LOCATION_CHANGED, self._update_object_cache)
        object_manager.register_callback(CallbackTypes.ON_OBJECT_ADD, self._update_object_cache)
        services.current_zone().wall_contour_update_callbacks.append(self._update_object_cache)

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

    def add_broadcaster(self, broadcaster):
        if broadcaster not in self._pending_broadcasters:
            self._pending_broadcasters.append(broadcaster)
            self._on_update_callbacks()

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

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

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

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

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

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

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

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

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

    def get_broadcasters_gen(self, inspect_only=False):
        for (cluster_request_key, cluster_request) in self._cluster_requests.items():
            is_cluster_dirty = cluster_request.is_dirty()
            if is_cluster_dirty:
                for broadcaster in self._get_broadcasters_for_cluster_request_gen(*cluster_request_key):
                    broadcaster.regenerate_constraint()
            while not is_cluster_dirty or not inspect_only:
                while True:
                    for cluster in cluster_request.get_clusters_gen():
                        broadcaster_iter = cluster.objects_gen()
                        master_broadcaster = next(broadcaster_iter)
                        master_broadcaster.set_linked_broadcasters(list(broadcaster_iter))
                        yield master_broadcaster
        for broadcaster in self._active_broadcasters:
            while not broadcaster.should_cluster():
                if self._is_valid_broadcaster(broadcaster):
                    yield broadcaster

    def get_pending_broadcasters_gen(self):
        yield self._pending_broadcasters

    def _get_all_objects_gen(self):
        if any(broadcaster.allow_objects for broadcaster in self._active_broadcasters):
            if self._object_cache is None:
                self._object_cache = WeakSet(services.object_manager().valid_objects())
            yield list(self._object_cache)
        else:
            self._object_cache = None
            yield services.sim_info_manager().instanced_sims_gen()

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

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

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

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

    def _update(self):
        try:
            self._activate_pending_broadcasters()
            current_broadcasters = set(self.get_broadcasters_gen())
            for obj in self._get_all_objects_gen():
                is_affected = False
                for broadcaster in current_broadcasters:
                    while broadcaster.can_affect(obj):
                        constraint = broadcaster.get_constraint()
                        if not constraint.valid:
                            pass
                        if constraint.geometry is None or constraint.geometry.contains_point(obj.position) and constraint.routing_surface == obj.routing_surface:
                            broadcaster.apply_broadcaster_effect(obj)
                            is_affected = True
                while not is_affected:
                    if self._object_cache is not None:
                        self._object_cache.remove(obj)
            for broadcaster in current_broadcasters:
                broadcaster.on_processed()
        finally:
            self._on_update_callbacks()
Ejemplo n.º 8
0
class Ensemble(metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(sims4.resources.Types.ENSEMBLE)):
    ENSEMBLE_PRIORITIES = TunableList(description='\n        A list of ensembles by priority.  Those with higher guids will be\n        considered more important than those with lower guids.\n        \n        IMPORTANT: All ensemble types must be referenced in this list.\n        ', tunable=TunableReference(description='\n            A single ensemble.\n            ', manager=services.get_instance_manager(sims4.resources.Types.ENSEMBLE), pack_safe=True))

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

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

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

    def __iter__(self):
        yield from self._sims

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

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

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

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

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

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

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

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

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

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

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

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

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

    def get_ensemble_autonomous_interactions_gen(self, context, **interaction_parameters):
        if self.is_within_ensemble_radius(context.sim):
            return
        for ensemble_affordance in self.ensemble_autonomous_interactions:
            affordance = EnsembleConstraintProxyInteraction.generate(ensemble_affordance, self)
            yield from affordance.potential_interactions(context.sim, context, **interaction_parameters)
Ejemplo n.º 9
0
class ObjectManager(DistributableObjectManager):
    __qualname__ = 'ObjectManager'
    FIREMETER_DISPOSABLE_OBJECT_CAP = Tunable(int, 5, description='Number of disposable objects a lot can have at any given moment.')
    BED_TAGS = TunableTuple(description='\n        Tags to check on an object to determine what type of bed an object is.\n        ', beds=TunableSet(description='\n            Tags that consider an object as a bed other than double beds.\n            ', tunable=TunableEnumWithFilter(tunable_type=tag.Tag, default=tag.Tag.INVALID, filter_prefixes=BED_PREFIX_FILTER)), double_beds=TunableSet(description='\n            Tags that consider an object as a double bed\n            ', tunable=TunableEnumWithFilter(tunable_type=tag.Tag, default=tag.Tag.INVALID, filter_prefixes=BED_PREFIX_FILTER)), kid_beds=TunableSet(description='\n            Tags that consider an object as a kid bed\n            ', tunable=TunableEnumWithFilter(tunable_type=tag.Tag, default=tag.Tag.INVALID, filter_prefixes=BED_PREFIX_FILTER)), other_sleeping_spots=TunableSet(description='\n            Tags that considered sleeping spots.\n            ', tunable=TunableEnumWithFilter(tunable_type=tag.Tag, default=tag.Tag.INVALID, filter_prefixes=BED_PREFIX_FILTER)))

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._crafting_cache = CraftingObjectCache()
        self._sim_spawn_conditions = collections.defaultdict(set)
        self._client_connect_callbacks = CallableList()
        self._portal_cache = WeakSet()
        self._portal_added_callbacks = CallableList()
        self._portal_removed_callbacks = CallableList()
        self._front_door_candidates_changed_callback = CallableList()
        self._all_bed_tags = self.BED_TAGS.beds | self.BED_TAGS.double_beds | self.BED_TAGS.kid_beds | self.BED_TAGS.other_sleeping_spots

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

    def portal_cache_gen(self):
        yield self._portal_cache

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

    def move_to_inventory(self, obj, inventory_manager):
        del self._objects[obj.id]
        obj.manager = inventory_manager
        inventory_manager._objects[obj.id] = obj
        if hasattr(obj, 'on_added_to_inventory'):
            obj.on_added_to_inventory()

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

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

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

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

    def save(self, object_list=None, zone_data=None, open_street_data=None, **kwargs):
        if object_list is None:
            return
        open_street_objects = file_serialization.ObjectList()
        total_beds = 0
        double_bed_exist = False
        kid_bed_exist = False
        alternative_sleeping_spots = 0
        for game_object in self._objects.values():
            while self._should_save_object_on_lot(game_object):
                if game_object.persistence_group == objects.persistence_groups.PersistenceGroups.OBJECT:
                    save_result = game_object.save_object(object_list.objects, ItemLocation.ON_LOT, 0)
                else:
                    if game_object.item_location == ItemLocation.ON_LOT or game_object.item_location == ItemLocation.INVALID_LOCATION:
                        item_location = ItemLocation.FROM_OPEN_STREET
                    else:
                        item_location = game_object.item_location
                    save_result = game_object.save_object(open_street_objects.objects, item_location, 0)
                if not save_result:
                    pass
                if zone_data is None:
                    pass
                def_build_buy_tags = game_object.definition.build_buy_tags
                if not def_build_buy_tags & self._all_bed_tags:
                    pass
                if def_build_buy_tags & self.BED_TAGS.double_beds:
                    double_bed_exist = True
                    total_beds += 1
                elif def_build_buy_tags & self.BED_TAGS.kid_beds:
                    total_beds += 1
                    kid_bed_exist = True
                elif def_build_buy_tags & self.BED_TAGS.other_sleeping_spots:
                    alternative_sleeping_spots += 1
                elif def_build_buy_tags & self.BED_TAGS.beds:
                    total_beds += 1
        if open_street_data is not None:
            open_street_data.objects = open_street_objects
        if zone_data is not None:
            bed_info_data = gameplay_serialization.ZoneBedInfoData()
            bed_info_data.num_beds = total_beds
            bed_info_data.double_bed_exist = double_bed_exist
            bed_info_data.kid_bed_exist = kid_bed_exist
            bed_info_data.alternative_sleeping_spots = alternative_sleeping_spots
            zone_data.gameplay_zone_data.bed_info_data = bed_info_data
        lot = services.current_zone().lot
        for (inventory_type, inventory) in lot.get_all_object_inventories_gen():
            for game_object in inventory:
                game_object.save_object(object_list.objects, ItemLocation.OBJECT_INVENTORY, inventory_type)

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

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

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

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

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

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

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

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

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

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

    def add_portal_to_cache(self, portal):
        self._portal_cache.add(portal)
        self._portal_added_callbacks(portal)

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

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

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

    def on_front_door_candidates_changed(self):
        self._front_door_candidates_changed_callback()

    def advertising_objects_gen(self, motives:set=DEFAULT):
        if not motives:
            return
        if motives is DEFAULT:
            for obj in self.valid_objects():
                while obj.commodity_flags:
                    yield obj
            return
        for obj in self.valid_objects():
            while obj.commodity_flags & motives:
                yield obj

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

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

    @classproperty
    def supports_parenting(self):
        return True