NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

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})