controller.py (22233B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\skins\controller.py 2 import gametime 3 import uthread2 4 from cosmetics.client.shipSkinApplicationSvc import get_ship_skin_application_svc 5 from cosmetics.client.ships.ship_skin_signals import on_skin_state_set, on_skin_license_activated, on_skin_state_reapplied 6 from cosmetics.client.ships.skins.live_data.ship_skin_state import ShipSkinState 7 from cosmetics.client.shipSkinLicensesSvc import get_ship_skin_license_svc 8 from cosmetics.common.ships.skins.static_data.skin_type import ShipSkinType 9 from cosmetics.common.ships.skins.static_data.slot_configuration import is_skinnable_ship 10 from player_messaging.client.ui_message import message_player 11 from eve.client.script.ui.shared.skins.skinUISignals import on_skin_selected 12 from eve.client.script.environment.spaceObject.cosmeticsManager import CosmeticsManager 13 from eve.client.script.environment.sofService import GetSofService 14 from eve.common.script.sys.idCheckers import IsShipType 15 from eveexceptions import UserError 16 from eveexceptions.exceptionEater import ExceptionEater 17 from itertoolsext import first 18 import locks 19 import uthread 20 import signals 21 import evetypes 22 from localization import GetByLabel 23 from structures import STATE_SHIELD_VULNERABLE 24 from evegraphics.utils import BuildSOFDNAFromTypeID 25 from inventorycommon.util import IsModularShip, GetSubSystemTypeIDsForDogmaLocation 26 import inventorycommon.const as invConst 27 from carbon.common.script.util.logUtil import LogException, LogInfo 28 from publicGateway.grpc.exceptions import GenericException 29 from stackless_response_router.exceptions import TimeoutException 30 SKIN_DELAY_STRUCTURES = 8 31 32 class ThirdPartySkinWrapper(object): 33 34 def __init__(self, third_party_skin_license): 35 self.skin = third_party_skin_license 36 self.skin_data = self.skin.skin_design 37 self.ship_type_id = self.skin.skin_design.ship_type_id 38 39 @property 40 def skin_type(self): 41 return ShipSkinType.THIRD_PARTY_SKIN 42 43 @property 44 def skinID(self): 45 return self.skin.skin_hex 46 47 @property 48 def name(self): 49 return self.skin.skin_design.name 50 51 @property 52 def licensed(self): 53 return True 54 55 @property 56 def expires(self): 57 return None 58 59 @property 60 def iconTexturePath(self): 61 return 'res:/UI/Texture/WindowIcons/paint_tool.png' 62 63 def __eq__(self, other): 64 if not isinstance(other, ThirdPartySkinWrapper): 65 return False 66 return self.skinID == other.skinID 67 68 69 class SkinPanelAdapter(object): 70 71 def ApplySkin(self, controller, itemID, hullTypeID, skin): 72 uthread.new(self._ApplySkinThread, controller, itemID, hullTypeID, skin) 73 74 def _ApplySkinThread(self, controller, itemID, hullTypeID, skin): 75 try: 76 if skin and skin.skin_type == ShipSkinType.THIRD_PARTY_SKIN: 77 self._ApplyThirdPartySkin(skin.skinID, itemID) 78 else: 79 self._ApplyFirstPartySkin(itemID, hullTypeID, skin) 80 except Exception: 81 controller.OnApplySkinFailed(itemID, skin, hullTypeID) 82 raise 83 84 def _ApplyFirstPartySkin(self, itemID, hullTypeID, skin): 85 try: 86 get_ship_skin_application_svc().apply_first_party_skin(itemID, hullTypeID, skin) 87 except UserError as e: 88 if e.msg != 'SkinAlreadyApplied': 89 raise 90 except Exception: 91 raise 92 93 def _ApplyThirdPartySkin(self, licenseID, itemID): 94 get_ship_skin_application_svc().apply_third_party_skin(itemID, licenseID, apply_license=True, activate_license=False) 95 96 def GetAppliedSkin(self, itemID, hullTypeID, licenseeID = None): 97 cosmeticsSvc = sm.GetService('cosmeticsSvc') 98 if licenseeID is None: 99 skin_state = cosmeticsSvc.GetAppliedSkinStateForCurrentSession(itemID) 100 else: 101 skin_state = cosmeticsSvc.GetAppliedSkinState(licenseeID, itemID) 102 if skin_state is None or skin_state.skin_type == ShipSkinType.NO_SKIN: 103 return 104 if skin_state.skin_type == ShipSkinType.FIRST_PARTY_SKIN: 105 if licenseeID is None: 106 return cosmeticsSvc.GetFirstPartySkinObjectForCurrentSession(skin_state.skin_data.skin_id) 107 else: 108 return cosmeticsSvc.GetFirstPartySkinObject(licenseeID, skin_state.skin_data.skin_id) 109 if skin_state.skin_type == ShipSkinType.THIRD_PARTY_SKIN: 110 if skin_state.character_id != session.charid: 111 return 112 else: 113 try: 114 skin_license = get_ship_skin_license_svc().get_license(skin_state.skin_data.skin_id, skin_state.character_id) 115 except (GenericException, TimeoutException): 116 message_player(GetByLabel('UI/Common/CannotConnectToServer')) 117 skin_license = None 118 119 if skin_license: 120 return ThirdPartySkinWrapper(skin_license) 121 return 122 123 def GetSkins(self, hullTypeID): 124 if not IsShipType(hullTypeID): 125 return [] 126 skins = sm.GetService('cosmeticsSvc').GetSkins(hullTypeID) 127 if is_skinnable_ship(hullTypeID): 128 try: 129 thirdPartySkins = [ ThirdPartySkinWrapper(skin_license) for skin_license in get_ship_skin_license_svc().get_my_activated_licenses(hullTypeID) ] 130 skins.extend(thirdPartySkins) 131 except: 132 pass 133 134 return sorted(skins, key=lambda s: s.name) 135 136 def GetTypesForSkin(self, skinID): 137 skin = sm.GetService('cosmeticsSvc').static.GetSkinByID(skinID) 138 return skin.types 139 140 def RegisterNotify(self, controller): 141 sm.RegisterNotify(controller) 142 143 def UnregisterNotify(self, controller): 144 sm.UnregisterNotify(controller) 145 146 147 class SkinPanelController(object): 148 __notifyevents__ = ['OnSkinLicenseActivated'] 149 150 def __init__(self, hullTypeID, adapter = None): 151 self._adapter = adapter or SkinPanelAdapter() 152 self._lock = locks.Lock() 153 self.Reset(hullTypeID) 154 self.onChange = signals.Signal(signalName='onChange') 155 self.onSkinsChange = signals.Signal(signalName='onSkinsChange') 156 self.on_position_changed = signals.Signal(signalName='on_position_changed') 157 self.onSkinsChange.connect(self.onChange) 158 on_skin_license_activated.connect(self._OnThirdPartySkinLicenseActivated) 159 self._adapter.RegisterNotify(self) 160 161 def Close(self): 162 on_skin_license_activated.disconnect(self._OnThirdPartySkinLicenseActivated) 163 self._adapter.UnregisterNotify(self) 164 self.onSkinsChange.clear() 165 self.onChange.clear() 166 167 def Reset(self, hullTypeID): 168 self._typeID = hullTypeID 169 self._applied = None 170 self._previewed = None 171 self._pending = None 172 self._skins = None 173 self._lastSkinChange = sm.GetService('cosmeticsSvc').lastSkinChange 174 self._forcedDelayInSec = 0 175 176 @property 177 def typeID(self): 178 return self._typeID 179 180 @typeID.setter 181 def typeID(self, newTypeID): 182 with self._lock: 183 self.Reset(newTypeID) 184 self.onSkinsChange() 185 186 @property 187 def applied(self): 188 return self._applied 189 190 @property 191 def previewed(self): 192 return self._previewed 193 194 @property 195 def pending(self): 196 return self._pending 197 198 @property 199 def skins(self): 200 if self._skins is None: 201 self._skins = self._adapter.GetSkins(self._typeID) 202 return self._skins 203 204 def PickSkinAndMoveToIt(self, skin, deselectIfSame = True): 205 self.PickSkin(skin, deselectIfSame) 206 uthread2.call_after_wallclocktime_delay(self.on_position_changed, 1) 207 208 def PickSkin(self, skin, deselectIfSame = True): 209 with self._lock: 210 if self._previewed and skin: 211 if hasattr(self._previewed, 'materialID') and hasattr(skin, 'materialID') and self._previewed.materialID == skin.materialID and deselectIfSame: 212 skin = None 213 elif self._previewed.skinID and skin.skinID and self._previewed.skinID == skin.skinID: 214 skin = None 215 self._previewed = skin 216 self.onChange() 217 218 def OnSkinLicenseActivated(self, skinID, licenseeID): 219 with self._lock: 220 types = self._adapter.GetTypesForSkin(skinID) 221 if self._typeID not in types: 222 return 223 self._skins = None 224 self._UpdateActivatedSkin(skinID) 225 self.onSkinsChange() 226 227 def _OnThirdPartySkinLicenseActivated(self, licenseID): 228 with self._lock: 229 self._skins = None 230 self._UpdateActivatedSkin(licenseID) 231 self.onSkinsChange() 232 233 def _UpdateActivatedSkin(self, skinID): 234 if self._previewed is None: 235 return 236 try: 237 skin = self._GetSkinByID(skinID) 238 if skin and skin == self._previewed: 239 self._previewed = skin 240 except StopIteration: 241 pass 242 243 def _GetSkinByID(self, skinID): 244 try: 245 return first(self.skins, lambda s: s.skinID == skinID) 246 except StopIteration: 247 return None 248 249 def IsSkinAccessRestricted(self): 250 return False 251 252 def IsDisabledDueToDamage(self): 253 return False 254 255 def GetAppliedSkin(self, itemID, hullTypeID): 256 return self._adapter.GetAppliedSkin(itemID, hullTypeID) 257 258 def GetTimeUntilNextAllowedSkinChange(self): 259 if self._lastSkinChange is None or self._forcedDelayInSec <= 0: 260 return 0 261 timeUntil = self._lastSkinChange + self._forcedDelayInSec * const.SEC - gametime.GetWallclockTime() 262 return max(0, float(timeUntil) / const.SEC) 263 264 265 class ShipInfoSkinPanelController(SkinPanelController): 266 267 def __init__(self, itemID, ownerID, hullTypeID, adapter = None): 268 self.itemID = itemID 269 self.ownerID = ownerID 270 super(ShipInfoSkinPanelController, self).__init__(hullTypeID, adapter) 271 on_skin_state_set.connect(self._OnSkinStateSet) 272 on_skin_state_reapplied.connect(self._OnSkinStateReapplied) 273 274 def Close(self): 275 super(ShipInfoSkinPanelController, self).Close() 276 on_skin_state_set.disconnect(self._OnSkinStateSet) 277 on_skin_state_reapplied.disconnect(self._OnSkinStateReapplied) 278 279 def Reset(self, hullTypeID): 280 super(ShipInfoSkinPanelController, self).Reset(hullTypeID) 281 self._applied = self.GetAppliedSkin(self.itemID, hullTypeID) 282 283 def GetAppliedSkin(self, itemID, hullTypeID): 284 return self._adapter.GetAppliedSkin(itemID, hullTypeID, self.ownerID) 285 286 def PickSkin(self, skin, deselectIfSame = True): 287 itemID = self.itemID 288 typeID = self.typeID 289 if not itemID or self.ownerID != session.charid: 290 super(ShipInfoSkinPanelController, self).PickSkin(skin, deselectIfSame) 291 return 292 with self._lock: 293 if skin is not None and skin not in self.skins: 294 raise SkinNotAvailableForType('%s not found in %s' % (skin, self.skins)) 295 if skin is None: 296 self._ResetPick(itemID, typeID) 297 elif skin.licensed and not self.IsSkinAccessRestricted() and not self.IsDisabledDueToDamage(): 298 if self.GetTimeUntilNextAllowedSkinChange(): 299 raise UserError('CustomNotify', {'notify': GetByLabel('UI/Skins/SkinChangeUnderway')}) 300 self._PickLicensedSkin(skin, itemID, typeID) 301 else: 302 self._PickUnlicensedSkin(skin) 303 304 def SetPreviewed(self, skin): 305 self._previewed = skin 306 307 def _ResetPick(self, itemID, hullTypeID): 308 if all((s is None for s in (self._applied, self._pending, self._previewed))): 309 return 310 self._applied = None 311 self._pending = None 312 self._previewed = None 313 self._adapter.ApplySkin(self, itemID, hullTypeID, None) 314 self.onChange() 315 316 def _PickUnlicensedSkin(self, skin): 317 if self._previewed == skin: 318 self._previewed = None 319 else: 320 self._previewed = skin 321 self.onChange() 322 323 def _PickLicensedSkin(self, skin, itemID, typeID): 324 if self._applied == skin or self._pending == skin: 325 skin = None 326 self._pending = skin 327 self._applied = None 328 if skin is not None: 329 self._previewed = None 330 self._adapter.ApplySkin(self, itemID, typeID, skin) 331 self.onChange() 332 on_skin_selected(skin) 333 334 def _OnSkinStateSet(self, itemID, skinState): 335 if itemID != self.itemID: 336 return 337 with self._lock: 338 if skinState is None and self._applied is None: 339 return 340 if skinState is None or skinState.skin_type == ShipSkinType.NO_SKIN: 341 skin = None 342 else: 343 skin = self._GetSkinByID(skinState.skin_data.skin_id) 344 if self._applied == skin: 345 return 346 if self._pending and self._pending == skin: 347 self._pending = None 348 self._applied = skin 349 self._lastSkinChange = gametime.GetWallclockTime() 350 sm.GetService('cosmeticsSvc').lastSkinChange = self._lastSkinChange 351 self.onChange() 352 353 def _OnSkinStateReapplied(self, itemID): 354 skin_state = sm.GetService('cosmeticsSvc').GetCachedAppliedSkinState(session.charid, itemID) 355 if skin_state: 356 self._OnSkinStateSet(itemID, skin_state) 357 358 359 class SkinNotAvailableForType(Exception): 360 pass 361 362 363 class FittingSkinPanelController(SkinPanelController): 364 __notifyevents__ = SkinPanelController.__notifyevents__ + ['OnSkinLicenseRemoved'] 365 366 def __init__(self, fitting, adapter = None): 367 self._fitting = fitting 368 super(FittingSkinPanelController, self).__init__(hullTypeID=fitting.typeID, adapter=adapter) 369 self._UpdateFittingMaterial() 370 self.onChange.connect(self._UpdateFittingMaterial) 371 on_skin_state_set.connect(self._OnSkinStateSet) 372 on_skin_state_reapplied.connect(self._OnSkinStateReapplied) 373 self._fitting.on_new_itemID.connect(self.OnNewItemID) 374 375 @property 376 def itemID(self): 377 return self._itemID 378 379 @property 380 def isStructureSkin(self): 381 return self._isStructureSkin 382 383 def Reset(self, hullTypeID): 384 super(FittingSkinPanelController, self).Reset(hullTypeID) 385 self._itemID = self._fitting.itemID 386 self._applied = self.GetAppliedSkin(self._itemID, hullTypeID) 387 self._isStructureSkin = evetypes.GetCategoryID(hullTypeID) == const.categoryStructure 388 if self.isStructureSkin: 389 self._forcedDelayInSec = SKIN_DELAY_STRUCTURES 390 391 def GetAppliedSkin(self, itemID, hullTypeID): 392 ownerID = self.GetOwnerID() 393 return self._adapter.GetAppliedSkin(itemID, hullTypeID, ownerID) 394 395 def PickSkin(self, skin): 396 itemID = self.itemID 397 typeID = self.typeID 398 with self._lock: 399 if skin is not None and skin not in self.skins: 400 raise SkinNotAvailableForType('%s not found in %s' % (skin, self.skins)) 401 if skin is None: 402 self._ResetPick(itemID, typeID) 403 elif skin.licensed and not self.IsSkinAccessRestricted() and not self.IsDisabledDueToDamage(): 404 if self.GetTimeUntilNextAllowedSkinChange(): 405 raise UserError('CustomNotify', {'notify': GetByLabel('UI/Skins/SkinChangeUnderway')}) 406 self._PickLicensedSkin(skin, itemID, typeID) 407 else: 408 self._PickUnlicensedSkin(skin) 409 410 def _ResetPick(self, itemID, hullTypeID): 411 if all((s is None for s in (self._applied, self._pending, self._previewed))): 412 return 413 self._applied = None 414 self._pending = None 415 self._previewed = None 416 self._adapter.ApplySkin(self, itemID, hullTypeID, None) 417 self.onChange() 418 419 def _PickLicensedSkin(self, skin, itemID, typeID): 420 if self._applied == skin or self._pending == skin: 421 skin = None 422 self._pending = skin 423 self._applied = None 424 if skin is not None: 425 self._previewed = None 426 self._adapter.ApplySkin(self, itemID, typeID, skin) 427 self.onChange() 428 on_skin_selected(skin) 429 430 def _PickUnlicensedSkin(self, skin): 431 if self._previewed == skin: 432 self._previewed = None 433 else: 434 self._previewed = skin 435 self.onChange() 436 437 def _OnSkinStateSet(self, itemID, skinState): 438 if itemID != self.itemID: 439 return 440 with self._lock: 441 if skinState is None and self._applied is None: 442 return 443 if skinState is None or skinState.skin_type == ShipSkinType.NO_SKIN: 444 skin = None 445 else: 446 skin = self._GetSkinByID(skinState.skin_data.skin_id) 447 if self._applied == skin: 448 return 449 if self._pending and self._pending == skin: 450 self._pending = None 451 self._applied = skin 452 self._lastSkinChange = gametime.GetWallclockTime() 453 sm.GetService('cosmeticsSvc').lastSkinChange = self._lastSkinChange 454 self.onChange() 455 456 def _OnSkinStateReapplied(self, itemID): 457 skin_state = sm.GetService('cosmeticsSvc').GetCachedAppliedSkinState(session.charid, itemID) 458 if skin_state: 459 self._OnSkinStateSet(itemID, skin_state) 460 461 def OnNewItemID(self, *args): 462 self.typeID = self._fitting.typeID 463 464 def OnApplySkinFailed(self, itemID, skin, hullTypeID): 465 if itemID != self.itemID: 466 return 467 with self._lock: 468 if self._pending == skin: 469 self._pending = None 470 self._applied = self.GetAppliedSkin(itemID, hullTypeID) 471 self.onChange() 472 473 def _UpdateActivatedSkin(self, skinID): 474 if self._applied is not None and self._applied.skinID == skinID: 475 self._applied = None 476 if self._pending is not None and self._pending.skinID == skinID: 477 self._pending = None 478 if self._previewed is not None: 479 try: 480 skin = self._GetSkinByID(skinID) 481 if self._previewed == skin: 482 self._previewed = None 483 except StopIteration: 484 pass 485 486 def _UpdateFittingMaterial(self): 487 if self._previewed: 488 skin = self._previewed 489 elif self._pending: 490 skin = self._pending 491 elif self._applied: 492 skin = self._applied 493 else: 494 skin = None 495 self._fitting.SetDisplayedSkin(skin) 496 497 def OnSkinLicenseRemoved(self, skinID, itemID, typeID): 498 with self._lock: 499 if self.itemID != itemID: 500 return 501 types = self._adapter.GetTypesForSkin(skinID) 502 if self._typeID not in types: 503 return 504 self._skins = None 505 self.onSkinsChange() 506 self._ResetPick(self.itemID, typeID) 507 508 def IsSkinAccessRestricted(self): 509 ownerID = self.GetOwnerID() 510 if ownerID not in (session.charid, session.corpid): 511 return True 512 return False 513 514 def GetOwnerID(self): 515 if self.itemID: 516 dogmaItem = self._fitting.dogmaItem 517 if dogmaItem: 518 return dogmaItem.ownerID 519 520 def IsDisabledDueToDamage(self): 521 return self._IsDamagedStructure() 522 523 def _IsDamagedStructure(self): 524 if not self.isStructureSkin: 525 return False 526 if not self.itemID or self.itemID != session.shipid: 527 return False 528 ballpark = sm.GetService('michelle').GetBallpark() 529 if not ballpark: 530 return False 531 slimItem = ballpark.GetInvItem(self.itemID) 532 if slimItem and slimItem.state != STATE_SHIELD_VULNERABLE: 533 return True 534 return False 535 536 def Close(self): 537 with ExceptionEater('FittingSkinPanelController:Closing '): 538 self._fitting.on_new_itemID.disconnect(self.OnNewItemID) 539 self._fitting = None 540 on_skin_state_set.disconnect(self._OnSkinStateSet) 541 on_skin_state_reapplied.disconnect(self._OnSkinStateReapplied) 542 SkinPanelController.Close(self) 543 544 545 def GetSkinnedShipModel(skin, typeID, multiHullTypeIDList = None): 546 try: 547 if skin and skin.skin_type == ShipSkinType.THIRD_PARTY_SKIN: 548 return GetSkinnedThirdPartyModel(skin, typeID) 549 return GetSkinnedFirstPartyModel(skin, typeID, multiHullTypeIDList) 550 except Exception as e: 551 LogException(str(e)) 552 return None 553 554 555 def GetSkinnedFirstPartyModel(skin, typeID, multiHullTypeIDList = None): 556 skinMaterialSetID = None 557 if skin and skin.skin_type == ShipSkinType.FIRST_PARTY_SKIN: 558 skinMaterialSetID = getattr(skin, 'materialSetID', None) 559 if not skinMaterialSetID: 560 skin_data = getattr(skin, 'skin_data', None) 561 if skin_data: 562 skinMaterialSetID = sm.GetService('cosmeticsSvc').GetFirstPartySkinMaterialSetID(skin_data.skin_id) 563 dna = BuildSOFDNAFromTypeID(typeID, materialSetID=skinMaterialSetID, multiHullTypeIDList=multiHullTypeIDList) 564 return GetSofService().spaceObjectFactory.BuildFromDNA(dna) 565 566 567 def GetSkinnedThirdPartyModel(skin, typeID): 568 sofDNA = CosmeticsManager.CreateSOFDNAfromSkinState(skin, typeID) 569 model = GetSofService().spaceObjectFactory.BuildFromDNA(sofDNA) 570 CosmeticsManager.UpdatePatternProjectionParametersFromSkinState(skin, model, typeID) 571 blend_mode = skin.skin_data.slot_layout.pattern_blend_mode 572 if blend_mode is not None: 573 CosmeticsManager.SetBlendMode(model, blendMode=blend_mode) 574 return model 575 576 577 def GetMultiHullTypeIDList(typeID, itemID, dogmaLocation): 578 if IsModularShip(typeID) and itemID and dogmaLocation: 579 multiHullTypeIDList = GetSubSystemTypeIDsForDogmaLocation(itemID, typeID, dogmaLocation) 580 if len(multiHullTypeIDList or []) < len(invConst.subsystemSlotFlags): 581 return 582 else: 583 multiHullTypeIDList = None 584 return multiHullTypeIDList