# uncompyle6 version 3.7.4
# Python bytecode 3.7 (3394)
# Decompiled from: Python 3.7.9 (tags/v3.7.9:13c94747c7, Aug 17 2020, 18:58:18) [MSC v.1900 64 bit (AMD64)]
# Embedded file name: T:\InGame\Gameplay\Scripts\Server\situations\service_npcs\modify_lot_items_tuning.py
# Compiled at: 2020-04-23 01:12:37
# Size of source mod 2**32: 18955 bytes
from civic_policies.street_civic_policy_tests import StreetCivicPolicyTest
from collections import defaultdict
from event_testing.resolver import SingleObjectResolver, GlobalResolver
from event_testing.statistic_tests import StatThresholdTest
from event_testing.tests import CompoundTestList
from interactions import ParticipantType
from objects.components.inventory_elements import InventoryTransferFakePerform, DeliverBillFakePerform
from objects.components.state import TunableStateValueReference
from objects.components.types import VEHICLE_COMPONENT
from sims4.random import random_chance, weighted_random_item
from sims4.resources import Types
from sims4.tuning.tunable import HasTunableFactory, TunableList, TunableTuple, AutoFactoryInit, TunableVariant, TunableSingletonFactory, TunableSimMinute, HasTunableSingletonFactory, TunablePercent, TunableRange, TunableSet, TunableReference
from zone_tests import ZoneTest
import event_testing.results, event_testing.state_tests, event_testing.test_base, event_testing.test_events, event_testing.tests, objects.object_tests, seasons, services, world.world_tests

class TimeElapsedZoneTest(HasTunableSingletonFactory, AutoFactoryInit, event_testing.test_base.BaseTest):
    FACTORY_TUNABLES = {'minutes_passed': TunableSimMinute(description='\n            This test will pass when the minutes passed is greater than the\n            number minutes since last loading into zone.\n            ',
                         default=720,
                         minimum=1)}

    def get_expected_args(self):
        return {}

    @event_testing.test_events.cached_test
    def __call__(self):
        elapsed_time = services.current_zone().time_elapsed_since_last_save().in_minutes()
        if elapsed_time <= self.minutes_passed:
            return event_testing.results.TestResult(False, 'TimeElapsedZoneTest: elapsed time ({}) since last save at this zone is not greater than {}', elapsed_time, self.minutes_passed)
        return event_testing.results.TestResult.TRUE


class TunableObjectModifyGlobalTestVariant(TunableVariant):

    def __init__(self, additional_tests=None, **kwargs):
        if additional_tests is not None:
            kwargs.update(additional_tests)
        (super().__init__)(season=seasons.season_tests.SeasonTest.TunableFactory(locked_args={'tooltip': None}), 
         elapsed_time=TimeElapsedZoneTest.TunableFactory(locked_args={'tooltip': None}), 
         zone=ZoneTest.TunableFactory(locked_args={'tooltip': None}), **kwargs)


class TunableObjectModifyGlobalTestList(event_testing.tests.TestListLoadingMixin):
    DEFAULT_LIST = event_testing.tests.TestList()

    def __init__(self, description=None, additional_tests=None, **kwargs):
        if description is None:
            description = 'A list of tests.  All tests must succeed to pass the test.'
        (super().__init__)(description=description, tunable=TunableObjectModifyGlobalTestVariant(additional_tests=additional_tests), **kwargs)


class TunableObjectModifyTestVariant(TunableVariant):

    def __init__(self, description='A single tunable test.', test_excluded=(), additional_tests=None, **kwargs):
        if additional_tests is not None:
            kwargs.update(additional_tests)
        (super().__init__)(description=description, 
         state=event_testing.state_tests.TunableStateTest(locked_args={'who':ParticipantType.Object,  'tooltip':None}), 
         object_definition=TunableObjectMatchesDefinitionOrTagTest(), 
         inventory=objects.object_tests.InventoryTest.TunableFactory(locked_args={'tooltip': None}), 
         custom_name=objects.object_tests.CustomNameTest.TunableFactory(locked_args={'tooltip': None}), 
         consumable_test=objects.object_tests.ConsumableTest.TunableFactory(locked_args={'tooltip': None}), 
         existence=objects.object_tests.ExistenceTest.TunableFactory(locked_args={'tooltip':None,  'require_instantiated':False}), 
         game_option=event_testing.game_option_tests.SimInfoGameplayOptionsTest.TunableFactory(), 
         location=world.world_tests.LocationTest.TunableFactory(locked_args={'tooltip':None,  'subject':ParticipantType.Object}), 
         season=seasons.season_tests.SeasonTest.TunableFactory(locked_args={'tooltip': None}), 
         street_civic_policy_test=StreetCivicPolicyTest.TunableFactory(locked_args={'tooltip': None}), 
         object_preference=objects.object_tests.ObjectScoringPreferenceTest.TunableFactory(locked_args={'tooltip': None}), **kwargs)


class TunableObjectModifyTestSet(event_testing.tests.CompoundTestListLoadingMixin):
    DEFAULT_LIST = event_testing.tests.CompoundTestList()

    def __init__(self, description=None, additional_tests=None, **kwargs):
        (super().__init__)(description=description, tunable=TunableList(TunableObjectModifyTestVariant(additional_tests=additional_tests), description='A list of tests.  All of these must pass for the group to pass.'), **kwargs)


class ObjectMatchesDefinitionOrTagTest(event_testing.test_base.BaseTest):
    FACTORY_TUNABLES = {'description':'Check to see if the specified object matches either a static definition or a set of tags', 
     'item':TunableVariant(actual_item=objects.object_tests.CraftActualItemFactory(), tagged_item=objects.object_tests.CraftTaggedItemFactory(),
       default='tagged_item',
       description='Whether to test for a specific item or item that has a set of tags')}

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

    def get_expected_args(self):
        return {'objects': ParticipantType.Object}

    def __call__(self, objects=None):
        obj = next(iter(objects))
        match = self.item(obj, None)
        if not match:
            return event_testing.results.TestResult(False, 'ObjectMatchesDefinitionOrTagTest: Object did not match specified checks.')
        return event_testing.results.TestResult.TRUE


TunableObjectMatchesDefinitionOrTagTest = TunableSingletonFactory.create_auto_factory(ObjectMatchesDefinitionOrTagTest)

class ModifyAllLotItems(HasTunableFactory, AutoFactoryInit):
    DESTROY_OBJECT = 0
    SET_STATE = 1
    INVENTORY_TRANSFER = 2
    DELIVER_BILLS = 3
    SET_ON_FIRE = 4
    CLEANUP_VEHICLE = 5
    LOOT = 6
    FACTORY_TUNABLES = {'description':'\n        Tune modifications to apply to all objects on a lot.\n        Can do state changes, destroy certain items, etc.\n        \n        EX: for auto cleaning, tune to have objects with Dirtiness state that\n        equals dirty to be set to the clean state and tune to have dirty dishes\n        and spoiled food to be deleted\n        ', 
     'modifications':TunableList(description="\n            A list of where the elements define how to modify objects on the\n            lot. Each entry is a triplet of an object modification action\n            (currently either destroy the object or set its state), a list of\n            tests to run on the object to determine if we should actually apply\n            the modification, and a priority in case some modifications should\n            take precedence over other ones when both of their tests pass.\n            \n            EX: test list: object's dirtiness state != dirtiness clean\n            action: set state to Dirtiness_clean\n            \n            So dirty objects will become clean\n            ",
       tunable=TunableTuple(action=TunableVariant(set_state=TunableTuple(action_value=TunableStateValueReference(description='An object state to set the object to', pack_safe=True),
       locked_args={'action_type': SET_STATE}),
       destroy_object=TunableTuple(locked_args={'action_type': DESTROY_OBJECT}),
       inventory_transfer=TunableTuple(action_value=(InventoryTransferFakePerform.TunableFactory()),
       locked_args={'action_type': INVENTORY_TRANSFER}),
       deliver_bills=TunableTuple(action_value=(DeliverBillFakePerform.TunableFactory()),
       locked_args={'action_type': DELIVER_BILLS}),
       set_on_fire=TunableTuple(locked_args={'action_type': SET_ON_FIRE}),
       cleanup_vehicle=TunableTuple(description='\n                        Cleanup vehicles that are left around.\n                        ',
       locked_args={'action_type': CLEANUP_VEHICLE}),
       loot=TunableTuple(description='\n                        Apply loots to the object.\n                        ',
       loot_actions=TunableSet(description='\n                            Loot(s) to apply.\n                            ',
       tunable=TunableReference(manager=(services.get_instance_manager(Types.ACTION)),
       pack_safe=True)),
       locked_args={'action_type': LOOT})),
       chance=TunablePercent(description='\n                    Chance this modification will occur.\n                    ',
       default=100,
       minimum=1),
       global_tests=TunableObjectModifyGlobalTestList(description="\n                    Non object-related tests that gate this modification from occurring.  Use this for any global\n                    tests that don't require the object, such as zone/location/time-elapsed tests.  These tests\n                    will run only ONCE for this action, unlike 'Tests', which runs PER OBJECT. \n                    "),
       tests=TunableObjectModifyTestSet(description='\n                    All least one subtest group (AKA one list item) must pass\n                    within this list before the action associated with this\n                    tuning will be run.\n                    ',
       additional_tests={'elapsed_time':TimeElapsedZoneTest.TunableFactory(locked_args={'tooltip': None}), 
      'statistic':StatThresholdTest.TunableFactory(locked_args={'tooltip': None})}),
       weighted_tests=TunableList(description='\n                    Weighted tests for the individual object. One is chosen \n                    based on weight, and all objects are run against that chosen\n                    test set.\n                    ',
       tunable=TunableTuple(tests=TunableObjectModifyTestSet(description='\n                            All least one subtest group (AKA one list item) must pass\n                            within this list before the action associated with this\n                            tuning will be run.\n                            ',
       additional_tests={'elapsed_time':TimeElapsedZoneTest.TunableFactory(locked_args={'tooltip': None}), 
      'statistic':StatThresholdTest.TunableFactory(locked_args={'tooltip': None})}),
       weight=TunableRange(description='\n                            Weight to use.\n                            ',
       tunable_type=int,
       default=1,
       minimum=1)))))}

    def modify_objects(self, object_criteria=None):
        objects_to_destroy = []
        num_modified = 0
        modifications = defaultdict(CompoundTestList)
        for mod in self.modifications:
            if not random_chance(mod.chance * 100):
                continue
            if mod.global_tests:
                if not mod.global_tests.run_tests(GlobalResolver()):
                    continue
            if mod.tests:
                modifications[mod.action].extend(mod.tests)
            if mod.weighted_tests:
                weighted_tests = []
                for test_weight_pair in mod.weighted_tests:
                    weighted_tests.append((test_weight_pair.weight, test_weight_pair.tests))

                modifications[mod.action].extend(weighted_random_item(weighted_tests))

        if not modifications:
            return num_modified
        all_objects = list(services.object_manager().values())
        for obj in all_objects:
            if obj.is_sim:
                continue
            if object_criteria is not None:
                if not object_criteria(obj):
                    continue
            resolver = SingleObjectResolver(obj)
            modified = False
            for action, tests in modifications.items():
                if not tests.run_tests(resolver):
                    continue
                modified = True
                action_type = action.action_type
                if action_type == ModifyAllLotItems.DESTROY_OBJECT:
                    objects_to_destroy.append(obj)
                    break
                elif action_type == ModifyAllLotItems.SET_STATE:
                    new_state_value = action.action_value
                    if obj.state_component and obj.has_state(new_state_value.state):
                        obj.set_state((new_state_value.state), new_state_value, immediate=True)
                elif action_type in (ModifyAllLotItems.INVENTORY_TRANSFER,
                 ModifyAllLotItems.DELIVER_BILLS):
                    element = action.action_value()
                    element._do_behavior()
                elif action_type == ModifyAllLotItems.SET_ON_FIRE:
                    fire_service = services.get_fire_service()
                    fire_service.spawn_fire_at_object(obj)
                elif action_type == ModifyAllLotItems.CLEANUP_VEHICLE:
                    if self._should_cleanup_vehicle(obj):
                        objects_to_destroy.append(obj)
                elif action_type == ModifyAllLotItems.LOOT:
                    for loot_action in action.loot_actions:
                        loot_action.apply_to_resolver(resolver)

                else:
                    raise NotImplementedError

            if modified:
                num_modified += 1

        for obj in objects_to_destroy:
            obj.destroy(source=self, cause='Destruction requested by modify lot tuning')

        objects_to_destroy = None
        return num_modified

    def _should_cleanup_vehicle(self, obj):
        vehicle_component = obj.get_component(VEHICLE_COMPONENT)
        if vehicle_component is None:
            return False
        household_owner_id = obj.get_household_owner_id()
        if household_owner_id is not None:
            if household_owner_id != 0:
                return False
        if obj.interaction_refs:
            return False
        return True