Пример #1
0
 def __init__(self):
     self.__progress = QuestsProgressRequester()
     self.__waitForSync = False
     self.__invalidateCbID = None
     self.__cache = defaultdict(dict)
     self.__actionsCache = defaultdict(lambda: defaultdict(dict))
     self.__questsDossierBonuses = defaultdict(set)
     self.__potapov = PQController(self)
     self.__companies = CompanyBattleController(self)
     self.onSyncStarted = Event()
     self.onSyncCompleted = Event()
Пример #2
0
 def __init__(self):
     self.__progress = QuestsProgressRequester()
     self.__waitForSync = False
     self.__invalidateCbID = None
     self.__cache = defaultdict(dict)
     self.__actionsCache = defaultdict(lambda : defaultdict(dict))
     self.__questsDossierBonuses = defaultdict(set)
     self.__potapov = PQController(self)
     self.__companies = CompanyBattleController(self)
     self.onSyncStarted = Event()
     self.onSyncCompleted = Event()
Пример #3
0
 def __init__(self):
     self.__waitForSync = False
     self.__invalidateCbID = None
     self.__cache = defaultdict(dict)
     self.__potapovHidden = {}
     self.__actionsCache = defaultdict(lambda: defaultdict(dict))
     self.__questsDossierBonuses = defaultdict(set)
     self.__random = RandomPQController()
     self.__fallout = FalloutPQController()
     self.__potapovComposer = _PotapovComposer(self.__random,
                                               self.__fallout)
     self.__questsProgress = QuestsProgressRequester()
     self.__companies = CompanyBattleController(self)
     self.__em = EventManager()
     self.onSyncStarted = Event(self.__em)
     self.onSyncCompleted = Event(self.__em)
     self.onSelectedQuestsChanged = Event(self.__em)
     self.onSlotsCountChanged = Event(self.__em)
     self.onProgressUpdated = Event(self.__em)
     self.__lockedQuestIds = {}
     return
Пример #4
0
 def __init__(self):
     self.__waitForSync = False
     self.__invalidateCbID = None
     self.__cache = defaultdict(dict)
     self.__potapovHidden = {}
     self.__actionsCache = defaultdict(lambda : defaultdict(dict))
     self.__questsDossierBonuses = defaultdict(set)
     self.__random = RandomPQController()
     self.__fallout = FalloutPQController()
     self.__potapovComposer = _PotapovComposer(self.__random, self.__fallout)
     self.__questsProgress = QuestsProgressRequester()
     self.__companies = CompanyBattleController(self)
     self.__em = EventManager()
     self.onSyncStarted = Event(self.__em)
     self.onSyncCompleted = Event(self.__em)
     self.onSelectedQuestsChanged = Event(self.__em)
     self.onSlotsCountChanged = Event(self.__em)
     self.onProgressUpdated = Event(self.__em)
     return
Пример #5
0
class _EventsCache(object):
    USER_QUESTS = (EVENT_TYPE.BATTLE_QUEST,
     EVENT_TYPE.TOKEN_QUEST,
     EVENT_TYPE.FORT_QUEST,
     EVENT_TYPE.PERSONAL_QUEST,
     EVENT_TYPE.POTAPOV_QUEST)
    SYSTEM_QUESTS = (EVENT_TYPE.REF_SYSTEM_QUEST,)

    def __init__(self):
        self.__waitForSync = False
        self.__invalidateCbID = None
        self.__cache = defaultdict(dict)
        self.__potapovHidden = {}
        self.__actionsCache = defaultdict(lambda : defaultdict(dict))
        self.__questsDossierBonuses = defaultdict(set)
        self.__random = RandomPQController()
        self.__fallout = FalloutPQController()
        self.__potapovComposer = _PotapovComposer(self.__random, self.__fallout)
        self.__questsProgress = QuestsProgressRequester()
        self.__companies = CompanyBattleController(self)
        self.__em = EventManager()
        self.onSyncStarted = Event(self.__em)
        self.onSyncCompleted = Event(self.__em)
        self.onSelectedQuestsChanged = Event(self.__em)
        self.onSlotsCountChanged = Event(self.__em)
        self.onProgressUpdated = Event(self.__em)
        return

    def init(self):
        self.__random.init()
        self.__fallout.init()

    def fini(self):
        self.__fallout.fini()
        self.__random.fini()
        self.__em.clear()
        self.__clearInvalidateCallback()

    def start(self):
        self.__companies.start()

    def stop(self):
        self.__companies.stop()

    def clear(self):
        self.stop()
        quests_caches.clearNavInfo()

    @property
    def waitForSync(self):
        return self.__waitForSync

    @property
    def falloutQuestsProgress(self):
        return self.__fallout.questsProgress

    @property
    def randomQuestsProgress(self):
        return self.__random.questsProgress

    @property
    def random(self):
        return self.__random

    @property
    def fallout(self):
        return self.__fallout

    @property
    def questsProgress(self):
        return self.__questsProgress

    @property
    def potapov(self):
        return self.__potapovComposer

    @property
    def companies(self):
        return self.__companies

    @async
    @process
    def update(self, diff = None, callback = None):
        if diff is not None:
            if diff.get('eventsData', {}).get(EVENT_CLIENT_DATA.INGAME_EVENTS):
                self.__companies.setNotificators()
        yield self.falloutQuestsProgress.request()
        yield self.randomQuestsProgress.request()
        yield self.__questsProgress.request()
        isNeedToInvalidate = True
        isNeedToClearItemsCaches = False

        def _cbWrapper(*args):
            self.__random.update(self, diff)
            self.__fallout.update(self, diff)
            callback(*args)

        if diff is not None:
            isQPUpdated = 'quests' in diff
            isEventsDataUpdated = ('eventsData', '_r') in diff or diff.get('eventsData', {})
            isNeedToInvalidate = isQPUpdated or isEventsDataUpdated
            hasVehicleUnlocks = False
            for intCD in diff.get('stats', {}).get('unlocks', set()):
                if getTypeOfCompactDescr(intCD) == GUI_ITEM_TYPE.VEHICLE:
                    hasVehicleUnlocks = True
                    break

            isNeedToClearItemsCaches = 'inventory' in diff and GUI_ITEM_TYPE.VEHICLE in diff['inventory'] or hasVehicleUnlocks
        if isNeedToInvalidate:
            self.__invalidateData(_cbWrapper)
            return
        else:
            if isNeedToClearItemsCaches:
                self.__clearQuestsItemsCache()
            _cbWrapper(True)
            return

    def getQuests(self, filterFunc = None):
        filterFunc = filterFunc or (lambda a: True)

        def userFilterFunc(q):
            return not q.isHidden() and filterFunc(q)

        return self._getQuests(userFilterFunc)

    def getGroups(self, filterFunc = None):
        svrGroups = self._getQuestsGroups(filterFunc)
        svrGroups.update(self._getActionsGroups(filterFunc))
        return svrGroups

    def getHiddenQuests(self, filterFunc = None):
        filterFunc = filterFunc or (lambda a: True)

        def hiddenFilterFunc(q):
            return q.isHidden() and filterFunc(q)

        return self._getQuests(hiddenFilterFunc)

    def getAllQuests(self, filterFunc = None, includePotapovQuests = False):
        return self._getQuests(filterFunc, includePotapovQuests)

    def getActions(self, filterFunc = None):
        filterFunc = filterFunc or (lambda a: True)

        def userFilterFunc(q):
            return filterFunc(q) and q.getType() != EVENT_TYPE.GROUP

        return self._getActions(userFilterFunc)

    def getEventBattles(self):
        battles = self.__getEventBattles()
        if len(battles):
            return EventBattles(battles.get('vehicleTags', set()), battles.get('vehicles', []), bool(battles.get('enabled', 0)), battles.get('arenaTypeID'))
        else:
            return EventBattles(set(), [], 0, None)
            return None

    def isEventEnabled(self):
        return len(self.__getEventBattles()) > 0

    def getEventVehicles(self):
        from gui.shared import g_itemsCache
        result = []
        for v in g_eventsCache.getEventBattles().vehicles:
            item = g_itemsCache.items.getItemByCD(v)
            if item.isInInventory:
                result.append(item)

        return sorted(result)

    def getEvents(self, filterFunc = None):
        svrEvents = self.getQuests(filterFunc)
        svrEvents.update(self.getActions(filterFunc))
        return svrEvents

    def getCurrentEvents(self):
        return self.getEvents(lambda q: q.getStartTimeLeft() <= 0 < q.getFinishTimeLeft())

    def getFutureEvents(self):
        return self.getEvents(lambda q: q.getStartTimeLeft() > 0)

    def getCompanyBattles(self):
        battle = self.__getCompanyBattlesData()
        startTime = battle.get('startTime', 0.0)
        finishTime = battle.get('finishTime', 0.0)
        return CompanyBattles(startTime=None if startTime is None else float(startTime), finishTime=None if finishTime is None else float(finishTime), peripheryIDs=battle.get('peripheryIDs', set()))

    def getFalloutConfig(self, battleType):
        return makeTupleByDict(FalloutConfig, self.__getFallout().get(battleType, {}))

    def getItemAction(self, item, isBuying = True, forCredits = False):
        result = []
        type = ACTION_MODIFIER_TYPE.DISCOUNT if isBuying else ACTION_MODIFIER_TYPE.SELLING
        itemTypeID = item.itemTypeID
        nationID = item.nationID
        intCD = item.intCD
        values = self.__actionsCache[ACTION_SECTION_TYPE.ALL][type].get(itemTypeID, {}).get(nationID, [])
        values += self.__actionsCache[ACTION_SECTION_TYPE.ALL][type].get(itemTypeID, {}).get(15, [])
        for (key, value), actionID in values:
            if item.isPremium and key in ('creditsPrice', 'creditsPriceMultiplier') and not forCredits:
                continue
            result.append((value, actionID))

        result.extend(self.__actionsCache[ACTION_SECTION_TYPE.ITEM][type].get(itemTypeID, {}).get(intCD, tuple()))
        return result

    def getRentAction(self, item, rentPackage):
        result = []
        type = ACTION_MODIFIER_TYPE.RENT
        itemTypeID = item.itemTypeID
        nationID = item.nationID
        intCD = item.intCD
        values = self.__actionsCache[ACTION_SECTION_TYPE.ALL][type].get(itemTypeID, {}).get(nationID, [])
        values += self.__actionsCache[ACTION_SECTION_TYPE.ALL][type].get(itemTypeID, {}).get(15, [])
        for (key, value), actionID in values:
            result.append((value, actionID))

        result.extend(self.__actionsCache[ACTION_SECTION_TYPE.ITEM][type].get(itemTypeID, {}).get((intCD, rentPackage), tuple()))
        return result

    def getEconomicsAction(self, name):
        result = self.__actionsCache[ACTION_SECTION_TYPE.ECONOMICS][ACTION_MODIFIER_TYPE.DISCOUNT].get(name, [])
        resultMult = self.__actionsCache[ACTION_SECTION_TYPE.ECONOMICS][ACTION_MODIFIER_TYPE.DISCOUNT].get('%sMultiplier' % name, [])
        return tuple(result + resultMult)

    def getCamouflageAction(self, vehicleIntCD):
        return tuple(self.__actionsCache[ACTION_SECTION_TYPE.CUSTOMIZATION][ACTION_MODIFIER_TYPE.DISCOUNT].get(vehicleIntCD, tuple()))

    def getEmblemsAction(self, group):
        return tuple(self.__actionsCache[ACTION_SECTION_TYPE.CUSTOMIZATION][ACTION_MODIFIER_TYPE.DISCOUNT].get(group, tuple()))

    def getQuestsDossierBonuses(self):
        return self.__questsDossierBonuses

    def getQuestsByTokenRequirement(self, token):
        result = []
        for q in self._getQuests(includePotapovQuests=True).itervalues():
            if token in map(lambda t: t.getID(), q.accountReqs.getTokens()):
                result.append(q)

        return result

    def getQuestsByTokenBonus(self, token):
        result = []
        for q in self._getQuests(includePotapovQuests=True).itervalues():
            for t in q.getBonuses('tokens'):
                if token in t.getTokens().keys():
                    result.append(q)
                    break

        return result

    def _getQuests(self, filterFunc = None, includePotapovQuests = False):
        result = {}
        groups = {}
        filterFunc = filterFunc or (lambda a: True)
        for qID, q in self.__getCommonQuestsIterator():
            if q.getType() == EVENT_TYPE.GROUP:
                groups[qID] = q
                continue
            if q.getDestroyingTimeLeft() <= 0:
                continue
            if not filterFunc(q):
                continue
            result[qID] = q

        if includePotapovQuests:
            for qID, q in self.potapov.getQuests().iteritems():
                if filterFunc(q):
                    result[qID] = q

        for gID, group in groups.iteritems():
            for qID in group.getGroupEvents():
                if qID in result:
                    result[qID].setGroupID(gID)

        children, parents = self._makeQuestsRelations(result)
        for qID, q in result.iteritems():
            if qID in children:
                q.setChildren(children[qID])
            if qID in parents:
                q.setParents(parents[qID])

        return result

    def _getQuestsGroups(self, filterFunc = None):
        filterFunc = filterFunc or (lambda a: True)
        result = {}
        for qID, q in self.__getCommonQuestsIterator():
            if q.getType() != EVENT_TYPE.GROUP:
                continue
            if not filterFunc(q):
                continue
            result[qID] = q

        return result

    def _getActions(self, filterFunc = None):
        filterFunc = filterFunc or (lambda a: True)
        actions = self.__getActionsData()
        result = {}
        groups = {}
        for aData in actions:
            if 'id' in aData:
                a = self._makeAction(aData['id'], aData)
                actionID = a.getID()
                if a.getType() == EVENT_TYPE.GROUP:
                    groups[actionID] = a
                    continue
                if not filterFunc(a):
                    continue
                result[actionID] = a

        for gID, group in groups.iteritems():
            for aID in group.getGroupEvents():
                if aID in result:
                    result[aID].setGroupID(gID)

        return result

    def _getActionsGroups(self, filterFunc = None):
        actions = self.__getActionsData()
        filterFunc = filterFunc or (lambda a: True)
        result = {}
        for aData in actions:
            if 'id' in aData:
                a = self._makeAction(aData['id'], aData)
                if a.getType() != EVENT_TYPE.GROUP:
                    continue
                if not filterFunc(a):
                    continue
                result[a.getID()] = a

        return result

    def _onResync(self, *args):
        self.__invalidateData()

    def _makeQuest(self, qID, qData, maker = _defaultQuestMaker, **kwargs):
        storage = self.__cache['quests']
        if qID in storage:
            return storage[qID]
        q = storage[qID] = maker(qID, qData, self.__questsProgress, **kwargs)
        return q

    def _makeAction(self, aID, aData):
        storage = self.__cache['actions']
        if aID in storage:
            return storage[aID]
        a = storage[aID] = createAction(aData.get('type', 0), aID, aData)
        return a

    @classmethod
    def _makeQuestsRelations(cls, quests):
        makeTokens = defaultdict(list)
        needTokens = defaultdict(list)
        for qID, q in quests.iteritems():
            if q.getType() != EVENT_TYPE.GROUP:
                tokens = q.getBonuses('tokens')
                if len(tokens):
                    for t in tokens[0].getTokens():
                        makeTokens[t].append(qID)

                for t in q.accountReqs.getTokens():
                    needTokens[qID].append(t.getID())

        children = defaultdict(dict)
        for parentID, tokensIDs in needTokens.iteritems():
            for tokenID in tokensIDs:
                children[parentID][tokenID] = makeTokens.get(tokenID, [])

        parents = defaultdict(dict)
        for parentID, tokens in children.iteritems():
            for tokenID, chn in tokens.iteritems():
                for childID in chn:
                    parents[childID][tokenID] = [parentID]

        return (children, parents)

    def __invalidateData(self, callback = lambda *args: None):
        self.__clearCache()
        self.__clearInvalidateCallback()
        self.__waitForSync = True
        self.onSyncStarted()

        def mergeValues(a, b):
            result = list(a)
            result.extend(b)
            return result

        for action in self.getActions().itervalues():
            for modifier in action.getModifiers():
                section = modifier.getSection()
                type = modifier.getType()
                itemType = modifier.getItemType()
                values = modifier.getValues(action)
                currentSection = self.__actionsCache[section][type]
                if itemType is not None:
                    currentSection = currentSection.setdefault(itemType, {})
                for k in values:
                    if k in currentSection:
                        currentSection[k] = mergeValues(currentSection[k], values[k])
                    else:
                        currentSection[k] = values[k]

        rareAchieves = set()
        invalidateTimeLeft = sys.maxint
        for q in self.getCurrentEvents().itervalues():
            dossierBonuses = q.getBonuses('dossier')
            if len(dossierBonuses):
                storage = self.__questsDossierBonuses[q.getID()]
                for bonus in dossierBonuses:
                    records = bonus.getRecords()
                    storage.update(records)
                    rareAchieves |= set((r for r in records if r[0] == ACHIEVEMENT_BLOCK.RARE))

            timeLeftInfo = q.getNearestActivityTimeLeft()
            if timeLeftInfo is not None:
                isAvailable, errorMsg = q.isAvailable()
                if not isAvailable:
                    if errorMsg in ('invalid_weekday', 'invalid_time_interval'):
                        invalidateTimeLeft = min(invalidateTimeLeft, timeLeftInfo[0])
                else:
                    intervalBeginTimeLeft, (intervalStart, intervalEnd) = timeLeftInfo
                    invalidateTimeLeft = min(invalidateTimeLeft, intervalBeginTimeLeft + intervalEnd - intervalStart)
            else:
                invalidateTimeLeft = min(invalidateTimeLeft, q.getFinishTimeLeft())

        g_rareAchievesCache.request(rareAchieves)
        for q in self.getFutureEvents().itervalues():
            timeLeftInfo = q.getNearestActivityTimeLeft()
            if timeLeftInfo is None:
                startTime = q.getStartTimeLeft()
            else:
                startTime = timeLeftInfo[0]
            invalidateTimeLeft = min(invalidateTimeLeft, startTime)

        if invalidateTimeLeft != sys.maxint:
            self.__loadInvalidateCallback(invalidateTimeLeft)
        self.__waitForSync = False
        self.onSyncCompleted()
        callback(True)
        from gui.shared import g_eventBus
        g_eventBus.handleEvent(events.LobbySimpleEvent(events.LobbySimpleEvent.EVENTS_UPDATED))
        return

    def __clearQuestsItemsCache(self):
        for qID, q in self._getQuests().iteritems():
            q.accountReqs.clearItemsCache()
            q.vehicleReqs.clearItemsCache()

    @classmethod
    def __getEventsData(cls, eventsTypeName):
        try:
            if isPlayerAccount():
                if eventsTypeName in BigWorld.player().eventsData:
                    return pickle.loads(zlib.decompress(BigWorld.player().eventsData[eventsTypeName]))
                return {}
            LOG_ERROR('Trying to get quests data from not account player', eventsTypeName, BigWorld.player())
        except Exception:
            LOG_CURRENT_EXCEPTION()

        return {}

    def __getQuestsData(self):
        return self.__getEventsData(EVENT_CLIENT_DATA.QUEST)

    def __getFortQuestsData(self):
        return self.__getEventsData(EVENT_CLIENT_DATA.FORT_QUEST)

    def __getPersonalQuestsData(self):
        return self.__getEventsData(EVENT_CLIENT_DATA.PERSONAL_QUEST)

    def __getActionsData(self):
        return self.__getEventsData(EVENT_CLIENT_DATA.ACTION)

    def __getEventBattles(self):
        return self.__getEventsData(EVENT_CLIENT_DATA.INGAME_EVENTS).get('eventBattles', {})

    def __getCompanyBattlesData(self):
        return self.__getEventsData(EVENT_CLIENT_DATA.INGAME_EVENTS).get('eventCompanies', {})

    def __getFallout(self):
        return self.__getEventsData(EVENT_CLIENT_DATA.FALLOUT)

    def __getCommonQuestsIterator(self):
        questsData = self.__getQuestsData()
        questsData.update(self.__getFortQuestsData())
        questsData.update(self.__getPersonalQuestsData())
        questsData.update(self.__getPotapovHiddenQuests())
        for qID, qData in questsData.iteritems():
            yield (qID, self._makeQuest(qID, qData))

        currentESportSeasonID = g_lobbyContext.getServerSettings().eSportCurrentSeason.getID()
        eSportQuests = clubs_quests.g_cache.getLadderQuestsBySeasonID(currentESportSeasonID) or []
        for questDescr in eSportQuests:
            yield (questDescr.questID, self._makeQuest(questDescr.questID, questDescr.questData, maker=_clubsQuestMaker, seasonID=currentESportSeasonID, questDescr=questDescr))

    def __loadInvalidateCallback(self, duration):
        LOG_DEBUG('load quest window invalidation callback (secs)', duration)
        self.__clearInvalidateCallback()
        self.__invalidateCbID = BigWorld.callback(math.ceil(duration), self.__invalidateData)

    def __clearInvalidateCallback(self):
        if self.__invalidateCbID is not None:
            BigWorld.cancelCallback(self.__invalidateCbID)
            self.__invalidateCbID = None
        return

    def __clearCache(self):
        self.__questsDossierBonuses.clear()
        self.__actionsCache.clear()
        for storage in self.__cache.itervalues():
            storage.clear()

    def __getPotapovHiddenQuests(self):
        if not self.__potapovHidden:
            xmlPath = _POTAPOV_QUEST_XML_PATH + '/tiles.xml'
            for quest in readQuestsFromFile(xmlPath, EVENT_TYPE.TOKEN_QUEST):
                self.__potapovHidden[quest[0]] = quest[3]

        return self.__potapovHidden.copy()
Пример #6
0
class EventsCache(IEventsCache):
    USER_QUESTS = (EVENT_TYPE.BATTLE_QUEST, EVENT_TYPE.TOKEN_QUEST,
                   EVENT_TYPE.FORT_QUEST, EVENT_TYPE.PERSONAL_QUEST,
                   EVENT_TYPE.POTAPOV_QUEST)
    SYSTEM_QUESTS = (EVENT_TYPE.REF_SYSTEM_QUEST, )

    def __init__(self):
        self.__waitForSync = False
        self.__invalidateCbID = None
        self.__cache = defaultdict(dict)
        self.__potapovHidden = {}
        self.__actionsCache = defaultdict(lambda: defaultdict(dict))
        self.__questsDossierBonuses = defaultdict(set)
        self.__random = RandomPQController()
        self.__fallout = FalloutPQController()
        self.__potapovComposer = _PotapovComposer(self.__random,
                                                  self.__fallout)
        self.__questsProgress = QuestsProgressRequester()
        self.__companies = CompanyBattleController(self)
        self.__em = EventManager()
        self.onSyncStarted = Event(self.__em)
        self.onSyncCompleted = Event(self.__em)
        self.onSelectedQuestsChanged = Event(self.__em)
        self.onSlotsCountChanged = Event(self.__em)
        self.onProgressUpdated = Event(self.__em)
        self.__lockedQuestIds = {}
        return

    def init(self):
        self.__random.init()
        self.__fallout.init()

    def fini(self):
        self.__fallout.fini()
        self.__random.fini()
        self.__em.clear()
        self.__clearInvalidateCallback()

    def start(self):
        self.__companies.start()
        self.__lockedQuestIds = BigWorld.player().potapovQuestsLock
        g_playerEvents.onPQLocksChanged += self.__onLockedQuestsChanged

    def stop(self):
        g_playerEvents.onPQLocksChanged -= self.__onLockedQuestsChanged
        self.__companies.stop()

    def clear(self):
        self.stop()
        quests_caches.clearNavInfo()

    @property
    def waitForSync(self):
        return self.__waitForSync

    @property
    def falloutQuestsProgress(self):
        return self.__fallout.questsProgress

    @property
    def randomQuestsProgress(self):
        return self.__random.questsProgress

    @property
    def random(self):
        return self.__random

    @property
    def fallout(self):
        return self.__fallout

    @property
    def questsProgress(self):
        return self.__questsProgress

    @property
    def potapov(self):
        return self.__potapovComposer

    @property
    def companies(self):
        return self.__companies

    def getLockedQuestTypes(self):
        questIDs = set()
        result = set()
        allQuests = self.potapov.getQuests()
        for lockedList in self.__lockedQuestIds.values():
            if lockedList is not None:
                questIDs.update(lockedList)

        for questID in questIDs:
            if questID in allQuests:
                result.add(allQuests[questID].getMajorTag())

        return result

    @async
    @process
    def update(self, diff=None, callback=None):
        if diff is not None:
            if diff.get('eventsData', {}).get(EVENT_CLIENT_DATA.INGAME_EVENTS):
                self.__companies.setNotificators()
        yield self.falloutQuestsProgress.request()
        yield self.randomQuestsProgress.request()
        yield self.__questsProgress.request()
        isNeedToInvalidate = True
        isNeedToClearItemsCaches = False

        def _cbWrapper(*args):
            self.__random.update(self, diff)
            self.__fallout.update(self, diff)
            callback(*args)

        if diff is not None:
            isQPUpdated = 'quests' in diff
            isEventsDataUpdated = ('eventsData', '_r') in diff or diff.get(
                'eventsData', {})
            isNeedToInvalidate = isQPUpdated or isEventsDataUpdated
            hasVehicleUnlocks = False
            for intCD in diff.get('stats', {}).get('unlocks', set()):
                if getTypeOfCompactDescr(intCD) == GUI_ITEM_TYPE.VEHICLE:
                    hasVehicleUnlocks = True
                    break

            isNeedToClearItemsCaches = 'inventory' in diff and GUI_ITEM_TYPE.VEHICLE in diff[
                'inventory'] or hasVehicleUnlocks
        if isNeedToInvalidate:
            self.__invalidateData(_cbWrapper)
            return
        else:
            if isNeedToClearItemsCaches:
                self.__clearQuestsItemsCache()
            _cbWrapper(True)
            return

    def getQuests(self, filterFunc=None):
        filterFunc = filterFunc or (lambda a: True)

        def userFilterFunc(q):
            return not q.isHidden() and filterFunc(q)

        return self._getQuests(userFilterFunc)

    def getMotiveQuests(self, filterFunc=None):
        filterFunc = filterFunc or (lambda a: True)

        def userFilterFunc(q):
            return q.getType() == EVENT_TYPE.MOTIVE_QUEST and filterFunc(q)

        return self.getQuests(userFilterFunc)

    def getBattleQuests(self, filterFunc=None):
        filterFunc = filterFunc or (lambda a: True)

        def userFilterFunc(q):
            return q.getType() == EVENT_TYPE.BATTLE_QUEST and filterFunc(q)

        return self.getQuests(userFilterFunc)

    def getGroups(self, filterFunc=None):
        svrGroups = self._getQuestsGroups(filterFunc)
        svrGroups.update(self._getActionsGroups(filterFunc))
        return svrGroups

    def getHiddenQuests(self, filterFunc=None):
        filterFunc = filterFunc or (lambda a: True)

        def hiddenFilterFunc(q):
            return q.isHidden() and filterFunc(q)

        return self._getQuests(hiddenFilterFunc)

    def getAllQuests(self, filterFunc=None, includePotapovQuests=False):
        return self._getQuests(filterFunc, includePotapovQuests)

    def getActions(self, filterFunc=None):
        filterFunc = filterFunc or (lambda a: True)

        def userFilterFunc(q):
            return filterFunc(q) and q.getType() != EVENT_TYPE.GROUP

        return self._getActions(userFilterFunc)

    def getEventBattles(self):
        battles = self.__getEventBattles()
        if len(battles):
            return EventBattles(battles.get('vehicleTags', set()),
                                battles.get('vehicles', []),
                                bool(battles.get('enabled', 0)),
                                battles.get('arenaTypeID'))
        else:
            return EventBattles(set(), [], 0, None)
            return None

    def isEventEnabled(self):
        return len(self.__getEventBattles()) > 0 and len(
            self.getEventVehicles()) > 0

    def isGasAttackEnabled(self):
        return len(self.__getGasAttack()) > 0

    def getEventVehicles(self):
        from gui.shared import g_itemsCache
        result = []
        for v in self.getEventBattles().vehicles:
            item = g_itemsCache.items.getItemByCD(v)
            if item.isInInventory:
                result.append(item)

        return sorted(result)

    def getEvents(self, filterFunc=None):
        svrEvents = self.getQuests(filterFunc)
        svrEvents.update(self.getActions(filterFunc))
        return svrEvents

    def getCurrentEvents(self):
        return self.getEvents(
            lambda q: q.getStartTimeLeft() <= 0 < q.getFinishTimeLeft())

    def getFutureEvents(self):
        return self.getEvents(lambda q: q.getStartTimeLeft() > 0)

    def getCompanyBattles(self):
        battle = self.__getCompanyBattlesData()
        startTime = battle.get('startTime', 0.0)
        finishTime = battle.get('finishTime', 0.0)
        return CompanyBattles(
            startTime=None if startTime is None else float(startTime),
            finishTime=None if finishTime is None else float(finishTime),
            peripheryIDs=battle.get('peripheryIDs', set()))

    def isFalloutEnabled(self):
        return bool(self.__getFallout().get('enabled', False))

    def getFalloutConfig(self, queueType):
        arenaBonusType = QUEUE_TYPE_TO_ARENA_BONUS_TYPE.get(
            queueType, ARENA_BONUS_TYPE.UNKNOWN)
        return makeTupleByDict(FalloutConfig,
                               self.__getFallout().get(arenaBonusType, {}))

    def getItemAction(self, item, isBuying=True, forCredits=False):
        result = []
        type = ACTION_MODIFIER_TYPE.DISCOUNT if isBuying else ACTION_MODIFIER_TYPE.SELLING
        itemTypeID = item.itemTypeID
        nationID = item.nationID
        intCD = item.intCD
        values = self.__actionsCache[ACTION_SECTION_TYPE.ALL][type].get(
            itemTypeID, {}).get(nationID, [])
        values += self.__actionsCache[ACTION_SECTION_TYPE.ALL][type].get(
            itemTypeID, {}).get(15, [])
        for (key, value), actionID in values:
            if item.isPremium and key in (
                    'creditsPrice',
                    'creditsPriceMultiplier') and not forCredits:
                continue
            result.append((value, actionID))

        result.extend(self.__actionsCache[ACTION_SECTION_TYPE.ITEM][type].get(
            itemTypeID, {}).get(intCD, tuple()))
        return result

    def getBoosterAction(self, booster, isBuying=True, forCredits=False):
        result = []
        type = ACTION_MODIFIER_TYPE.DISCOUNT if isBuying else ACTION_MODIFIER_TYPE.SELLING
        boosterID = booster.boosterID
        values = self.__actionsCache[
            ACTION_SECTION_TYPE.ALL_BOOSTERS][type].get(
                nations.NONE_INDEX, [])
        for (key, value), actionID in values:
            if forCredits and key == 'creditsPriceMultiplier':
                result.append((value, actionID))
            elif not forCredits and key == 'goldPriceMultiplier':
                result.append((value, actionID))

        result.extend(
            self.__actionsCache[ACTION_SECTION_TYPE.BOOSTER][type].get(
                boosterID, tuple()))
        return result

    def getRentAction(self, item, rentPackage):
        result = []
        type = ACTION_MODIFIER_TYPE.RENT
        itemTypeID = item.itemTypeID
        nationID = item.nationID
        intCD = item.intCD
        values = self.__actionsCache[ACTION_SECTION_TYPE.ALL][type].get(
            itemTypeID, {}).get(nationID, [])
        values += self.__actionsCache[ACTION_SECTION_TYPE.ALL][type].get(
            itemTypeID, {}).get(15, [])
        for (key, value), actionID in values:
            result.append((value, actionID))

        result.extend(self.__actionsCache[ACTION_SECTION_TYPE.ITEM][type].get(
            itemTypeID, {}).get((intCD, rentPackage), tuple()))
        return result

    def getEconomicsAction(self, name):
        result = self.__actionsCache[ACTION_SECTION_TYPE.ECONOMICS][
            ACTION_MODIFIER_TYPE.DISCOUNT].get(name, [])
        resultMult = self.__actionsCache[ACTION_SECTION_TYPE.ECONOMICS][
            ACTION_MODIFIER_TYPE.DISCOUNT].get('%sMultiplier' % name, [])
        return tuple(result + resultMult)

    def getCamouflageAction(self, vehicleIntCD):
        return tuple(self.__actionsCache[ACTION_SECTION_TYPE.CUSTOMIZATION][
            ACTION_MODIFIER_TYPE.DISCOUNT].get(vehicleIntCD, tuple()))

    def getEmblemsAction(self, group):
        return tuple(self.__actionsCache[ACTION_SECTION_TYPE.CUSTOMIZATION][
            ACTION_MODIFIER_TYPE.DISCOUNT].get(group, tuple()))

    def isBalancedSquadEnabled(self):
        return bool(self.__getUnitRestrictions().get('enabled', False))

    def getBalancedSquadBounds(self):
        return (self.__getUnitRestrictions().get('lowerBound', 0),
                self.__getUnitRestrictions().get('upperBound', 0))

    def isSquadXpFactorsEnabled(self):
        return bool(self.__getUnitXpFactors().get('enabled', False))

    def getSquadBonusLevelDistance(self):
        return set(self.__getUnitXpFactors().get('levelDistanceWithBonuses',
                                                 ()))

    def getSquadPenaltyLevelDistance(self):
        return set(self.__getUnitXpFactors().get('levelDistanceWithPenalties',
                                                 ()))

    def getSquadZeroBonuses(self):
        return set(self.__getUnitXpFactors().get('zeroBonusesFor', ()))

    def getQuestsDossierBonuses(self):
        return self.__questsDossierBonuses

    def getQuestsByTokenRequirement(self, token):
        result = []
        for q in self._getQuests(includePotapovQuests=True).itervalues():
            if token in map(lambda t: t.getID(), q.accountReqs.getTokens()):
                result.append(q)

        return result

    def getQuestsByTokenBonus(self, token):
        result = []
        for q in self._getQuests(includePotapovQuests=True).itervalues():
            for t in q.getBonuses('tokens'):
                if token in t.getTokens().keys():
                    result.append(q)
                    break

        return result

    def _getQuests(self, filterFunc=None, includePotapovQuests=False):
        result = {}
        groups = {}
        filterFunc = filterFunc or (lambda a: True)
        for qID, q in self.__getCommonQuestsIterator():
            if q.getType() == EVENT_TYPE.GROUP:
                groups[qID] = q
                continue
            if q.getDestroyingTimeLeft() <= 0:
                continue
            if not filterFunc(q):
                continue
            result[qID] = q

        if includePotapovQuests:
            for qID, q in self.potapov.getQuests().iteritems():
                if filterFunc(q):
                    result[qID] = q

        for gID, group in groups.iteritems():
            for qID in group.getGroupEvents():
                if qID in result:
                    result[qID].setGroupID(gID)

        children, parents, parentsName = self._makeQuestsRelations(result)
        for qID, q in result.iteritems():
            if qID in children:
                q.setChildren(children[qID])
            if qID in parents:
                q.setParents(parents[qID])
            if qID in parentsName:
                q.setParentsName(parentsName[qID])

        return result

    def _getQuestsGroups(self, filterFunc=None):
        filterFunc = filterFunc or (lambda a: True)
        result = {}
        for qID, q in self.__getCommonQuestsIterator():
            if q.getType() != EVENT_TYPE.GROUP:
                continue
            if not filterFunc(q):
                continue
            result[qID] = q

        return result

    def _getActions(self, filterFunc=None):
        filterFunc = filterFunc or (lambda a: True)
        actions = self.__getActionsData()
        result = {}
        groups = {}
        for aData in actions:
            if 'id' in aData:
                a = self._makeAction(aData['id'], aData)
                actionID = a.getID()
                if a.getType() == EVENT_TYPE.GROUP:
                    groups[actionID] = a
                    continue
                if not filterFunc(a):
                    continue
                result[actionID] = a

        for gID, group in groups.iteritems():
            for aID in group.getGroupEvents():
                if aID in result:
                    result[aID].setGroupID(gID)

        return result

    def _getActionsGroups(self, filterFunc=None):
        actions = self.__getActionsData()
        filterFunc = filterFunc or (lambda a: True)
        result = {}
        for aData in actions:
            if 'id' in aData:
                a = self._makeAction(aData['id'], aData)
                if a.getType() != EVENT_TYPE.GROUP:
                    continue
                if not filterFunc(a):
                    continue
                result[a.getID()] = a

        return result

    def _onResync(self, *args):
        self.__invalidateData()

    def _makeQuest(self, qID, qData, maker=_defaultQuestMaker, **kwargs):
        storage = self.__cache['quests']
        if qID in storage:
            return storage[qID]
        q = storage[qID] = maker(qID, qData, self.__questsProgress, **kwargs)
        return q

    def _makeAction(self, aID, aData):
        storage = self.__cache['actions']
        if aID in storage:
            return storage[aID]
        a = storage[aID] = createAction(aData.get('type', 0), aID, aData)
        return a

    @classmethod
    def _makeQuestsRelations(cls, quests):
        makeTokens = defaultdict(list)
        needTokens = defaultdict(list)
        for qID, q in quests.iteritems():
            if q.getType() != EVENT_TYPE.GROUP:
                tokens = q.getBonuses('tokens')
                if len(tokens):
                    for t in tokens[0].getTokens():
                        makeTokens[t].append(qID)

                for t in q.accountReqs.getTokens():
                    needTokens[qID].append(t.getID())

        children = defaultdict(dict)
        for parentID, tokensIDs in needTokens.iteritems():
            for tokenID in tokensIDs:
                children[parentID][tokenID] = makeTokens.get(tokenID, [])

        parents = defaultdict(dict)
        parentsName = defaultdict(dict)
        for parentID, tokens in children.iteritems():
            for tokenID, chn in tokens.iteritems():
                for childID in chn:
                    parents[childID][tokenID] = [parentID]
                    parentsName[childID][tokenID] = [
                        quests[parentID].getUserName()
                    ]

        return (children, parents, parentsName)

    def __invalidateData(self, callback=lambda *args: None):
        self.__clearCache()
        self.__clearInvalidateCallback()
        self.__waitForSync = True
        self.onSyncStarted()

        def mergeValues(a, b):
            result = list(a)
            result.extend(b)
            return result

        for action in self.getActions().itervalues():
            for modifier in action.getModifiers():
                section = modifier.getSection()
                type = modifier.getType()
                itemType = modifier.getItemType()
                values = modifier.getValues(action)
                currentSection = self.__actionsCache[section][type]
                if itemType is not None:
                    currentSection = currentSection.setdefault(itemType, {})
                for k in values:
                    if k in currentSection:
                        currentSection[k] = mergeValues(
                            currentSection[k], values[k])
                    else:
                        currentSection[k] = values[k]

        rareAchieves = set()
        invalidateTimeLeft = sys.maxint
        for q in self.getCurrentEvents().itervalues():
            dossierBonuses = q.getBonuses('dossier')
            if len(dossierBonuses):
                storage = self.__questsDossierBonuses[q.getID()]
                for bonus in dossierBonuses:
                    records = bonus.getRecords()
                    storage.update(set(bonus.getRecords().keys()))
                    rareAchieves |= set((rId for r, rId in records.iteritems()
                                         if r[0] == ACHIEVEMENT_BLOCK.RARE))

            timeLeftInfo = q.getNearestActivityTimeLeft()
            if timeLeftInfo is not None:
                isAvailable, errorMsg = q.isAvailable()
                if not isAvailable:
                    if errorMsg in ('invalid_weekday',
                                    'invalid_time_interval'):
                        invalidateTimeLeft = min(invalidateTimeLeft,
                                                 timeLeftInfo[0])
                else:
                    intervalBeginTimeLeft, (intervalStart,
                                            intervalEnd) = timeLeftInfo
                    invalidateTimeLeft = min(
                        invalidateTimeLeft,
                        intervalBeginTimeLeft + intervalEnd - intervalStart)
            else:
                invalidateTimeLeft = min(invalidateTimeLeft,
                                         q.getFinishTimeLeft())

        g_rareAchievesCache.request(rareAchieves)
        for q in self.getFutureEvents().itervalues():
            timeLeftInfo = q.getNearestActivityTimeLeft()
            if timeLeftInfo is None:
                startTime = q.getStartTimeLeft()
            else:
                startTime = timeLeftInfo[0]
            invalidateTimeLeft = min(invalidateTimeLeft, startTime)

        if invalidateTimeLeft != sys.maxint:
            self.__loadInvalidateCallback(invalidateTimeLeft)
        self.__waitForSync = False
        self.onSyncCompleted()
        callback(True)
        from gui.shared import g_eventBus
        g_eventBus.handleEvent(
            events.LobbySimpleEvent(events.LobbySimpleEvent.EVENTS_UPDATED))
        return

    def __clearQuestsItemsCache(self):
        for qID, q in self._getQuests().iteritems():
            q.accountReqs.clearItemsCache()
            q.vehicleReqs.clearItemsCache()

    @classmethod
    def __getEventsData(cls, eventsTypeName):
        try:
            if isPlayerAccount():
                if eventsTypeName in BigWorld.player().eventsData:
                    return pickle.loads(
                        zlib.decompress(
                            BigWorld.player().eventsData[eventsTypeName]))
                return {}
            LOG_DEBUG('Trying to get quests data from not account player',
                      eventsTypeName, BigWorld.player())
        except Exception:
            LOG_CURRENT_EXCEPTION()

        return {}

    def __getQuestsData(self):
        return self.__getEventsData(EVENT_CLIENT_DATA.QUEST)

    def __getFortQuestsData(self):
        return self.__getEventsData(EVENT_CLIENT_DATA.FORT_QUEST)

    def __getPersonalQuestsData(self):
        return self.__getEventsData(EVENT_CLIENT_DATA.PERSONAL_QUEST)

    def __getActionsData(self):
        return self.__getEventsData(EVENT_CLIENT_DATA.ACTION)

    def __getIngameEventsData(self):
        return self.__getEventsData(EVENT_CLIENT_DATA.INGAME_EVENTS)

    def __getEventBattles(self):
        return self.__getIngameEventsData().get('eventBattles', {})

    def __getCompanyBattlesData(self):
        return self.__getIngameEventsData().get('eventCompanies', {})

    def __getUnitRestrictions(self):
        return self.__getUnitData().get('restrictions', {})

    def __getUnitXpFactors(self):
        return self.__getUnitData().get('xpFactors', {})

    def __getFallout(self):
        return self.__getEventsData(EVENT_CLIENT_DATA.FALLOUT)

    def __getUnitData(self):
        return self.__getEventsData(EVENT_CLIENT_DATA.SQUAD_BONUSES)

    def __getGasAttack(self):
        return self.__getEventsData(EVENT_CLIENT_DATA.INGAME_EVENTS).get(
            'gasAttack', {})

    def __getCommonQuestsIterator(self):
        questsData = self.__getQuestsData()
        questsData.update(self.__getFortQuestsData())
        questsData.update(self.__getPersonalQuestsData())
        questsData.update(self.__getPotapovHiddenQuests())
        for qID, qData in questsData.iteritems():
            yield (qID, self._makeQuest(qID, qData))

        currentESportSeasonID = g_lobbyContext.getServerSettings(
        ).eSportCurrentSeason.getID()
        eSportQuests = clubs_quests.g_cache.getLadderQuestsBySeasonID(
            currentESportSeasonID) or []
        for questDescr in eSportQuests:
            yield (questDescr.questID,
                   self._makeQuest(questDescr.questID,
                                   questDescr.questData,
                                   maker=_clubsQuestMaker,
                                   seasonID=currentESportSeasonID,
                                   questDescr=questDescr))

        motiveQuests = motivation_quests.g_cache.getAllQuests() or []
        for questDescr in motiveQuests:
            yield (questDescr.questID,
                   self._makeQuest(questDescr.questID,
                                   questDescr.questData,
                                   maker=_motiveQuestMaker))

    def __loadInvalidateCallback(self, duration):
        LOG_DEBUG('load quest window invalidation callback (secs)', duration)
        self.__clearInvalidateCallback()
        self.__invalidateCbID = BigWorld.callback(math.ceil(duration),
                                                  self.__invalidateData)

    def __clearInvalidateCallback(self):
        if self.__invalidateCbID is not None:
            BigWorld.cancelCallback(self.__invalidateCbID)
            self.__invalidateCbID = None
        return

    def __clearCache(self):
        self.__questsDossierBonuses.clear()
        self.__actionsCache.clear()
        for storage in self.__cache.itervalues():
            storage.clear()

    def __getPotapovHiddenQuests(self):
        if not self.__potapovHidden:
            xmlPath = _POTAPOV_QUEST_XML_PATH + '/tiles.xml'
            for quest in readQuestsFromFile(xmlPath, EVENT_TYPE.TOKEN_QUEST):
                self.__potapovHidden[quest[0]] = quest[3]

        return self.__potapovHidden.copy()

    def __onLockedQuestsChanged(self):
        self.__lockedQuestIds = BigWorld.player().potapovQuestsLock