Example #1
0
class LotTuningMaps:
    __qualname__ = 'LotTuningMaps'
    LOT_TO_LOTTUNING_MAP = TunableMapping(
        description=
        "\n            Mapping of Lot Description ID to lot tuning. This is a reference to \n            a specific lot in one of our regions. e.g. Goth's mansion lot.\n            ",
        key_name='Lot Description ID',
        key_type=TunableLotDescription(),
        value_name='Lot Tuning',
        value_type=LotTuning.TunableReference())
    STREET_TO_LOTTUNING_MAP = TunableMapping(
        description=
        '\n            Mapping of Street Description ID to lot tuning. Street and world\n            are analogous terms. e.g. suburbs street in Garden District.\n            \n            This represents the tuning for all lots within this street that does\n            not have a specific LotTuning specified for itself in the \n            LOT_TO_LOTTUNING_MAP.\n            ',
        key_name='Street Description ID',
        key_type=TunableWorldDescription(),
        value_name='Lot Tuning',
        value_type=LotTuning.TunableReference())
    REGION_TO_LOTTUNING_MAP = TunableMapping(
        description=
        '\n            Mapping of Region Description ID to spawner tuning. Region and \n            neighborhood are analogous terms. e.g. Garden District.\n            \n            This represents the tuning for all lots in the region that does\n            not have a specific LotTuning specified for itself in either the \n            LOT_TO_LOTTUNING_MAP or via STREET_TO_LOTTUNING_MAP.\n            ',
        key_name='Region Description ID',
        key_type=TunableRegionDescription(),
        value_name='Lot Tuning',
        value_type=LotTuning.TunableReference())

    @classmethod
    def get_lot_tuning(cls):
        current_zone = services.current_zone()
        lot = current_zone.lot
        if lot is None:
            logger.warn(
                'Attempting to get LotTuning when the current zone does not have a lot.',
                owner='manus')
            return
        (world_description_id, lot_description_id
         ) = services.get_world_and_lot_description_id_from_zone_id(
             current_zone.id)
        lot_tuning = cls.LOT_TO_LOTTUNING_MAP.get(lot_description_id)
        if lot_tuning is not None:
            return lot_tuning
        lot_tuning = cls.STREET_TO_LOTTUNING_MAP.get(world_description_id,
                                                     None)
        if lot_tuning is not None:
            return lot_tuning
        neighborhood_id = current_zone.neighborhood_id
        if neighborhood_id == 0:
            logger.warn(
                'Attempting to get LotTuning when the current zone does not have a neighborhood.',
                owner='manus')
            return
        neighborhood_proto_buff = services.get_persistence_service(
        ).get_neighborhood_proto_buff(neighborhood_id)
        region_id = neighborhood_proto_buff.region_id
        lot_tuning = cls.REGION_TO_LOTTUNING_MAP.get(region_id, None)
        return lot_tuning
class NeighborhoodPopulationService(Service):
    __qualname__ = 'NeighborhoodPopulationService'
    REGION_TO_HOUSEHOLD_POPULATION_DATA = TunableMapping(description='\n        Mapping of Region Description ID to household population data.  This is\n        used to fill households for the different type of regions.\n        ', key_name='Region Description', key_type=TunableRegionDescription(), value_name='Household Population Data', value_type=HouseholdPopulationData.TunableFactory())
    HOMELESS_HOUSEHOLD_TEMPLATES = TunableList(description='\n        A List of household templates that will be considered for homelesss\n        households.\n        ', tunable=TunableHouseholdTemplateWeightTuple())
    NUM_BEDS_TO_IDEAL_HOUSEHOLD_CURVE = TunableMapping(description='\n        Based on the number of beds and the number of sims in the household, a\n        multiplier will be applied to the household to determine if household\n        will be selected and added to zone.\n        ', key_name='Num Beds', key_type=Tunable(tunable_type=int, default=1), value_name='Ideal Household Curve', value_type=TunableCurve(x_axis_name='num_sim_in_household', y_axis_name='bonus_multiplier'))
    KID_TO_KID_BED_MULTIPLIER = TunableRange(description='\n        When trying to populate a lot if lot has a kids bed and household has a\n        kid in it.  This multiplier will be applied to the weight of household\n        when selecting household to move in.\n        ', tunable_type=float, default=1, minimum=1)
    SIGNIFICANT_OTHER_MULTIPLIER = TunableRange(description='\n        When trying to populate a lot and if lot has a double bed and household\n        contains a pair of sims that are considered significant other.  This\n        multiplier will be applied to the weight of household when selecting\n        household to move in.\n        ', tunable_type=float, default=1, minimum=1)

    def __init__(self):
        self._requests = []
        self._processing_element_handle = None

    def _process_population_request_gen(self, timeline):
        while self._requests:
            request = self._requests.pop(0)
            try:
                yield request.process_request_gen(timeline)
                request.process_completed(True)
            except GeneratorExit:
                raise
            except BaseException:
                request.process_completed(False)
                logger.exception('Exception raised while processing creating npc households')
            while self._requests:
                yield element_utils.run_child(timeline, element_utils.sleep_until_next_tick_element())
                continue
        self._processing_element_handle = None

    def add_population_request(self, num_to_fill, neighborhood_id, completion_callback, available_zone_ids, try_existing_households):
        account = self._get_account()
        if account is None:
            return False
        request = _FillZonePopulationRequest(account, num_to_fill, neighborhood_id, completion_callback, available_zone_ids=available_zone_ids, try_existing_households=try_existing_households)
        self._add_request(request)
        return True

    def add_homeless_household_request(self, num_to_fill, completion_callback):
        account = self._get_account()
        if account is None:
            return False
        request = _CreateHomelessHouseholdRequest(account, num_to_fill, None, completion_callback)
        self._add_request(request)
        return True

    def _get_account(self):
        client = services.client_manager().get_first_client()
        if client.account is not None or client.household is not None:
            return client.account

    @property
    def is_processing_requests(self):
        return self._processing_element_handle or len(self._requests) > 0

    def _add_request(self, request):
        self._requests.append(request)
        if self._processing_element_handle is None:
            timeline = services.time_service().sim_timeline
            element = elements.GeneratorElement(self._process_population_request_gen)
            self._processing_element_handle = timeline.schedule(element)
Example #3
0
class StoryProgressionPopulateAction(_StoryProgressionAction):
    FACTORY_TUNABLES = {
        '_region_to_population_density':
        TunableMapping(
            description=
            '\n        Based on region what percent of available lots will be filled.\n        ',
            key_name='Region Description',
            key_type=TunableRegionDescription(pack_safe=True),
            value_name='Population Density',
            value_type=TunableTuple(
                density=TunablePercent(
                    description=
                    '\n                Percent of how much of the residential lots will be occupied of\n                all the available lots in that region.  If the current lot\n                density is greater than this value, then no household will be\n                moved in.\n                ',
                    default=40),
                min_empty=TunableRange(
                    description=
                    '\n                Minimum number of empty lots that should stay empty for this neighborhood.\n                ',
                    tunable_type=int,
                    default=2,
                    minimum=0))),
        '_time_of_week':
        TunableTuple(
            description=
            '\n        Only run this action when it is between a certain time of the week.\n        ',
            start_time=TunableTimeOfWeek(default_day=Days.SUNDAY,
                                         default_hour=2),
            end_time=TunableTimeOfWeek(default_day=Days.SUNDAY,
                                       default_hour=6))
    }

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._last_time_checked = None

    def _get_neighborhood_proto(self):
        neighborhood_id = services.current_zone().neighborhood_id
        if neighborhood_id == 0:
            return
        return services.get_persistence_service().get_neighborhood_proto_buff(
            neighborhood_id)

    def _get_neighborhood_availability_data(self, neighborhood_proto_buff):
        num_zones_filled = 0
        available_zone_ids = set()
        venue_manager = services.get_instance_manager(
            sims4.resources.Types.VENUE)
        for lot_owner_info in neighborhood_proto_buff.lots:
            for lot_owner in lot_owner_info.lot_owner:
                if lot_owner.household_id > 0:
                    num_zones_filled += 1
                    break
            else:
                venue_tuning = venue_manager.get(lot_owner_info.venue_key)
                if not venue_tuning is None:
                    if venue_tuning.venue_type == VenueTypes.RESIDENTIAL:
                        if lot_owner_info.lot_template_id > 0:
                            available_zone_ids.add(
                                lot_owner_info.zone_instance_id)
                if lot_owner_info.lot_template_id > 0:
                    available_zone_ids.add(lot_owner_info.zone_instance_id)
        available_zone_ids.discard(services.current_zone_id())
        return (num_zones_filled, available_zone_ids)

    def _should_process(self):
        client = services.client_manager().get_first_client()
        if client is None:
            return False
        if client.account is None or client.household is None:
            return False
        neighborhood_population_service = services.neighborhood_population_service(
        )
        if neighborhood_population_service is None:
            return False
        elif neighborhood_population_service.is_processing_requests:
            return False
        return True

    def should_process(self, options):
        if not self._should_process():
            return False
        if options & StoryProgressionFlags.ALLOW_POPULATION_ACTION:
            current_time = services.time_service().sim_now
            if not current_time.time_between_week_times(
                    self._time_of_week.start_time(),
                    self._time_of_week.end_time()):
                return False
            if self._last_time_checked is not None:
                time_elapsed = current_time - self._last_time_checked
                if time_elapsed.in_days() <= 1:
                    return False
        else:
            return False
        return True

    def _zone_population_completed_callback(self, success):
        pass

    def _add_population_request(self,
                                desired_population_data,
                                neighborhood_proto_buff,
                                try_existing_households,
                                max_to_fill=None):
        (num_zones_filled, available_zone_ids
         ) = self._get_neighborhood_availability_data(neighborhood_proto_buff)
        if len(available_zone_ids) <= desired_population_data.min_empty:
            return False
        neighborhood_population_service = services.neighborhood_population_service(
        )
        if neighborhood_population_service is None:
            return False
        total_zones = num_zones_filled + len(available_zone_ids)
        max_allowed_to_fill = total_zones - desired_population_data.min_empty
        num_desired_zones_filled = min(
            math.floor(total_zones * desired_population_data.density),
            max_allowed_to_fill)
        if num_desired_zones_filled <= num_zones_filled:
            return False
        num_zones_to_fill = num_desired_zones_filled - num_zones_filled
        if max_to_fill is not None:
            num_zones_to_fill = min(max_to_fill, num_zones_to_fill)
        if num_zones_to_fill <= 0:
            return False
        return neighborhood_population_service.add_population_request(
            num_zones_to_fill, neighborhood_proto_buff.neighborhood_id,
            self._zone_population_completed_callback, available_zone_ids,
            try_existing_households)

    def process_action(self, story_progression_flags):
        self._last_time_checked = services.time_service().sim_now
        client = services.client_manager().get_first_client()
        if client is None:
            return
        neighborhood_proto_buff = self._get_neighborhood_proto()
        if neighborhood_proto_buff is None:
            return
        desired_population_data = self._region_to_population_density.get(
            neighborhood_proto_buff.region_id, None)
        if desired_population_data is not None:
            self._add_population_request(desired_population_data,
                                         neighborhood_proto_buff,
                                         True,
                                         max_to_fill=1)
Example #4
0
class StoryProgressionDestinationPopulateAction(_StoryProgressionAction):
    FACTORY_TUNABLES = {
        '_region_to_rentable_zone_density':
        TunableMapping(
            description=
            '\n        Based on region what percent of available lots will be filled.\n        ',
            key_name='Region Description',
            key_type=TunableRegionDescription(pack_safe=True),
            value_name='Rentable Zone Density',
            value_type=TunableTuple(
                _venues_to_populate=TunableSet(
                    description=
                    '\n                A set of venue references that are considered to be rentable.\n                ',
                    tunable=TunableReference(
                        manager=services.
                        get_instance_manager(
                            sims4.resources.
                            Types.VENUE),
                        pack_safe
                        =True)),
                household_description_to_ideal_travel_group_size=TunableMapping(
                    description=
                    '\n                Based on the house description how many sims should go on vacation\n                ',
                    key_name='House Description',
                    key_type=TunableHouseDescription(pack_safe=True),
                    value_name='Travel Group Size',
                    value_type=TunableLiteralOrRandomValue(
                        description=
                        '\n                    The maximum number of sims that should go on vacation to\n                    that lot.\n                    ',
                        tunable_type=int,
                        minimum=0)),
                bed_count_to_travel_group_size=TunableMapping(
                    description=
                    '\n                Based on the house description how many sims should go on vacation\n                ',
                    key_name='Number of beds',
                    key_type=Tunable(
                        description=
                        '\n                    The number of beds on the lot to determine how many sims\n                    can go in the vacation group.\n                    ',
                        tunable_type=int,
                        default=1),
                    value_name='Travel Group Size',
                    value_type=TunableLiteralOrRandomValue(
                        description=
                        '\n                    The maximum number of sims that should go on vacation to\n                    that lot.\n                    ',
                        tunable_type=int,
                        minimum=0)),
                travel_group_size_to_household_template=TunableMapping(
                    description=
                    '\n                Mapping to travel group size to household templates. If there\n                are no household that fulfill the requirement of renting a\n                zone, then random household template will chosen to be created\n                to rent a zone.\n                ',
                    key_type=Tunable(tunable_type=int, default=1),
                    value_type=TunableList(
                        description=
                        '\n                    Household template that will be created for renting a zone.\n                    ',
                        tunable=HouseholdTemplate.TunableReference())),
                density=TunablePercent(
                    description=
                    '\n                Percent of lots will be occupied once a user sim has rented a lot.\n                ',
                    default=80),
                min_to_populate=TunableRange(
                    description=
                    '\n                Minimum number of lots that should be rented.\n                ',
                    tunable_type=int,
                    default=3,
                    minimum=0),
                duration=TunableLiteralOrRandomValue(
                    description=
                    "\n                The maximum in sim days npc's should stay on vacation.\n                ",
                    tunable_type=int,
                    minimum=1,
                    default=1)))
    }

    def should_process(self, options):
        if services.active_household_id() == 0:
            return False
        return True

    def _get_rentable_zones(self, rentable_zone_density_data,
                            neighborhood_proto_buff):
        num_zones_rented = 0
        available_zone_ids = []
        travel_group_manager = services.travel_group_manager()
        venue_manager = services.get_instance_manager(
            sims4.resources.Types.VENUE)
        for lot_owner_info in neighborhood_proto_buff.lots:
            if lot_owner_info.venue_key == 0:
                continue
            if venue_manager.get(
                    lot_owner_info.venue_key
            ) not in rentable_zone_density_data._venues_to_populate:
                continue
            zone_id = lot_owner_info.zone_instance_id
            if not travel_group_manager.is_zone_rentable(zone_id):
                num_zones_rented += 1
            else:
                available_zone_ids.append(zone_id)
        return (num_zones_rented, available_zone_ids)

    def process_action(self, story_progression_flags):
        zone = services.current_zone()
        neighborhood_proto = services.get_persistence_service(
        ).get_neighborhood_proto_buff(zone.neighborhood_id)
        region_id = neighborhood_proto.region_id
        rentable_zone_density_data = self._region_to_rentable_zone_density.get(
            region_id)
        if rentable_zone_density_data is None:
            return
        (num_zones_rented, available_zone_ids) = self._get_rentable_zones(
            rentable_zone_density_data, neighborhood_proto)
        num_available_zone_ids = len(available_zone_ids)
        if num_available_zone_ids == 0:
            return
        number_zones_to_fill = 0
        num_desired_zones_filled = math.floor(
            (num_zones_rented + num_available_zone_ids) *
            rentable_zone_density_data.density)
        if num_desired_zones_filled < rentable_zone_density_data.min_to_populate:
            num_desired_zones_filled = rentable_zone_density_data.min_to_populate
        if num_zones_rented < num_desired_zones_filled:
            number_zones_to_fill = num_desired_zones_filled - num_zones_rented
        neighborhood_population_service = services.neighborhood_population_service(
        )
        if neighborhood_population_service is None:
            return
        neighborhood_population_service.add_rentable_lot_request(
            number_zones_to_fill, zone.neighborhood_id, None,
            available_zone_ids, rentable_zone_density_data)
Example #5
0
class OrganizationService(Service):
    ORGANIZATION_EVENTS = TunableList(
        description=
        '\n        The list of organization event drama nodes that will be scheduled\n        at the start of the game.\n        \n        NOTE: These should not include venue drama nodes, which are handled\n        separately in the Venue Org Event Mapping.\n        ',
        tunable=TunableReference(
            description=
            "\n            Drama Node that is part of an organization's events.\n            ",
            manager=services.get_instance_manager(
                sims4.resources.Types.DRAMA_NODE),
            pack_safe=True))
    VENUE_ORG_EVENT_MAPPING = TunableMapping(
        description=
        '\n        Each entry in the venue org event mapping maps a venue-type to the org\n        events that should be scheduled at the venue.\n        ',
        key_type=TunableReference(manager=services.get_instance_manager(
            sims4.resources.Types.VENUE),
                                  pack_safe=True),
        value_type=TunableTuple(
            org_drama_node_events=TunableList(
                description=
                "\n                A list of drama nodes that provide organization events to venues\n                that they're a part of.\n                ",
                tunable=TunableReference(
                    description=
                    '\n                    A drama node that will contain an organization event on a venue.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.
                        DRAMA_NODE),
                    class_restrictions=('OrganizationEventDramaNode', ),
                    pack_safe=True)),
            org_zones_to_schedule=TunableRange(
                description=
                '\n                The number of zones with this venue type on which to schedule\n                org drama nodes.\n                ',
                tunable_type=int,
                minimum=0,
                default=1),
            org_preferred_regions=TunableList(
                description=
                '\n                A list of regions that will be used to initially attempt to schedule\n                a venue event on. If no venues of the venue type exist within that region,\n                any available venue will be used, as long as it is not blacklisted.\n                ',
                tunable=TunableRegionDescription(
                    description=
                    "\n                    The venue's street owner that is preferred to schedule a venue event.\n                    ",
                    pack_safe=True)),
            org_blacklisted_regions=TunableList(
                description=
                '\n                A list of regions that are invalid for scheduling venue event on.\n                ',
                tunable=TunableRegionDescription(
                    description=
                    "\n                    The venue's street owner that is invalid for scheduling a venue event.\n                    ",
                    pack_safe=True))))
    ADDITIONAL_FILTER_TERMS_ON_GENERATING_NEW_MEMBERS = TunableList(
        description=
        '\n        A list of additional filter terms to apply on sims that are considered\n        for membership in any organization. \n        ',
        tunable=FilterTermVariant())

    @classmethod
    def verify_tunable_callback(cls):
        for (i, drama_node) in enumerate(cls.ORGANIZATION_EVENTS):
            if type(drama_node) is VenueEventDramaNode:
                logger.error(
                    'Drama Node ({}) at index ({}) is tuned on Organization Events but is                            a Venue Event Drama Node and cannot be. Try moving it to VENUE_ORG_EVENT_MAPPING',
                    drama_node, i)

    def __init__(self, *_, **__):
        self._organization_members = defaultdict(list)
        self._event_updates = defaultdict(list)
        self._organization_festival_events = {}
        self._organization_venue_events = {}
        self._schedule_cancelled_venue_event_alarm = {}

    def save(self, save_slot_data=None, **__):
        organization_proto = GameplaySaveData_pb2.PersistableOrganizationService(
        )
        for (org_id, members_list) in self._organization_members.items():
            with ProtocolBufferRollback(
                    organization_proto.organizations) as organization_msg:
                organization_msg.organization_id = org_id
                for member_id in members_list:
                    with ProtocolBufferRollback(
                            organization_msg.organization_members
                    ) as organization_members_msg:
                        organization_members_msg.organization_member_id = member_id
        for (drama_node_id, alarm_handle
             ) in self._schedule_cancelled_venue_event_alarm.items():
            with ProtocolBufferRollback(
                    organization_proto.schedule_cancelled_event_data
            ) as schedule_cancelled_event_msg:
                schedule_cancelled_event_msg.schedule_venue_event_time = alarm_handle.get_remaining_time(
                ).in_ticks()
                schedule_cancelled_event_msg.org_event_id = drama_node_id
        save_slot_data.gameplay_data.organization_service = organization_proto

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

    def load(self, **__):
        save_slot_data_msg = services.get_persistence_service(
        ).get_save_slot_proto_buff()
        if save_slot_data_msg.gameplay_data.HasField('organization_service'):
            data = save_slot_data_msg.gameplay_data.organization_service
            for org_data in data.organizations:
                members_list = []
                for org_member_data in org_data.organization_members:
                    members_list.append(org_member_data.organization_member_id)
                self._organization_members[
                    org_data.organization_id] = members_list
            for schedule_cancelled_event_data in data.schedule_cancelled_event_data:
                if schedule_cancelled_event_data.HasField(
                        'schedule_venue_event_time'):
                    if schedule_cancelled_event_data.HasField('org_event_id'):
                        alarm_handle = alarms.add_alarm(
                            self,
                            TimeSpan(schedule_cancelled_event_data.
                                     schedule_venue_event_time),
                            lambda *_: self.
                            _schedule_cancelled_organization_event(
                                schedule_cancelled_event_data.org_event_id),
                            cross_zone=True)
                        self._schedule_cancelled_venue_event_alarm[
                            schedule_cancelled_event_data.
                            org_event_id] = alarm_handle

    def _is_organization_event_type(self, drama_node):
        return issubclass(
            type(drama_node), OrganizationEventDramaNode) or issubclass(
                type(drama_node), MajorOrganizationEventDramaNode)

    def _schedule_cancelled_organization_event(self, org_event_id, *args):
        drama_node_manager = services.get_instance_manager(
            sims4.resources.Types.DRAMA_NODE)
        node_type = drama_node_manager.get(org_event_id)
        if node_type is None:
            return
        if org_event_id in self._schedule_cancelled_venue_event_alarm:
            del self._schedule_cancelled_venue_event_alarm[org_event_id]
        event_venue_tuning = self.get_organization_venue_tuning(node_type)
        if event_venue_tuning is None:
            return
        org_venue_event_info = self.VENUE_ORG_EVENT_MAPPING.get(
            event_venue_tuning)
        max_allowed = org_venue_event_info.org_zones_to_schedule
        if max_allowed == 0:
            return

        def schedule_cancelled_org_event(zone_ids_gen, org_event_type,
                                         max_allowed):
            for zone_id in zone_ids_gen:
                self._schedule_venue_organization_event(node_type, zone_id)
                max_allowed -= 1
                if max_allowed <= 0:
                    return max_allowed
            return max_allowed

        venue_service = services.venue_service()
        try_zones_in_preferred_regions = True
        preferred_zone_ids_gen = self.get_preferred_zones_gen(
            org_venue_event_info.org_preferred_regions, event_venue_tuning)
        if preferred_zone_ids_gen is None:
            try_zones_in_preferred_regions = False
        if try_zones_in_preferred_regions:
            max_allowed = schedule_cancelled_org_event(preferred_zone_ids_gen,
                                                       node_type, max_allowed)
            if max_allowed <= 0:
                self.update_organization_events_panel()
                return
        all_zones_with_venue_tuning_gen = venue_service.get_zones_for_venue_type_gen(
            event_venue_tuning,
            region_blacklist=org_venue_event_info.org_blacklisted_regions)
        max_allowed = schedule_cancelled_org_event(
            all_zones_with_venue_tuning_gen, node_type, max_allowed)
        if max_allowed <= 0:
            self.update_organization_events_panel()

    def _schedule_venue_organization_event(self, org_drama_node, zone_id):
        drama_scheduler = services.drama_scheduler_service()
        resolver = DataResolver(None)
        org_start_time = self.verify_valid_time(org_drama_node)
        if org_start_time is None:
            return
        gsi_data = None
        if is_scoring_archive_enabled():
            gsi_data = GSIDramaScoringData()
            gsi_data.bucket = 'Venue Organization Event'
        uid = drama_scheduler.schedule_node(org_drama_node,
                                            resolver,
                                            specific_time=org_start_time,
                                            setup_kwargs={
                                                'gsi_data': gsi_data,
                                                'zone_id': zone_id
                                            })
        if uid is not None:
            self._organization_venue_events[uid] = str(org_drama_node)
            drama_scheduler.add_complete_callback(
                uid, self._reschedule_venue_org_event)

    def get_preferred_zones_gen(self, preferred_regions, event_venue_tuning):
        venue_service = services.venue_service()
        preferred_zone_ids_gen = None
        for region_id in preferred_regions:
            region = Region.REGION_DESCRIPTION_TUNING_MAP.get(region_id)
            if preferred_zone_ids_gen is None:
                preferred_zone_ids_gen = venue_service.get_zones_for_venue_type_gen(
                    event_venue_tuning,
                    compatible_region=region,
                    ignore_region_compatability_tags=True)
            else:
                preferred_zone_ids_gen = itertools.chain(
                    preferred_zone_ids_gen,
                    venue_service.get_zones_for_venue_type_gen(
                        event_venue_tuning,
                        compatible_region=region,
                        ignore_region_compatability_tags=True))
        return preferred_zone_ids_gen

    def verify_valid_time(self, drama_node):
        time_option = drama_node.time_option
        if time_option.option != TimeSelectionOption.SINGLE_TIME:
            logger.error(
                'Drama Node ({}) does not have a valid time tuned and will not schedule.',
                drama_node)
            return
        else:
            now = services.time_service().sim_now
            org_day_and_hour = create_date_and_time(
                days=time_option.valid_time.day,
                hours=time_option.valid_time.hour)
            org_start_time = date_and_time_from_week_time(
                now.week(), org_day_and_hour)
            if org_start_time < now:
                org_start_time = date_and_time_from_week_time(
                    now.week() + 1, org_day_and_hour)
            if org_start_time < now:
                return
        return org_start_time

    def schedule_org_events(self):
        resolver = DataResolver(None)
        drama_scheduler = services.drama_scheduler_service()
        scheduled_org_events = [
            type(drama_node)
            for drama_node in drama_scheduler.scheduled_nodes_gen()
            if self._is_organization_event_type(drama_node)
        ]
        active_org_events = [
            type(drama_node)
            for drama_node in drama_scheduler.active_nodes_gen()
            if self._is_organization_event_type(drama_node)
        ]
        for org_drama_node in self.ORGANIZATION_EVENTS:
            if not org_drama_node in scheduled_org_events:
                if org_drama_node in active_org_events:
                    continue
                org_start_time = self.verify_valid_time(org_drama_node)
                if org_start_time is None:
                    continue
                gsi_data = None
                if is_scoring_archive_enabled():
                    gsi_data = GSIDramaScoringData()
                    gsi_data.bucket = 'Organization Event'
                uid = drama_scheduler.schedule_node(
                    org_drama_node,
                    resolver,
                    gsi_data=gsi_data,
                    specific_time=org_start_time,
                    setup_kwargs={'gsi_data': gsi_data})
                if uid is not None:
                    self._organization_festival_events[uid] = str(
                        org_drama_node)
                    drama_scheduler.add_complete_callback(
                        uid, self._reschedule_festival_org_event)
        venue_service = services.venue_service()
        for (event_venue_tuning,
             org_venue_event_info) in self.VENUE_ORG_EVENT_MAPPING.items():
            if not org_venue_event_info.org_drama_node_events:
                continue
            org_drama_nodes = [
                drama_node
                for drama_node in org_venue_event_info.org_drama_node_events
                if drama_node not in scheduled_org_events
                if drama_node not in active_org_events if drama_node.guid64
                not in self._schedule_cancelled_venue_event_alarm.keys()
            ]
            if not org_drama_nodes:
                continue
            max_allowed = org_venue_event_info.org_zones_to_schedule
            if max_allowed == 0:
                continue
            try_zones_in_preferred_regions = True
            preferred_zone_ids_gen = self.get_preferred_zones_gen(
                org_venue_event_info.org_preferred_regions, event_venue_tuning)
            if preferred_zone_ids_gen is None:
                try_zones_in_preferred_regions = False

            def schedule_events(zone_ids, max_allowed, org_drama_nodes):
                for zone_id in zone_ids:
                    for org_drama_node in org_drama_nodes:
                        self._schedule_venue_organization_event(
                            org_drama_node, zone_id)
                    max_allowed -= 1
                    if max_allowed <= 0:
                        break
                return max_allowed

            if try_zones_in_preferred_regions:
                max_allowed = schedule_events(preferred_zone_ids_gen,
                                              max_allowed, org_drama_nodes)
                if max_allowed <= 0:
                    continue
            else:
                all_zones_with_venue_tuning_gen = venue_service.get_zones_for_venue_type_gen(
                    event_venue_tuning,
                    region_blacklist=org_venue_event_info.
                    org_blacklisted_regions)
                schedule_events(all_zones_with_venue_tuning_gen, max_allowed,
                                org_drama_nodes)

    def on_zone_load(self):
        for (org_id, sims) in self._organization_members.items():
            for sim_id in sims:
                sim = services.sim_info_manager().get(sim_id)
                if sim is None:
                    continue
                organization_tracker = sim.organization_tracker
                if organization_tracker.get_organization_status(
                        org_id) == OrganizationStatusEnum.ACTIVE:
                    organization_tracker.send_organization_update_message(
                        DistributorOps_pb2.OrganizationUpdate.ADD, org_id)
        self.update_organization_events_panel()

    def post_game_services_zone_load(self):
        self.cleanup_scheduled_or_active_events()
        self.schedule_org_events()
        self.update_organization_events_panel()

    def cleanup_scheduled_or_active_events(self):
        drama_scheduler = services.drama_scheduler_service()
        persistence_service = services.get_persistence_service()
        for uid in self._organization_festival_events.keys():
            drama_scheduler.add_complete_callback(
                uid, self._reschedule_festival_org_event)
        cancelled_venue_event_nodes_uids = []
        for uid in self._organization_venue_events.keys():
            drama_node_inst = drama_scheduler.get_scheduled_node_by_uid(uid)
            if drama_node_inst is not None:
                zone_data = persistence_service.get_zone_proto_buff(
                    drama_node_inst.zone_id)
                if zone_data is None:
                    drama_scheduler.cancel_scheduled_node(uid)
                    cancelled_venue_event_nodes_uids.append(uid)
                else:
                    venue_tuning = services.venue_service().get_venue_tuning(
                        drama_node_inst.zone_id)
                    if venue_tuning is not self.get_organization_venue_tuning(
                            type(drama_node_inst)):
                        drama_scheduler.cancel_scheduled_node(uid)
                        cancelled_venue_event_nodes_uids.append(uid)
                    else:
                        drama_scheduler.add_complete_callback(
                            uid, self._reschedule_venue_org_event)
            else:
                drama_scheduler.add_complete_callback(
                    uid, self._reschedule_venue_org_event)
        for cancelled_node_uid in cancelled_venue_event_nodes_uids:
            if cancelled_node_uid in self._organization_venue_events:
                del self._organization_venue_events[cancelled_node_uid]
            self.remove_event_update(cancelled_node_uid)

    def get_organization_venue_tuning(self, drama_node):
        for (venue_tuning,
             org_venue_data) in self.VENUE_ORG_EVENT_MAPPING.items():
            if drama_node in org_venue_data.org_drama_node_events:
                return venue_tuning

    def event_is_scheduled(self, org_id, drama_node):
        return type(drama_node) in [
            type(org_event_info.drama_node)
            for org_event_info in self._event_updates.get(org_id, [])
        ]

    def add_festival_event_update(self, org_id, org_event_info, drama_node_uid,
                                  drama_node_name):
        self._organization_festival_events[drama_node_uid] = drama_node_name
        self.add_event_update(org_id, org_event_info)

    def validate_venue_event(self, drama_node):
        venue_tuning = services.venue_service().get_venue_tuning(
            drama_node.zone_id)
        if venue_tuning is not self.get_organization_venue_tuning(
                type(drama_node)):
            drama_scheduler = services.drama_scheduler_service()
            if drama_scheduler is None:
                return False
            else:
                self.remove_event_update(drama_node.uid)
                drama_scheduler.complete_node(drama_node.uid)
                alarm_handle = drama_node.schedule_duration_alarm(
                    lambda *_: self._schedule_cancelled_organization_event(
                        drama_node.guid64),
                    cross_zone=True)
                self._schedule_cancelled_venue_event_alarm[
                    drama_node.guid64] = alarm_handle
                return False
        return True

    def add_venue_event_update(self, org_id, org_event_info, drama_node_uid,
                               drama_node_name):
        self._organization_venue_events[drama_node_uid] = drama_node_name
        self.add_event_update(org_id, org_event_info)

    def add_event_update(self, org_id, org_event_info):
        if not self.event_is_scheduled(org_id, org_event_info.drama_node):
            self._event_updates[org_id].append(org_event_info)

    def get_scheduled_org_event_info_from_drama_node_uid(self, drama_node_uid):
        for (org_id, org_event_infos) in self._event_updates.items():
            for org_event_info in org_event_infos:
                if org_event_info.drama_node.uid == drama_node_uid:
                    return (org_id, org_event_info)
        return (None, None)

    def remove_event_update(self, drama_node_uid):
        (org_id, event_info_to_remove
         ) = self.get_scheduled_org_event_info_from_drama_node_uid(
             drama_node_uid)
        if event_info_to_remove is None:
            return
        if org_id not in self._event_updates:
            return
        org_event_infos = self._event_updates[org_id]
        org_event_infos.remove(event_info_to_remove)
        if not org_event_infos:
            del self._event_updates[org_id]
        else:
            self._event_updates[org_id] = org_event_infos

    def send_event_update_message(self,
                                  org_id,
                                  event_infos,
                                  no_events_string=None):
        send_event_update_op = SendOrganizationEventUpdateOp(
            org_id, event_infos, no_events_string)
        distributor = Distributor.instance()
        distributor.add_op_with_no_owner(send_event_update_op)

    def update_organization_events_panel(self):
        no_events_orgs = {}
        snippet_manager = services.get_instance_manager(
            sims4.resources.Types.SNIPPET)
        for org_id in OrganizationTracker.ALL_ORGANIZATION_IDS:
            organization = snippet_manager.get(org_id)
            no_events_orgs[
                org_id] = organization.no_events_are_scheduled_string
        for (org_id, event_infos) in self._event_updates.items():
            self.send_event_update_message(org_id, event_infos)
            if no_events_orgs.get(org_id) is not None:
                del no_events_orgs[org_id]
        for (org_id, no_events_string) in no_events_orgs.items():
            self.send_event_update_message(org_id, [], no_events_string)

    def clear_stored_organization_events(self):
        self._event_updates.clear()

    def get_organization_members(self, org_id):
        org_members = self._organization_members.get(org_id)
        if org_members is not None:
            return org_members
        return []

    def remove_organization_member(self, sim_info, org_id):
        members_list = self._organization_members.get(org_id)
        if members_list is None:
            return
        if sim_info.id in members_list:
            members_list.remove(sim_info.id)

    def add_organization_member(self, sim_info, org_id):
        organization_tracker = sim_info.organization_tracker
        if organization_tracker is None:
            return False
        organization_tracker.join_organization(org_id)
        members_list = self._organization_members.get(org_id)
        if members_list is None:
            self._organization_members[org_id] = [sim_info.id]
        elif sim_info.id not in members_list:
            self._organization_members[org_id].append(sim_info.id)
        return True

    def generate_organization_members(self,
                                      org_id,
                                      amount=0,
                                      blacklist_sims=(),
                                      additional_filter_terms=None,
                                      minimum=None):
        snippet_manager = services.get_instance_manager(
            sims4.resources.Types.SNIPPET)
        filter_service = services.sim_filter_service()
        sim_info_manager = services.sim_info_manager()
        if snippet_manager is None or filter_service is None or sim_info_manager is None:
            return []
        organization_snippet = snippet_manager.get(org_id)
        member_result_ids = self._organization_members.get(org_id)
        if member_result_ids is None:
            member_result_ids = []
        else:
            valid_org_member_ids = []
            invalid_org_member_ids = []
            for org_member_id in member_result_ids:
                if filter_service.does_sim_match_filter(
                        org_member_id,
                        sim_filter=organization_snippet.organization_filter,
                        gsi_source_fn=lambda: str(self) + ': ' + str(
                            organization_snippet)):
                    valid_org_member_ids.append(org_member_id)
                else:
                    invalid_org_member_ids.append(org_member_id)
            if invalid_org_member_ids:
                for invalid_org_member_id in invalid_org_member_ids:
                    invalid_org_member_info = sim_info_manager.get(
                        invalid_org_member_id)
                    if invalid_org_member_info is not None and invalid_org_member_info.is_played_sim:
                        continue
                    member_result_ids.remove(invalid_org_member_id)
                    if invalid_org_member_info is None:
                        continue
                    organization_tracker = invalid_org_member_info.organization_tracker
                    if organization_tracker is not None:
                        organization_tracker.leave_organization(org_id)
            valid_org_member_ids = []
            for org_member_id in valid_org_member_ids:
                if org_member_id not in blacklist_sims:
                    if filter_service.does_sim_match_filter(
                            org_member_id,
                            sim_filter=organization_snippet.
                            organization_filter,
                            additional_filter_terms=additional_filter_terms,
                            gsi_source_fn=lambda: str(self) + ': ' + str(
                                organization_snippet)):
                        valid_org_member_ids.append(org_member_id)
            picked_sims = []
            if amount < len(valid_org_member_ids):
                while valid_org_member_ids:
                    while len(picked_sims) < amount:
                        random_choice = valid_org_member_ids.pop(
                            random.randint(0,
                                           len(valid_org_member_ids) - 1))
                        picked_sims.append(random_choice)
                return picked_sims
            member_result_ids = valid_org_member_ids
        additional_membership_filter_terms = additional_filter_terms + OrganizationService.ADDITIONAL_FILTER_TERMS_ON_GENERATING_NEW_MEMBERS
        members_needed = minimum - len(
            member_result_ids) if minimum is not None and len(
                member_result_ids) <= minimum else amount - len(
                    member_result_ids)
        if members_needed > 0:
            new_members = filter_service.submit_matching_filter(
                number_of_sims_to_find=members_needed,
                sim_filter=organization_snippet.organization_filter,
                callback=None,
                allow_yielding=False,
                allow_instanced_sims=True,
                additional_filter_terms=additional_membership_filter_terms,
                blacklist_sim_ids=blacklist_sims,
                gsi_source_fn=lambda: str(self) + ': ' + str(
                    organization_snippet))
            for new_member in new_members:
                sim_info = new_member.sim_info
                if sim_info:
                    if self.add_organization_member(sim_info, org_id):
                        member_result_ids.append(sim_info.id)
        return member_result_ids

    def _reschedule_org_event(self, drama_node, **setup_kwargs):
        drama_scheduler = services.drama_scheduler_service()
        if drama_scheduler is None:
            return
        resolver = drama_node._get_resolver()
        next_time = self.verify_valid_time(drama_node)
        uid = drama_scheduler.schedule_node(type(drama_node),
                                            resolver,
                                            specific_time=next_time,
                                            update_org_panel_immediately=True,
                                            setup_kwargs=setup_kwargs)
        return uid

    def _reschedule_venue_org_event(self, drama_node, **kwargs):
        if drama_node.uid in self._organization_venue_events:
            del self._organization_venue_events[drama_node.uid]
        setup_kwargs = {}
        if is_scoring_archive_enabled():
            gsi_data = GSIDramaScoringData()
            gsi_data.bucket = 'Venue Organization Event'
            setup_kwargs['gsi_data'] = gsi_data
        setup_kwargs['zone_id'] = drama_node.zone_id
        new_uid = self._reschedule_org_event(drama_node, **setup_kwargs)
        if new_uid is None:
            logger.error('Organization event ({}) was not rescheduled',
                         drama_node)
        self._organization_venue_events[new_uid] = str(type(drama_node))

    def _reschedule_festival_org_event(self, drama_node, from_shutdown=False):
        if drama_node.uid in self._organization_festival_events:
            del self._organization_festival_events[drama_node.uid]
        if from_shutdown and isinstance(services.time_service().sim_timeline,
                                        FrozenTimeline):
            return
        setup_kwargs = {}
        if is_scoring_archive_enabled():
            gsi_data = GSIDramaScoringData()
            gsi_data.bucket = 'Festival Organization Event'
            setup_kwargs['gsi_data'] = gsi_data
        new_uid = self._reschedule_org_event(drama_node, **setup_kwargs)
        if new_uid is None:
            logger.error('Organization event ({}) was not rescheduled',
                         drama_node)
        self._organization_festival_events[new_uid] = str(type(drama_node))
Example #6
0
class Region(HasTunableReference,
             metaclass=HashedTunedInstanceMetaclass,
             manager=services.region_manager()):
    REGION_DESCRIPTION_TUNING_MAP = TunableMapping(
        description=
        '\n        A mapping between Catalog region description and tuning instance. This\n        way we can find out what region description the current zone belongs to\n        at runtime then grab its tuning instance.\n        ',
        key_type=TunableRegionDescription(
            description=
            '\n            Catalog-side Region Description.\n            ',
            pack_safe=True,
            export_modes=ExportModes.All),
        value_type=TunableReference(
            description=
            "\n            Region Tuning instance. This is retrieved at runtime based on what\n            the active zone's region description is.\n            ",
            pack_safe=True,
            manager=services.region_manager(),
            export_modes=ExportModes.All),
        key_name='RegionDescription',
        value_name='Region',
        tuple_name='RegionDescriptionMappingTuple',
        export_modes=ExportModes.All)
    INSTANCE_TUNABLES = {
        'gallery_download_venue_map':
        TunableMapping(
            description=
            '\n            A map from gallery venue to instanced venue. We need to be able to\n            convert gallery venues into other venues that are only compatible\n            with that region.\n            ',
            key_type=TunableReference(
                description=
                '\n                A venue type that exists in the gallery.\n                ',
                manager=services.venue_manager(),
                export_modes=ExportModes.All,
                pack_safe=True),
            value_type=TunableReference(
                description=
                '\n                The venue type that the gallery venue will become when it is\n                downloaded into this region.\n                ',
                manager=services.venue_manager(),
                export_modes=ExportModes.All,
                pack_safe=True),
            key_name='gallery_venue_type',
            value_name='region_venue_type',
            tuple_name='GalleryDownloadVenueMappingTuple',
            export_modes=ExportModes.All),
        'compatible_venues':
        TunableList(
            description=
            '\n            A list of venues that are allowed to be set by the player in this\n            region.\n            ',
            tunable=TunableReference(
                description=
                '\n                A venue that the player can set in this region.\n                ',
                manager=services.venue_manager(),
                export_modes=ExportModes.All,
                pack_safe=True),
            export_modes=ExportModes.All),
        'tags':
        TunableList(
            description=
            '\n            Tags that are used to group regions. Destination Regions will\n            likely have individual tags, but Home/Residential Regions will\n            share a tag.\n            ',
            tunable=TunableEnumEntry(
                description=
                '\n                A Tag used to group this region. Destination Regions will\n                likely have individual tags, but Home/Residential Regions will\n                share a tag.\n                ',
                tunable_type=tag.Tag,
                default=tag.Tag.INVALID,
                pack_safe=True)),
        'region_buffs':
        TunableList(
            description=
            '\n            A list of buffs that are added on Sims while they are instanced in\n            this region.\n            ',
            tunable=TunableReference(
                description=
                '\n                A buff that exists on Sims while they are instanced in this\n                region.\n                ',
                manager=services.buff_manager(),
                pack_safe=True)),
        'store_travel_group_placed_objects':
        Tunable(
            description=
            '\n            If checked, any placed objects while in a travel group will be returned to household inventory once\n            travel group is disbanded.\n            ',
            tunable_type=bool,
            default=False),
        'travel_group_build_disabled_tooltip':
        TunableLocalizedString(
            description=
            '\n            The string that will appear in the tooltip of the grayed out build\n            mode button if build is being disabled because of a travel group in\n            this region.\n            ',
            allow_none=True,
            export_modes=ExportModes.All),
        'sunrise_time':
        TunableTimeOfDay(
            description=
            '\n            The time, in Sim-time, the sun rises in this region.\n            ',
            default_hour=6,
            tuning_group=GroupNames.TIME),
        'seasonal_sunrise_time':
        TunableMapping(
            description=
            '\n            A mapping between season and sunrise time.  If the current season\n            is not found then we will default to the tuned sunrise time.\n            ',
            key_type=TunableEnumEntry(
                description='\n                The season.\n                ',
                tunable_type=SeasonType,
                default=SeasonType.SUMMER),
            value_type=TunableTimeOfDay(
                description=
                '\n                The time, in Sim-time, the sun rises in this region, in this\n                season.\n                ',
                default_hour=6,
                tuning_group=GroupNames.TIME)),
        'sunset_time':
        TunableTimeOfDay(
            description=
            '\n            The time, in Sim-time, the sun sets in this region.\n            ',
            default_hour=20,
            tuning_group=GroupNames.TIME),
        'seasonal_sunset_time':
        TunableMapping(
            description=
            '\n            A mapping between season and sunset time.  If the current season\n            is not found then we will default to the tuned sunset time.\n            ',
            key_type=TunableEnumEntry(
                description='\n                The season.\n                ',
                tunable_type=SeasonType,
                default=SeasonType.SUMMER),
            value_type=TunableTimeOfDay(
                description=
                '\n                The time, in Sim-time, the sun sets in this region, in this\n                season.\n                ',
                default_hour=20,
                tuning_group=GroupNames.TIME)),
        'provides_sunlight':
        Tunable(
            description=
            '\n            If enabled, this region provides sunlight between the tuned Sunrise\n            Time and Sunset Time. This is used for gameplay effect (i.e.\n            Vampires).\n            ',
            tunable_type=bool,
            default=True,
            tuning_group=GroupNames.TIME),
        'weather':
        TunableMapping(
            description=
            '\n            Forecasts for this region for the various seasons\n            ',
            key_type=TunableEnumEntry(
                description='\n                The Season.\n                ',
                tunable_type=SeasonType,
                default=SeasonType.SPRING),
            value_type=TunableWeatherSeasonalForecastsReference(
                description=
                '\n                The forecasts for the season by part of season\n                ',
                pack_safe=True)),
        'weather_supports_fresh_snow':
        Tunable(
            description=
            '\n            If enabled, this region supports fresh snow.\n            ',
            tunable_type=bool,
            default=True),
        'seasonal_parameters':
        TunableMapping(
            description='\n            ',
            key_type=TunableEnumEntry(
                description=
                '\n                The parameter that we wish to change.\n                ',
                tunable_type=SeasonParameters,
                default=SeasonParameters.LEAF_ACCUMULATION),
            value_type=TunableList(
                description=
                '\n                A list of the different seasonal parameter changes that we want to\n                send over the course of a year.\n                ',
                tunable=TunableTuple(
                    season=TunableEnumEntry(
                        description=
                        '\n                        The Season that this change is in.\n                        ',
                        tunable_type=SeasonType,
                        default=SeasonType.SPRING),
                    time_in_season=TunableRange(
                        description=
                        '\n                        The time within the season that this will occur.\n                        ',
                        tunable_type=float,
                        minimum=0.0,
                        maximum=1.0,
                        default=0.0),
                    value=Tunable(
                        description=
                        '\n                        The value that we will set this parameter to in the\n                        season\n                        ',
                        tunable_type=float,
                        default=0.0))),
            verify_tunable_callback=verify_seasonal_parameters),
        'fishing_data':
        OptionalTunable(
            description=
            '\n            If enabled, define all of the data for fishing locations in this region.\n            Only used if objects are tuned to use region fishing data.\n            ',
            tunable=TunableFishingDataSnippet()),
        'welcome_wagon_replacement':
        OptionalTunable(
            description=
            '\n            If enabled then we will replace the Welcome Wagon with a new situation.\n            \n            If the narrative is also set to replace the welcome wagon that will take precedent over this replacement.\n            ',
            tunable=TunableReference(
                description=
                '\n                The situation we will use to replace the welcome wagon.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.SITUATION)))
    }

    @classmethod
    def _cls_repr(cls):
        return "Region: <class '{}.{}'>".format(cls.__module__, cls.__name__)

    @classmethod
    def is_region_compatible(cls, region_instance, ignore_tags=False):
        if region_instance is cls or region_instance is None:
            return True
        if ignore_tags:
            return False
        for tag in cls.tags:
            if tag in region_instance.tags:
                return True
        return False

    @classmethod
    def is_sim_info_compatible(cls, sim_info):
        other_region = get_region_instance_from_zone_id(sim_info.zone_id)
        if cls.is_region_compatible(other_region):
            return True
        else:
            travel_group_id = sim_info.travel_group_id
            if travel_group_id:
                travel_group = services.travel_group_manager().get(
                    travel_group_id)
                if travel_group is not None and not travel_group.played:
                    return True
        return False

    @classmethod
    def get_sunrise_time(cls):
        season_service = services.season_service()
        if season_service is None:
            return cls.sunrise_time
        return cls.seasonal_sunrise_time.get(season_service.season,
                                             cls.sunrise_time)

    @classmethod
    def get_sunset_time(cls):
        season_service = services.season_service()
        if season_service is None:
            return cls.sunset_time
        return cls.seasonal_sunset_time.get(season_service.season,
                                            cls.sunset_time)
Example #7
0
class UiTuning:
    LOADING_SCREEN_STRINGS = TunableMapping(
        description=
        '\n        Mapping from the Pack to its associated loading strings.\n        ',
        key_type=TunableEnumEntry(
            description=
            '\n            The pack containing the strings.\n            ',
            tunable_type=Pack,
            default=Pack.BASE_GAME),
        value_type=TunableList(
            description=
            '\n            The list of loading screen strings which belongs to the pack.\n            We always display the strings from base game AND from the latest\n            pack which the player is entitled to and has installed. \n            ',
            tunable=TunableLocalizedString()),
        export_modes=(ExportModes.ClientBinary, ),
        tuple_name='LoadingScreenStringsTuple')
    GO_HOME_INTERACTION = TunableReference(
        description=
        '\n        The interaction to push a Sim to go home.\n        ',
        manager=services.affordance_manager(),
        export_modes=(ExportModes.ClientBinary, ))
    COME_NEAR_ACTIVE_SIM = TunableReference(
        description=
        '\n        An affordance to push on a Sim so they come near the active Sim.\n        ',
        manager=services.affordance_manager())
    BRING_HERE_INTERACTION = TunableReference(
        description=
        '\n        An affordance to push on household members to summon them to the\n        current lot if they are not instanced.\n        ',
        manager=services.affordance_manager())
    NEW_CONTENT_ALERT_TUNING = TunableMapping(
        description=
        '\n        Mapping from Pack to its associated new content alert tuning\n        ',
        key_type=TunableEnumEntry(
            description=
            '\n            The pack containing the new content tuning. NOTE: this should never\n            be tuned to BASE_GAME. That would trigger for all users.\n            ',
            tunable_type=Pack,
            default=Pack.BASE_GAME),
        value_type=TunableTuple(
            description=
            '\n            Each pack will have a set of tuning of images and text to display\n            to inform the user what new features have been introduced in the \n            pack.\n            ',
            export_class_name='TunablePackContentTuple',
            title=TunableLocalizedString(
                description=
                '\n                The title to be displayed at the top of the New Content Alert\n                UI for this pack.\n                '
            ),
            cycle_images=TunableList(
                description=
                '\n                A list of images (screenshots) that the UI cycles through to\n                show off some of the new features.\n                ',
                tunable=TunableResourceKey(
                    resource_types=sims4.resources.CompoundTypes.IMAGE)),
            feature_list=TunableList(
                description=
                '\n                A list of tuples that describe each new feature in the New\n                Content Alert UI. NOTE: This should NEVER have more than 4\n                elements in it.\n                ',
                maxlength=4,
                tunable=TunableTuple(
                    description=
                    '\n                    A tuple that contains title text, description, an icon,\n                    and a reference to the matching lesson for this new \n                    feature.\n                    ',
                    export_class_name='TunableFeatureTuple',
                    title_text=TunableLocalizedString(
                        description=
                        '\n                        A title to be displayed in bold for the feature.\n                        '
                    ),
                    description_text=TunableLocalizedString(
                        description=
                        '\n                        A short description of the new feature.\n                        '
                    ),
                    icon=TunableResourceKey(
                        description=
                        '\n                        An icon that represents the feature.\n                        ',
                        resource_types=sims4.resources.CompoundTypes.IMAGE),
                    lesson=TunableReference(
                        description=
                        '\n                        A reference to the lesson that the user can go look at\n                        for this new feature.\n                        ',
                        manager=services.get_instance_manager(
                            sims4.resources.Types.TUTORIAL),
                        allow_none=True,
                        pack_safe=True)))),
        export_modes=(ExportModes.ClientBinary, ),
        tuple_name='NewContentAlertTuple')
    PACK_SPECIFIC_DATA = TunableMapping(
        description=
        '\n        Mapping from a Pack to its associated data.  This includes pack icons,\n        filter strings, and the credits file.\n        ',
        key_name='packId',
        key_type=TunableEnumEntry(
            description=
            '\n            The pack id for the associated data.\n            ',
            tunable_type=Pack,
            default=Pack.BASE_GAME),
        value_name='packData',
        value_type=TunableTuple(
            description=
            '\n            Each pack will have a set icons and can have an optional filter \n            string for use in Build/CAS and an optional Credits Title\n            ',
            export_class_name='TunablePackDataTuple',
            credits_title=TunableLocalizedString(
                description=
                '\n                The title used in the credits dropdown to select this packs credits.\n                If set, there must be a creditsxml file for this pack\n                in Assets/InGame/UI/Flash/data/\n                ',
                allow_none=True),
            filter_name=TunableLocalizedString(
                description=
                '\n                The name to used to describe the pack in CAS and BuildBuy filters.\n                If set, this pack will appear in the filter list.\n                ',
                allow_none=True),
            pack_type=TunableEnumEntry(
                description=
                '\n                Which type of pack is this.\n                ',
                tunable_type=PackTypes,
                default=PackTypes.BASE),
            icon_32=TunableResourceKey(
                description=
                '\n                Pack icon. 32x32.\n                ',
                resource_types=sims4.resources.CompoundTypes.IMAGE),
            icon_64=TunableResourceKey(
                description=
                '\n                Pack icon. 64x64.\n                ',
                resource_types=sims4.resources.CompoundTypes.IMAGE),
            icon_128=TunableResourceKey(
                description=
                '\n                Pack icon. 128x128.\n                ',
                resource_types=sims4.resources.CompoundTypes.IMAGE),
            icon_owned=TunableIcon(
                description=
                '\n                Pack icon that is displayed in the main menu\n                pack display when the player owns that pack.\n                ',
                allow_none=True),
            icon_unowned=TunableIcon(
                description=
                '\n                Pack icon that is displayed in the main menu\n                pack display when the player does not own that pack.\n                ',
                allow_none=True),
            webstore_id=Tunable(
                description=
                '\n                web store pack specific url identifier\n\t\t\t\t',
                tunable_type=str,
                default=None),
            region_list=TunableList(
                description=
                '\n                A list of tuples that describe each new region in the pack.\n                ',
                tunable=TunableTuple(
                    description=
                    '\n                    A tuple that contains metadata for a world select region.\n                    ',
                    export_class_name='TunablePackRegionTuple',
                    region_resource=TunableRegionDescription(
                        description=
                        '\n                            Reference to the region description catalog resource associated with this region\n                        ',
                        pack_safe=True),
                    is_player_facing=Tunable(
                        description=
                        '\n                            Whether to display this region in world select when the user does not own the associated pack\n                        ',
                        tunable_type=bool,
                        default=False),
                    region_name=TunableLocalizedString(
                        description=
                        '\n                        Localized name of region.\n                        ',
                        allow_none=True),
                    region_description=TunableLocalizedString(
                        description=
                        '\n                        Localized description of region.\n                        ',
                        allow_none=True),
                    overlay_layer=TunableResourceKey(
                        description=
                        '\n                        Hero image displayed on mouse over of region in\n\t\t\t\t\t\tworld selection UI.\n                        ',
                        resource_types=sims4.resources.CompoundTypes.IMAGE,
                        allow_none=True),
                    parallax_layers=TunableList(
                        description=
                        '\n                        Images used for scrolling parallax layers for region\n                        in world selection UI. Max number of images = 5.\n                        ',
                        maxlength=5,
                        tunable=TunableResourceKey(
                            resource_types=sims4.resources.CompoundTypes.IMAGE
                        )),
                    is_destination_region=Tunable(
                        description=
                        '\n                        Whether this region is a destination world.\n                        ',
                        tunable_type=bool,
                        default=False))),
            promo_cycle_images=TunableList(
                description=
                '\n                A list of promo screenshots and titles to display in the \n                Pack Detail panel.\n                ',
                tunable=PromoCycleImagesTuning(
                    description=
                    '\n                    Screenshots and label displayed in the Pack Detail Panel\n                    and Pack Preview Panel.\n                    '
                )),
            short_description=TunableLocalizedString(
                description=
                '\n                Short description of the pack meant to be displayed in \n                a tooltip.\n                ',
                allow_none=True)),
        export_modes=(ExportModes.ClientBinary, ),
        tuple_name='PackSpecificDataTuple')
    BUNDLE_SPECIFIC_DATA = TunableMapping(
        description=
        '\n        Mapping from an MTX Bundle to its associated data. This is for bundles that\n        should appear in the ui, but are not packs. This includes main menu icons,\n        description, and the action associated with that bundle.\n        ',
        key_type=TunableMTXBundle(
            description=
            '\n            The MTX bundle id for the associated data.\n            ',
            pack_safe=True),
        value_type=TunableTuple(
            description=
            '\n            Each bundle has icons and a description, as well as an\n            data for the action performed when the bundle is interacted \n            with either the PromotionDialog or the PackDisplayPanel.\n            ',
            bundle_name=TunableLocalizedString(
                description=
                '\n                Name used in pack detail panel and main menu. If empty,\n                we fall back to using the MTX product name.\n                ',
                allow_none=True),
            icon_owned=TunableIcon(
                description=
                '\n                Bundle icon that is displayed in the main menu\n                pack display when the player is entitled to that bundle.\n                '
            ),
            icon_unowned=TunableIcon(
                description=
                '\n                Bundle icon that is displayed in the main menu\n                pack display when the player is not entitled to that bundle.\n                '
            ),
            short_description=TunableLocalizedString(
                description=
                '\n                Short description of the bundle meant to be displayed in \n                a tooltip.\n                '
            ),
            action=TunableVariant(
                description=
                '\n                The action that should be performed when this bundle is interacted with\n                in either the PromotionDialog or the PackDisplayPanel.\n                ',
                url=Tunable(
                    description=
                    '\n                    External url to open from PackDisplayPanel.\n                    ',
                    tunable_type=str,
                    default=None),
                promo_data=TunableTuple(
                    description=
                    '\n                    Data that populates PromotionDialog.\n                    ',
                    title=TunableLocalizedString(
                        description=
                        '\n                        Title of the promotion.\n                        '
                    ),
                    text=TunableLocalizedString(
                        description=
                        '\n                        Text describing the promotion.\n                        '
                    ),
                    image=TunableIcon(
                        description=
                        '\n                        Image displayed in the promotion dialog.\n                        '
                    ),
                    legal_text=TunableLocalizedString(
                        description=
                        '\n                        Legal text required for this promotion.\n                        ',
                        allow_none=True),
                    export_class_name='TunablePromoDataTuple'),
                default='url'),
            export_class_name='TunableBundleDataTuple'),
        export_modes=(ExportModes.ClientBinary, ),
        tuple_name='BundleSpecificDataTuple')
    PACK_RELEASE_ORDER = TunableList(
        description='\n        List of Pack Ids in release order.\n        ',
        tunable=TunableEnumEntry(
            description='\n            A pack Id.\n            ',
            tunable_type=Pack,
            default=Pack.BASE_GAME),
        export_modes=(ExportModes.ClientBinary, ))
    CHALLENGE_DATA = TunableList(
        description=
        '\n        List of challenge event data for engagement challenge notification UI.\n        ',
        tunable=TunableTuple(
            description=
            '\n            Data for each engagement challenge event.\n            ',
            export_class_name='TunableChallengeNotificationTuple',
            challenge_list=TunableList(
                description=
                '\n                A list of tuples that describe each challenge.\n                ',
                tunable=TunableTuple(
                    description=
                    '\n                    A tuple that contains data for a challenge.\n                    ',
                    export_class_name='TunableChallengeDataTuple',
                    challenge_description=TunableLocalizedString(
                        description=
                        '\n                        The description of the challenge.\n                        ',
                        allow_none=True),
                    challenge_name=TunableLocalizedString(
                        description=
                        '\n                        The name of the challenge.\n                        ',
                        allow_none=True),
                    image=TunableResourceKey(
                        description=
                        '\n                        The main image displayed for challenge info.\n                        ',
                        resource_types=sims4.resources.CompoundTypes.IMAGE),
                    info_link=Tunable(
                        description=
                        '\n                        The url link to page for more info on a challenge.\n                        ',
                        tunable_type=str,
                        default='',
                        allow_empty=True),
                    event_display=TunableTuple(
                        description=
                        '\n                        Display data for a challenge event.\n                        ',
                        export_class_name='TunableChallengeEventDisplayTuple',
                        event_icon=TunableIcon(
                            description=
                            '\n                            An icon to use for the challenge event.\n                            ',
                            allow_none=True),
                        event_title=TunableLocalizedString(
                            description=
                            '\n                            Title to display.  If not provided, challenge name will be used.\n                            ',
                            allow_none=True),
                        end_time=TunableTuple(
                            description=
                            '\n                            Date and time (UTC) for when the challenge event is expected to end.\n                            This is currently used to compute the time remaining in the UI.\n                            ',
                            display_name='End Time (UTC)',
                            export_class_name='TunableChallengeDateTuple',
                            year=TunableRange(
                                description=
                                '\n                                Year\n                                ',
                                tunable_type=int,
                                default=2016,
                                minimum=2014),
                            month=TunableRange(
                                description=
                                '\n                                Month\n                                ',
                                tunable_type=int,
                                default=1,
                                minimum=1,
                                maximum=12),
                            day=TunableRange(
                                description=
                                '\n                                Day\n                                ',
                                tunable_type=int,
                                default=1,
                                minimum=1,
                                maximum=31),
                            hour=TunableRange(
                                description=
                                '\n                                Hour (24-hour)\n                                ',
                                tunable_type=int,
                                default=0,
                                minimum=0,
                                maximum=23),
                            minute=TunableRange(
                                description=
                                '\n                                Minute\n                                ',
                                tunable_type=int,
                                default=0,
                                minimum=0,
                                maximum=59)),
                        activity_icon=TunableIcon(
                            description=
                            '\n                            Icon to display beside the activity progress bar.\n                            ',
                            allow_none=True),
                        activity_progress_text=TunableLocalizedString(
                            description=
                            "\n                            Status text for when the player is still making progress towards\n                            the challenge goal.  This is currently displayed on a tooltip.\n                            A CSS class of 'timeremaining' will have its color changed\n                            when the event is close to ending.\n                            The following tokens are available:\n                            0 - Number: Current collection progress, if available.\n                            1 - Number: Collection goal, if available.\n                            2 - Number: Hours remaining.\n                            3 - Number: Days remaining.\n                            ",
                            allow_none=True),
                        activity_progress_icon=TunableIcon(
                            description=
                            '\n                            Icon to be paired with the progress text.\n                            ',
                            allow_none=True),
                        activity_complete_text=TunableLocalizedString(
                            description=
                            "\n                            Status text for when the player has met the challenge goal.\n                            This is currently displayed on a tooltip.\n                            If not specified, the in-progress text will be used.\n                            A CSS class of 'timeremaining' will have its color changed\n                            when the event is close to ending.\n                            The following tokens are available (same as the in-progress text):\n                            0 - Number: Current collection progress, if available.\n                            1 - Number: Collection goal, if available.\n                            2 - Number: Hours remaining.\n                            3 - Number: Days remaining.\n                            ",
                            allow_none=True),
                        activity_complete_icon=TunableIcon(
                            description=
                            '\n                            Icon to be paired with the challenge complete text.\n                            If not specified, the in-progress icon will be used.\n                            ',
                            allow_none=True),
                        community_progress_text=TunableLocalizedString(
                            description=
                            "\n                            Status text describing the community's progress.\n                            This is currently displayed on a tooltip.\n                            This text is displayed even when challenges do not have\n                            community goals.\n                            Two Number tokens are available:\n                            0 - Current community collection progress.\n                            1 - Community collection goal, if any.\n                            ",
                            allow_none=True),
                        community_progress_icon=TunableIcon(
                            description=
                            '\n                            Icon to be paired with the community status text.\n                            ',
                            allow_none=True),
                        community_complete_text=TunableLocalizedString(
                            description=
                            '\n                            Status text for when the community has met the challenge goal.\n                            This text is only used when a goal is defined.\n                            If not specified, the in-progress status text will be used.\n                            Two Number tokens are available:\n                            0 - Current community collection progress.\n                            1 - Community collection goal, if any.\n                            ',
                            allow_none=True),
                        community_complete_icon=TunableIcon(
                            description=
                            '\n                            Icon to be paired with the community challenge complete text.\n                            ',
                            allow_none=True),
                        community_goal_amount=Tunable(
                            description=
                            '\n                            Optional collection goal for the community to reach.\n                            ',
                            tunable_type=int,
                            default=0)),
                    collection_id=TunableEnumEntry(
                        description=
                        "\n                        A CollectionIdentifier that is associated with this\n                        challenge. This is used by the UI to tie a collectible \n                        with this challenge.\n                        \n                        Use the default of Unindentified for challenges that\n                        aren't associated with a particular collection.\n                        ",
                        tunable_type=CollectionIdentifier,
                        default=CollectionIdentifier.Unindentified,
                        export_modes=ExportModes.All),
                    reward_items=TunableList(
                        description=
                        '\n                        A list of tuples that describe rewards for challenge.\n                        ',
                        tunable=TunableTuple(
                            description=
                            '\n                            A tuple that contains data for a challenge reward item.\n                            ',
                            export_class_name='TunableChallengeRewardTuple',
                            reward_icon=TunableResourceKey(
                                description=
                                '\n                                The icon of reward item.\n                                ',
                                resource_types=sims4.resources.CompoundTypes.
                                IMAGE),
                            reward_name=TunableLocalizedString(
                                description=
                                '\n                                The name of reward item.\n                                ',
                                allow_none=True))))),
            challenge_subtitle=TunableLocalizedString(
                description=
                '\n                The subtitle text to be displayed in notification UI.\n                ',
                allow_none=True),
            challenge_title=TunableLocalizedString(
                description=
                '\n                The title text to be displayed in notification UI.\n                ',
                allow_none=True),
            switch_name=Tunable(
                description=
                '\n                Server switch name to check whether challenge is active.\n                ',
                tunable_type=str,
                default='',
                allow_empty=True)),
        export_modes=(ExportModes.ClientBinary, ))
    PLATFORM_STRING_REPLACEMENTS = TunableList(
        description=
        '\n        A list of strings that will be swapped out when in use on different \n        platforms. Each entry contains the original and replacement LocKey, the platforms\n        to perform the swap on, and the input method that is in use when the\n        LocKey is used.\n        ',
        tunable=TunableTuple(
            original_string=TunableLocalizedString(
                description=
                '\n                The string that will be replaced or ignored.\n                '
            ),
            replacement_string=OptionalTunable(
                description=
                '\n                The string that will be used in place of original_string. If\n                omitted, original_string will simply be ignored entirely.\n                ',
                tunable=TunableLocalizedString()),
            platform=TunableEnumEntry(
                description=
                '\n                The platforms on which the string will be replaced.\n                ',
                tunable_type=Platform,
                default=Platform.CONSOLE),
            input_method=TunableEnumEntry(
                description=
                '\n                The input method that should be in use when attempting to replace\n                the original_string.\n                ',
                tunable_type=InputMethod,
                default=InputMethod.ANY),
            export_modes=ExportModes.ClientBinary,
            export_class_name='PlatformStringReplacementTuple'))
    SCALING = TunableList(
        description=
        '\n        Defines a min/max ui scaling value for a screen resolution.\n        ',
        tunable=TunableTuple(
            screen_width=Tunable(
                description=
                '\n                Provide an integer value.\n                ',
                tunable_type=int,
                default=0),
            screen_height=Tunable(
                description=
                '\n                Provide an integer value.\n                ',
                tunable_type=int,
                default=0),
            scale_max=Tunable(
                description=
                '\n                Provide a float value.\n                ',
                tunable_type=float,
                default=1),
            scale_min=Tunable(
                description=
                '\n                Provide a float value.\n                ',
                tunable_type=float,
                default=1),
            export_modes=ExportModes.ClientBinary,
            export_class_name='UIScaleTuple'))
    CG_CHALLENGE_DATAS = TunableList(
        description="\n        A list of a challenge's data.\n        ",
        tunable=TunableTuple(
            cg_challenge_hashtag=TunableLocalizedString(
                description=
                '\n                Hashtag of this challenge\n                '
            ),
            cg_challenge_name=TunableLocalizedString(
                description=
                '\n                Name of this challenge\n                '),
            export_modes=ExportModes.ClientBinary,
            export_class_name='CGChallengeTuning'))
    DEFAULT_OVERLAY_MAP = TunableMapping(
        description=
        '\n        This is a mapping of MapOverlayEnum -> List of MapOverlayEnums. The key\n        is used as the layer to be shown when no other overlays are present.\n        The value is a list of overlay types that would result in the default\n        layer being turned off if both are active.\n        ',
        key_type=TunableEnumEntry(
            description=
            '\n            This is the OverlayType that acts as the default for the grouping\n            of OverlayTypes.\n            ',
            tunable_type=MapOverlayEnum,
            default=MapOverlayEnum.NONE),
        value_type=TunableList(
            description=
            '\n            A list of OverlayTypes, that if turned on would result in the\n            default OverlayType being shut off.\n            ',
            tunable=TunableEnumEntry(
                description=
                '\n                The OverlayType that causes the default value to turn off.\n                ',
                tunable_type=MapOverlayEnum,
                default=MapOverlayEnum.NONE)),
        export_modes=ExportModes.All,
        tuple_name='OverlayDefaultData')