neocomInventoryBadging.py (21378B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\inventory\neocomInventoryBadging.py 2 from carbon.common.lib.const import ixFlag, ixItemID, ixLocationID, ixOwnerID 3 from collections import defaultdict 4 from eve.client.script.ui.shared.inventory.invWindow import Inventory 5 from eve.client.script.ui.shared.neocom.neocom.btnData.btnDataNodeNotification import BtnDataNodeNotification 6 from eve.client.script.ui.shared.neocom.neocom.fixedButtonExtension import AmountStorage, SeenItemMultipleLocationsStorage, UnseenItemsExtensionForExistingButton 7 from eve.client.script.ui.shared.neocom.neocom.neocomConst import BTNTYPE_INVENTORY 8 from eve.common.lib.appConst import locationAbstract, locationJunkyard 9 from eve.common.script.sys.idCheckers import IsCapsule, IsCharacter, IsPlex, IsShip, IsSolarSystem, IsStation 10 from evetypes import GetCategoryID 11 import inventorycommon.const as invConst 12 from localization import GetByLabel 13 from log import LogException 14 from plex.client.api.repository import get_repository 15 from uthread2 import call_after_wallclocktime_delay, BufferedCall 16 OPEN_INVENTORY_WINDOW_COMMAND_NAME = 'OpenInventory' 17 INVENTORY_NAME_LABEL = 'UI/Neocom/InventoryBtn' 18 INVENTORY_ICON_PATH = 'res:/UI/Texture/WindowIcons/items.png' 19 INVENTORY_ICON_ID = 'inventory' 20 NEW_ITEMS_UI_HIGHLIGHT_ID = 2241 21 NEW_ITEMS_UI_HIGHLIGHT_DELAY_SECS = 0.25 22 SETTINGS_KEY_SEEN_PLEX = 'SeenPlex' 23 SETTINGS_GROUP_UNSEEN_ITEMS = 'unseenInventoryItems' 24 SETTINGS_GROUP_SEEN_ITEMS = 'seenInventoryItems' 25 26 class UnseenInventoryItemsBtnData(BtnDataNodeNotification): 27 default_btnType = BTNTYPE_INVENTORY 28 default_cmdName = OPEN_INVENTORY_WINDOW_COMMAND_NAME 29 default_iconPath = INVENTORY_ICON_PATH 30 default_btnID = INVENTORY_ICON_ID 31 default_label = GetByLabel(INVENTORY_NAME_LABEL) 32 default_isBlinking = False 33 default_isDraggable = True 34 __notifyevents__ = ['OnInventoryItemUnseen'] 35 36 def GetItemCount(self): 37 return sm.GetService('neocom').GetUnseenInventoryItemsCount() 38 39 def OnInventoryItemUnseen(self, item_ids): 40 self.OnBadgeCountChanged() 41 42 43 class NeocomInventoryBadging(object): 44 __notifyevents__ = ['OnSessionChanged', 45 'OnSessionMutated', 46 'OnItemChange', 47 'OnInventoryContainerShown', 48 'OnInventoryClosed', 49 'OnStationServicesHangarDeselected', 50 'OnCrateLootSpawned', 51 'OnTokensRedeemed', 52 'OnClientEvent_MarketItemReceived'] 53 54 def __init__(self, inventory_cache, neocom): 55 self.inventory_cache = inventory_cache 56 self.neocom = neocom 57 self._pending_OnInventoryItemUnseen = set() 58 self._pending_OnInventoryItemSeen = set() 59 sm.RegisterNotify(self) 60 self.item_storage_locations = self.build_storage_locations() 61 self.item_storages = defaultdict(dict) 62 self.tracked_item_storage = None 63 self.queued_item_changes = [] 64 self.inventory_badging_extension = UnseenItemsExtensionForExistingButton(button_data_class=UnseenInventoryItemsBtnData, get_badge_count=self.get_unseen_items_count, button_id=INVENTORY_ICON_ID) 65 self.neocom.RegisterFixedButtonExtension(self.inventory_badging_extension) 66 self.start_tracking() 67 self.update_badging() 68 self._remove_deprecated_settings_group() 69 70 def _remove_deprecated_settings_group(self): 71 settings.char.Remove(SETTINGS_GROUP_SEEN_ITEMS) 72 73 def clear(self): 74 self._disconnect_item_changes() 75 self.tracked_item_storage = None 76 self.queued_item_changes = [] 77 self.inventory_badging_extension.reset_badge_count() 78 self.inventory_badging_extension = None 79 sm.UnregisterNotify(self) 80 settings.user.ui.Delete(SETTINGS_KEY_SEEN_PLEX) 81 settings.char.Remove(SETTINGS_GROUP_UNSEEN_ITEMS) 82 83 def build_storage_locations(self): 84 return {invConst.InventoryType.SHIP: [(invConst.INVENTORY_ID_SHIP_CARGO, self.inventory_cache.GetItemsInCurrentShipCargo)], 85 invConst.InventoryType.STATION: [(invConst.INVENTORY_ID_STATION_SHIPS, self.inventory_cache.GetItemsInCurrentStationShipHangar), (invConst.INVENTORY_ID_STATION_ITEMS, self.inventory_cache.GetItemsInCurrentStationItemHangar)], 86 invConst.InventoryType.STRUCTURE: [(invConst.INVENTORY_ID_STRUCTURE_SHIPS, self.inventory_cache.GetItemsInCurrentStructureShipHangar), (invConst.INVENTORY_ID_STRUCTURE_ITEMS, self.inventory_cache.GetItemsInCurrentStructureItemHangar)]} 87 88 def _get_current_inventory(self): 89 station_id = session.stationid 90 if station_id: 91 return (invConst.InventoryType.STATION, station_id) 92 structure_id = session.structureid 93 if structure_id: 94 return (invConst.InventoryType.STRUCTURE, structure_id) 95 ship_id = session.shipid 96 if ship_id: 97 return (invConst.InventoryType.SHIP, ship_id) 98 99 def _connect_item_changes(self): 100 if not self.tracked_item_storage or not self.inventory_badging_extension: 101 return 102 _, _, item_storage = self.tracked_item_storage 103 if hasattr(item_storage, 'on_items_changed'): 104 self.inventory_badging_extension.connect_item_changes(item_storage.on_items_changed) 105 106 def _disconnect_item_changes(self): 107 if not self.tracked_item_storage or not self.inventory_badging_extension: 108 return 109 _, _, item_storage = self.tracked_item_storage 110 if hasattr(item_storage, 'on_items_changed'): 111 self.inventory_badging_extension.disconnect_item_changes(item_storage.on_items_changed) 112 113 def start_tracking(self): 114 current_inventory = self._get_current_inventory() 115 if current_inventory: 116 storage_type, storage_id = current_inventory 117 storage = self.create_storage(storage_type, storage_id) 118 storage.remove_obsolete_unseen() 119 self.tracked_item_storage = (storage_type, storage_id, storage) 120 else: 121 self.tracked_item_storage = None 122 self._connect_item_changes() 123 self.process_queued_item_changes() 124 sm.GetService('sessionMgr').RegisterNotifyOfSessionChange(self, self.stop_tracking) 125 126 def stop_tracking(self): 127 self._disconnect_item_changes() 128 self.tracked_item_storage = None 129 sm.GetService('sessionMgr').UnregisterNotifyOfSessionChange(self) 130 131 def restart_tracking(self): 132 self.stop_tracking() 133 self.start_tracking() 134 135 def is_local_change(self, storage): 136 if self.tracked_item_storage is None: 137 return False 138 _, _, tracked_storage = self.tracked_item_storage 139 return storage == tracked_storage 140 141 def create_storage(self, storage_type, storage_id): 142 if storage_id not in self.item_storages[storage_type]: 143 settings.char.CreateGroup(SETTINGS_GROUP_UNSEEN_ITEMS) 144 storage = SeenItemMultipleLocationsStorage(settings_container=settings.char.unseenInventoryItems) 145 locations = self.item_storage_locations[storage_type] 146 for location_id, item_getter in locations: 147 storage.add_location_storage(location_id=location_id, item_getter=item_getter, setting_key='%s_%s' % (location_id, storage_id)) 148 149 self.item_storages[storage_type][storage_id] = storage 150 return self.item_storages[storage_type][storage_id] 151 152 def get_tracked_item_storage(self): 153 if self.tracked_item_storage: 154 storage_type, storage_id, item_storage = self.tracked_item_storage 155 if (storage_type, storage_id) == self._get_current_inventory(): 156 return item_storage 157 158 def get_item_storage_by_location(self, location): 159 if location: 160 storage_type, storage_id, location_id = location 161 if storage_id in self.item_storages[storage_type]: 162 return self.item_storages[storage_type][storage_id] 163 if self.tracked_item_storage: 164 _, _, storage = self.tracked_item_storage 165 return storage 166 167 def get_or_create_item_storage_by_location(self, location): 168 if location: 169 storage_type, storage_id, location_id = location 170 if storage_id in self.item_storages[storage_type]: 171 return self.item_storages[storage_type][storage_id] 172 return self.create_storage(storage_type, storage_id) 173 174 def _get_item_location(self, type_id, flag_id, location_id): 175 if flag_id == invConst.flagCargo: 176 return (invConst.InventoryType.SHIP, location_id, invConst.INVENTORY_ID_SHIP_CARGO) 177 if flag_id == invConst.flagHangar: 178 is_ship = IsShip(categoryID=GetCategoryID(type_id)) 179 if IsStation(location_id): 180 if is_ship: 181 return (invConst.InventoryType.STATION, location_id, invConst.INVENTORY_ID_STATION_SHIPS) 182 return (invConst.InventoryType.STATION, location_id, invConst.INVENTORY_ID_STATION_ITEMS) 183 if sm.GetService('structureDirectory').IsStructure(location_id): 184 if is_ship: 185 return (invConst.InventoryType.STRUCTURE, location_id, invConst.INVENTORY_ID_STRUCTURE_SHIPS) 186 return (invConst.InventoryType.STRUCTURE, location_id, invConst.INVENTORY_ID_STRUCTURE_ITEMS) 187 188 def has_unseen_items(self): 189 return bool(self.get_unseen_items_count()) 190 191 def get_unseen_items_count(self): 192 item_storage = self.get_tracked_item_storage() 193 unseen_items = item_storage.unseen_count if item_storage else 0 194 return unseen_items 195 196 def is_item_unseen(self, item_id, type_id, flag_id, location_id): 197 location = self._get_item_location(type_id, flag_id, location_id) 198 item_storage = self.get_item_storage_by_location(location) 199 if item_storage: 200 return item_storage.is_unseen(item_id) 201 return False 202 203 def mark_item_seen(self, item_id, type_id, flag_id, location_id): 204 location = self._get_item_location(type_id, flag_id, location_id) 205 item_storage = self.get_item_storage_by_location(location) 206 if not item_storage: 207 return 208 item_storage.mark_as_seen(item_id) 209 sm.ScatterEvent('OnInventoryItemSeen', {item_id}) 210 if self.is_local_change(item_storage): 211 sm.ScatterEvent('OnInventoryBadgingUpdated') 212 self.neocom.BlinkOff(INVENTORY_ICON_ID) 213 214 def has_unseen_items_in_location(self, location_id): 215 if location_id == invConst.INVENTORY_ID_PLEX_VAULT: 216 return False 217 item_storage = self.get_tracked_item_storage() 218 if item_storage: 219 return item_storage.has_unseen_items_in_location(location_id) 220 return False 221 222 def mark_items_seen_in_location(self, location_id): 223 if location_id == invConst.INVENTORY_ID_PLEX_VAULT: 224 return 225 item_storage = self.get_tracked_item_storage() 226 if not item_storage: 227 return 228 item_storage.mark_seen_in_location(location_id) 229 sm.ScatterEvent('OnInventoryBadgingUpdated') 230 sm.ScatterEvent('OnInventoryLocationSeen', location_id) 231 self.neocom.BlinkOff(INVENTORY_ICON_ID) 232 233 def update_badging(self): 234 item_storage = self.get_tracked_item_storage() 235 if item_storage: 236 item_storage.update_unseen_count() 237 sm.ScatterEvent('OnInventoryBadgingUpdated') 238 239 def is_capsule_change(self, change, item): 240 return self.is_automatic_change(change) and IsCapsule(item.groupID) 241 242 def is_my_ship(self, item): 243 return item.itemID == session.shipid 244 245 def is_seen_item_id_change(self, change): 246 if ixItemID not in change: 247 return False 248 item_storage = self.get_tracked_item_storage() 249 if not item_storage: 250 return False 251 previous_item_id = change[ixItemID] 252 return previous_item_id not in item_storage.get_unseen() 253 254 def has_custom_info_attribute(self, item, name, default_value = False): 255 return item.customInfo and hasattr(item.customInfo, 'get') and item.customInfo.get(name, default_value) 256 257 def is_plex_from_vault(self, item): 258 return self.has_custom_info_attribute(item, 'isPlexFromVault') 259 260 def is_mined_ore(self, item): 261 return self.has_custom_info_attribute(item, 'isMined') 262 263 def is_ammo(self, item): 264 return self.has_custom_info_attribute(item, 'isAmmo') 265 266 def is_salvaged_item(self, item): 267 return self.has_custom_info_attribute(item, 'isSalvaged') 268 269 def is_item_empty(self, item): 270 return item.quantity == 0 and item.stacksize == 0 271 272 def is_item_removed(self, item): 273 return item.locationID == locationJunkyard 274 275 def is_item_from_space(self, change): 276 return ixLocationID in change and IsSolarSystem(change[ixLocationID]) 277 278 def is_booster_consumed(self, item): 279 return item.flagID == invConst.flagBooster 280 281 def is_item_from_my_ship(self, change, item): 282 if not session.shipid: 283 return False 284 if ixLocationID in change and change[ixLocationID] == session.shipid: 285 return True 286 try: 287 return any([ each == session.shipid for each in item.customInfo ]) 288 except Exception: 289 pass 290 291 return False 292 293 def is_automatic_change(self, change): 294 return ixLocationID in change and change[ixLocationID] == locationAbstract 295 296 def is_owner_change_from_system_to_me(self, change): 297 if ixOwnerID not in change: 298 return False 299 previous_owner_id = change[ixOwnerID] 300 return previous_owner_id == invConst.ownerSystem 301 302 def is_owner_change_from_another_player_to_me(self, change): 303 if ixOwnerID not in change: 304 return False 305 previous_owner_id = change[ixOwnerID] 306 return session.charid != previous_owner_id and bool(IsCharacter(previous_owner_id)) 307 308 def is_crate_loot(self, change): 309 if ixFlag not in change: 310 return False 311 previous_flag_id = change[ixFlag] 312 return previous_flag_id == invConst.flagCrateLoot 313 314 def is_seen_item(self, change, item): 315 if self.is_capsule_change(change, item): 316 return True 317 if self.is_my_ship(item): 318 return True 319 if self.is_plex_from_vault(item): 320 return True 321 if self.is_mined_ore(item): 322 return True 323 if self.is_ammo(item): 324 return True 325 if self.is_salvaged_item(item): 326 return True 327 if self.is_seen_item_id_change(change): 328 return True 329 if self.is_item_empty(item): 330 return True 331 if self.is_item_removed(item): 332 return True 333 if self.is_item_from_space(change): 334 return True 335 if self.is_booster_consumed(item): 336 return True 337 if self.is_item_from_my_ship(change, item): 338 return True 339 return False 340 341 def is_new_item(self, change, item): 342 if self.is_automatic_change(change): 343 return True 344 if self.is_owner_change_from_system_to_me(change): 345 return True 346 if self.is_owner_change_from_another_player_to_me(change): 347 return True 348 if self.is_crate_loot(change): 349 return True 350 return False 351 352 def OnSessionChanged(self, *args, **kwargs): 353 self.on_session_updated() 354 355 def OnSessionMutated(self, *args, **kwargs): 356 self.on_session_updated() 357 358 def on_session_updated(self): 359 if not session.IsItSafe(): 360 return 361 self.restart_tracking() 362 ship_id = session.shipid 363 if ship_id: 364 item_storage = self.get_tracked_item_storage() 365 if item_storage: 366 item_storage.mark_as_seen(ship_id) 367 sm.ScatterEvent('OnInventoryBadgingUpdated') 368 369 def is_inventory_window_open(self): 370 return Inventory.IsOpenByWindowClass() 371 372 def is_badging_triggered(self, change, item, is_local_change): 373 return not (is_local_change and self.is_inventory_window_open()) and not self.is_seen_item(change, item) and self.is_new_item(change, item) 374 375 def process_item_change(self, item, change, location): 376 storage = self.get_item_storage_by_location(location) 377 if not storage: 378 return 379 item_id = item.itemID 380 is_local_change = self.is_local_change(storage) 381 is_new_item = self.is_badging_triggered(change, item, is_local_change) 382 if is_new_item: 383 storage.mark_as_unseen_in_location(item_id, location) 384 else: 385 storage.mark_as_seen_in_location(item_id, location) 386 if is_new_item: 387 self._pending_OnInventoryItemUnseen.add(item_id) 388 self._pending_OnInventoryItemSeen.discard(item_id) 389 self.scatter_buffered_unseen_event() 390 else: 391 self._pending_OnInventoryItemSeen.add(item_id) 392 self._pending_OnInventoryItemUnseen.discard(item_id) 393 self.scatter_buffered_seen_event() 394 if is_local_change: 395 if is_new_item: 396 sm.ScatterEvent('OnInventoryBadgingUpdated') 397 else: 398 self.neocom.BlinkOff(INVENTORY_ICON_ID) 399 400 @BufferedCall(500) 401 def scatter_buffered_unseen_event(self): 402 unseen_items = self._pending_OnInventoryItemUnseen.copy() 403 self._pending_OnInventoryItemUnseen.clear() 404 if unseen_items: 405 sm.ScatterEvent('OnInventoryItemUnseen', unseen_items) 406 407 @BufferedCall(500) 408 def scatter_buffered_seen_event(self): 409 seen_items = self._pending_OnInventoryItemSeen.copy() 410 self._pending_OnInventoryItemSeen.clear() 411 if seen_items: 412 sm.ScatterEvent('OnInventoryItemSeen', seen_items) 413 414 def process_queued_item_changes(self): 415 if not self.tracked_item_storage: 416 return 417 while self.queued_item_changes: 418 self.process_item_change(*self.queued_item_changes.pop()) 419 420 def OnItemChange(self, item, change, location): 421 if self.tracked_item_storage: 422 self.process_item_change(item, change, location) 423 else: 424 self.queued_item_changes.append((item, change, location)) 425 426 def OnInventoryContainerShown(self, current_inventory_container_id, previous_inventory_container_id): 427 if previous_inventory_container_id and current_inventory_container_id != previous_inventory_container_id: 428 location_id = previous_inventory_container_id[0] 429 self.mark_items_seen_in_location(location_id) 430 431 def OnInventoryClosed(self, inventory_container_id): 432 if inventory_container_id: 433 location_id = inventory_container_id[0] 434 self.mark_items_seen_in_location(location_id) 435 436 def OnStationServicesHangarDeselected(self, location_id): 437 self.mark_items_seen_in_location(location_id) 438 439 def get_plex_balance(self): 440 try: 441 return get_repository().get_balance() 442 except Exception: 443 LogException('Failed to retrieve the PLEX balance for NEOCOM Inventory badging') 444 return 0 445 446 def show_new_items_tooltip(self): 447 call_after_wallclocktime_delay(tasklet_func=sm.GetService('uiHighlightingService').highlight_ui_element, delay=NEW_ITEMS_UI_HIGHLIGHT_DELAY_SECS, ui_highlight_id=NEW_ITEMS_UI_HIGHLIGHT_ID) 448 449 def OnCrateLootSpawned(self): 450 self.show_new_items_tooltip() 451 452 def OnTokensRedeemed(self, character_id, station_id, tokens, numItemsCreated): 453 if station_id not in (session.stationid, session.structureid): 454 return 455 if numItemsCreated: 456 self.show_new_items_tooltip() 457 458 def _get_item_location(self, type_id, flag_id, location_id): 459 if flag_id == invConst.flagCargo: 460 return (invConst.InventoryType.SHIP, location_id, invConst.INVENTORY_ID_SHIP_CARGO) 461 if flag_id == invConst.flagHangar: 462 is_ship = IsShip(categoryID=GetCategoryID(type_id)) 463 if IsStation(location_id): 464 if is_ship: 465 return (invConst.InventoryType.STATION, location_id, invConst.INVENTORY_ID_STATION_SHIPS) 466 return (invConst.InventoryType.STATION, location_id, invConst.INVENTORY_ID_STATION_ITEMS) 467 if sm.GetService('structureDirectory').IsStructure(location_id): 468 if is_ship: 469 return (invConst.InventoryType.STRUCTURE, location_id, invConst.INVENTORY_ID_STRUCTURE_SHIPS) 470 return (invConst.InventoryType.STRUCTURE, location_id, invConst.INVENTORY_ID_STRUCTURE_ITEMS) 471 472 def _is_remote_item_purchase_in_hangar(self, type_id, flag_id, station_id): 473 is_local_change = station_id and station_id in (session.stationid, session.structureid) 474 is_hangar_change = flag_id == invConst.flagHangar 475 is_plex = IsPlex(type_id) 476 return not is_local_change and is_hangar_change and not is_plex 477 478 def OnClientEvent_MarketItemReceived(self, item_id, type_id, flag_id, location_id): 479 if not self._is_remote_item_purchase_in_hangar(type_id, flag_id, location_id): 480 return 481 location = self._get_item_location(type_id, flag_id, location_id) 482 storage = self.get_or_create_item_storage_by_location(location) 483 if not storage: 484 return 485 storage.mark_as_unseen_in_location(item_id, location) 486 sm.ScatterEvent('OnInventoryItemUnseen', {item_id})