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

skillExtractorController.py (21413B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\skilltrading\skillExtractorController.py
      2 import characterskills
      3 import clonegrade
      4 import collections
      5 from eve.common.lib.appConst import SKILL_TRADING_BUCKET_SIZE
      6 import signals
      7 from omega.client.clone_grade_svc import get_clone_grade_svc
      8 
      9 class SkillExtractorError(Exception):
     10     pass
     11 
     12 
     13 class NoSkillpointsError(SkillExtractorError):
     14     pass
     15 
     16 
     17 class SkillNotFoundError(SkillExtractorError):
     18     pass
     19 
     20 
     21 class SkillLockedError(SkillExtractorError):
     22     pass
     23 
     24 
     25 class NotEnoughExtractedError(SkillExtractorError):
     26     pass
     27 
     28 
     29 class GoalAlreadyReachedError(SkillExtractorError):
     30     pass
     31 
     32 
     33 class SkillNotExtractedError(SkillExtractorError):
     34     pass
     35 
     36 
     37 class SkillSourceInterface(object):
     38 
     39     def GetInjectedSkills(self):
     40         raise NotImplementedError()
     41 
     42     def GetSkillQueue(self):
     43         raise NotImplementedError()
     44 
     45     def GetSkillRestrictions(self):
     46         raise NotImplementedError()
     47 
     48     def PerformExtraction(self, itemID, skills):
     49         raise NotImplementedError()
     50 
     51 
     52 SkillRestriction = collections.namedtuple('SkillRestriction', ['level', 'reason'])
     53 RESTRICTION_JUMP_CLONE = 0
     54 RESTRICTION_PI_UPGRADE = 1
     55 RESTRICTION_PI_COLONY = 2
     56 RESTRICTION_IMPLANT = 3
     57 RESTRICTION_CLONE_STATE = 4
     58 RESTRICTION_OVER_TRAINED = 5
     59 
     60 class SkillExtractorControllerBase(SkillSourceInterface):
     61     SKILL_POINT_GOAL = SKILL_TRADING_BUCKET_SIZE
     62 
     63     def __init__(self, itemID):
     64         super(SkillExtractorControllerBase, self).__init__()
     65         self.itemID = itemID
     66         self.isCompleted = False
     67         self._skills = None
     68         self._skillsByID = None
     69         self._extracted = collections.defaultdict(list)
     70         self.onUpdate = signals.Signal(signalName='onUpdate3')
     71         self.onSkillListUpdate = signals.Signal(signalName='onSkillListUpdate')
     72         self.onSkillListUpdate.connect(self.onUpdate)
     73 
     74     @property
     75     def extractedPoints(self):
     76         return sum((sum(p) for p in self._extracted.itervalues()), 0)
     77 
     78     @property
     79     def progress(self):
     80         return self.extractedPoints / float(self.SKILL_POINT_GOAL)
     81 
     82     @property
     83     def skills(self):
     84         if self._skills is None:
     85             self._skills = self.GetInjectedSkills()
     86             for skill in self._skills:
     87                 self._UpdateSkillExtracted(skill)
     88                 self._UpdateSkillRequired(skill)
     89                 self._UpdateSkillQueued(skill)
     90                 self._UpdateSkillRestricted(skill)
     91 
     92         return self._skills
     93 
     94     @property
     95     def skillsByID(self):
     96         if self._skillsByID is None:
     97             self._skillsByID = {s.typeID:s for s in self.skills}
     98         return self._skillsByID
     99 
    100     def ExtractSkill(self, skillID):
    101         if self.extractedPoints >= self.SKILL_POINT_GOAL:
    102             raise GoalAlreadyReachedError()
    103         if skillID not in self.skillsByID:
    104             raise SkillNotFoundError()
    105         skill = self.skillsByID[skillID]
    106         if skill.IsLocked():
    107             raise SkillLockedError()
    108         clamped_points = self._GetClampedSkillPointAdjustment(skill)
    109         self._extracted[skill.typeID].append(clamped_points)
    110         self._UpdateSkillExtracted(skill)
    111         self._UpdateSkillRequired(skill)
    112         self._UpdateSkillRestricted(skill)
    113         for requiredSkillID in skill.requirements:
    114             if requiredSkillID not in self.skillsByID:
    115                 continue
    116             s = self.skillsByID[requiredSkillID]
    117             self._UpdateSkillRequired(s)
    118             s.NotifyOnUpdateIfDirty()
    119 
    120         skill.NotifyOnUpdateIfDirty()
    121         self.onUpdate()
    122 
    123     def Revert(self, skillID):
    124         if skillID not in self._extracted:
    125             raise SkillNotExtractedError()
    126         self._RevertSingleLevel(skillID)
    127         self._RevertRequirements(skillID)
    128         skill = self.skillsByID[skillID]
    129         self._UpdateSkillRestricted(skill)
    130         for skill in self.skills:
    131             skill.NotifyOnUpdateIfDirty()
    132 
    133         self.onUpdate()
    134 
    135     def _RevertRequirements(self, skillID):
    136         skill = self.skillsByID[skillID]
    137         for requiredSkillID, requiredLevel in skill.requirements.iteritems():
    138             if requiredSkillID not in self.skillsByID:
    139                 continue
    140             requiredSkill = self.skillsByID[requiredSkillID]
    141             while requiredSkill.level < requiredLevel and requiredSkillID in self._extracted:
    142                 self._RevertSingleLevel(requiredSkillID)
    143 
    144             self._RevertRequirements(requiredSkillID)
    145             self._UpdateSkillRequired(requiredSkill)
    146 
    147         self._UpdateSkillRequired(skill)
    148 
    149     def _RevertSingleLevel(self, skillID):
    150         self._extracted[skillID].pop()
    151         if len(self._extracted[skillID]) == 0:
    152             del self._extracted[skillID]
    153         skill = self.skillsByID[skillID]
    154         self._UpdateSkillExtracted(skill)
    155 
    156     def _UpdateSkillExtracted(self, skill):
    157         extractedPoints = sum(self._extracted.get(skill.typeID, []))
    158         skill.points = skill.unmodifiedPoints - extractedPoints
    159 
    160     def _UpdateSkillRequired(self, skill):
    161         required = False
    162         for dependentSkillID, level in skill.dependencies.iteritems():
    163             if dependentSkillID not in self.skillsByID:
    164                 continue
    165             isDependentQueued = dependentSkillID in (s for s, _ in self.GetSkillQueue())
    166             dependentSkillPoints = self.skillsByID[dependentSkillID].points
    167             if dependentSkillPoints == 0 and not isDependentQueued:
    168                 continue
    169             requiredPoints = characterskills.GetSPForLevelRaw(skill.rank, level)
    170             if skill.points <= requiredPoints:
    171                 required = True
    172                 break
    173 
    174         skill.isRequired = required
    175 
    176     def _UpdateSkillQueued(self, skill):
    177         queuedSkills = set((skillID for skillID, _ in self.GetSkillQueue()))
    178         skill.isQueued = skill.typeID in queuedSkills
    179 
    180     def _GetClampedSkillPointAdjustment(self, skill):
    181         adjustment = self._GetNextExtractionIncrement(skill)
    182         return self._ClampToRemainingSkillPoints(adjustment)
    183 
    184     def _GetNextExtractionIncrement(self, skill):
    185         if skill.points == 0:
    186             raise NoSkillpointsError()
    187         current_level_absolute_points = characterskills.GetSPForLevelRaw(skill.rank, skill.level)
    188         if current_level_absolute_points < skill.points:
    189             adjust_to_level = skill.level
    190         else:
    191             adjust_to_level = skill.level - 1
    192         return skill.points - characterskills.GetSPForLevelRaw(skill.rank, adjust_to_level)
    193 
    194     def _ClampToRemainingSkillPoints(self, points):
    195         remaining = self.SKILL_POINT_GOAL - self.extractedPoints
    196         return min(remaining, max(0, points))
    197 
    198     def Commit(self):
    199         if self.extractedPoints < self.SKILL_POINT_GOAL:
    200             raise NotEnoughExtractedError()
    201         skills = {sid:sum(p) for sid, p in self._extracted.iteritems()}
    202         self.PerformExtraction(self.itemID, skills)
    203         self.isCompleted = True
    204         self.onUpdate()
    205 
    206     def OnSkillTrained(self, skillID, points):
    207         if skillID not in self.skillsByID:
    208             self.Clear()
    209             return
    210         skill = self.skillsByID[skillID]
    211         if points == -1:
    212             self._CheckAndRemoveExtracted(skill)
    213             self.Clear()
    214         else:
    215             skill.points = points
    216             skill._unmodifiedPoints = points
    217             wasExtracted = self._CheckAndRemoveExtracted(skill)
    218             if wasExtracted:
    219                 self.onUpdate()
    220             skill.NotifyOnUpdateIfDirty()
    221 
    222     def Clear(self):
    223         self._skills = None
    224         self._skillsByID = None
    225         self.onSkillListUpdate()
    226 
    227     def OnSkillQueueUpdated(self):
    228         extractedBefore = self.extractedPoints
    229         for skill in self.skills:
    230             self._UpdateSkillQueued(skill)
    231 
    232         queuedSkills = [ self.skillsByID[sid] for sid, _ in self.GetSkillQueue() ]
    233         for skill in queuedSkills:
    234             self._CheckAndRemoveExtracted(skill)
    235             self._RevertRequirements(skill.typeID)
    236 
    237         for skill in self.skills:
    238             self._UpdateSkillRequired(skill)
    239             skill.NotifyOnUpdateIfDirty()
    240 
    241         if extractedBefore != self.extractedPoints:
    242             self.onUpdate()
    243 
    244     def _CheckAndRemoveExtracted(self, skill):
    245         isExtracted = skill.typeID in self._extracted
    246         if isExtracted:
    247             del self._extracted[skill.typeID]
    248             self._UpdateSkillExtracted(skill)
    249             self._UpdateSkillRestricted(skill)
    250         return isExtracted
    251 
    252     def Close(self):
    253         self.onSkillListUpdate.clear()
    254         self.onUpdate.clear()
    255         if self._skills is not None:
    256             for skill in self._skills:
    257                 skill.onUpdate.clear()
    258 
    259     def OnSkillRestrictionsUpdated(self):
    260         for skill in self.skills:
    261             self._UpdateSkillRestricted(skill)
    262 
    263         extractedBefore = self.extractedPoints
    264         restrictions = self.GetSkillRestrictions()
    265         for skillID, restriction in restrictions.iteritems():
    266             if skillID not in self.skillsByID:
    267                 continue
    268             skill = self.skillsByID[skillID]
    269             self._RevertToLevel(skill, restriction.level)
    270             self._RevertRequirements(skillID)
    271             skill.NotifyOnUpdateIfDirty()
    272 
    273         for skill in self.skills:
    274             skill.NotifyOnUpdateIfDirty()
    275 
    276         if self.extractedPoints != extractedBefore:
    277             self.onUpdate()
    278 
    279     def _RevertToLevel(self, skill, level):
    280         while skill.level < level and skill.typeID in self._extracted:
    281             self._extracted[skill.typeID].pop()
    282             if len(self._extracted[skill.typeID]) == 0:
    283                 del self._extracted[skill.typeID]
    284             self._UpdateSkillExtracted(skill)
    285 
    286     def _UpdateSkillRestricted(self, skill):
    287         restrictions = self.GetSkillRestrictions()
    288         if skill.typeID not in restrictions:
    289             skill.isRestricted = False
    290         else:
    291             restrictedLevel = restrictions[skill.typeID].level
    292             if restrictedLevel == characterskills.MAX_SKILL_LEVEL:
    293                 restrictedPoints = skill.points
    294             else:
    295                 restrictedPoints = characterskills.GetSPForLevelRaw(skill.rank, restrictedLevel)
    296             skill.isRestricted = skill.points <= restrictedPoints
    297             skill.restrictionReason = restrictions[skill.typeID].reason
    298 
    299 
    300 class SkillStaticDataInterface(object):
    301 
    302     def GetRequirements(self):
    303         raise NotImplementedError()
    304 
    305     def GetDependencies(self):
    306         raise NotImplementedError()
    307 
    308     def GetRank(self):
    309         raise NotImplementedError()
    310 
    311 
    312 class SkillBase(SkillStaticDataInterface):
    313 
    314     def __init__(self, typeID, points):
    315         super(SkillBase, self).__init__()
    316         self._typeID = typeID
    317         self._points = points
    318         self._unmodifiedPoints = points
    319         self._isRequired = False
    320         self._isQueued = False
    321         self._isRestricted = False
    322         self._restrictionReason = None
    323         self._rank = None
    324         self._level = None
    325         self._dirty = False
    326         self.onUpdate = signals.Signal(signalName='onUpdate4')
    327 
    328     @property
    329     def typeID(self):
    330         return self._typeID
    331 
    332     @property
    333     def points(self):
    334         return self._points
    335 
    336     @points.setter
    337     def points(self, points):
    338         self._dirty |= points != self._points
    339         self._points = points
    340         self._level = None
    341 
    342     @property
    343     def unmodifiedPoints(self):
    344         return self._unmodifiedPoints
    345 
    346     @property
    347     def progress(self):
    348         return self._CalculateProgress(self.points)
    349 
    350     @property
    351     def unmodifiedProgress(self):
    352         return self._CalculateProgress(self.unmodifiedPoints)
    353 
    354     def _CalculateProgress(self, points):
    355         level = characterskills.GetSkillLevelRaw(points, self.rank)
    356         if level == 5:
    357             return 0.0
    358         last_level_points = characterskills.GetSPForLevelRaw(self.rank, level)
    359         next_level_points = characterskills.GetSPForLevelRaw(self.rank, level + 1)
    360         current_level_points = points - last_level_points
    361         return current_level_points / float(next_level_points - last_level_points)
    362 
    363     @property
    364     def level(self):
    365         if self._level is None:
    366             self._level = characterskills.GetSkillLevelRaw(self.points, self.rank)
    367         return self._level
    368 
    369     @property
    370     def unmodifiedLevel(self):
    371         return characterskills.GetSkillLevelRaw(self.unmodifiedPoints, self.rank)
    372 
    373     @property
    374     def rank(self):
    375         if self._rank is None:
    376             self._rank = self.GetRank()
    377         return self._rank
    378 
    379     @property
    380     def requirements(self):
    381         return self.GetRequirements()
    382 
    383     @property
    384     def dependencies(self):
    385         return self.GetDependencies()
    386 
    387     @property
    388     def isRequired(self):
    389         return self._isRequired
    390 
    391     @isRequired.setter
    392     def isRequired(self, required):
    393         self._dirty |= required != self._isRequired
    394         self._isRequired = required
    395 
    396     @property
    397     def isQueued(self):
    398         return self._isQueued
    399 
    400     @isQueued.setter
    401     def isQueued(self, queued):
    402         self._dirty |= queued != self._isQueued
    403         self._isQueued = queued
    404 
    405     @property
    406     def isRestricted(self):
    407         return self._isRestricted
    408 
    409     @isRestricted.setter
    410     def isRestricted(self, restricted):
    411         self._dirty |= restricted != self._isRestricted
    412         self._isRestricted = restricted
    413 
    414     @property
    415     def restrictionReason(self):
    416         return self._restrictionReason
    417 
    418     @restrictionReason.setter
    419     def restrictionReason(self, reason):
    420         self._dirty |= reason != self._restrictionReason
    421         self._restrictionReason = reason
    422 
    423     def NotifyOnUpdateIfDirty(self):
    424         if self._dirty:
    425             self._dirty = False
    426             self.onUpdate()
    427 
    428     def IsLocked(self):
    429         return self.isRequired or self.isQueued or self.isRestricted
    430 
    431 
    432 class SkillServiceStaticDataInterface(SkillStaticDataInterface):
    433 
    434     def GetRequirements(self):
    435         return sm.GetService('skills').GetRequiredSkills(self.typeID)
    436 
    437     def GetDependencies(self):
    438         return sm.GetService('skills').GetDependentSkills(self.typeID)
    439 
    440     def GetRank(self):
    441         return sm.GetService('skills').GetSkillRank(self.typeID)
    442 
    443 
    444 class SkillImpl(SkillBase, SkillServiceStaticDataInterface):
    445     pass
    446 
    447 
    448 class SkillServiceSkillSource(SkillSourceInterface):
    449 
    450     def GetInjectedSkills(self):
    451         raw_skills = sm.GetService('skills').GetSkillsIncludingLapsed().values()
    452         return [ SkillImpl(typeID=s.typeID, points=s.trainedSkillPoints) for s in raw_skills if s.trainedSkillPoints is not None ]
    453 
    454     def GetSkillQueue(self):
    455         queue = sm.GetService('skillqueue').GetQueue()
    456         return [ (x.trainingTypeID, x.trainingToLevel) for x in queue ]
    457 
    458     def GetSkillRestrictions(self):
    459         restrictions = {}
    460 
    461         def updateRestrictions(new_restrictions):
    462             for sid, r in new_restrictions.iteritems():
    463                 if sid not in restrictions or r.level > restrictions[sid].level:
    464                     restrictions[sid] = r
    465 
    466         updateRestrictions(self._GetRestrictedAlphaSkills())
    467         updateRestrictions(self._GetRestrictedPlanetSkills())
    468         updateRestrictions(self._GetRestrictedCloneSkills())
    469         updateRestrictions(self._GetRestrictedImplantSkills())
    470         updateRestrictions(self._GetOverTrainedSkills())
    471         return restrictions
    472 
    473     def _GetRestrictedPlanetSkills(self):
    474         restrictions = {}
    475         colonies = sm.GetService('planetSvc').GetMyPlanets()
    476         if len(colonies) > 0:
    477             colonySkillLimit = max(len(colonies) - 1, 0)
    478             restriction = SkillRestriction(colonySkillLimit, RESTRICTION_PI_COLONY)
    479             restrictions[const.typeInterplanetaryConsolidation] = restriction
    480         maxUpgradeLevel = 0
    481         for planetID in (c.planetID for c in colonies):
    482             planet = sm.GetService('planetSvc').GetPlanet(planetID)
    483             colonyLevel = planet.GetCommandCenterLevel(session.charid)
    484             maxUpgradeLevel = max(colonyLevel, maxUpgradeLevel)
    485 
    486         if maxUpgradeLevel > 0:
    487             restriction = SkillRestriction(maxUpgradeLevel, RESTRICTION_PI_UPGRADE)
    488             restrictions[const.typeCommandCenterUpgrade] = restriction
    489         return restrictions
    490 
    491     def _GetRestrictedCloneSkills(self):
    492         restrictions = {}
    493         cloneCount = len(sm.GetService('clonejump').GetClones())
    494         if cloneCount > 0:
    495             restriction = SkillRestriction(min(cloneCount, 5), RESTRICTION_JUMP_CLONE)
    496             restrictions[const.typeInfomorphPsychology] = restriction
    497         if cloneCount > 5:
    498             restriction = SkillRestriction(cloneCount - 5, RESTRICTION_JUMP_CLONE)
    499             restrictions[const.typeAdvancedInfomorphPsychology] = restriction
    500         if cloneCount > 10:
    501             restriction = SkillRestriction(cloneCount - 10, RESTRICTION_JUMP_CLONE)
    502             restrictions[const.typeEliteInfomorphPsychology] = restriction
    503         return restrictions
    504 
    505     def _GetRestrictedImplantSkills(self):
    506         restrictions = {}
    507         implants = self._GetAllMyImplants()
    508         for typeID in implants:
    509             requirements = sm.GetService('skills').GetRequiredSkillsRecursive(typeID)
    510             for requiredSkillID, requiredLevel in requirements.iteritems():
    511                 if requiredSkillID in restrictions:
    512                     maxRequiredLevel = max(requiredLevel, restrictions[requiredSkillID].level)
    513                 else:
    514                     maxRequiredLevel = requiredLevel
    515                 restriction = SkillRestriction(maxRequiredLevel, RESTRICTION_IMPLANT)
    516                 restrictions[requiredSkillID] = restriction
    517 
    518         return restrictions
    519 
    520     def _GetRestrictedAlphaSkills(self):
    521         if get_clone_grade_svc().is_omega_with_grace_period():
    522             return {}
    523         restrictions = {}
    524         alpha = clonegrade.CloneGradeStorage()[session.raceID]
    525         for skillID, level in alpha.skills:
    526             restrictions[skillID] = SkillRestriction(level, RESTRICTION_CLONE_STATE)
    527 
    528         return restrictions
    529 
    530     def _GetAllMyImplants(self):
    531         implants = set()
    532         currentImplants = sm.GetService('skills').GetImplants().values()
    533         implants.update((i.typeID for i in currentImplants))
    534         jumpCloneImplants = sm.GetService('clonejump').GetCloneImplants()
    535         implants.update((i.typeID for i in jumpCloneImplants or []))
    536         return implants
    537 
    538     def _GetOverTrainedSkills(self):
    539         restrictions = {}
    540         skills = sm.GetService('skills').GetSkillsIncludingLapsed().values()
    541         for skill in skills:
    542             maxPoints = characterskills.GetSPForLevelRaw(skill.skillRank, characterskills.MAX_SKILL_LEVEL)
    543             if skill.trainedSkillPoints > maxPoints:
    544                 restrictions[skill.typeID] = SkillRestriction(characterskills.MAX_SKILL_LEVEL, RESTRICTION_OVER_TRAINED)
    545 
    546         return restrictions
    547 
    548     def PerformExtraction(self, itemID, skills):
    549         sm.GetService('skills').ExtractSkills(skills, itemID)
    550 
    551 
    552 class SkillExtractorController(SkillExtractorControllerBase, SkillServiceSkillSource):
    553     __notifyevents__ = ['OnCloneJumpUpdate',
    554      'OnImplantsChanged',
    555      'OnNewSkillQueueSaved',
    556      'OnPlanetChangesSubmitted',
    557      'OnPlanetCommandCenterDeployedOrRemoved',
    558      'OnSkillsChanged']
    559 
    560     def __init__(self, *args, **kwargs):
    561         super(SkillExtractorController, self).__init__(*args, **kwargs)
    562         self._cached_queue = None
    563         self._cached_restrictions = None
    564         sm.RegisterNotify(self)
    565 
    566     def Close(self):
    567         super(SkillExtractorController, self).Close()
    568         sm.UnregisterNotify(self)
    569 
    570     def GetSkillQueue(self):
    571         if self._cached_queue is None:
    572             self._cached_queue = super(SkillExtractorController, self).GetSkillQueue()
    573         return self._cached_queue
    574 
    575     def GetSkillRestrictions(self):
    576         if self._cached_restrictions is None:
    577             self._cached_restrictions = super(SkillExtractorController, self).GetSkillRestrictions()
    578         return self._cached_restrictions
    579 
    580     def PerformExtraction(self, itemID, skills):
    581         super(SkillExtractorController, self).PerformExtraction(itemID, skills)
    582         sm.UnregisterNotify(self)
    583 
    584     def OnCloneJumpUpdate(self):
    585         self._cached_restrictions = None
    586         self.OnSkillRestrictionsUpdated()
    587 
    588     def OnImplantsChanged(self):
    589         self._cached_restrictions = None
    590         self.OnSkillRestrictionsUpdated()
    591 
    592     def OnNewSkillQueueSaved(self, newQueue):
    593         self._cached_queue = None
    594         self.OnSkillQueueUpdated()
    595 
    596     def OnPlanetChangesSubmitted(self, planetID):
    597         self._cached_restrictions = None
    598         self.OnSkillRestrictionsUpdated()
    599 
    600     def OnPlanetCommandCenterDeployedOrRemoved(self):
    601         self._cached_restrictions = None
    602         self.OnSkillRestrictionsUpdated()
    603 
    604     def OnSkillsChanged(self, skillInfo):
    605         for skillID, info in skillInfo.iteritems():
    606             self.OnSkillTrained(skillID, info.trainedSkillPoints)
    607 
    608 
    609 def __SakeReloadHook():
    610     import log
    611     try:
    612         from eve.client.script.ui.skilltrading.test import test_skillExtractorController
    613         import unittest
    614         import sys
    615         suite = unittest.TestLoader().loadTestsFromModule(test_skillExtractorController)
    616         unittest.TextTestRunner(stream=sys.stdout, verbosity=1).run(suite)
    617     except:
    618         log.LogException()