from direct.actor import Actor from direct.directnotify.DirectNotifyGlobal import * from direct.distributed import ClockDelta from direct.interval.IntervalGlobal import * from direct.showbase import PythonUtil from direct.showutil import Rope from direct.task import Task from pandac.PandaModules import * import DistributedFurnitureItem import PhoneGlobals from toontown.catalog import CatalogItem from toontown.catalog.CatalogGUI import CatalogGUI from toontown.catalog.CatalogItemListGUI import CatalogItemListGUI from toontown.catalog.CatalogItemSorter import CatalogItemSorter from toontown.quest import Quests from toontown.toonbase import TTLocalizer from toontown.toonbase import ToontownGlobals from toontown.toontowngui import TTDialog class DistributedPhone(DistributedFurnitureItem.DistributedFurnitureItem): notify = directNotify.newCategory('DistributedPhone') movieDelay = 0.5 def __init__(self, cr): DistributedFurnitureItem.DistributedFurnitureItem.__init__(self, cr) self.lastAvId = 0 self.hasLocalAvatar = 0 self.lastTime = 0 self.initialScale = None self.usedInitialScale = 0 self.toonScale = None self.phoneGui = None self.phoneDialog = None self.model = None self.cord = None self.receiverGeom = None self.receiverJoint = None self.phoneSphereEvent = 'phoneSphere' self.phoneSphereEnterEvent = 'enter' + self.phoneSphereEvent self.phoneGuiDoneEvent = 'phoneGuiDone' self.pickupMovieDoneEvent = 'phonePickupDone' self.numHouseItems = None self.interval = None self.intervalAvatar = None self.phoneInUse = 0 self.origToonHpr = None def announceGenerate(self): self.notify.debug('announceGenerate') DistributedFurnitureItem.DistributedFurnitureItem.announceGenerate(self) self.accept(self.phoneSphereEnterEvent, self.__handleEnterSphere) self.load() taskMgr.doMethodLater(6, self.ringIfHasPhoneQuest, self.uniqueName('ringDoLater')) def loadModel(self): self.model = Actor.Actor('phase_5.5/models/estate/prop_phone-mod', {'SS_phoneOut': 'phase_5.5/models/estate/prop_phone-SS_phoneOut', 'SS_takePhone': 'phase_5.5/models/estate/prop_phone-SS_takePhone', 'SS_phoneNeutral': 'phase_5.5/models/estate/prop_phone-SS_phoneNeutral', 'SS_phoneBack': 'phase_5.5/models/estate/prop_phone-SS_phoneBack', 'SM_phoneOut': 'phase_5.5/models/estate/prop_phone-SM_phoneOut', 'SM_takePhone': 'phase_5.5/models/estate/prop_phone-SM_takePhone', 'SM_phoneNeutral': 'phase_5.5/models/estate/prop_phone-SM_phoneNeutral', 'SM_phoneBack': 'phase_5.5/models/estate/prop_phone-SM_phoneBack', 'SL_phoneOut': 'phase_5.5/models/estate/prop_phone-SL_phoneOut', 'SL_takePhone': 'phase_5.5/models/estate/prop_phone-SL_takePhone', 'SL_phoneNeutral': 'phase_5.5/models/estate/prop_phone-SL_phoneNeutral', 'SL_phoneBack': 'phase_5.5/models/estate/prop_phone-SL_phoneBack', 'MS_phoneOut': 'phase_5.5/models/estate/prop_phone-MS_phoneOut', 'MS_takePhone': 'phase_5.5/models/estate/prop_phone-MS_takePhone', 'MS_phoneNeutral': 'phase_5.5/models/estate/prop_phone-MS_phoneNeutral', 'MS_phoneBack': 'phase_5.5/models/estate/prop_phone-MS_phoneBack', 'MM_phoneOut': 'phase_5.5/models/estate/prop_phone-MM_phoneOut', 'MM_takePhone': 'phase_5.5/models/estate/prop_phone-MM_takePhone', 'MM_phoneNeutral': 'phase_5.5/models/estate/prop_phone-MM_phoneNeutral', 'MM_phoneBack': 'phase_5.5/models/estate/prop_phone-MM_phoneBack', 'ML_phoneOut': 'phase_5.5/models/estate/prop_phone-ML_phoneOut', 'ML_takePhone': 'phase_5.5/models/estate/prop_phone-ML_takePhone', 'ML_phoneNeutral': 'phase_5.5/models/estate/prop_phone-ML_phoneNeutral', 'ML_phoneBack': 'phase_5.5/models/estate/prop_phone-ML_phoneBack', 'LS_phoneOut': 'phase_5.5/models/estate/prop_phone-LS_phoneOut', 'LS_takePhone': 'phase_5.5/models/estate/prop_phone-LS_takePhone', 'LS_phoneNeutral': 'phase_5.5/models/estate/prop_phone-LS_phoneNeutral', 'LS_phoneBack': 'phase_5.5/models/estate/prop_phone-LS_phoneBack', 'LM_phoneOut': 'phase_5.5/models/estate/prop_phone-LM_phoneOut', 'LM_takePhone': 'phase_5.5/models/estate/prop_phone-LM_takePhone', 'LM_phoneNeutral': 'phase_5.5/models/estate/prop_phone-LM_phoneNeutral', 'LM_phoneBack': 'phase_5.5/models/estate/prop_phone-LM_phoneBack', 'LL_phoneOut': 'phase_5.5/models/estate/prop_phone-LL_phoneOut', 'LL_takePhone': 'phase_5.5/models/estate/prop_phone-LL_takePhone', 'LL_phoneNeutral': 'phase_5.5/models/estate/prop_phone-LL_phoneNeutral', 'LL_phoneBack': 'phase_5.5/models/estate/prop_phone-LL_phoneBack'}) self.model.pose('SS_phoneOut', 0) self.receiverJoint = self.model.find('**/joint_receiver') self.receiverGeom = self.receiverJoint.getChild(0) mount = loader.loadModel('phase_5.5/models/estate/phoneMount-mod') mount.setTransparency(0, 1) self.model.reparentTo(mount) self.ringSfx = loader.loadSfx('phase_3.5/audio/sfx/telephone_ring.ogg') self.handleSfx = loader.loadSfx('phase_5.5/audio/sfx/telephone_handle2.ogg') self.hangUpSfx = loader.loadSfx('phase_5.5/audio/sfx/telephone_hang_up.ogg') self.pickUpSfx = loader.loadSfx('phase_5.5/audio/sfx/telephone_pickup1.ogg') if self.initialScale: mount.setScale(*self.initialScale) self.usedInitialScale = 1 phoneSphere = CollisionSphere(0, -0.66, 0, 0.2) phoneSphere.setTangible(0) phoneSphereNode = CollisionNode(self.phoneSphereEvent) phoneSphereNode.setIntoCollideMask(ToontownGlobals.WallBitmask) phoneSphereNode.addSolid(phoneSphere) mount.attachNewNode(phoneSphereNode) if not self.model.find('**/CurveNode7').isEmpty(): self.setupCord() return mount def setupCamera(self, mode): camera.wrtReparentTo(render) if mode == PhoneGlobals.PHONE_MOVIE_PICKUP: camera.posQuatInterval(1, (4, -4, base.localAvatar.getHeight()- 0.5), (35, -8, 0), other=base.localAvatar, blendType='easeOut').start() def setupCord(self): if self.cord: self.cord.detachNode() self.cord = None self.cord = Rope.Rope(self.uniqueName('phoneCord')) self.cord.setColor(0, 0, 0, 1) self.cord.setup(4, ((self.receiverGeom, (0, 0, 0)), (self.model.find('**/joint_curveNode1'), (0, 0, 0)), (self.model.find('**/joint_curveNode2'), (0, 0, 0)), (self.model.find('**/joint_curveNode3'), (0, 0, 0)), (self.model.find('**/joint_curveNode4'), (0, 0, 0)), (self.model.find('**/joint_curveNode5'), (0, 0, 0)), (self.model.find('**/joint_curveNode6'), (0, 0, 0)), (self.model.find('**/CurveNode7'), (0, 0, 0)))) self.cord.reparentTo(self.model) self.cord.node().setBounds(BoundingSphere(Point3(-1.0, -3.2, 2.6), 2.0)) def disable(self): self.notify.debug('disable') taskMgr.remove(self.uniqueName('ringDoLater')) self.clearInterval() if self.phoneGui: self.phoneGui.hide() self.phoneGui.unload() self.phoneGui = None if self.phoneDialog: self.phoneDialog.cleanup() self.phoneDialog = None self.__receiverToPhone() if self.hasLocalAvatar: self.freeAvatar() self.ignoreAll() DistributedFurnitureItem.DistributedFurnitureItem.disable(self) def delete(self): self.notify.debug('delete') self.model.cleanup() DistributedFurnitureItem.DistributedFurnitureItem.delete(self) def setInitialScale(self, sx, sy, sz): self.initialScale = (sx, sy, sz) if not self.usedInitialScale and self.model: self.setScale(*self.initialScale) self.usedInitialScale = 1 def __handleEnterSphere(self, collEntry): if self.smoothStarted: return if base.localAvatar.doId == self.lastAvId and globalClock.getFrameTime() <= self.lastTime + 0.5: self.notify.debug('Ignoring duplicate entry for avatar.') return if self.hasLocalAvatar: self.freeAvatar() if hasattr(base, 'wantPets') and base.wantPets: base.localAvatar.lookupPetDNA() self.notify.debug('Entering Phone Sphere....') taskMgr.remove(self.uniqueName('ringDoLater')) self.ignore(self.phoneSphereEnterEvent) self.cr.playGame.getPlace().detectedPhoneCollision() self.hasLocalAvatar = 1 self.sendUpdate('avatarEnter') def __handlePhoneDone(self): self.sendUpdate('avatarExit') self.ignore(self.phoneGuiDoneEvent) self.phoneGui = None def freeAvatar(self): if self.hasLocalAvatar: base.localAvatar.speed = 0 taskMgr.remove(self.uniqueName('lerpCamera')) base.localAvatar.posCamera(0, 0) if base.cr.playGame.place != None: base.cr.playGame.getPlace().setState('walk') self.hasLocalAvatar = 0 self.ignore(self.pickupMovieDoneEvent) self.accept(self.phoneSphereEnterEvent, self.__handleEnterSphere) self.lastTime = globalClock.getFrameTime() def setLimits(self, numHouseItems): self.numHouseItems = numHouseItems def setMovie(self, mode, avId, timestamp): elapsed = ClockDelta.globalClockDelta.localElapsedTime(timestamp, bits=32) elapsed = max(elapsed - self.movieDelay, 0) self.ignore(self.pickupMovieDoneEvent) if avId != 0: self.lastAvId = avId self.lastTime = globalClock.getFrameTime() isLocalToon = avId == base.localAvatar.doId avatar = self.cr.doId2do.get(avId) if mode == PhoneGlobals.PHONE_MOVIE_CLEAR: if self.phoneInUse: self.clearInterval() self.numHouseItems = None self.phoneInUse = 0 elif mode == PhoneGlobals.PHONE_MOVIE_EMPTY: if isLocalToon: self.phoneDialog = TTDialog.TTDialog(dialogName='PhoneEmpty', style=TTDialog.Acknowledge, text=TTLocalizer.DistributedPhoneEmpty, text_wordwrap=15, fadeScreen=1, command=self.__clearDialog) self.numHouseItems = None self.phoneInUse = 0 elif mode == PhoneGlobals.PHONE_MOVIE_NO_HOUSE: if isLocalToon: self.phoneDialog = TTDialog.TTDialog(dialogName='PhoneNoHouse', style=TTDialog.Acknowledge, text=TTLocalizer.DistributedPhoneNoHouse, text_wordwrap=15, fadeScreen=1, command=self.__clearDialog) self.numHouseItems = None self.phoneInUse = 0 elif mode == PhoneGlobals.PHONE_MOVIE_PICKUP: if avatar: interval = self.takePhoneInterval(avatar) if isLocalToon: self.setupCamera(mode) interval.setDoneEvent(self.pickupMovieDoneEvent) self.acceptOnce(self.pickupMovieDoneEvent, self.__showPhoneGui) self.playInterval(interval, elapsed, avatar) self.phoneInUse = 1 elif mode == PhoneGlobals.PHONE_MOVIE_HANGUP: if avatar: interval = self.replacePhoneInterval(avatar) self.playInterval(interval, elapsed, avatar) self.numHouseItems = None self.phoneInUse = 0 else: self.notify.warning('unknown mode in setMovie: %s' % mode) def __showPhoneGui(self): if self.toonScale: self.sendUpdate('setNewScale', [self.toonScale[0], self.toonScale[1], self.toonScale[2]]) self.phoneGui = CatalogGUI(self, doneEvent=self.phoneGuiDoneEvent) # Hide the phone until we get our popular items set. self.phoneGui.hide() self.__generateCatalogPages() self.acceptOnce('PopularItemsSet', self.__setPopularItems) self.cr.catalogManager.fetchPopularItems() self.accept(self.phoneGuiDoneEvent, self.__handlePhoneDone) self.accept('phoneAsleep', self.__handlePhoneAsleep) def __generateCatalogPages(self): itemList = base.localAvatar.monthlyCatalog.generateList() itemList += base.localAvatar.weeklyCatalog.generateList() itemList += base.localAvatar.backCatalog.generateList() sortedItems = CatalogItemSorter(itemList).sortItems() catalogItemList = CatalogItemListGUI(self.phoneGui) for item in sortedItems['FURNITURE']: catalogItemList.addItem(item, 'Furniture') for item in sortedItems['UNSORTED']: catalogItemList.addItem(item, 'Unsorted Items') self.phoneGui.tabButtons['FURNITURE_TAB'].setCatalogItemPages(catalogItemList.generatePages()) self.phoneGui.tabButtons['FURNITURE_TAB'].tabClicked() catalogItemList = CatalogItemListGUI(self.phoneGui) for item in sortedItems['EMOTIONS']: catalogItemList.addItem(item, 'Emotions') self.phoneGui.tabButtons['EMOTE_TAB'].setCatalogItemPages(catalogItemList.generatePages()) self.phoneGui.tabButtons['EMOTE_TAB'].tabClicked() catalogItemList = CatalogItemListGUI(self.phoneGui) for item in sortedItems['SPECIAL']: catalogItemList.addItem(item, 'Special') self.phoneGui.tabButtons['SPECIAL_TAB'].setCatalogItemPages(catalogItemList.generatePages()) self.phoneGui.tabButtons['SPECIAL_TAB'].tabClicked() catalogItemList = CatalogItemListGUI(self.phoneGui) for item in sortedItems['CLOTHING']: catalogItemList.addItem(item, 'Clothing') self.phoneGui.tabButtons['CLOTHING_TAB'].setCatalogItemPages(catalogItemList.generatePages()) self.phoneGui.tabButtons['CLOTHING_TAB'].tabClicked() catalogItemList = CatalogItemListGUI(self.phoneGui) for item in sortedItems['PHRASES']: catalogItemList.addItem(item, 'Phrases') self.phoneGui.tabButtons['PHRASES_TAB'].setCatalogItemPages(catalogItemList.generatePages()) self.phoneGui.tabButtons['PHRASES_TAB'].tabClicked() catalogItemList = CatalogItemListGUI(self.phoneGui) for item in sortedItems['NAMETAG']: catalogItemList.addItem(item, 'Nametag') self.phoneGui.tabButtons['NAMETAG_TAB'].setCatalogItemPages(catalogItemList.generatePages()) self.phoneGui.tabButtons['NAMETAG_TAB'].tabClicked() def __setPopularItems(self): # Generate a list of popular items. itemList = self.cr.catalogManager.popularItems.generateList() catalogItemList = CatalogItemListGUI(self.phoneGui) for item in itemList: catalogItemList.addItem(item, 'Popular') self.phoneGui.tabButtons['POPULAR_TAB'].setCatalogItemPages(catalogItemList.generatePages()) # Now that the popular items are set we can show the CatalogGUI self.phoneGui.show() # We want our default tab to be the popular tab. We need to click it twice to prevent a glitch. self.phoneGui.tabButtons['POPULAR_TAB'].tabClicked() def __handlePhoneAsleep(self): self.ignore('phoneAsleep') if self.phoneGui: self.phoneGui.unload() self.__handlePhoneDone() def requestPurchase(self, item, callback, optional = -1): blob = item.getBlob(store=CatalogItem.Customization) context = self.getCallbackContext(callback, [item]) self.sendUpdate('requestPurchaseMessage', [context, blob, optional]) def requestGiftPurchase(self, item, targetDoID, callback, optional = -1): blob = item.getBlob(store=CatalogItem.Customization) context = self.getCallbackContext(callback, [item]) self.sendUpdate('requestGiftPurchaseMessage', [context, targetDoID, blob, optional]) def requestPurchaseResponse(self, context, retcode): self.doCallbackContext(context, [retcode]) def requestGiftPurchaseResponse(self, context, retcode): self.doCallbackContext(context, [retcode]) def __clearDialog(self, event): self.phoneDialog.cleanup() self.phoneDialog = None self.freeAvatar() def takePhoneInterval(self, toon): torso = TextEncoder.upper(toon.style.torso[0]) legs = TextEncoder.upper(toon.style.legs[0]) phoneOutAnim = '%s%s_phoneOut' % (torso, legs) takePhoneAnim = '%s%s_takePhone' % (torso, legs) phoneNeutralAnim = '%s%s_phoneNeutral' % (torso, legs) self.toonScale = toon.getGeomNode().getChild(0).getScale(self.getParent()) walkTime = 1.0 scaleTime = 1.0 origScale = self.getScale() origToonPos = toon.getPos() origToonHpr = toon.getHpr() self.origToonHpr = origToonHpr self.setScale(self.toonScale) toon.setPosHpr(self, 0, -4.5, 0, 0, 0, 0) destToonPos = toon.getPos() destToonHpr = toon.getHpr() destToonHpr = VBase3(PythonUtil.fitSrcAngle2Dest(destToonHpr[0], origToonHpr[0]), destToonHpr[1], destToonHpr[2]) self.setScale(origScale) toon.setPos(origToonPos) toon.setHpr(origToonHpr) walkToPhone = Sequence(Func(toon.stopSmooth), Func(toon.loop, 'walk'), Func(base.playSfx, base.localAvatar.soundWalk), toon.posHprInterval(walkTime, destToonPos, destToonHpr, blendType='easeInOut'), Func(toon.loop, 'neutral'), Func(toon.startSmooth)) interval = Sequence(Parallel(walkToPhone, ActorInterval(self.model, phoneOutAnim), self.scaleInterval(scaleTime, self.toonScale, blendType='easeInOut')), Parallel(ActorInterval(self.model, takePhoneAnim), ActorInterval(toon, 'takePhone'), Sequence(Wait(0.625), Func(base.playSfx, self.pickUpSfx), Func(self.__receiverToHand, toon), Wait(1), Func(base.playSfx, self.handleSfx))), Func(self.model.loop, phoneNeutralAnim), Func(toon.loop, 'phoneNeutral'), Func(base.playSfx, self.ringSfx)) return interval def replacePhoneInterval(self, toon): torso = TextEncoder.upper(toon.style.torso[0]) legs = TextEncoder.upper(toon.style.legs[0]) phoneBackAnim = '%s%s_phoneBack' % (torso, legs) scaleTime = 1.0 interval = Sequence(Parallel(ActorInterval(self.model, phoneBackAnim), ActorInterval(toon, 'phoneBack'), Sequence(Wait(1.0), Func(self.__receiverToPhone), Func(base.playSfx, self.hangUpSfx))), self.scaleInterval(scaleTime, localAvatar.getGeomNode().getScale()[2], blendType='easeInOut'), Func(toon.loop, 'neutral')) if self.origToonHpr: interval.append(Func(toon.setHpr, self.origToonHpr)) self.origToonHpr = None if toon == base.localAvatar: interval.append(Func(self.freeAvatar)) return interval def __receiverToHand(self, toon): self.receiverGeom.reparentTo(toon.leftHand) self.receiverGeom.setPosHpr(0.0906813, 0.380375, 0.1, 32.41, 70.68, 137.04) def __receiverToPhone(self): self.receiverGeom.reparentTo(self.receiverJoint) self.receiverGeom.setPosHpr(0, 0, 0, 0, 0, 0) def playInterval(self, interval, elapsed, avatar): if self.interval != None: self.interval.finish() self.interval = None self.interval = interval self.interval.start(elapsed) if self.intervalAvatar != avatar: if self.intervalAvatar: self.ignore(self.intervalAvatar.uniqueName('disable')) if avatar: self.accept(avatar.uniqueName('disable'), self.clearInterval) self.intervalAvatar = avatar def clearInterval(self): if self.interval != None: self.interval.finish() self.interval = None if self.intervalAvatar: self.ignore(self.intervalAvatar.uniqueName('disable')) self.intervalAvatar = None self.__receiverToPhone() self.model.pose('SS_phoneOut', 0) self.phoneInUse = 0 def ringIfHasPhoneQuest(self, task): if Quests.avatarHasPhoneQuest(base.localAvatar) and not Quests.avatarHasCompletedPhoneQuest(base.localAvatar): self.ring() return Task.done def ring(self): if self.phoneInUse: return 0 phone = self.find('**/prop_phone') r = 2.0 w = 0.05 shakeOnce = Sequence(Func(phone.setR, r), Wait(w), Func(phone.setR, -r), Wait(w)) shakeSeq = Sequence() for i in xrange(16): shakeSeq.append(shakeOnce) ringIval = Parallel(Func(base.playSfx, self.ringSfx), shakeSeq, Func(phone.setR, 0)) self.playInterval(ringIval, 0.0, None) def purchaseItemComplete(self): self.phoneGui.updateItems()