diff --git a/toontown/ai/FishManagerAI.py b/toontown/ai/FishManagerAI.py index fe0b7c20..bf45b494 100755 --- a/toontown/ai/FishManagerAI.py +++ b/toontown/ai/FishManagerAI.py @@ -4,6 +4,7 @@ from otp.ai.MagicWordGlobal import * from toontown.fishing import FishGlobals from toontown.fishing.FishBase import FishBase from toontown.toonbase import TTLocalizer +from toontown.uberdog import TopToonsGlobals class FishManagerAI: @@ -66,6 +67,7 @@ class FishManagerAI: av.fishTank.addFish(fish) netlist = av.fishTank.getNetLists() av.d_setFishTank(netlist[0], netlist[1], netlist[2]) + messenger.send('topToonsManager-event', [av.doId, TopToonsGlobals.CAT_FISH, 1]) return [itemType, genus, species, weight] elif itemType == FishGlobals.BootItem: return [itemType, 0, 0, 0] @@ -89,6 +91,7 @@ class FishManagerAI: av.fishTank.addFish(fish) netlist = av.fishTank.getNetLists() av.d_setFishTank(netlist[0], netlist[1], netlist[2]) + messenger.send('topToonsManager-event', [av.doId, TopToonsGlobals.CAT_FISH, 1]) return [itemType, genus, species, weight] else: money = FishGlobals.Rod2JellybeanDict[av.getFishingRod()] diff --git a/toontown/ai/QuestManagerAI.py b/toontown/ai/QuestManagerAI.py index b965e85a..0309b98a 100755 --- a/toontown/ai/QuestManagerAI.py +++ b/toontown/ai/QuestManagerAI.py @@ -3,7 +3,7 @@ from toontown.building import FADoorCodes from otp.ai.MagicWordGlobal import * from toontown.hood import ZoneUtil from toontown.quest import Quests - +from toontown.uberdog import TopToonsGlobals QuestIdIndex = 0 QuestFromNpcIdIndex = 1 @@ -75,6 +75,7 @@ class QuestManagerAI: # If they've completed a quest. if completeStatus == Quests.COMPLETE: # ToonUp the toon to max health. + messenger.send('topToonsManager-event', [av.doId, TopToonsGlobals.CAT_TASKS, 1]) av.toonUp(av.maxHp) # If it's a TrackChoiceQuest then present their track choices. @@ -437,6 +438,7 @@ class QuestManagerAI: def toonKilledBuilding(self, av, type, difficulty, floors, zoneId, cogdo): # Get the avatars current quests. + messenger.send('topToonsManager-event', [av.doId, TopToonsGlobals.CAT_BLDG, 1]) avQuests = av.getQuests() questList = [] zoneId = ZoneUtil.getBranchZone(zoneId) @@ -491,6 +493,7 @@ class QuestManagerAI: def toonKilledCogs(self, av, suitsKilled, zoneId): # Get the avatar's current quests. + messenger.send('topToonsManager-event', [av.doId, TopToonsGlobals.CAT_COGS, len(suitsKilled)]) avQuests = av.getQuests() questList = [] diff --git a/toontown/ai/ToontownAIRepository.py b/toontown/ai/ToontownAIRepository.py index 62f2d0d4..d272506b 100755 --- a/toontown/ai/ToontownAIRepository.py +++ b/toontown/ai/ToontownAIRepository.py @@ -51,6 +51,7 @@ from toontown.toon import NPCToons from toontown.toonbase import ToontownGlobals from toontown.tutorial.TutorialManagerAI import TutorialManagerAI from toontown.uberdog.DistributedPartyManagerAI import DistributedPartyManagerAI +from toontown.uberdog.DistributedTopToonsManagerAI import DistributedTopToonsManagerAI #from toontown.uberdog.DistributedLobbyManagerAI import DistributedLobbyManagerAI class ToontownAIRepository(ToontownInternalRepository): @@ -72,6 +73,7 @@ class ToontownAIRepository(ToontownInternalRepository): self.lawOfficeMgr = None self.countryClubMgr = None self.groupManager = GroupManagerAI(self) + self.topToonsMgr = DistributedTopToonsManagerAI(self) self.zoneAllocator = UniqueIdAllocator(ToontownGlobals.DynamicZonesBegin, ToontownGlobals.DynamicZonesEnd) @@ -103,7 +105,7 @@ class ToontownAIRepository(ToontownInternalRepository): self.tutorialManager.generateWithRequired(2) self.friendManager = FriendManagerAI(self) self.friendManager.generateWithRequired(2) - self.questManager = QuestManagerAI(self) + self.questManager = QuestManagerAI(self) self.banManager = BanManagerAI.BanManagerAI(self) self.suitInvasionManager = SuitInvasionManagerAI(self) self.blackCatMgr = DistributedBlackCatMgrAI(self) diff --git a/toontown/golf/DistributedGolfCourseAI.py b/toontown/golf/DistributedGolfCourseAI.py index cb4b26e8..a5b2df81 100755 --- a/toontown/golf/DistributedGolfCourseAI.py +++ b/toontown/golf/DistributedGolfCourseAI.py @@ -6,6 +6,7 @@ from panda3d.core import * from direct.fsm.FSM import FSM from toontown.ai.ToonBarrier import * from toontown.golf import GolfGlobals +from toontown.uberdog import TopToonsGlobals INITIAL = 0 EXITED = 1 EXPECTED = 2 @@ -970,12 +971,17 @@ class DistributedGolfCourseAI(DistributedObjectAI.DistributedObjectAI, FSM): stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: scoreList = self.scores[avId] + ns = 0 for holeIndex in xrange(len(scoreList)): strokes = scoreList[holeIndex] if strokes == 1: + ns +=1 holeId = self.holeIds[holeIndex] self.air.writeServerEvent('golf_ace', avId, '%d|%d|%s' % (self.courseId, holeId, stillPlaying)) + if ns: + messenger.send('topToonsManager-event', [avId, TopToonsGlobals.CAT_HOLE_IN_ONE, ns]) + def recordCourseUnderPar(self): coursePar = self.calcCoursePar() stillPlaying = self.getStillPlayingAvIds() @@ -984,7 +990,8 @@ class DistributedGolfCourseAI(DistributedObjectAI.DistributedObjectAI, FSM): netScore = totalScore - coursePar if netScore < 0: self.air.writeServerEvent('golf_underPar', avId, '%d|%d|%s' % (self.courseId, netScore, stillPlaying)) - + messenger.send('topToonsManager-event', [avId, TopToonsGlobals.CAT_COURSE_UNDER_PAR, 1]) + def addAimTime(self, avId, aimTime): if avId in self.aimTimes: self.aimTimes[avId] += aimTime diff --git a/toontown/minigame/MinigameCreatorAI.py b/toontown/minigame/MinigameCreatorAI.py index 877e4255..154f98a5 100755 --- a/toontown/minigame/MinigameCreatorAI.py +++ b/toontown/minigame/MinigameCreatorAI.py @@ -20,7 +20,7 @@ import DistributedTwoDGameAI import DistributedVineGameAI from otp.ai.MagicWordGlobal import * from toontown.toonbase import ToontownGlobals - +from toontown.uberdog import TopToonsGlobals simbase.forcedMinigameId = simbase.config.GetInt('force-minigame', 0) RequestMinigame = {} @@ -84,6 +84,8 @@ def createMinigame(air, playerArray, trolleyZone, minigameZone=None, toon = simbase.air.doId2do.get(doId) if toon is not None: toons.append(toon) + for toon in toons: + messenger.send('topToonsManager-event', [toon.doId, TopToonsGlobals.CAT_TROLLEY, 1]) for toon in toons: simbase.air.questManager.toonPlayedMinigame(toon, toons) retVal = {} diff --git a/toontown/racing/DistributedRaceAI.py b/toontown/racing/DistributedRaceAI.py index 5a9f942f..4fce5142 100755 --- a/toontown/racing/DistributedRaceAI.py +++ b/toontown/racing/DistributedRaceAI.py @@ -3,6 +3,7 @@ from direct.distributed.ClockDelta import * from direct.distributed.DistributedObjectAI import DistributedObjectAI from direct.fsm.FSM import FSM from direct.task import Task +from toontown.uberdog import TopToonsGlobals import random from toontown.racing import RaceGlobals @@ -320,6 +321,8 @@ class DistributedRaceAI(DistributedObjectAI, FSM): self.sendUpdate('setPlace', [avId, totalTime, place, entryFee, qualify, max((winnings-entryFee), 0), bonus, trophies, [], 0]) def calculateTrophies(self, avId, won, qualify, time): + if won: + messenger.send('topToonsManager-event', [avId, TopToonsGlobals.CAT_RACE_WON, 1]) av = self.air.doId2do[avId] kartingHistory = av.getKartingHistory() avTrophies = av.getKartingTrophies() diff --git a/toontown/suit/DistributedBossCogAI.py b/toontown/suit/DistributedBossCogAI.py index 6b1ba371..b73af025 100755 --- a/toontown/suit/DistributedBossCogAI.py +++ b/toontown/suit/DistributedBossCogAI.py @@ -145,6 +145,10 @@ class DistributedBossCogAI(DistributedAvatarAI.DistributedAvatarAI): def d_setBattleExperience(self): self.sendUpdate('setBattleExperience', self.getBattleExperience()) + for toonId in self.involvedToons: + toon = simbase.air.doId2do.get(toonId) + if toon: + self.air.topToonsMgr.toonKilledBoss(toon, self.BossName) def getBattleExperience(self): result = BattleExperienceAI.getBattleExperience(8, self.involvedToons, self.toonExp, self.toonSkillPtsGained, self.toonOrigQuests, self.toonItems, self.toonOrigMerits, self.toonMerits, self.toonParts, self.suitsKilled, self.helpfulToons) diff --git a/toontown/suit/DistributedBossbotBossAI.py b/toontown/suit/DistributedBossbotBossAI.py index ae453cec..5e9d1a7a 100755 --- a/toontown/suit/DistributedBossbotBossAI.py +++ b/toontown/suit/DistributedBossbotBossAI.py @@ -28,6 +28,7 @@ class DistributedBossbotBossAI(DistributedBossCogAI.DistributedBossCogAI, FSM.FS 2, 3, 4] + BossName = "CEO" def __init__(self, air): DistributedBossCogAI.DistributedBossCogAI.__init__(self, air, 'c') diff --git a/toontown/suit/DistributedCashbotBossAI.py b/toontown/suit/DistributedCashbotBossAI.py index 49e1f196..ec40d9d3 100755 --- a/toontown/suit/DistributedCashbotBossAI.py +++ b/toontown/suit/DistributedCashbotBossAI.py @@ -17,6 +17,7 @@ import math class DistributedCashbotBossAI(DistributedBossCogAI.DistributedBossCogAI, FSM.FSM): notify = DirectNotifyGlobal.directNotify.newCategory('DistributedCashbotBossAI') maxGoons = 8 + BossName = "CFO" def __init__(self, air): DistributedBossCogAI.DistributedBossCogAI.__init__(self, air, 'm') diff --git a/toontown/suit/DistributedLawbotBossAI.py b/toontown/suit/DistributedLawbotBossAI.py index 2f1fd77d..ce42202c 100755 --- a/toontown/suit/DistributedLawbotBossAI.py +++ b/toontown/suit/DistributedLawbotBossAI.py @@ -29,6 +29,7 @@ class DistributedLawbotBossAI(DistributedBossCogAI.DistributedBossCogAI, FSM.FSM hitCountDamage = 35 numPies = 10 maxToonLevels = 77 + BossName = "CJ" def __init__(self, air): DistributedBossCogAI.DistributedBossCogAI.__init__(self, air, 'l') diff --git a/toontown/suit/DistributedSellbotBossAI.py b/toontown/suit/DistributedSellbotBossAI.py index a5590795..5cd80854 100755 --- a/toontown/suit/DistributedSellbotBossAI.py +++ b/toontown/suit/DistributedSellbotBossAI.py @@ -20,6 +20,7 @@ class DistributedSellbotBossAI(DistributedBossCogAI.DistributedBossCogAI, FSM.FS limitHitCount = 6 hitCountDamage = 35 numPies = ToontownGlobals.FullPies + BossName = "VP" def __init__(self, air): DistributedBossCogAI.DistributedBossCogAI.__init__(self, air, 's') diff --git a/toontown/toon/DistributedToonAI.py b/toontown/toon/DistributedToonAI.py index 9e071657..802e7622 100755 --- a/toontown/toon/DistributedToonAI.py +++ b/toontown/toon/DistributedToonAI.py @@ -32,6 +32,7 @@ from toontown.toonbase import TTLocalizer, ToontownBattleGlobals, ToontownGlobal from toontown.toonbase.ToontownGlobals import * from NPCToons import npcFriends import Experience, InventoryBase, ToonDNA, random, time +from toontown.uberdog import TopToonsGlobals if simbase.wantPets: from toontown.pets import PetLookerAI, PetObserve @@ -2271,6 +2272,8 @@ class DistributedToonAI(DistributedPlayerAI.DistributedPlayerAI, DistributedSmoo return self.maxBankMoney def addMoney(self, deltaMoney): + if deltaMoney > 0: + messenger.send('topToonsManager-event', [self.doId, TopToonsGlobals.CAT_JELLYBEAN, deltaMoney]) money = deltaMoney + self.money pocketMoney = min(money, self.maxMoney) self.b_setMoney(pocketMoney) diff --git a/toontown/uberdog/DistributedTopToonsManagerAI.py b/toontown/uberdog/DistributedTopToonsManagerAI.py new file mode 100644 index 00000000..f91ae38d --- /dev/null +++ b/toontown/uberdog/DistributedTopToonsManagerAI.py @@ -0,0 +1,32 @@ +from direct.distributed.DistributedObjectAI import * +from direct.showbase.DirectObject import * +import TopToonsGlobals + +class DistributedTopToonsManagerAI(DirectObject): + def __init__(self, air): + self.air = air + + self.accept('topToonsManager-event', self.__handleEvent) + + def toonKilledBoss(self, av, boss): + cat = {'VP': TopToonsGlobals.CAT_VP, + 'CFO': TopToonsGlobals.CAT_CFO, + 'CJ': TopToonsGlobals.CAT_CJ, + 'CEO': TopToonsGlobals.CAT_CEO}.get(boss, 0) + self.__handleEvent(av.doId, cat, 1) + + def __handleEvent(self, *args): # avId, categories, score + self.air.sendNetEvent('topToonsManager-AI-score-site', list(args)) + +from otp.ai.MagicWordGlobal import * +@magicWord(types=[int, int]) +def topToon(score, cat=TopToonsGlobals._CAT_ALL): + av = spellbook.getTarget() + mgr = av.air.topToonsMgr + if not mgr: + return 'No manager!' + + if cat > TopToonsGlobals._CAT_ALL: + return 'Max value: %d' % TopToonsGlobals._CAT_ALL + + messenger.send('topToonsManager-event', [av.doId, cat, score]) \ No newline at end of file diff --git a/toontown/uberdog/DistributedTopToonsManagerUD.py b/toontown/uberdog/DistributedTopToonsManagerUD.py new file mode 100644 index 00000000..eb0b55a5 --- /dev/null +++ b/toontown/uberdog/DistributedTopToonsManagerUD.py @@ -0,0 +1,247 @@ +# CLEANUP IMPORTS + +from direct.directnotify import DirectNotifyGlobal +from direct.fsm.FSM import FSM +from direct.distributed.DistributedObjectUD import * +from direct.showbase.DirectObject import * +from toontown.toon.ToonDNA import ToonDNA, getSpeciesName +import TopToonsGlobals +import time, cPickle, random +import datetime, json +import urllib +import urllib2 +import hashlib + +def getCurrentMonth(): + dt = datetime.date.today() + month = dt.month + year = dt.year + return year * 100 + month + +def getPrevMonth(): + current = getCurrentMonth() + year, month = divmod(current, 100) + month -= 1 + if not month: + month = 12 + year -= 1 + + return year * 100 + month + +def getNextMonth(): + current = getCurrentMonth() + year, month = divmod(current, 100) + month += 1 + if month > 12: + month = 1 + year += 1 + + return year * 100 + month + +def timeToNextMonth(): + now = datetime.datetime.now() + year, month = divmod(getNextMonth(), 100) + return (datetime.datetime(year, month, 1) - now).total_seconds() + +def getEmptySiteToonsColl(month): + coll = {} + + start = TopToonsGlobals._CAT_BEGIN + end = TopToonsGlobals._CAT_END + while start <= end: + coll[str(start)] = {} + start *= 2 + + coll['month'] = month + return coll + +class SiteUploadFSM(FSM): + notify = DirectNotifyGlobal.directNotify.newCategory('SiteUploadFSM') + URL = config.GetString('toptoons-api-endpoint', 'http://toontownstride.com/toptoons/post/') # Let's hope jumbleweed hasn't changed this + + def __init__(self, mgr, data): + FSM.__init__(self, 'SiteUploadFSM') + + self.mgr = mgr + self.data = {} + self.month = data.pop('month') + for category, avs in data.items(): + self.data[int(category)] = sorted(avs.items(), key=lambda x: -x[1]) + + self.__cat = TopToonsGlobals._CAT_BEGIN + self.__responses = {} + self.__cache = {} + self.__waiting = {} + self.__dataToSend = {} + self.__failures = -1 + + self.demand('QueryAvatars') + + def enterQueryAvatars(self): + avs = self.data[self.__cat] + cutoff = self.__failures + if cutoff == -1: + cutoff = 5 + selected, remaining = avs[:cutoff], avs[cutoff:] + self.data[self.__cat] = remaining + + self.__waiting = {int(x[0]): x[1] for x in selected} + avIds = self.__waiting.keys() + for avId in avIds: + if avId in self.__cache: + self.__responses[avId] = (self.__cache[avId][0], self.__waiting.pop(avId)) + + self.__failures = 0 + for avId in self.__waiting: + def response(x, y, avId=avId): + self.__handleToon(avId, x, y) + + self.mgr.air.dbInterface.queryObject(self.mgr.air.dbId, avId, response) + + if not self.__waiting: + self.demand('SortResults') + + def __handleToon(self, avId, dclass, fields): + if avId not in self.__waiting: + return + + if dclass != self.mgr.air.dclassesByName['DistributedToonUD']: + self.__failures += 1 + self.notify.warning('%d query failed!' % avId) + del self.__waiting[avId] + if not self.__waiting: + self.demand('QueryAvatars') + return + + name = fields['setName'][0] + hp = fields['setMaxHp'][0] + + dna = ToonDNA(fields['setDNAString'][0]) + species = getSpeciesName(dna.head) + color = dna.headColor + + if species == 'pig': + dna = 'pig' + + else: + if species == 'cat' and color == 26: + dna = 'blackcat' + + else: + if color > 23: + color = 0 + + dna = '%s_%s_%d' % (species, dna.head[1:], color) + + self.__responses[avId] = ((name, dna, hp), self.__waiting.pop(avId)) + + if not self.__waiting: + self.demand('QueryAvatars') + + def enterSortResults(self): + responses = sorted(self.__responses.values(), key=lambda x: -x[-1]) + self.__dataToSend[self.__cat] = responses + self.__cache.update(self.__responses) + self.__failures = -1 + self.__responses = {} + self.__cat *= 2 + if self.__cat * 2 == TopToonsGlobals._CAT_END: + self.demand('Upload') + return + + self.demand('QueryAvatars') + + def enterUpload(self): + self.__dataToSend['month'] = self.month + + (success, error), res = self.post(self.URL, self.__dataToSend) + print (success, error), res + + def post(self, url, data): + headers = {'User-Agent' : 'TTUberAgent'} + + innerData = json.dumps(data) + hmac = hashlib.sha512(innerData + self.mgr.air.getApiKey()).hexdigest() # XXX PROVIDE THE KEY HERE + + data = 'data=%s' % urllib.quote(innerData) + data += '&hmac=%s' % urllib.quote(hmac) + + success = True + error = None + res = {} + + try: + req = urllib2.Request(url, data, headers) + res = json.loads(urllib2.urlopen(req).read()) + success = res['success'] + error = res.get('error') + + except Exception as e: + if hasattr(e, 'read'): + with open('../e.html', 'wb') as f: + f.write(e.read()) + + success = False + error = str(e) + + return (success, error), res + +class DistributedTopToonsManagerUD(DirectObject): + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedTopToonsManagerUD') + + def __init__(self, air): + self.air = air + + self.__curMonth = getCurrentMonth() + coll = None + if self.air.dbConn: + coll = self.air.dbGlobalCursor.strideToons.find_one({'month': self.__curMonth}) + if not coll: + lastMonthColl = self.air.dbGlobalCursor.strideToons.find_one({'month': getPrevMonth()}) + if lastMonthColl: + self.__uploadLastMonth(lastMonthColl) + + if not coll: + coll = getEmptySiteToonsColl(self.__curMonth) + + self.__topToonsData = coll + self.__topToonsData.pop('_id', None) + + self.accept('topToonsManager-AI-score-site', self.__topToonsScore) + self.waitForNextMonth() + + def __uploadLastMonth(self, data): + self.notify.info('Sending last month result to site...') + SiteUploadFSM(self, data) + + def waitForNextMonth(self): + def _nm(task): + self.__uploadLastMonth(self.__topToonsData) + + self.__curMonth = getCurrentMonth() + self.__topToonsData = getEmptySiteToonsColl(self.__curMonth) + + self.waitForNextMonth() + + return task.done + + taskMgr.doMethodLater(timeToNextMonth() + 1, _nm, 'DistributedTopToonsManagerUD-nextMonth') + + def saveSite(self): + if self.air.dbConn: + self.air.dbGlobalCursor.strideToons.update({'month': self.__curMonth}, {'$set': self.__topToonsData}, upsert=True) + + def __topToonsScore(self, avId, categories, score): + def _add(cat): + cd = self.__topToonsData[str(cat)] + cd[str(avId)] = cd.get(str(avId), 0) + score + + start = TopToonsGlobals._CAT_BEGIN + end = TopToonsGlobals._CAT_END + while start <= end: + if categories & start: + _add(start) + + start *= 2 + + self.saveSite() \ No newline at end of file diff --git a/toontown/uberdog/ToontownUberRepository.py b/toontown/uberdog/ToontownUberRepository.py index 7ead157f..88958d61 100755 --- a/toontown/uberdog/ToontownUberRepository.py +++ b/toontown/uberdog/ToontownUberRepository.py @@ -4,28 +4,16 @@ from otp.distributed.OtpDoGlobals import * from otp.distributed.DistributedDirectoryAI import DistributedDirectoryAI from toontown.distributed.ToontownInternalRepository import ToontownInternalRepository import toontown.minigame.MinigameCreatorAI +from toontown.uberdog.DistributedTopToonsManagerUD import DistributedTopToonsManagerUD if config.GetBool('want-rpc-server', False): from toontown.rpc.ToontownRPCServer import ToontownRPCServer from toontown.rpc.ToontownRPCHandler import ToontownRPCHandler -if config.GetBool('want-mongo-client', False): - import pymongo - class ToontownUberRepository(ToontownInternalRepository): def __init__(self, baseChannel, serverId): ToontownInternalRepository.__init__(self, baseChannel, serverId, dcSuffix='UD') - if config.GetBool('want-mongo-client', False): - url = config.GetString('mongodb-url', 'mongodb://localhost') - replicaset = config.GetString('mongodb-replicaset', '') - if replicaset: - self.mongo = pymongo.MongoClient(url, replicaset=replicaset) - else: - self.mongo = pymongo.MongoClient(url) - db = (urlparse.urlparse(url).path or '/test')[1:] - self.mongodb = self.mongo[db] - self.notify.setInfo(True) def handleConnected(self): @@ -51,4 +39,5 @@ class ToontownUberRepository(ToontownInternalRepository): self.friendsManager = simbase.air.generateGlobalObject(OTP_DO_ID_TTS_FRIENDS_MANAGER, 'TTSFriendsManager') self.globalPartyMgr = simbase.air.generateGlobalObject(OTP_DO_ID_GLOBAL_PARTY_MANAGER, 'GlobalPartyManager') self.groupManager = simbase.air.generateGlobalObject(OPT_DO_ID_GROUP_MANAGER, 'GroupManager') + self.topToonsMgr = DistributedTopToonsManagerUD(self) diff --git a/toontown/uberdog/TopToonsGlobals.py b/toontown/uberdog/TopToonsGlobals.py index 51daf629..7786d828 100644 --- a/toontown/uberdog/TopToonsGlobals.py +++ b/toontown/uberdog/TopToonsGlobals.py @@ -13,3 +13,7 @@ CAT_VP = 2048 CAT_CFO = 4096 CAT_CJ = 8192 CAT_CEO = 16384 + +_CAT_BEGIN = CAT_COGS +_CAT_END = CAT_CEO +_CAT_ALL = (_CAT_END << 1) - 1 \ No newline at end of file