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

shipTreeSvc.py (29029B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\shipTree\shipTreeSvc.py
      2 from collections import defaultdict
      3 import evetypes
      4 import geo2
      5 import shipgroup
      6 import uthread
      7 from carbon.common.script.sys.service import Service
      8 from clonegrade.const import CLONE_STATE_ALPHA
      9 from eve.client.script.ui.shared.shipTree import shipTreeConst
     10 from eve.client.script.ui.shared.shipTree.shipTreeConst import NODETYPE_CONNECTOR, NODETYPE_GROUP, NODETYPE_OTHERFACTIONGROUP, NODETYPE_ROOT
     11 from eve.client.script.ui.control.treeData import TreeData
     12 from eve.common.lib import appConst as const
     13 from omega.client.clone_grade_svc import get_clone_grade_svc
     14 K2 = 1.0 / 2
     15 K3 = 1.0 / 3
     16 K4 = 1.0 / 4
     17 K8 = 1.0 / 8
     18 K12 = 1.0 / 12
     19 K24 = 1.0 / 24
     20 
     21 class ShipTree(Service):
     22     __guid__ = 'svc.shipTree'
     23     __servicename__ = 'Ship tree service'
     24     __displayname__ = 'Ship tree service'
     25 
     26     def Run(self, *args):
     27         self.shipTypeIDsByFactionIDAndGroupID = None
     28         self.shipGroupIDsByTypeIDs = None
     29         self._recentlyChangedSkills = None
     30 
     31     def _InitShipTypeIDDict(self):
     32         self.shipTypeIDsByFactionIDAndGroupID = defaultdict(list)
     33         for groupID in evetypes.GetGroupIDsByCategory(const.categoryShip):
     34             if not evetypes.IsGroupPublishedByGroup(groupID):
     35                 continue
     36             for typeID in evetypes.GetTypeIDsByGroup(groupID):
     37                 try:
     38                     if evetypes.IsPublished(typeID):
     39                         key = (evetypes.GetFactionID(typeID), evetypes.GetShipGroupID(typeID))
     40                         self.shipTypeIDsByFactionIDAndGroupID[key].append(typeID)
     41                 except (KeyError, AttributeError):
     42                     pass
     43 
     44     def GetShipTypeIDs(self, factionID, isisGroupID):
     45         if not self.shipTypeIDsByFactionIDAndGroupID:
     46             self._InitShipTypeIDDict()
     47         if (factionID, isisGroupID) in self.shipTypeIDsByFactionIDAndGroupID:
     48             return self.shipTypeIDsByFactionIDAndGroupID[factionID, isisGroupID]
     49         return []
     50 
     51     def GetShipTypeIDsByGroupID(self, isisGroupID):
     52         if not self.shipTypeIDsByFactionIDAndGroupID:
     53             self._InitShipTypeIDDict()
     54         results = set()
     55         for factionID, groupID in self.shipTypeIDsByFactionIDAndGroupID:
     56             if groupID == isisGroupID:
     57                 results.update(self.shipTypeIDsByFactionIDAndGroupID[factionID, isisGroupID])
     58 
     59         return results
     60 
     61     def GetShipGroupID(self, typeID):
     62         if not self.shipGroupIDsByTypeIDs:
     63             self._InitShipGroupIDDict()
     64         return self.shipGroupIDsByTypeIDs.get(typeID, None)
     65 
     66     def _InitShipGroupIDDict(self):
     67         if not self.shipTypeIDsByFactionIDAndGroupID:
     68             self._InitShipTypeIDDict()
     69         self.shipGroupIDsByTypeIDs = {}
     70         for (_, isisGroupID), typeIDs in self.shipTypeIDsByFactionIDAndGroupID.iteritems():
     71             for typeID in typeIDs:
     72                 self.shipGroupIDsByTypeIDs[typeID] = isisGroupID
     73 
     74     def GetRootNode(self, factionID):
     75         if factionID == const.factionORE:
     76             return self._GetOREData(factionID)
     77         if factionID == const.factionTriglavian:
     78             return self._GetTriglavianData(factionID)
     79         if factionID == const.factionEDENCOM:
     80             return self._GetEdenComData(factionID)
     81         if factionID == const.factionCONCORDAssembly:
     82             return self._GetConcordData(factionID)
     83         if factionID == const.factionSocietyOfConsciousThought:
     84             return self._GetSoctData(factionID)
     85         if factionID == const.factionSistersOfEVE:
     86             return self._GetSoeData(factionID)
     87         if factionID in (const.factionGuristasPirates,
     88          const.factionSanshasNation,
     89          const.factionTheBloodRaiderCovenant,
     90          const.factionAngelCartel,
     91          const.factionSerpentis,
     92          const.factionMordusLegion,
     93          const.factionDeathless):
     94             root, _ = self._GetPirateFactionData(factionID)
     95             return root
     96         return self._GetFactionData(factionID)
     97 
     98     def _GetFactionData(self, factionID):
     99 
    100         def Grp(parent, position, shipGroupID, iconsPerRow = 3):
    101             return GroupNode(parent=parent, position=position, shipGroupID=shipGroupID, iconsPerRow=iconsPerRow, factionID=factionID, treeID=factionID)
    102 
    103         def Con(parent, position):
    104             return ConnectorNode(parent=parent, position=position, treeID=factionID)
    105 
    106         root = RootNode(parent=None, position=(0, 0))
    107         c = Grp(root, (K2, 0), shipgroup.groupRookie)
    108         cIndustr = Con(c, (0, 3 * K4))
    109         c = Con(c, (3 * K4, 0))
    110         c = Con(c, (K12, -K12))
    111         c = Grp(c, (0, -K2), shipgroup.groupFrigate)
    112         c2 = Grp(c, (0, -K2), shipgroup.groupNavyFrigate, 4)
    113         c2 = Con(c2, (0, -K3))
    114         Grp(c2, (K3, -K3), shipgroup.groupInterceptor, 6)
    115         c2 = Con(c2, (0, -K2))
    116         Grp(c2, (K3, -K3), shipgroup.groupAssault, 6)
    117         c2 = Con(c2, (0, -K2))
    118         Grp(c2, (K3, -K3), shipgroup.groupCovertOps, 6)
    119         c2 = Con(c2, (0, -K2))
    120         Grp(c2, (K3, -K3), shipgroup.groupElectronicAttack, 6)
    121         c2 = Con(c2, (0, -K2))
    122         Grp(c2, (K3, -K3), shipgroup.groupLogisticsFrigates, 6)
    123         c = Grp(c, (5 * K4, 0), shipgroup.groupDestroyer)
    124         c2 = Grp(c, (0, -K2), shipgroup.groupNavyDestroyers, 4)
    125         c2 = Con(c2, (0, -K3))
    126         Grp(c2, (K3, -K3), shipgroup.groupInterdictor)
    127         c2 = Con(c2, (0, -K2))
    128         Grp(c2, (K3, -K3), shipgroup.groupCommandDestroyers)
    129         c2 = Con(c2, (0, -K2))
    130         Grp(c2, (K3, -K3), shipgroup.groupTacticalDestroyer)
    131         c = Grp(c, (5 * K4, 0), shipgroup.groupCruiser)
    132         c2 = Grp(c, (0, -K2), shipgroup.groupNavyCruiser, 4)
    133         c2 = Con(c2, (0, -K3))
    134         Grp(c2, (K3, -K3), shipgroup.groupRecon, 6)
    135         c2 = Con(c2, (0, -K2))
    136         Grp(c2, (K3, -K3), shipgroup.groupHeavyAssault, 6)
    137         c2 = Con(c2, (0, -K2))
    138         Grp(c2, (K3, -K3), shipgroup.groupHeavyInterdictor, 6)
    139         c2 = Con(c2, (0, -K2))
    140         Grp(c2, (K3, -K3), shipgroup.groupLogistics, 6)
    141         c2 = Con(c2, (0, -K2))
    142         Grp(c2, (K2, -K2), shipgroup.groupStrategicCruiser, 6)
    143         c = Grp(c, (3 * K2, 0), shipgroup.groupBattlecruiser)
    144         c2 = Grp(c, (0, -K2), shipgroup.groupNavyBattlecruiser)
    145         c2 = Con(c2, (0, -K3))
    146         Grp(c2, (K3, -K3), shipgroup.groupCommandship)
    147         c = Grp(c, (6 * K4, 0), shipgroup.groupBattleship)
    148         c2 = Grp(c, (0, -K2), shipgroup.groupNavyBattleship)
    149         c2 = Con(c2, (0, -3 * K8))
    150         Grp(c2, (K3, -K3), shipgroup.groupBlackOps)
    151         c2 = Con(c2, (0, -K2))
    152         Grp(c2, (K3, -K3), shipgroup.groupMarauder)
    153         c = Con(c, (7 * K4, 0))
    154         c2 = Grp(c, (K4, 0), shipgroup.groupDreadnought)
    155         c2 = Grp(c2, (0, -K3 * 2), shipgroup.groupNavyDreadnoughts, 4)
    156         c2 = Con(c2, (0, -K3 * 1.5))
    157         Grp(c2, (K3, -K3), shipgroup.groupLancerDreadnought, 4)
    158         c2 = Con(c, (0, -K3 * 6))
    159         Grp(c2, (K3, -K3), shipgroup.groupCarrier)
    160         c2 = Con(c2, (0, -K3 * 2.5))
    161         Grp(c2, (K3, -K3), shipgroup.groupCommandCarrier)
    162         c2 = Con(c2, (0, -K3 * 2.5))
    163         Grp(c2, (K3, -K3), shipgroup.groupTitan)
    164         c = Con(cIndustr, (K12, K12))
    165         c = Grp(c, (K2, 0), shipgroup.groupShuttle)
    166         c = Grp(c, (17 * K4, 0), shipgroup.groupIndustrial)
    167         c2 = Con(c, (0, -K3))
    168         Grp(c2, (K4, -K4), shipgroup.groupTransport)
    169         c = Grp(c, (3.0, 0), shipgroup.groupFreighter)
    170         c2 = Con(c, (0, -K8))
    171         Grp(c2, (K2, -K2), shipgroup.groupJumpFreighter)
    172         return root
    173 
    174     def _GetTriglavianData(self, factionID):
    175 
    176         def Grp(parent, position, shipGroupID, iconsPerRow = 3):
    177             return GroupNode(parent=parent, position=position, shipGroupID=shipGroupID, iconsPerRow=iconsPerRow, factionID=factionID, treeID=factionID)
    178 
    179         def Con(parent, position):
    180             return ConnectorNode(parent=parent, position=position, treeID=factionID)
    181 
    182         root = RootNode(parent=None, position=(0, 0))
    183         c = Grp(root, (K2, 0), shipgroup.groupFrigate)
    184         c2 = Con(c, (0, -K3))
    185         Grp(c2, (K3, -K3), shipgroup.groupAssault, 6)
    186         c = Grp(c, (5 * K4, 0), shipgroup.groupDestroyer)
    187         c2 = Con(c, (0, -K3))
    188         Grp(c2, (K3, -K3), shipgroup.groupCommandDestroyers)
    189         c = Grp(c, (5 * K4, 0), shipgroup.groupCruiser)
    190         c2 = Con(c, (0, -K3))
    191         Grp(c2, (K3, -K3), shipgroup.groupHeavyAssault)
    192         c2 = Con(c, (0, -K2))
    193         c2 = Con(c2, (0, -K3))
    194         Grp(c2, (K3, -K3), shipgroup.groupLogistics)
    195         c = Grp(c, (5 * K4, 0), shipgroup.groupBattlecruiser)
    196         c = Grp(c, (5 * K4, 0), shipgroup.groupBattleship)
    197         c2 = Con(c, (0, -K3))
    198         Grp(c2, (K3, -K3), shipgroup.groupMarauder)
    199         c = Con(c, (5 * K4, 0))
    200         Grp(c, (K4, 0), shipgroup.groupDreadnought)
    201         return root
    202 
    203     def _GetEdenComData(self, factionID):
    204 
    205         def Grp(parent, position, shipGroupID, iconsPerRow = 3):
    206             return GroupNode(parent=parent, position=position, shipGroupID=shipGroupID, iconsPerRow=iconsPerRow, factionID=factionID, treeID=factionID)
    207 
    208         def Con(parent, position):
    209             return ConnectorNode(parent=parent, position=position, treeID=factionID)
    210 
    211         root = RootNode(parent=None, position=(0, 0))
    212         c = Con(root, (K2, 0))
    213         cLeft = Con(c, (0, 0))
    214         cRight = Con(c, (0, 1.0))
    215         c = Grp(cLeft, (K2, 0), shipgroup.groupFrigate)
    216         c = Grp(c, (5 * K4, 0), shipgroup.groupCruiser)
    217         c = Grp(c, (5 * K4, 0), shipgroup.groupBattleship)
    218         c = Grp(cRight, (3 * K4, 0), shipgroup.groupIndustrial)
    219         c2 = Con(c, (0, -K3))
    220         c3 = Grp(c2, (K4, -K4), shipgroup.groupTransport)
    221         c = Grp(c, (4 * K2, 0), shipgroup.groupFreighter)
    222         return root
    223 
    224     def _GetOREData(self, factionID):
    225 
    226         def Grp(parent, position, shipGroupID, iconsPerRow = 3):
    227             return GroupNode(parent=parent, position=position, shipGroupID=shipGroupID, iconsPerRow=iconsPerRow, factionID=factionID, treeID=factionID)
    228 
    229         def Con(parent, position):
    230             return ConnectorNode(parent=parent, position=position, treeID=factionID)
    231 
    232         root = RootNode(parent=None, position=(0, 0))
    233         c = Con(root, (K2, 0))
    234         cLeft = Con(c, (0, 1.0))
    235         cRight = Con(c, (0, 1.0))
    236         c = Grp(c, (K2, 0), shipgroup.groupMiningFrigate)
    237         c1 = Grp(c, (0, -K2), shipgroup.groupNavyMiningFrigate, 4)
    238         c1 = Con(c1, (0, -K4))
    239         Grp(c1, (K4, -K4), shipgroup.groupExpeditionFrigate, 6)
    240         c = Grp(c, (2.5 * K2, 0), shipgroup.groupMiningDestroyer)
    241         c2 = Grp(c, (0, -K2), shipgroup.groupNavyMiningDestroyer)
    242         c2 = Con(c2, (0, -K4))
    243         Grp(c2, (K4, -K4), shipgroup.groupMiningCommandDestroyer)
    244         c = Grp(c, (1.0, 0), shipgroup.groupMiningBarge)
    245         c3 = Con(c, (0, -K4))
    246         Grp(c3, (K4, -K4), shipgroup.groupExhumers)
    247         c = Grp(cRight, (5 * K2, 0), shipgroup.groupOreIndustrial)
    248         cDown = Con(c, (0, 1.0))
    249         c4 = Grp(cDown, (4 * K2, 0), shipgroup.groupFreighter)
    250         c = Grp(c, (1.0, 0), shipgroup.groupIndustrialCommand)
    251         c = Grp(c, (1.0, 0), shipgroup.groupCapitalIndustrial)
    252         return root
    253 
    254     def _GetPirateFactionData(self, factionID):
    255 
    256         def Grp(parent, position, shipGroupID, facID, showLinksTo, iconsPerRow = 3):
    257             return GroupNode(parent=parent, position=position, shipGroupID=shipGroupID, factionID=facID, showLinksTo=showLinksTo, treeID=factionID, iconsPerRow=iconsPerRow)
    258 
    259         def OtherGrp(parent, position, shipGroupID, facID, showLinksTo, iconsPerRow = 3):
    260             return OtherFactionGroupNode(parent=parent, position=position, shipGroupID=shipGroupID, factionID=facID, showLinksTo=showLinksTo, treeID=factionID, iconsPerRow=iconsPerRow)
    261 
    262         def Con(parent, position):
    263             return ConnectorNode(parent=parent, position=position, treeID=factionID)
    264 
    265         factionID1, factionID2 = shipTreeConst.PARENT_FACTIONIDS_BY_FACTIONID[factionID]
    266         root = RootNode(parent=None, position=(0, 0))
    267         c = firstConnectionNode = Con(root, (K4, 0))
    268         shipTreeSvc = sm.GetService('shipTree')
    269         i = 0
    270         for shipGroupID in (shipgroup.groupFrigate,
    271          shipgroup.groupDestroyer,
    272          shipgroup.groupCruiser,
    273          shipgroup.groupBattlecruiser,
    274          shipgroup.groupBattleship):
    275             if shipTreeSvc.GetShipTypeIDs(factionID, shipGroupID):
    276                 x = K4 if i == 0 else 3 * K4
    277                 c = Grp(c, (x, 0), shipGroupID, factionID, False)
    278                 top = OtherGrp(root, (K2 + i * x, -0.5), shipGroupID, factionID1, True)
    279                 Grp(top, (0, 0.5), shipGroupID, factionID, False)
    280                 bottom = OtherGrp(root, (K2 + i * x, 0.5), shipGroupID, factionID2, True)
    281                 Grp(bottom, (0, -0.5), shipGroupID, factionID, False)
    282                 i += 1
    283 
    284         i = 0
    285         for shipGroupID in (shipgroup.groupDreadnought, shipgroup.groupCarrier, shipgroup.groupTitan):
    286             if shipTreeSvc.GetShipTypeIDs(factionID, shipGroupID):
    287                 h = 0.0 if i == 0 else -0.8
    288                 c = Con(c, (K2, h))
    289                 g = Grp(c, (K2, 0), shipGroupID, factionID, False)
    290                 top = OtherGrp(g, (0, -K2), shipGroupID, factionID1, True)
    291                 Grp(top, (0, K2), shipGroupID, factionID, False)
    292                 bottom = OtherGrp(g, (0, K2), shipGroupID, factionID2, True)
    293                 Grp(bottom, (0, -K2), shipGroupID, factionID, False)
    294                 i += 1
    295 
    296         return (root, firstConnectionNode)
    297 
    298     def _GetSoeData(self, factionID):
    299         root, firstConnectionNode = self._GetPirateFactionData(factionID)
    300         shipGroupID = shipgroup.groupExpeditionCommandShip
    301         if not self.GetShipTypeIDs(factionID, shipGroupID):
    302             return root
    303 
    304         def Grp(parent, position, shipGroupID, facID, showLinksTo, iconsPerRow = 3):
    305             return GroupNode(parent=parent, position=position, shipGroupID=shipGroupID, factionID=facID, showLinksTo=showLinksTo, treeID=factionID, iconsPerRow=iconsPerRow)
    306 
    307         def OtherGrp(parent, position, shipGroupID, facID, showLinksTo, iconsPerRow = 3):
    308             return OtherFactionGroupNode(parent=parent, position=position, shipGroupID=shipGroupID, factionID=facID, showLinksTo=showLinksTo, treeID=factionID, iconsPerRow=iconsPerRow)
    309 
    310         factionID1, factionID2 = shipTreeConst.PARENT_FACTIONIDS_BY_FACTIONID[factionID]
    311         c = ConnectorNode(parent=firstConnectionNode, position=(0, 1.0), treeID=factionID)
    312         c = ConnectorNode(parent=c, position=(2 * K2, 0.0), treeID=factionID)
    313         g = Grp(c, (K2, 0), shipGroupID, factionID, False)
    314         top = OtherGrp(g, (0, -K2), shipGroupID, factionID1, True)
    315         Grp(top, (0, K2), shipGroupID, factionID, False)
    316         bottom = OtherGrp(g, (0, K2), shipGroupID, factionID2, True)
    317         Grp(bottom, (0, -K2), shipGroupID, factionID, False)
    318         return root
    319 
    320     def _GetConcordData(self, factionID):
    321 
    322         def Grp(parent, position, shipGroupID, iconsPerRow = 3):
    323             return GroupNode(parent=parent, position=position, shipGroupID=shipGroupID, iconsPerRow=iconsPerRow, factionID=factionID, treeID=factionID)
    324 
    325         def Con(parent, position):
    326             return ConnectorNode(parent=parent, position=position, treeID=factionID)
    327 
    328         root = RootNode(parent=None, position=(0, 0))
    329         c = Grp(root, (K2, 0), shipgroup.groupShuttle)
    330         c = Grp(c, (2 * K2, 0), shipgroup.groupCovertOps)
    331         c = Grp(c, (5 * K4, 0), shipgroup.groupRecon)
    332         c2 = Con(c, (0, -K3))
    333         Grp(c2, (K3, -K3), shipgroup.groupFlagCruiser)
    334         c = Grp(c, (5 * K4, 0), shipgroup.groupBlackOps)
    335         return root
    336 
    337     def _GetSoctData(self, factionID):
    338 
    339         def Grp(parent, position, shipGroupID, iconsPerRow = 3):
    340             return GroupNode(parent=parent, position=position, shipGroupID=shipGroupID, iconsPerRow=iconsPerRow, factionID=factionID, treeID=factionID)
    341 
    342         root = RootNode(parent=None, position=(0, 0))
    343         c = Grp(root, (K2, 0), shipgroup.groupShuttle)
    344         c = Grp(c, (K2 * 2, 0), shipgroup.groupFrigate)
    345         c = Grp(c, (K2 * 2, 0), shipgroup.groupDestroyer)
    346         c = Grp(c, (K2 * 2, 0), shipgroup.groupBattlecruiser)
    347         c = Grp(c, (K2 * 2, 0), shipgroup.groupBattleship)
    348         return root
    349 
    350     def IsInShipTree(self, typeID):
    351         hasShipGroup = evetypes.GetShipGroupID(typeID) is not None
    352         return hasShipGroup and evetypes.GetGroupID(typeID) != const.groupCapsule
    353 
    354     def GetRecentlyChangedSkills(self):
    355         if self._recentlyChangedSkills is not None:
    356             return self._recentlyChangedSkills
    357         uthread.Lock(self, 'GetRecentlyChangedSkills')
    358         try:
    359             if self._recentlyChangedSkills is None:
    360                 self._recentlyChangedSkills = sm.GetService('skills').GetRecentlyTrainedSkills()
    361         finally:
    362             uthread.UnLock(self, 'GetRecentlyChangedSkills')
    363 
    364         return self._recentlyChangedSkills
    365 
    366     def IsShipMasteryRecentlyIncreased(self, typeID):
    367         masteryLevel = sm.GetService('certificates').GetCurrCharMasteryLevel(typeID)
    368         certs = sm.GetService('certificates').GetCertificatesForShipByMasteryLevel(typeID, masteryLevel)
    369         changed = self.GetRecentlyChangedSkills()
    370         for cert in certs:
    371             for skillTypeID, level in changed.iteritems():
    372                 if skillTypeID in cert.skills:
    373                     currCertSkillLevel = cert.skills[skillTypeID][cert.currentLevel]
    374                     if currCertSkillLevel > level:
    375                         return True
    376 
    377         return False
    378 
    379     def IsGroupsRecentlyUnlocked(self, factionID):
    380         factionData = self.GetRootNode(factionID)
    381         shipGroups = [ node for node in factionData.GetDescendants().values() if isinstance(node, GroupNode) ]
    382         for shipGroup in shipGroups:
    383             if shipGroup.IsRecentlyUnlocked():
    384                 return True
    385 
    386         return False
    387 
    388     def EmptyRecentlyChangedSkillsCache(self):
    389         self._recentlyChangedSkills = {}
    390 
    391     def FlushRecentlyChangedSkillsCache(self):
    392         self._recentlyChangedSkills = None
    393 
    394 
    395 class ShipTreeNodeBase(TreeData):
    396     __guid__ = 'shipTree.NodeBase'
    397     nodeType = None
    398 
    399     def __init__(self, treeID = None, showLinksTo = True, iconsPerRow = 3, *args, **kw):
    400         TreeData.__init__(self, *args, **kw)
    401         self._isLocked = None
    402         self._isRestricted = None
    403         self._isPathToElite = None
    404         self.showLinksTo = showLinksTo
    405         self.treeID = treeID
    406         self._boundingBox = None
    407         self._position = (0, 0)
    408         self._iconsPerRow = iconsPerRow
    409 
    410     def GetNodeType(self):
    411         return self.nodeType
    412 
    413     def GetDimensions(self):
    414         p0, p1 = self.GetBoundingBox()
    415         return geo2.Vec2Subtract(p1, p0)
    416 
    417     def GetCenter(self):
    418         p0, p1 = self.GetBoundingBox()
    419         return geo2.Vec3Lerp(p0, p1, 0.5)
    420 
    421     def GetPosition(self):
    422         return self._position
    423 
    424     def GetPositionProportional(self):
    425         rootNode = self.GetRootNode()
    426         (minX, minY), (maxX, maxY) = rootNode.GetBoundingBox()
    427         x, y = self.GetPosition()
    428         if minX == maxX:
    429             x = 0.0
    430         else:
    431             x = (x - minX) / (maxX - minX)
    432         if minY == maxY:
    433             y = 0.0
    434         else:
    435             y = (y - minY) / (maxY - minY)
    436         return (x, y)
    437 
    438     def SetPosition(self, position):
    439         self._position = position
    440 
    441     def GetBoundingBox(self):
    442         if self._boundingBox:
    443             return self._boundingBox
    444         minX, minY = self.GetPosition()
    445         maxX, maxY = self.GetPosition()
    446         children = self.GetChildren()
    447         if not children:
    448             return (self.GetPosition(), self.GetPosition())
    449         for child in self.GetChildren():
    450             (x1, y1), (x2, y2) = child.GetBoundingBox()
    451             minX = min(minX, x1, x2)
    452             minY = min(minY, y1, y2)
    453             maxX = max(maxX, x1, x2)
    454             maxY = max(maxY, y1, y2)
    455 
    456         self._boundingBox = ((minX, minY), (maxX, maxY))
    457         return self._boundingBox
    458 
    459     def GetGroupNodesByLevel(self):
    460         nodes = []
    461         self._GetGroupNodesByLevel(nodes, 0)
    462         return nodes
    463 
    464     def GetIconsPerRow(self):
    465         return self._iconsPerRow
    466 
    467     def IsAllChildrenElite(self):
    468         if not self.children:
    469             return self.IsElite()
    470         for child in self.children:
    471             if child.nodeType == NODETYPE_OTHERFACTIONGROUP:
    472                 continue
    473             if not child._IsAllChildrenElite(child):
    474                 return False
    475 
    476         return True
    477 
    478     def _IsAllChildrenElite(self, node):
    479         if not node.IsElite():
    480             return False
    481         for child in node.children:
    482             if not child._IsAllChildrenElite(child):
    483                 return False
    484 
    485         return True
    486 
    487     def IsElite(self):
    488         return True
    489 
    490     def FlushCache(self):
    491         self._isLocked = None
    492         self._isRestricted = None
    493         self._isPathToElite = None
    494         for child in self.children:
    495             child.FlushCache()
    496 
    497     def IsPathToElite(self):
    498         if self._isPathToElite is not None:
    499             return self._isPathToElite
    500         parNode = self
    501         while True:
    502             childNode = parNode
    503             parNode = parNode.parent
    504             if parNode is None or parNode.nodeType != NODETYPE_CONNECTOR:
    505                 if childNode.nodeType == NODETYPE_CONNECTOR:
    506                     self._isPathToElite = childNode.IsAllChildrenElite()
    507                 else:
    508                     self._isPathToElite = childNode.IsElite() and childNode.IsAllChildrenElite()
    509                 return self._isPathToElite
    510 
    511     def IsRestricted(self):
    512         if self._isRestricted is None:
    513             self._isRestricted = self._IsRestricted()
    514         return self._isRestricted
    515 
    516     def _IsRestricted(self):
    517         return False
    518 
    519 
    520 class GroupNode(ShipTreeNodeBase):
    521     nodeType = NODETYPE_GROUP
    522 
    523     def __init__(self, shipGroupID, factionID, position, *args, **kw):
    524         ShipTreeNodeBase.__init__(self, *args, **kw)
    525         self.shipGroupID = shipGroupID
    526         self.factionID = factionID
    527         if self.parent:
    528             self.SetPosition(geo2.Add(self.parent.GetPosition(), position))
    529         else:
    530             self.SetPosition(position)
    531 
    532     def GetID(self):
    533         return self.shipGroupID
    534 
    535     def IsLocked(self):
    536         if self._isLocked is not None:
    537             return self._isLocked
    538         mySkills = sm.GetService('skills').GetSkill
    539         for skillTypeID, data in self._LoopInfoBubbleGroupsPreReqSkills():
    540             mySkill = mySkills(skillTypeID)
    541             if mySkill is None or mySkill.effectiveSkillLevel < data['level']:
    542                 self._isLocked = True
    543                 return self._isLocked
    544 
    545         self._isLocked = False
    546         return self._isLocked
    547 
    548     def IsLockedWithoutExpertSystem(self):
    549         mySkills = sm.GetService('skills').GetSkill
    550         for skillTypeID, data in self._LoopInfoBubbleGroupsPreReqSkills():
    551             mySkill = mySkills(skillTypeID)
    552             if mySkill is None or mySkill.trainedSkillLevel < data['level']:
    553                 return True
    554 
    555         return False
    556 
    557     def IsRecentlyUnlocked(self):
    558         if self.IsLocked():
    559             return False
    560         oldSkills = sm.GetService('shipTree').GetRecentlyChangedSkills()
    561         for skillTypeID, data in self._LoopInfoBubbleGroupsPreReqSkills():
    562             myOldLevel = oldSkills.get(skillTypeID, None)
    563             if myOldLevel is not None and myOldLevel < data['level']:
    564                 return True
    565 
    566         return False
    567 
    568     def IsBeingTrained(self):
    569         skillQueue = sm.GetService('skillqueue').GetQueue()
    570         requiredSkills = self.GetRequiredSkills()
    571         for trainingRecord in skillQueue:
    572             if trainingRecord.trainingTypeID in requiredSkills:
    573                 return True
    574 
    575         return False
    576 
    577     def GetSkillLevel(self):
    578         lowest = 5
    579         skillSvc = sm.GetService('skills')
    580         for skillTypeID, _ in self._LoopInfoBubbleGroupsPreReqSkills():
    581             mySkill = skillSvc.GetSkill(skillTypeID)
    582             if not mySkill:
    583                 return 0
    584             if mySkill.skillLevel < lowest:
    585                 lowest = mySkill.skillLevel
    586 
    587         return lowest
    588 
    589     def GetRequiredSkills(self, onlyVisible = False):
    590         skillsWithLevel = {}
    591         for skillTypeID, data in self._LoopInfoBubbleGroupsPreReqSkills():
    592             if onlyVisible and not data['display']:
    593                 continue
    594             skillsWithLevel[skillTypeID] = data['level']
    595 
    596         return skillsWithLevel
    597 
    598     def GetRequiredSkillsSorted(self, onlyVisible = False):
    599 
    600         def Compare(x, y):
    601             typeID1, level1 = x
    602             typeID2, level2 = y
    603             if level1 != level2:
    604                 return level2 - level1
    605             typeName1 = evetypes.GetName(typeID1)
    606             typeName2 = evetypes.GetName(typeID2)
    607             if typeName1 > typeName2:
    608                 return 1
    609             else:
    610                 return -1
    611 
    612         skills = self.GetRequiredSkills(onlyVisible)
    613         skills = [ (typeID, level) for typeID, level in skills.iteritems() ]
    614         return sorted(skills, cmp=Compare)
    615 
    616     def _LoopInfoBubbleGroupsPreReqSkills(self):
    617         infoBubbleGroupPreReqSkills = {int(k):v for k, v in cfg.infoBubbleGroups[self.shipGroupID]['preReqSkills'].items()}
    618         if self.factionID in infoBubbleGroupPreReqSkills:
    619             skillData = {int(k):v for k, v in infoBubbleGroupPreReqSkills[self.factionID]['skills'].items()}
    620             for skillTypeID, data in skillData.iteritems():
    621                 yield (skillTypeID, data)
    622 
    623     def GetBonusSkills(self):
    624         return self.GetRequiredSkills(onlyVisible=True)
    625 
    626     def GetBonusSkillsSorted(self):
    627         return self.GetRequiredSkillsSorted(onlyVisible=True)
    628 
    629     def _GetGroupNodesByLevel(self, nodes, level):
    630         if len(nodes) <= level:
    631             nodes.append([])
    632         nodes[level].append(self)
    633         for child in self.GetChildren():
    634             child._GetGroupNodesByLevel(nodes, level + 1)
    635 
    636     def GetIconSize(self):
    637         if self.shipGroupID in (shipgroup.groupJumpFreighter, shipgroup.groupFreighter):
    638             rigSize = 4
    639         else:
    640             typeIDs = self.GetShipTypeIDs()
    641             if typeIDs:
    642                 rigSize = sm.GetService('godma').GetType(typeIDs[0]).rigSize
    643             else:
    644                 rigSize = 0
    645         return 82 + 16 * rigSize
    646 
    647     def IsElite(self):
    648         if self.IsLocked():
    649             return False
    650         for typeID in self.GetShipTypeIDs():
    651             masteryLevel = sm.GetService('certificates').GetCurrCharMasteryLevel(typeID)
    652             if masteryLevel != 5:
    653                 return False
    654 
    655         return True
    656 
    657     def GetShipTypeIDs(self):
    658         return sm.GetService('shipTree').GetShipTypeIDs(self.factionID, self.shipGroupID)
    659 
    660     def GetTimeToUnlock(self):
    661         typeIDs = self.GetShipTypeIDs()
    662         if typeIDs:
    663             return sm.GetService('skills').GetSkillTrainingTimeLeftToUseType(typeIDs[0], includeBoosters=False)
    664         return 0
    665 
    666     def GetGroupWidth(self):
    667         numIcons = min(len(self.GetShipTypeIDs()), self.GetIconsPerRow())
    668         return (self.GetIconSize() + 1) * numIcons
    669 
    670     def _IsRestricted(self):
    671         requiredSkills = self.GetRequiredSkills()
    672         for skillID, requiredLevel in requiredSkills.iteritems():
    673             if get_clone_grade_svc().get_max_skill_level(skillID, CLONE_STATE_ALPHA) < requiredLevel:
    674                 return True
    675 
    676         return super(GroupNode, self)._IsRestricted()
    677 
    678 
    679 class OtherFactionGroupNode(GroupNode):
    680     nodeType = NODETYPE_OTHERFACTIONGROUP
    681 
    682 
    683 class ConnectorNode(ShipTreeNodeBase):
    684     nodeType = NODETYPE_CONNECTOR
    685 
    686     def __init__(self, position, *args, **kw):
    687         ShipTreeNodeBase.__init__(self, *args, **kw)
    688         self._skillLevel = None
    689         if self.parent:
    690             self.SetPosition(geo2.Add(self.parent.GetPosition(), position))
    691         else:
    692             self.SetPosition(position)
    693 
    694     def GetID(self):
    695         return '%s_%s' % self.GetPosition()
    696 
    697     def IsLocked(self):
    698         if self._isLocked is not None:
    699             return self._isLocked
    700         for child in self.GetChildren():
    701             if not child.IsLocked():
    702                 self._isLocked = False
    703                 return self._isLocked
    704 
    705         self._isLocked = True
    706         return self._isLocked
    707 
    708     def GetSkillLevel(self):
    709         if self._skillLevel is not None:
    710             return self._skillLevel
    711         self._skillLevel = max([ child.GetSkillLevel() for child in self.GetChildren() ])
    712         return self._skillLevel
    713 
    714     def _GetGroupNodesByLevel(self, nodes, level):
    715         for child in self.GetChildren():
    716             child._GetGroupNodesByLevel(nodes, level)
    717 
    718     def _IsRestricted(self):
    719         return all((c.IsRestricted() for c in self.GetChildren()))
    720 
    721 
    722 class RootNode(ConnectorNode):
    723     nodeType = NODETYPE_ROOT
    724 
    725     def _IsRestricted(self):
    726         return False