slot_layout.py (13533B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\cosmetics\common\ships\skins\live_data\slot_layout.py 2 import logging 3 from collections import defaultdict 4 from cosmetics.common.ships.skins.errors import SlotConfigurationUnavailable, ComponentsUnavailable, SlotDataUnavailable, ComponentDataUnavailable, ComponentNotAllowedInSlot, SlotDisallowedForComponent, InvalidBlendMode, UnusedComponentsFitted, MissingPatternColor 5 from cosmetics.common.ships.skins.live_data.component_instance import BasePatternComponentInstance 6 from cosmetics.common.ships.skins.live_data.component_license_type import ComponentLicenseType 7 from cosmetics.common.ships.skins.sequencing_util import build_component_licenses_per_type_dict 8 from cosmetics.common.ships.skins.static_data.pattern_blend_mode import PatternBlendMode, NO_SECONDARY_COLOR_BLEND_MODES 9 from cosmetics.common.ships.skins.static_data.slot import SlotsDataLoader 10 from cosmetics.common.ships.skins.static_data.slot_configuration import SlotConfiguration, SlotConfigurationsDataLoader 11 from cosmetics.common.ships.skins.static_data.slot_name import SlotID 12 from signals import Signal 13 log = logging.getLogger(__name__) 14 DEFAULT_BLEND_MODE = PatternBlendMode.OVERLAY 15 16 class BaseSlotLayout(object): 17 LAYOUT_PROTO = None 18 SLOT_IDENTIFIER_PROTO = None 19 SHIP_TYPE_PROTO = None 20 21 def __init__(self, slot_config): 22 self._slot_config = slot_config 23 self._slots = {} 24 self._pattern_blend_mode = DEFAULT_BLEND_MODE 25 self._init_slots() 26 self.on_slot_fitting_changed = Signal('on_slot_fitting_changed') 27 self.on_pattern_blend_mode_changed = Signal('on_pattern_blend_mode_changed') 28 29 def _init_slots(self): 30 for slot_id in self._slot_config.slots: 31 self._slots[slot_id] = None 32 33 @property 34 def slot_config(self): 35 return self._slot_config 36 37 @property 38 def slots(self): 39 return self._slots 40 41 @property 42 def pattern_blend_mode(self): 43 return self._pattern_blend_mode 44 45 @pattern_blend_mode.setter 46 def pattern_blend_mode(self, value): 47 if value == PatternBlendMode.UNSPECIFIED: 48 value = DEFAULT_BLEND_MODE 49 self._pattern_blend_mode = value 50 self.on_pattern_blend_mode_changed(value) 51 52 def get_component(self, slot_id): 53 return self.slots[slot_id] 54 55 def get_slot_id(self, component_instance): 56 for slot_id, _component in self._slots.items(): 57 if component_instance == _component: 58 return slot_id 59 60 @property 61 def fitted_slots(self): 62 return {slot_id:instance for slot_id, instance in self.slots.items() if instance} 63 64 def fit_slot(self, slot_id, component_instance = None, notify = True): 65 self._fit_slot(slot_id, component_instance, notify, check_license=True) 66 if component_instance: 67 component_instance.apply_user_authored_values() 68 69 def _fit_slot(self, slot_id, component_instance = None, notify = True, check_license = False): 70 if slot_id not in self._slots: 71 return 72 if component_instance is None: 73 self.unfit_slot(slot_id, notify) 74 elif self._validate(slot_id, component_instance): 75 previous_component = self._slots[slot_id] 76 if previous_component is not None: 77 component_instance.copy_params_from_other(previous_component) 78 self._slots[slot_id] = component_instance 79 if check_license: 80 self._check_select_default_component_instance_license(component_instance) 81 if notify: 82 self.on_slot_fitting_changed(slot_id, component_instance) 83 84 def swap_slots(self, slot_id_1, slot_id_2): 85 instance_1 = self._slots[slot_id_1] 86 instance_2 = self._slots[slot_id_2] 87 self._slots[slot_id_1] = instance_2 88 self._slots[slot_id_2] = instance_1 89 self.on_slot_fitting_changed(slot_id_1, instance_2) 90 self.on_slot_fitting_changed(slot_id_2, instance_1) 91 92 def unfit_slot(self, slot_id, notify = True): 93 self._slots[slot_id] = None 94 if notify: 95 self.on_slot_fitting_changed(slot_id, None) 96 if slot_id == SlotID.PATTERN: 97 self.unfit_slot(SlotID.PATTERN_MATERIAL, notify) 98 self.unfit_slot(SlotID.SECONDARY_PATTERN, notify) 99 self.pattern_blend_mode = DEFAULT_BLEND_MODE 100 elif slot_id == SlotID.SECONDARY_PATTERN: 101 self.unfit_slot(SlotID.SECONDARY_PATTERN_MATERIAL, notify) 102 self.pattern_blend_mode = DEFAULT_BLEND_MODE 103 104 def unfit_all(self): 105 for slot_id in self._slots.keys(): 106 self.unfit_slot(slot_id) 107 108 def _check_select_default_component_instance_license(self, component_instance): 109 if not component_instance.component_license_to_use: 110 component_type = component_instance.get_component_data().category 111 license = self.get_component_license(component_instance.component_id, component_type) 112 if license: 113 component_instance.component_license_to_use = license 114 115 def copy_slots_from_other(self, other_layout, should_copy_components_to_use = False): 116 for slot_id, component_instance in other_layout.slots.items(): 117 if component_instance: 118 component_instance_copy = self.create_component_instance(component_instance.component_id) 119 component_instance_copy.copy_params_from_other(component_instance, should_copy_components_to_use) 120 self._fit_slot(slot_id, component_instance_copy, notify=False, check_license=True) 121 else: 122 self._fit_slot(slot_id, None, notify=False, check_license=False) 123 124 self.pattern_blend_mode = other_layout.pattern_blend_mode 125 126 def _validate(self, slot_id, component_instance): 127 slot_data = SlotsDataLoader.get_slot_data(slot_id) 128 if not slot_data: 129 raise SlotDataUnavailable() 130 component_data = component_instance.get_component_data() 131 if not component_data: 132 raise ComponentDataUnavailable() 133 if component_data.category not in slot_data.allowed_component_categories: 134 raise ComponentNotAllowedInSlot() 135 if slot_id in component_data.disallowed_slot_ids: 136 raise SlotDisallowedForComponent() 137 return True 138 139 def validate_layout(self): 140 try: 141 for slot_id, component_instance in self.fitted_slots.items(): 142 self._validate(slot_id, component_instance) 143 144 except (SlotDataUnavailable, 145 ComponentDataUnavailable, 146 ComponentNotAllowedInSlot, 147 SlotDisallowedForComponent) as e: 148 log.exception(e) 149 return False 150 151 try: 152 self.validate_patterns() 153 except (MissingPatternColor, InvalidBlendMode, UnusedComponentsFitted) as e: 154 log.exception(e) 155 return False 156 157 return True 158 159 def validate_patterns(self): 160 nb_patterns = len([ x for x, y in self.fitted_slots.iteritems() if isinstance(y, BasePatternComponentInstance) ]) 161 if nb_patterns > 1 and self.pattern_blend_mode == PatternBlendMode.UNSPECIFIED: 162 raise InvalidBlendMode() 163 if self.pattern_blend_mode in NO_SECONDARY_COLOR_BLEND_MODES: 164 secondary_color = self.get_component(SlotID.SECONDARY_PATTERN_MATERIAL) 165 if secondary_color is not None: 166 raise UnusedComponentsFitted() 167 else: 168 nb_pattern_colors = len([ x for x, y in self.fitted_slots.iteritems() if x in [SlotID.SECONDARY_PATTERN_MATERIAL, SlotID.PATTERN_MATERIAL] ]) 169 if nb_pattern_colors != nb_patterns: 170 raise MissingPatternColor() 171 172 def validate_licenses(self, number_of_runs, licenses, exact_match = False): 173 if any([ x is None for x in licenses ]): 174 return False 175 licenses_available = build_component_licenses_per_type_dict(licenses) 176 component_required_amounts = defaultdict(int) 177 for component_instance in self.fitted_slots.values(): 178 component_required_amounts[component_instance.component_id] += number_of_runs 179 180 for component_id, nb_required in component_required_amounts.iteritems(): 181 if component_id not in licenses_available: 182 return False 183 if ComponentLicenseType.LIMITED in licenses_available[component_id]: 184 if exact_match: 185 if nb_required < licenses_available[component_id][ComponentLicenseType.LIMITED].remaining_license_uses: 186 return False 187 if nb_required != licenses_available[component_id][ComponentLicenseType.LIMITED].remaining_license_uses: 188 if ComponentLicenseType.UNLIMITED not in licenses_available[component_id]: 189 return False 190 elif ComponentLicenseType.UNLIMITED in licenses_available[component_id]: 191 return False 192 elif nb_required > licenses_available[component_id][ComponentLicenseType.LIMITED].remaining_license_uses: 193 if ComponentLicenseType.UNLIMITED not in licenses_available[component_id]: 194 return False 195 elif ComponentLicenseType.UNLIMITED not in licenses_available[component_id]: 196 return False 197 198 return True 199 200 def get_amount_of_components_with_license_type(self, component_id, license_type = None): 201 component_amount = 0 202 for component_instance in self.fitted_slots.values(): 203 if component_instance.component_id == component_id: 204 license = component_instance.component_license_to_use 205 if license_type is None or license is None: 206 component_amount += 1 207 elif license.license_type == license_type: 208 component_amount += 1 209 210 return component_amount 211 212 def __eq__(self, other): 213 if other is None: 214 return False 215 if len(self.slots) != len(other.slots): 216 return False 217 for slot_id, slot_data in self.slots.items(): 218 if slot_id not in other.slots: 219 return False 220 if slot_data != other.slots[slot_id]: 221 return False 222 223 if self.pattern_blend_mode != other.pattern_blend_mode: 224 return False 225 return True 226 227 def __ne__(self, other): 228 return not self.__eq__(other) 229 230 def __str__(self): 231 result = '' 232 for slot_id, slot_data in self._slots.iteritems(): 233 result += 'slot %s: %s\n' % (slot_id, slot_data) 234 235 result += 'blend mode: %s' % self.pattern_blend_mode 236 return result 237 238 @classmethod 239 def copy_from_other(cls, other_layout): 240 layout_copy = cls(other_layout.slot_config) 241 layout_copy.copy_from_other(other_layout) 242 return layout_copy 243 244 @classmethod 245 def build_from_proto(cls, payload): 246 ship_type_id = payload.ship_type.sequential 247 slot_config = SlotConfigurationsDataLoader.get_for_ship(ship_type_id) 248 if not slot_config: 249 raise SlotConfigurationUnavailable() 250 slot_layout = cls(slot_config) 251 slot_layout.fit_slots_from_proto(payload) 252 slot_layout.pattern_blend_mode = payload.pattern_blend_mode 253 return slot_layout 254 255 def fit_slots_from_proto(self, payload): 256 for slot in payload.slots: 257 component_id = None 258 if slot.configuration.HasField('coating'): 259 component_id = slot.configuration.coating.coating.sequential 260 elif slot.configuration.HasField('pattern'): 261 component_id = slot.configuration.pattern.pattern.sequential 262 if component_id is not None: 263 component_instance = self.create_component_instance(component_id) 264 if not component_instance: 265 log.error('unable to find component id %s, skipping it from layout %s' % (component_id, payload)) 266 continue 267 if slot.configuration.HasField('coating'): 268 component_instance.build_params_from_proto(slot.configuration.coating.configuration) 269 elif slot.configuration.HasField('pattern'): 270 component_instance.build_params_from_proto(slot.configuration.pattern.configuration) 271 self._fit_slot(slot.id.sequential, component_instance, check_license=False) 272 273 self.pattern_blend_mode = payload.pattern_blend_mode 274 275 @classmethod 276 def build_proto_from_layout(cls, ship_type_id, layout): 277 proto_layout = cls.LAYOUT_PROTO() 278 slot_list = [] 279 for slot_id, slot_data in layout.slots.iteritems(): 280 if slot_data is not None: 281 slot_list.append(cls.LAYOUT_PROTO.Slot(id=cls.SLOT_IDENTIFIER_PROTO(sequential=slot_id), configuration=cls.build_config_proto_from_component_instance(slot_data))) 282 283 proto_layout.slots.extend(slot_list) 284 proto_layout.ship_type.CopyFrom(cls.SHIP_TYPE_PROTO(sequential=ship_type_id)) 285 proto_layout.pattern_blend_mode = layout.pattern_blend_mode 286 return proto_layout 287 288 def create_component_instance(self, component_id): 289 raise NotImplementedError 290 291 @classmethod 292 def build_config_proto_from_component_instance(cls, component_instance): 293 raise NotImplementedError 294 295 def get_component_license(self, component_id, component_type): 296 raise NotImplementedError