class UnitInterpolator: def __init__(self, unit): self.unit = unit self.smooth_mover = SmoothMover() self.smooth_mover.set_smooth_mode(SmoothMover.SM_on) def update(self): self.smooth_mover.setPos(self.unit.x, self.unit.y, self.unit.z) self.smooth_mover.setHpr(self.unit.h, self.unit.p, self.unit.r) self.smooth_mover.setTimestamp(globalClock.get_frame_time()) self.smooth_mover.markPosition() def interpolate(self): self.smooth_mover.compute_and_apply_smooth_pos_hpr( self.unit.base_node, self.unit.base_node)
class DistributedBanquetTable(DistributedObject.DistributedObject, FSM.FSM, BanquetTableBase.BanquetTableBase): notify = DirectNotifyGlobal.directNotify.newCategory( 'DistributedBanquetTable') rotationsPerSeatIndex = [90, 90, 0, 0, -90, -90, 180, 180] pitcherMinH = -360 pitcherMaxH = 360 rotateSpeed = 30 waterPowerSpeed = base.config.GetDouble('water-power-speed', 15) waterPowerExponent = base.config.GetDouble('water-power-exponent', 0.75) useNewAnimations = True TugOfWarControls = False OnlyUpArrow = True if OnlyUpArrow: BASELINE_KEY_RATE = 3 else: BASELINE_KEY_RATE = 6 UPDATE_KEY_PRESS_RATE_TASK = 'BanquetTableUpdateKeyPressRateTask' YELLOW_POWER_THRESHOLD = 0.75 RED_POWER_THRESHOLD = 0.97 def __init__(self, cr): DistributedObject.DistributedObject.__init__(self, cr) FSM.FSM.__init__(self, 'DistributedBanquetTable') self.boss = None self.index = -1 self.diners = {} self.dinerStatus = {} self.serviceLocs = {} self.chairLocators = {} self.sitLocators = {} self.activeIntervals = {} self.dinerStatusIndicators = {} self.preparedForPhaseFour = False self.avId = 0 self.toon = None self.pitcherSmoother = SmoothMover() self.pitcherSmoother.setSmoothMode(SmoothMover.SMOn) self.smoothStarted = 0 self.__broadcastPeriod = 0.2 self.changeSeq = 0 self.lastChangeSeq = 0 self.pitcherAdviceLabel = None self.fireLength = 250 self.fireTrack = None self.hitObject = None self.setupPowerBar() self.aimStart = None self.toonPitcherPosition = Point3(0, -2, 0) self.allowLocalRequestControl = True self.fadeTrack = None self.grabTrack = None self.gotHitByBoss = False self.keyTTL = [] self.keyRate = 0 self.buttons = [0, 1] self.lastPowerFired = 0 self.moveSound = None self.releaseTrack = None return def disable(self): DistributedObject.DistributedObject.disable(self) taskMgr.remove(self.triggerName) taskMgr.remove(self.smoothName) taskMgr.remove(self.watchControlsName) taskMgr.remove(self.pitcherAdviceName) taskMgr.remove(self.posHprBroadcastName) taskMgr.remove(self.waterPowerTaskName) if self.releaseTrack: self.releaseTrack.finish() self.releaseTrack = None if self.fireTrack: self.fireTrack.finish() self.fireTrack = None self.cleanupIntervals() return def delete(self): DistributedObject.DistributedObject.delete(self) self.boss = None self.ignoreAll() for indicator in self.dinerStatusIndicators.values(): indicator.delete() self.dinerStatusIndicators = {} for diner in self.diners.values(): diner.delete() self.diners = {} self.powerBar.destroy() self.powerBar = None self.pitcherMoveSfx.stop() return def announceGenerate(self): DistributedObject.DistributedObject.announceGenerate(self) self.loadAssets() self.smoothName = self.uniqueName('pitcherSmooth') self.pitcherAdviceName = self.uniqueName('pitcherAdvice') self.posHprBroadcastName = self.uniqueName('pitcherBroadcast') self.waterPowerTaskName = self.uniqueName('updateWaterPower') self.triggerName = self.uniqueName('trigger') self.watchControlsName = self.uniqueName('watchControls') def setBossCogId(self, bossCogId): self.bossCogId = bossCogId self.boss = base.cr.doId2do[bossCogId] self.boss.setTable(self, self.index) def setIndex(self, index): self.index = index def setState(self, state, avId, extraInfo): self.gotHitByBoss = extraInfo if state == 'F': self.demand('Off') elif state == 'N': self.demand('On') elif state == 'I': self.demand('Inactive') elif state == 'R': self.demand('Free') elif state == 'C': self.demand('Controlled', avId) elif state == 'L': self.demand('Flat', avId) else: self.notify.error('Invalid state from AI: %s' % state) def setNumDiners(self, numDiners): self.numDiners = numDiners def setDinerInfo(self, hungryDurations, eatingDurations, dinerLevels): self.dinerInfo = {} for i in xrange(len(hungryDurations)): hungryDur = hungryDurations[i] eatingDur = eatingDurations[i] dinerLevel = dinerLevels[i] self.dinerInfo[i] = (hungryDur, eatingDur, dinerLevel) def loadAssets(self): self.tableGroup = loader.loadModel( 'phase_12/models/bossbotHQ/BanquetTableChairs') tableLocator = self.boss.geom.find('**/TableLocator_%d' % (self.index + 1)) if tableLocator.isEmpty(): self.tableGroup.reparentTo(render) self.tableGroup.setPos(0, 75, 0) else: self.tableGroup.reparentTo(tableLocator) self.tableGeom = self.tableGroup.find('**/Geometry') self.setupDiners() self.setupChairCols() self.squirtSfx = loader.loadSfx( 'phase_4/audio/sfx/AA_squirt_seltzer_miss.ogg') self.hitBossSfx = loader.loadSfx( 'phase_5/audio/sfx/SA_watercooler_spray_only.ogg') self.hitBossSoundInterval = SoundInterval(self.hitBossSfx, node=self.boss, volume=1.0) self.serveFoodSfx = loader.loadSfx( 'phase_4/audio/sfx/MG_sfx_travel_game_bell_for_trolley.ogg') self.pitcherMoveSfx = base.loader.loadSfx( 'phase_4/audio/sfx/MG_cannon_adjust.ogg') def setupDiners(self): for i in xrange(self.numDiners): newDiner = self.createDiner(i) self.diners[i] = newDiner self.dinerStatus[i] = self.HUNGRY def createDiner(self, i): diner = Suit.Suit() diner.dna = SuitDNA.SuitDNA() level = self.dinerInfo[i][2] level -= 4 diner.dna.newSuitRandom(level=level, dept='c') diner.setDNA(diner.dna) if self.useNewAnimations: diner.loop('sit', fromFrame=i) else: diner.pose('landing', 0) locator = self.tableGroup.find('**/chair_%d' % (i + 1)) locatorScale = locator.getNetTransform().getScale()[0] correctHeadingNp = locator.attachNewNode('correctHeading') self.chairLocators[i] = correctHeadingNp heading = self.rotationsPerSeatIndex[i] correctHeadingNp.setH(heading) sitLocator = correctHeadingNp.attachNewNode('sitLocator') base.sitLocator = sitLocator pos = correctHeadingNp.getPos(render) if SuitDNA.getSuitBodyType(diner.dna.name) == 'c': sitLocator.setPos(0.5, 3.65, -3.75) else: sitLocator.setZ(-2.4) sitLocator.setY(2.5) sitLocator.setX(0.5) self.sitLocators[i] = sitLocator diner.setScale(1.0 / locatorScale) diner.reparentTo(sitLocator) newLoc = NodePath('serviceLoc-%d-%d' % (self.index, i)) newLoc.reparentTo(correctHeadingNp) newLoc.setPos(0, 3.0, 1) self.serviceLocs[i] = newLoc base.serviceLoc = newLoc head = diner.find('**/joint_head') newIndicator = DinerStatusIndicator.DinerStatusIndicator(parent=head, pos=Point3( 0, 0, 3.5), scale=5.0) newIndicator.wrtReparentTo(diner) self.dinerStatusIndicators[i] = newIndicator return diner def setupChairCols(self): for i in xrange(self.numDiners): chairCol = self.tableGroup.find('**/collision_chair_%d' % (i + 1)) colName = 'ChairCol-%d-%d' % (self.index, i) chairCol.setTag('chairIndex', str(i)) chairCol.setName(colName) chairCol.setCollideMask(ToontownGlobals.WallBitmask) self.accept('enter' + colName, self.touchedChair) def touchedChair(self, colEntry): chairIndex = int(colEntry.getIntoNodePath().getTag('chairIndex')) if chairIndex in self.dinerStatus: status = self.dinerStatus[chairIndex] if status in (self.HUNGRY, self.ANGRY): self.boss.localToonTouchedChair(self.index, chairIndex) def serveFood(self, food, chairIndex): self.removeFoodModel(chairIndex) serviceLoc = self.serviceLocs.get(chairIndex) if not food or food.isEmpty(): foodModel = loader.loadModel('phase_12/models/bossbotHQ/canoffood') foodModel.setScale(ToontownGlobals.BossbotFoodModelScale) foodModel.reparentTo(serviceLoc) else: food.wrtReparentTo(serviceLoc) tray = food.find('**/tray') if not tray.isEmpty(): tray.hide() ivalDuration = 1.5 foodMoveIval = Parallel( SoundInterval(self.serveFoodSfx, node=food), ProjectileInterval(food, duration=ivalDuration, startPos=food.getPos(serviceLoc), endPos=serviceLoc.getPos(serviceLoc)), LerpHprInterval(food, ivalDuration, Point3(0, -360, 0))) intervalName = 'serveFood-%d-%d' % (self.index, chairIndex) foodMoveIval.start() self.activeIntervals[intervalName] = foodMoveIval def setDinerStatus(self, chairIndex, status): if chairIndex in self.dinerStatus: oldStatus = self.dinerStatus[chairIndex] self.dinerStatus[chairIndex] = status if oldStatus != status: if status == self.EATING: self.changeDinerToEating(chairIndex) elif status == self.HUNGRY: self.changeDinerToHungry(chairIndex) elif status == self.ANGRY: self.changeDinerToAngry(chairIndex) elif status == self.DEAD: self.changeDinerToDead(chairIndex) elif status == self.HIDDEN: self.changeDinerToHidden(chairIndex) def removeFoodModel(self, chairIndex): serviceLoc = self.serviceLocs.get(chairIndex) if serviceLoc: for i in xrange(serviceLoc.getNumChildren()): serviceLoc.getChild(0).removeNode() def changeDinerToEating(self, chairIndex): indicator = self.dinerStatusIndicators.get(chairIndex) eatingDuration = self.dinerInfo[chairIndex][1] if indicator: indicator.request('Eating', eatingDuration) diner = self.diners[chairIndex] intervalName = 'eating-%d-%d' % (self.index, chairIndex) eatInTime = 32.0 / 24.0 eatOutTime = 21.0 / 24.0 eatLoopTime = 19 / 24.0 rightHand = diner.getRightHand() waitTime = 5 loopDuration = eatingDuration - eatInTime - eatOutTime - waitTime serviceLoc = self.serviceLocs[chairIndex] def foodAttach(self=self, diner=diner): foodModel = self.serviceLocs[chairIndex].getChild(0) (foodModel.reparentTo(diner.getRightHand()), ) (foodModel.setHpr(Point3(0, -94, 0)), ) (foodModel.setPos(Point3(-0.15, -0.7, -0.4)), ) scaleAdj = 1 if SuitDNA.getSuitBodyType(diner.dna.name) == 'c': scaleAdj = 0.6 (foodModel.setPos(Point3(0.1, -0.25, -0.31)), ) else: scaleAdj = 0.8 (foodModel.setPos(Point3(-0.25, -0.85, -0.34)), ) oldScale = foodModel.getScale() newScale = oldScale * scaleAdj foodModel.setScale(newScale) def foodDetach(self=self, diner=diner): foodModel = diner.getRightHand().getChild(0) (foodModel.reparentTo(serviceLoc), ) (foodModel.setPosHpr(0, 0, 0, 0, 0, 0), ) scaleAdj = 1 if SuitDNA.getSuitBodyType(diner.dna.name) == 'c': scaleAdj = 0.6 else: scakeAdj = 0.8 oldScale = foodModel.getScale() newScale = oldScale / scaleAdj foodModel.setScale(newScale) eatIval = Sequence( ActorInterval(diner, 'sit', duration=waitTime), ActorInterval(diner, 'sit-eat-in', startFrame=0, endFrame=6), Func(foodAttach), ActorInterval(diner, 'sit-eat-in', startFrame=6, endFrame=32), ActorInterval(diner, 'sit-eat-loop', duration=loopDuration, loop=1), ActorInterval(diner, 'sit-eat-out', startFrame=0, endFrame=12), Func(foodDetach), ActorInterval(diner, 'sit-eat-out', startFrame=12, endFrame=21)) eatIval.start() self.activeIntervals[intervalName] = eatIval def changeDinerToHungry(self, chairIndex): intervalName = 'eating-%d-%d' % (self.index, chairIndex) if intervalName in self.activeIntervals: self.activeIntervals[intervalName].finish() self.removeFoodModel(chairIndex) indicator = self.dinerStatusIndicators.get(chairIndex) if indicator: indicator.request('Hungry', self.dinerInfo[chairIndex][0]) diner = self.diners[chairIndex] if random.choice([0, 1]): diner.loop('sit-hungry-left') else: diner.loop('sit-hungry-right') def changeDinerToAngry(self, chairIndex): self.removeFoodModel(chairIndex) indicator = self.dinerStatusIndicators.get(chairIndex) if indicator: indicator.request('Angry') diner = self.diners[chairIndex] diner.loop('sit-angry') def changeDinerToDead(self, chairIndex): def removeDeathSuit(suit, deathSuit): if not deathSuit.isEmpty(): deathSuit.detachNode() suit.cleanupLoseActor() self.removeFoodModel(chairIndex) indicator = self.dinerStatusIndicators.get(chairIndex) if indicator: indicator.request('Dead') diner = self.diners[chairIndex] deathSuit = diner locator = self.tableGroup.find('**/chair_%d' % (chairIndex + 1)) deathSuit = diner.getLoseActor() ival = Sequence( Func(self.notify.debug, 'before actorinterval sit-lose'), ActorInterval(diner, 'sit-lose'), Func(self.notify.debug, 'before deathSuit.setHpr'), Func(deathSuit.setHpr, diner.getHpr()), Func(self.notify.debug, 'before diner.hide'), Func(diner.hide), Func(self.notify.debug, 'before deathSuit.reparentTo'), Func(deathSuit.reparentTo, self.chairLocators[chairIndex]), Func(self.notify.debug, 'befor ActorInterval lose'), ActorInterval(deathSuit, 'lose', duration=MovieUtil.SUIT_LOSE_DURATION), Func(self.notify.debug, 'before remove deathsuit'), Func(removeDeathSuit, diner, deathSuit, name='remove-death-suit-%d-%d' % (chairIndex, self.index)), Func(self.notify.debug, 'diner.stash'), Func(diner.stash)) spinningSound = base.loader.loadSfx( 'phase_3.5/audio/sfx/Cog_Death.ogg') deathSound = base.loader.loadSfx( 'phase_3.5/audio/sfx/ENC_cogfall_apart.ogg') deathSoundTrack = Sequence( Wait(0.8), SoundInterval(spinningSound, duration=1.2, startTime=1.5, volume=0.2, node=deathSuit), SoundInterval(spinningSound, duration=3.0, startTime=0.6, volume=0.8, node=deathSuit), SoundInterval(deathSound, volume=0.32, node=deathSuit)) intervalName = 'dinerDie-%d-%d' % (self.index, chairIndex) deathIval = Parallel(ival, deathSoundTrack) deathIval.start() self.activeIntervals[intervalName] = deathIval def changeDinerToHidden(self, chairIndex): self.removeFoodModel(chairIndex) indicator = self.dinerStatusIndicators.get(chairIndex) if indicator: indicator.request('Inactive') diner = self.diners[chairIndex] diner.hide() def setAllDinersToSitNeutral(self): startFrame = 0 for diner in self.diners.values(): if not diner.isHidden(): diner.loop('sit', fromFrame=startFrame) startFrame += 1 def cleanupIntervals(self): for interval in self.activeIntervals.values(): interval.finish() self.activeIntervals = {} def clearInterval(self, name, finish=1): if name in self.activeIntervals: ival = self.activeIntervals[name] if finish: ival.finish() else: ival.pause() if name in self.activeIntervals: del self.activeIntervals[name] else: self.notify.debug('interval: %s already cleared' % name) def finishInterval(self, name): if name in self.activeIntervals: interval = self.activeIntervals[name] interval.finish() def getNotDeadInfo(self): notDeadList = [] for i in xrange(self.numDiners): if self.dinerStatus[i] != self.DEAD: notDeadList.append((self.index, i, 12)) return notDeadList def enterOn(self): pass def exitOn(self): pass def enterInactive(self): for chairIndex in xrange(self.numDiners): indicator = self.dinerStatusIndicators.get(chairIndex) if indicator: indicator.request('Inactive') self.removeFoodModel(chairIndex) def exitInactive(self): pass def enterFree(self): self.resetPowerBar() if self.fadeTrack: self.fadeTrack.finish() self.fadeTrack = None self.prepareForPhaseFour() if self.avId == localAvatar.doId: self.tableGroup.setAlphaScale(0.3) self.tableGroup.setTransparency(1) taskMgr.doMethodLater(5, self.__allowDetect, self.triggerName) self.fadeTrack = Sequence( Func(self.tableGroup.setTransparency, 1), self.tableGroup.colorScaleInterval(0.2, VBase4(1, 1, 1, 0.3))) self.fadeTrack.start() self.allowLocalRequestControl = False else: self.allowLocalRequestControl = True self.avId = 0 return def exitFree(self): pass def touchedTable(self, colEntry): tableIndex = int(colEntry.getIntoNodePath().getTag('tableIndex')) if self.state == 'Free' and self.avId == 0 and self.allowLocalRequestControl: self.d_requestControl() def prepareForPhaseFour(self): if not self.preparedForPhaseFour: for i in xrange(8): chair = self.tableGroup.find('**/chair_%d' % (i + 1)) if not chair.isEmpty(): chair.hide() colChairs = self.tableGroup.findAllMatches('**/ChairCol*') for i in xrange(colChairs.getNumPaths()): col = colChairs.getPath(i) col.stash() colChairs = self.tableGroup.findAllMatches( '**/collision_chair*') for i in xrange(colChairs.getNumPaths()): col = colChairs.getPath(i) col.stash() tableCol = self.tableGroup.find('**/collision_table') colName = 'TableCol-%d' % self.index tableCol.setTag('tableIndex', str(self.index)) tableCol.setName(colName) tableCol.setCollideMask(ToontownGlobals.WallBitmask | ToontownGlobals.BanquetTableBitmask) self.accept('enter' + colName, self.touchedTable) self.preparedForPhaseFour = True self.waterPitcherModel = loader.loadModel( 'phase_12/models/bossbotHQ/tt_m_ara_bhq_seltzerBottle') lampNode = self.tableGroup.find('**/lamp_med_5') pos = lampNode.getPos(self.tableGroup) lampNode.hide() bottleLocator = self.tableGroup.find('**/bottle_locator') pos = bottleLocator.getPos(self.tableGroup) self.waterPitcherNode = self.tableGroup.attachNewNode( 'pitcherNode') self.waterPitcherNode.setPos(pos) self.waterPitcherModel.reparentTo(self.waterPitcherNode) self.waterPitcherModel.ls() self.nozzle = self.waterPitcherModel.find('**/nozzle_tip') self.handLocator = self.waterPitcherModel.find('**/hand_locator') self.handPos = self.handLocator.getPos() def d_requestControl(self): self.sendUpdate('requestControl') def d_requestFree(self, gotHitByBoss): self.sendUpdate('requestFree', [gotHitByBoss]) def enterControlled(self, avId): self.prepareForPhaseFour() self.avId = avId toon = base.cr.doId2do.get(avId) if not toon: return self.toon = toon self.grabTrack = self.makeToonGrabInterval(toon) self.notify.debug('grabTrack=%s' % self.grabTrack) self.pitcherCamPos = Point3(0, -50, 40) self.pitcherCamHpr = Point3(0, -21, 0) if avId == localAvatar.doId: self.boss.toMovieMode() self.__enableControlInterface() self.startPosHprBroadcast() self.grabTrack = Sequence( self.grabTrack, Func(camera.wrtReparentTo, localAvatar), LerpPosHprInterval(camera, 1, self.pitcherCamPos, self.pitcherCamHpr), Func(self.boss.toCraneMode)) if self.TugOfWarControls: self.__spawnUpdateKeyPressRateTask() self.accept('exitCrane', self.gotBossZapped) else: self.startSmooth() toon.stopSmooth() self.grabTrack.start() def exitControlled(self): self.ignore('exitCrane') if self.grabTrack: self.grabTrack.finish() self.grabTrack = None nextState = self.getCurrentOrNextState() self.notify.debug('nextState=%s' % nextState) if nextState == 'Flat': place = base.cr.playGame.getPlace() self.notify.debug('%s' % place.fsm) if self.avId == localAvatar.doId: self.__disableControlInterface() else: if self.toon and not self.toon.isDisabled(): self.toon.loop('neutral') self.toon.startSmooth() self.releaseTrack = self.makeToonReleaseInterval(self.toon) self.stopPosHprBroadcast() self.stopSmooth() if self.avId == localAvatar.doId: localAvatar.wrtReparentTo(render) self.__disableControlInterface() camera.reparentTo(base.localAvatar) camera.setPos(base.localAvatar.cameraPositions[0][0]) camera.setHpr(0, 0, 0) self.goToFinalBattle() self.safeBossToFinalBattleMode() else: toon = base.cr.doId2do.get(self.avId) if toon: toon.wrtReparentTo(render) self.releaseTrack.start() return def safeBossToFinalBattleMode(self): if self.boss: self.boss.toFinalBattleMode() def goToFinalBattle(self): if self.cr: place = self.cr.playGame.getPlace() if place and hasattr(place, 'fsm'): if place.fsm.getCurrentState().getName() == 'crane': place.setState('finalBattle') def makeToonGrabInterval(self, toon): toon.pose('leverNeutral', 0) toon.update() rightHandPos = toon.rightHand.getPos(toon) self.toonPitcherPosition = Point3(self.handPos[0] - rightHandPos[0], self.handPos[1] - rightHandPos[1], 0) destZScale = rightHandPos[2] / self.handPos[2] grabIval = Sequence( Func(toon.wrtReparentTo, self.waterPitcherNode), Func(toon.loop, 'neutral'), Parallel( ActorInterval(toon, 'jump'), Sequence( Wait(0.43), Parallel( ProjectileInterval(toon, duration=0.9, startPos=toon.getPos( self.waterPitcherNode), endPos=self.toonPitcherPosition), LerpHprInterval(toon, 0.9, Point3(0, 0, 0)), LerpScaleInterval(self.waterPitcherModel, 0.9, Point3(1, 1, destZScale))))), Func(toon.setPos, self.toonPitcherPosition), Func(toon.loop, 'leverNeutral')) return grabIval def makeToonReleaseInterval(self, toon): temp1 = self.waterPitcherNode.attachNewNode('temp1') temp1.setPos(self.toonPitcherPosition) temp2 = self.waterPitcherNode.attachNewNode('temp2') temp2.setPos(0, -10, -self.waterPitcherNode.getZ()) startPos = temp1.getPos(render) endPos = temp2.getPos(render) temp1.removeNode() temp2.removeNode() def getSlideToPos(toon=toon): return render.getRelativePoint(toon, Point3(0, -10, 0)) if self.gotHitByBoss: self.notify.debug('creating zap interval instead') grabIval = Sequence( Func(toon.loop, 'neutral'), Func(toon.wrtReparentTo, render), Parallel(ActorInterval(toon, 'slip-backward'), toon.posInterval(0.5, getSlideToPos, fluid=1))) else: grabIval = Sequence( Func(toon.loop, 'neutral'), Func(toon.wrtReparentTo, render), Parallel( ActorInterval(toon, 'jump'), Sequence( Wait(0.43), ProjectileInterval(toon, duration=0.9, startPos=startPos, endPos=endPos)))) return grabIval def b_clearSmoothing(self): self.d_clearSmoothing() self.clearSmoothing() def d_clearSmoothing(self): self.sendUpdate('clearSmoothing', [0]) def clearSmoothing(self, bogus=None): self.pitcherSmoother.clearPositions(1) def doSmoothTask(self, task): self.pitcherSmoother.computeAndApplySmoothHpr(self.waterPitcherNode) return Task.cont def startSmooth(self): if not self.smoothStarted: taskName = self.smoothName taskMgr.remove(taskName) self.reloadPosition() taskMgr.add(self.doSmoothTask, taskName) self.smoothStarted = 1 def stopSmooth(self): if self.smoothStarted: taskName = self.smoothName taskMgr.remove(taskName) self.forceToTruePosition() self.smoothStarted = 0 def __enableControlInterface(self): gui = loader.loadModel('phase_3.5/models/gui/avatar_panel_gui') self.closeButton = DirectButton(image=(gui.find('**/CloseBtn_UP'), gui.find('**/CloseBtn_DN'), gui.find('**/CloseBtn_Rllvr'), gui.find('**/CloseBtn_UP')), relief=None, scale=2, text=TTLocalizer.BossbotPitcherLeave, text_scale=0.04, text_pos=(0, -0.07), text_fg=VBase4(1, 1, 1, 1), pos=(1.05, 0, -0.82), command=self.__exitPitcher) self.accept('escape', self.__exitPitcher) self.accept('control', self.__controlPressed) self.accept('control-up', self.__controlReleased) self.accept('InputState-forward', self.__upArrow) self.accept('InputState-reverse', self.__downArrow) self.accept('InputState-turnLeft', self.__leftArrow) self.accept('InputState-turnRight', self.__rightArrow) self.accept('arrow_up', self.__upArrowKeyPressed) self.accept('arrow_down', self.__downArrowKeyPressed) taskMgr.add(self.__watchControls, self.watchControlsName) taskMgr.doMethodLater(5, self.__displayPitcherAdvice, self.pitcherAdviceName) self.arrowVert = 0 self.arrowHorz = 0 self.powerBar.show() return def __disableControlInterface(self): if self.closeButton: self.closeButton.destroy() self.closeButton = None self.__cleanupPitcherAdvice() self.ignore('escape') self.ignore('control') self.ignore('control-up') self.ignore('InputState-forward') self.ignore('InputState-reverse') self.ignore('InputState-turnLeft') self.ignore('InputState-turnRight') self.ignore('arrow_up') self.ignore('arrow_down') self.arrowVert = 0 self.arrowHorz = 0 taskMgr.remove(self.watchControlsName) taskMgr.remove(self.waterPowerTaskName) self.resetPowerBar() self.aimStart = None self.powerBar.hide() if self.TugOfWarControls: self.__killUpdateKeyPressRateTask() self.keyTTL = [] self.__setMoveSound(None) return def __displayPitcherAdvice(self, task): if self.pitcherAdviceLabel == None: self.pitcherAdviceLabel = DirectLabel( text=TTLocalizer.BossbotPitcherAdvice, text_fg=VBase4(1, 1, 1, 1), text_align=TextNode.ACenter, relief=None, pos=(0, 0, 0.69), scale=0.1) return def __cleanupPitcherAdvice(self): if self.pitcherAdviceLabel: self.pitcherAdviceLabel.destroy() self.pitcherAdviceLabel = None taskMgr.remove(self.pitcherAdviceName) return def showExiting(self): if self.closeButton: self.closeButton.destroy() self.closeButton = DirectLabel( relief=None, text=TTLocalizer.BossbotPitcherLeaving, pos=(1.05, 0, -0.88), text_pos=(0, 0), text_scale=0.06, text_fg=VBase4(1, 1, 1, 1)) self.__cleanupPitcherAdvice() return def __exitPitcher(self): self.showExiting() self.d_requestFree(False) def __controlPressed(self): self.__cleanupPitcherAdvice() if self.TugOfWarControls: if self.power: self.aimStart = 1 self.__endFireWater() elif self.state == 'Controlled': self.__beginFireWater() def __controlReleased(self): if self.TugOfWarControls: pass elif self.state == 'Controlled': self.__endFireWater() def __upArrow(self, pressed): self.__incrementChangeSeq() self.__cleanupPitcherAdvice() if pressed: self.arrowVert = 1 elif self.arrowVert > 0: self.arrowVert = 0 def __downArrow(self, pressed): self.__incrementChangeSeq() self.__cleanupPitcherAdvice() if pressed: self.arrowVert = -1 elif self.arrowVert < 0: self.arrowVert = 0 def __rightArrow(self, pressed): self.__incrementChangeSeq() self.__cleanupPitcherAdvice() if pressed: self.arrowHorz = 1 elif self.arrowHorz > 0: self.arrowHorz = 0 def __leftArrow(self, pressed): self.__incrementChangeSeq() self.__cleanupPitcherAdvice() if pressed: self.arrowHorz = -1 elif self.arrowHorz < 0: self.arrowHorz = 0 def __incrementChangeSeq(self): self.changeSeq = self.changeSeq + 1 & 255 def stopPosHprBroadcast(self): taskName = self.posHprBroadcastName taskMgr.remove(taskName) def startPosHprBroadcast(self): taskName = self.posHprBroadcastName self.b_clearSmoothing() self.d_sendPitcherPos() taskMgr.remove(taskName) taskMgr.doMethodLater(self.__broadcastPeriod, self.__posHprBroadcast, taskName) def __posHprBroadcast(self, task): self.d_sendPitcherPos() taskName = self.posHprBroadcastName taskMgr.doMethodLater(self.__broadcastPeriod, self.__posHprBroadcast, taskName) return Task.done def d_sendPitcherPos(self): timestamp = globalClockDelta.getFrameNetworkTime() self.sendUpdate( 'setPitcherPos', [self.changeSeq, self.waterPitcherNode.getH(), timestamp]) def setPitcherPos(self, changeSeq, h, timestamp): self.changeSeq = changeSeq if self.smoothStarted: now = globalClock.getFrameTime() local = globalClockDelta.networkToLocalTime(timestamp, now) self.pitcherSmoother.setH(h) self.pitcherSmoother.setTimestamp(local) self.pitcherSmoother.markPosition() else: self.waterPitcherNode.setH(h) def __watchControls(self, task): if self.arrowHorz: self.__movePitcher(self.arrowHorz) else: self.__setMoveSound(None) return Task.cont def __movePitcher(self, xd): dt = globalClock.getDt() h = self.waterPitcherNode.getH() - xd * self.rotateSpeed * dt h %= 360 self.notify.debug( 'rotSpeed=%.2f curH=%.2f xd =%.2f, dt = %.2f, h=%.2f' % (self.rotateSpeed, self.waterPitcherNode.getH(), xd, dt, h)) limitH = h self.waterPitcherNode.setH(limitH) if xd: self.__setMoveSound(self.pitcherMoveSfx) def reloadPosition(self): self.pitcherSmoother.clearPositions(0) self.pitcherSmoother.setHpr(self.waterPitcherNode.getHpr()) self.pitcherSmoother.setPhonyTimestamp() def forceToTruePosition(self): if self.pitcherSmoother.getLatestPosition(): self.pitcherSmoother.applySmoothHpr(self.waterPitcherNode) self.pitcherSmoother.clearPositions(1) def getSprayTrack(self, color, origin, target, dScaleUp, dHold, dScaleDown, horizScale=1.0, vertScale=1.0, parent=render): track = Sequence() SPRAY_LEN = 1.5 sprayProp = MovieUtil.globalPropPool.getProp('spray') sprayScale = hidden.attachNewNode('spray-parent') sprayRot = hidden.attachNewNode('spray-rotate') spray = sprayRot spray.setColor(color) if color[3] < 1.0: spray.setTransparency(1) def showSpray(sprayScale, sprayRot, sprayProp, origin, target, parent): if callable(origin): origin = origin() if callable(target): target = target() sprayRot.reparentTo(parent) sprayRot.clearMat() sprayScale.reparentTo(sprayRot) sprayScale.clearMat() sprayProp.reparentTo(sprayScale) sprayProp.clearMat() sprayRot.setPos(origin) sprayRot.lookAt(Point3(target)) track.append( Func(showSpray, sprayScale, sprayRot, sprayProp, origin, target, parent)) def calcTargetScale(target=target, origin=origin, horizScale=horizScale, vertScale=vertScale): if callable(target): target = target() if callable(origin): origin = origin() distance = Vec3(target - origin).length() yScale = distance / SPRAY_LEN targetScale = Point3(yScale * horizScale, yScale, yScale * vertScale) return targetScale track.append( LerpScaleInterval(sprayScale, dScaleUp, calcTargetScale, startScale=Point3(0.01, 0.01, 0.01))) track.append(Func(self.checkHitObject)) track.append(Wait(dHold)) def prepareToShrinkSpray(spray, sprayProp, origin, target): if callable(target): target = target() if callable(origin): origin = origin() sprayProp.setPos(Point3(0.0, -SPRAY_LEN, 0.0)) spray.setPos(target) track.append( Func(prepareToShrinkSpray, spray, sprayProp, origin, target)) track.append( LerpScaleInterval(sprayScale, dScaleDown, Point3(0.01, 0.01, 0.01))) def hideSpray(spray, sprayScale, sprayRot, sprayProp, propPool): sprayProp.detachNode() MovieUtil.removeProp(sprayProp) sprayRot.removeNode() sprayScale.removeNode() track.append( Func(hideSpray, spray, sprayScale, sprayRot, sprayProp, MovieUtil.globalPropPool)) return track def checkHitObject(self): if not self.hitObject: return if self.avId != base.localAvatar.doId: return tag = self.hitObject.getNetTag('pieCode') pieCode = int(tag) if pieCode == ToontownGlobals.PieCodeBossCog: self.hitBossSoundInterval.start() self.sendUpdate('waterHitBoss', [self.index]) if self.TugOfWarControls: damage = 1 if self.lastPowerFired < self.YELLOW_POWER_THRESHOLD: damage = 1 elif self.lastPowerFired < self.RED_POWER_THRESHOLD: damage = 2 else: damage = 3 self.boss.d_hitBoss(damage) else: damage = 1 if self.lastPowerFired < self.YELLOW_POWER_THRESHOLD: damage = 1 elif self.lastPowerFired < self.RED_POWER_THRESHOLD: damage = 2 else: damage = 3 self.boss.d_hitBoss(damage) def waterHitBoss(self, tableIndex): if self.index == tableIndex: self.hitBossSoundInterval.start() def setupPowerBar(self): self.powerBar = DirectWaitBar(pos=(0.0, 0, -0.94), relief=DGG.SUNKEN, frameSize=(-2.0, 2.0, -0.2, 0.2), borderWidth=(0.02, 0.02), scale=0.25, range=1, sortOrder=50, frameColor=(0.5, 0.5, 0.5, 0.5), barColor=(0.75, 0.75, 1.0, 0.8), text='', text_scale=0.26, text_fg=(1, 1, 1, 1), text_align=TextNode.ACenter, text_pos=(0, -0.05)) self.power = 0 self.powerBar['value'] = self.power self.powerBar.hide() def resetPowerBar(self): self.power = 0 self.powerBar['value'] = self.power self.powerBar['text'] = '' self.keyTTL = [] def __beginFireWater(self): if self.fireTrack and self.fireTrack.isPlaying(): return if self.aimStart != None: return if not self.state == 'Controlled': return if not self.avId == localAvatar.doId: return time = globalClock.getFrameTime() self.aimStart = time messenger.send('wakeup') taskMgr.add(self.__updateWaterPower, self.waterPowerTaskName) return def __endFireWater(self): if self.aimStart == None: return if not self.state == 'Controlled': return if not self.avId == localAvatar.doId: return taskMgr.remove(self.waterPowerTaskName) messenger.send('wakeup') self.aimStart = None origin = self.nozzle.getPos(render) target = self.boss.getPos(render) angle = deg2Rad(self.waterPitcherNode.getH() + 90) x = math.cos(angle) y = math.sin(angle) fireVector = Point3(x, y, 0) if self.power < 0.001: self.power = 0.001 self.lastPowerFired = self.power fireVector *= self.fireLength * self.power target = origin + fireVector segment = CollisionSegment(origin[0], origin[1], origin[2], target[0], target[1], target[2]) fromObject = render.attachNewNode(CollisionNode('pitcherColNode')) fromObject.node().addSolid(segment) fromObject.node().setFromCollideMask(ToontownGlobals.PieBitmask | ToontownGlobals.CameraBitmask | ToontownGlobals.FloorBitmask) fromObject.node().setIntoCollideMask(BitMask32.allOff()) queue = CollisionHandlerQueue() base.cTrav.addCollider(fromObject, queue) base.cTrav.traverse(render) queue.sortEntries() self.hitObject = None if queue.getNumEntries(): entry = queue.getEntry(0) target = entry.getSurfacePoint(render) self.hitObject = entry.getIntoNodePath() base.cTrav.removeCollider(fromObject) fromObject.removeNode() self.d_firingWater(origin, target) self.fireWater(origin, target) self.resetPowerBar() return def __updateWaterPower(self, task): if not self.powerBar: print '### no power bar!!!' return task.done newPower = self.__getWaterPower(globalClock.getFrameTime()) self.power = newPower self.powerBar['value'] = newPower if self.power < self.YELLOW_POWER_THRESHOLD: self.powerBar['barColor'] = VBase4(0.75, 0.75, 1.0, 0.8) elif self.power < self.RED_POWER_THRESHOLD: self.powerBar['barColor'] = VBase4(1.0, 1.0, 0.0, 0.8) else: self.powerBar['barColor'] = VBase4(1.0, 0.0, 0.0, 0.8) return task.cont def __getWaterPower(self, time): elapsed = max(time - self.aimStart, 0.0) t = elapsed / self.waterPowerSpeed exponent = self.waterPowerExponent if t > 1: t = t % 1 power = 1 - math.pow(1 - t, exponent) if power > 1.0: power = 1.0 return power def d_firingWater(self, origin, target): self.sendUpdate( 'firingWater', [origin[0], origin[1], origin[2], target[0], target[1], target[2]]) def firingWater(self, startX, startY, startZ, endX, endY, endZ): origin = Point3(startX, startY, startZ) target = Point3(endX, endY, endZ) self.fireWater(origin, target) def fireWater(self, origin, target): color = VBase4(0.75, 0.75, 1, 0.8) dScaleUp = 0.1 dHold = 0.3 dScaleDown = 0.1 horizScale = 0.1 vertScale = 0.1 sprayTrack = self.getSprayTrack(color, origin, target, dScaleUp, dHold, dScaleDown, horizScale, vertScale) duration = self.squirtSfx.length() if sprayTrack.getDuration() < duration: duration = sprayTrack.getDuration() soundTrack = SoundInterval(self.squirtSfx, node=self.waterPitcherModel, duration=duration) self.fireTrack = Parallel(sprayTrack, soundTrack) self.fireTrack.start() def getPos(self, wrt=render): return self.tableGroup.getPos(wrt) def getLocator(self): return self.tableGroup def enterFlat(self, avId): self.prepareForPhaseFour() self.resetPowerBar() self.notify.debug('enterFlat %d' % self.index) if self.avId: toon = base.cr.doId2do.get(self.avId) if toon: toon.wrtReparentTo(render) toon.setZ(0) self.tableGroup.setScale(1, 1, 0.01) if self.avId and self.avId == localAvatar.doId: localAvatar.b_squish(ToontownGlobals.BossCogDamageLevels[ ToontownGlobals.BossCogMoveAttack]) def exitFlat(self): self.tableGroup.setScale(1.0) if self.avId: toon = base.cr.doId2do.get(self.avId) if toon: if toon == localAvatar: self.boss.toCraneMode() toon.b_setAnimState('neutral') toon.setAnimState('neutral') toon.loop('leverNeutral') def __allowDetect(self, task): if self.fadeTrack: self.fadeTrack.finish() self.fadeTrack = Sequence( self.tableGroup.colorScaleInterval(0.2, VBase4(1, 1, 1, 1)), Func(self.tableGroup.clearColorScale), Func(self.tableGroup.clearTransparency)) self.fadeTrack.start() self.allowLocalRequestControl = True def gotBossZapped(self): self.showExiting() self.d_requestFree(True) def __upArrowKeyPressed(self): if self.TugOfWarControls: self.__pressHandler(0) def __downArrowKeyPressed(self): if self.TugOfWarControls: self.__pressHandler(1) def __pressHandler(self, index): if index == self.buttons[0]: self.keyTTL.insert(0, 1.0) if not self.OnlyUpArrow: self.buttons.reverse() def __spawnUpdateKeyPressRateTask(self): taskMgr.remove(self.taskName(self.UPDATE_KEY_PRESS_RATE_TASK)) taskMgr.doMethodLater(0.1, self.__updateKeyPressRateTask, self.taskName(self.UPDATE_KEY_PRESS_RATE_TASK)) def __killUpdateKeyPressRateTask(self): taskMgr.remove(self.taskName(self.UPDATE_KEY_PRESS_RATE_TASK)) def __updateKeyPressRateTask(self, task): if self.state not in 'Controlled': return Task.done for i in xrange(len(self.keyTTL)): self.keyTTL[i] -= 0.1 for i in xrange(len(self.keyTTL)): if self.keyTTL[i] <= 0: a = self.keyTTL[0:i] del self.keyTTL self.keyTTL = a break self.keyRate = len(self.keyTTL) keyRateDiff = self.keyRate - self.BASELINE_KEY_RATE diffPower = keyRateDiff / 300.0 if self.power < 1 and diffPower > 0: diffPower = diffPower * math.pow(1 - self.power, 1.25) newPower = self.power + diffPower if newPower > 1: newPower = 1 elif newPower < 0: newPower = 0 self.notify.debug('diffPower=%.2f keyRate = %d, newPower=%.2f' % (diffPower, self.keyRate, newPower)) self.power = newPower self.powerBar['value'] = newPower if self.power < self.YELLOW_POWER_THRESHOLD: self.powerBar['barColor'] = VBase4(0.75, 0.75, 1.0, 0.8) elif self.power < self.RED_POWER_THRESHOLD: self.powerBar['barColor'] = VBase4(1.0, 1.0, 0.0, 0.8) else: self.powerBar['barColor'] = VBase4(1.0, 0.0, 0.0, 0.8) self.__spawnUpdateKeyPressRateTask() return Task.done def __setMoveSound(self, sfx): if sfx != self.moveSound: if self.moveSound: self.moveSound.stop() self.moveSound = sfx if self.moveSound: base.playSfx(self.moveSound, looping=1, volume=0.5)
class DistributedCashbotBossCrane(DistributedObject.DistributedObject, FSM.FSM): notify = DirectNotifyGlobal.directNotify.newCategory( 'DistributedCashbotBossCrane') firstMagnetBit = 21 craneMinY = 8 craneMaxY = 25 armMinH = -45 armMaxH = 45 shadowOffset = 1 emptyFrictionCoef = 0.1 emptySlideSpeed = 10 emptyRotateSpeed = 20 lookAtPoint = Point3(0.3, 0, 0.1) lookAtUp = Vec3(0, -1, 0) neutralStickHinge = VBase3(0, 90, 0) def __init__(self, cr): DistributedObject.DistributedObject.__init__(self, cr) FSM.FSM.__init__(self, 'DistributedCashbotBossCrane') self.boss = None self.index = None self.avId = 0 self.cableLength = 20 self.numLinks = 3 self.initialArmPosition = (0, 20, 0) self.slideSpeed = self.emptySlideSpeed self.rotateSpeed = self.emptyRotateSpeed self.changeSeq = 0 self.lastChangeSeq = 0 self.moveSound = None self.links = [] self.activeLinks = [] self.collisions = NodePathCollection() self.physicsActivated = 0 self.snifferActivated = 0 self.magnetOn = 0 self.root = NodePath('root') self.hinge = self.root.attachNewNode('hinge') self.hinge.setPos(0, -17.6, 38.5) self.controls = self.root.attachNewNode('controls') self.controls.setPos(0, -4.9, 0) self.arm = self.hinge.attachNewNode('arm') self.crane = self.arm.attachNewNode('crane') self.cable = self.hinge.attachNewNode('cable') self.topLink = self.crane.attachNewNode('topLink') self.topLink.setPos(0, 0, -1) self.shadow = None self.p0 = Point3(0, 0, 0) self.v1 = Vec3(1, 1, 1) self.armSmoother = SmoothMover() self.armSmoother.setSmoothMode(SmoothMover.SMOn) self.linkSmoothers = [] self.smoothStarted = 0 self.__broadcastPeriod = 0.2 self.cable.node().setFinal(1) self.crane.setPos(*self.initialArmPosition) self.heldObject = None self.closeButton = None self.craneAdviceLabel = None self.magnetAdviceLabel = None self.atLimitSfx = base.loadSfx( 'phase_4/audio/sfx/MG_cannon_adjust.mp3') self.magnetOnSfx = base.loadSfx( 'phase_10/audio/sfx/CBHQ_CFO_magnet_on.mp3') self.magnetLoopSfx = base.loadSfx( 'phase_10/audio/sfx/CBHQ_CFO_magnet_loop.wav') self.magnetSoundInterval = Parallel( SoundInterval(self.magnetOnSfx), Sequence(Wait(0.5), Func(base.playSfx, self.magnetLoopSfx, looping=1))) self.craneMoveSfx = base.loadSfx( 'phase_9/audio/sfx/CHQ_FACT_elevator_up_down.mp3') self.fadeTrack = None return def announceGenerate(self): DistributedObject.DistributedObject.announceGenerate(self) self.name = 'crane-%s' % self.doId self.root.setName(self.name) self.root.setPosHpr( *ToontownGlobals.CashbotBossCranePosHprs[self.index]) self.rotateLinkName = self.uniqueName('rotateLink') self.snifferEvent = self.uniqueName('sniffer') self.triggerName = self.uniqueName('trigger') self.triggerEvent = 'enter%s' % self.triggerName self.shadowName = self.uniqueName('shadow') self.flickerName = self.uniqueName('flicker') self.smoothName = self.uniqueName('craneSmooth') self.posHprBroadcastName = self.uniqueName('craneBroadcast') self.craneAdviceName = self.uniqueName('craneAdvice') self.magnetAdviceName = self.uniqueName('magnetAdvice') self.controlModel = self.boss.controls.copyTo(self.controls) self.cc = NodePath('cc') column = self.controlModel.find('**/column') column.getChildren().reparentTo(self.cc) self.cc.reparentTo(column) self.stickHinge = self.cc.attachNewNode('stickHinge') self.stick = self.boss.stick.copyTo(self.stickHinge) self.stickHinge.setHpr(self.neutralStickHinge) self.stick.setHpr(0, -90, 0) self.stick.flattenLight() self.bottom = self.controlModel.find('**/bottom') self.bottom.wrtReparentTo(self.cc) self.bottomPos = self.bottom.getPos() cs = CollisionSphere(0, -5, -2, 3) cs.setTangible(0) cn = CollisionNode(self.triggerName) cn.addSolid(cs) cn.setIntoCollideMask(OTPGlobals.WallBitmask) self.trigger = self.root.attachNewNode(cn) self.trigger.stash() cs = CollisionTube(0, 2.7, 0, 0, 2.7, 3, 1.2) cn = CollisionNode('tube') cn.addSolid(cs) cn.setIntoCollideMask(OTPGlobals.WallBitmask) self.tube = self.controlModel.attachNewNode(cn) cs = CollisionSphere(0, 0, 2, 3) cn = CollisionNode('safetyBubble') cn.addSolid(cs) cn.setIntoCollideMask(ToontownGlobals.PieBitmask) self.controls.attachNewNode(cn) arm = self.boss.craneArm.copyTo(self.crane) self.boss.cranes[self.index] = self def disable(self): DistributedObject.DistributedObject.disable(self) del self.boss.cranes[self.index] self.cleanup() def cleanup(self): if self.state != 'Off': self.demand('Off') self.boss = None return def accomodateToon(self, toon): origScale = self.controlModel.getSz() origCcPos = self.cc.getPos() origBottomPos = self.bottom.getPos() origStickHingeHpr = self.stickHinge.getHpr() scale = toon.getGeomNode().getChild(0).getSz(render) self.controlModel.setScale(scale) self.cc.setPos(0, 0, 0) toon.setPosHpr(self.controls, 0, 0, 0, 0, 0, 0) toon.pose('leverNeutral', 0) toon.update() pos = toon.rightHand.getPos(self.cc) self.cc.setPos(pos[0], pos[1], pos[2] - 1) self.bottom.setZ(toon, 0.0) self.bottom.setPos(self.bottomPos[0], self.bottomPos[1], self.bottom.getZ()) self.stickHinge.lookAt(toon.rightHand, self.lookAtPoint, self.lookAtUp) lerpTime = 0.5 return Parallel( self.controlModel.scaleInterval(lerpTime, scale, origScale, blendType='easeInOut'), self.cc.posInterval(lerpTime, self.cc.getPos(), origCcPos, blendType='easeInOut'), self.bottom.posInterval(lerpTime, self.bottom.getPos(), origBottomPos, blendType='easeInOut'), self.stickHinge.quatInterval(lerpTime, self.stickHinge.getHpr(), origStickHingeHpr, blendType='easeInOut')) def getRestoreScaleInterval(self): lerpTime = 1 return Parallel( self.controlModel.scaleInterval(lerpTime, 1, blendType='easeInOut'), self.cc.posInterval(lerpTime, Point3(0, 0, 0), blendType='easeInOut'), self.bottom.posInterval(lerpTime, self.bottomPos, blendType='easeInOut'), self.stickHinge.quatInterval(lerpTime, self.neutralStickHinge, blendType='easeInOut')) def makeToonGrabInterval(self, toon): origPos = toon.getPos() origHpr = toon.getHpr() a = self.accomodateToon(toon) newPos = toon.getPos() newHpr = toon.getHpr() origHpr.setX(PythonUtil.fitSrcAngle2Dest(origHpr[0], newHpr[0])) toon.setPosHpr(origPos, origHpr) walkTime = 0.2 reach = ActorInterval(toon, 'leverReach') if reach.getDuration() < walkTime: reach = Sequence( ActorInterval(toon, 'walk', loop=1, duration=walkTime - reach.getDuration()), reach) i = Sequence( Parallel(toon.posInterval(walkTime, newPos, origPos), toon.hprInterval(walkTime, newHpr, origHpr), reach), Func(self.startWatchJoystick, toon)) i = Parallel(i, a) return i def __toonPlayWithCallback(self, animName, numFrames): duration = numFrames / 24.0 self.toon.play(animName) taskMgr.doMethodLater(duration, self.__toonPlayCallback, self.uniqueName('toonPlay')) def __toonPlayCallback(self, task): if self.changeSeq == self.lastChangeSeq: self.__toonPlayWithCallback('leverNeutral', 40) else: self.__toonPlayWithCallback('leverPull', 40) self.lastChangeSeq = self.changeSeq def startWatchJoystick(self, toon): self.toon = toon taskMgr.add(self.__watchJoystick, self.uniqueName('watchJoystick')) self.__toonPlayWithCallback('leverNeutral', 40) self.accept(toon.uniqueName('disable'), self.__handleUnexpectedExit, extraArgs=[toon.doId]) def stopWatchJoystick(self): taskMgr.remove(self.uniqueName('toonPlay')) taskMgr.remove(self.uniqueName('watchJoystick')) if self.toon: self.ignore(self.toon.uniqueName('disable')) self.toon = None return def __watchJoystick(self, task): self.toon.setPosHpr(self.controls, 0, 0, 0, 0, 0, 0) self.toon.update() self.stickHinge.lookAt(self.toon.rightHand, self.lookAtPoint, self.lookAtUp) return Task.cont def __handleUnexpectedExit(self, toonId): self.notify.warning('%s: unexpected exit for %s' % (self.doId, toonId)) if self.toon and self.toon.doId == toonId: self.stopWatchJoystick() def __activatePhysics(self): if not self.physicsActivated: for an, anp, cnp in self.activeLinks: self.boss.physicsMgr.attachPhysicalNode(an) base.cTrav.addCollider(cnp, self.handler) self.collisions.unstash() self.physicsActivated = 1 def __deactivatePhysics(self): if self.physicsActivated: for an, anp, cnp in self.activeLinks: self.boss.physicsMgr.removePhysicalNode(an) base.cTrav.removeCollider(cnp) self.collisions.stash() self.physicsActivated = 0 def __straightenCable(self): for linkNum in range(self.numLinks): an, anp, cnp = self.activeLinks[linkNum] an.getPhysicsObject().setVelocity(0, 0, 0) z = float(linkNum + 1) / float(self.numLinks) * self.cableLength anp.setPos(self.crane.getPos(self.cable)) anp.setZ(-z) def setCableLength(self, length): self.cableLength = length linkWidth = float(length) / float(self.numLinks) self.shell.setRadius(linkWidth + 1) def setupCable(self): activated = self.physicsActivated self.clearCable() self.handler = PhysicsCollisionHandler() self.handler.setStaticFrictionCoef(0.1) self.handler.setDynamicFrictionCoef(self.emptyFrictionCoef) linkWidth = float(self.cableLength) / float(self.numLinks) self.shell = CollisionInvSphere(0, 0, 0, linkWidth + 1) self.links = [] self.links.append((self.topLink, Point3(0, 0, 0))) anchor = self.topLink for linkNum in range(self.numLinks): anchor = self.__makeLink(anchor, linkNum) self.collisions.stash() self.bottomLink = self.links[-1][0] self.middleLink = self.links[-2][0] self.magnet = self.bottomLink.attachNewNode('magnet') self.wiggleMagnet = self.magnet.attachNewNode('wiggleMagnet') taskMgr.add(self.__rotateMagnet, self.rotateLinkName) magnetModel = self.boss.magnet.copyTo(self.wiggleMagnet) magnetModel.setHpr(90, 45, 90) self.gripper = magnetModel.attachNewNode('gripper') self.gripper.setPos(0, 0, -4) cn = CollisionNode('sniffer') self.sniffer = magnetModel.attachNewNode(cn) self.sniffer.stash() cs = CollisionSphere(0, 0, -10, 6) cs.setTangible(0) cn.addSolid(cs) cn.setIntoCollideMask(BitMask32(0)) cn.setFromCollideMask(ToontownGlobals.CashbotBossObjectBitmask) self.snifferHandler = CollisionHandlerEvent() self.snifferHandler.addInPattern(self.snifferEvent) self.snifferHandler.addAgainPattern(self.snifferEvent) rope = self.makeSpline() rope.reparentTo(self.cable) rope.setTexture(self.boss.cableTex) ts = TextureStage.getDefault() rope.setTexScale(ts, 0.15, 0.13) rope.setTexOffset(ts, 0.83, 0.01) if activated: self.__activatePhysics() def clearCable(self): self.__deactivatePhysics() taskMgr.remove(self.rotateLinkName) self.links = [] self.activeLinks = [] self.linkSmoothers = [] self.collisions.clear() self.cable.getChildren().detach() self.topLink.getChildren().detach() self.gripper = None return def makeSpline(self): rope = Rope.Rope() rope.setup(min(len(self.links), 4), self.links) rope.curve.normalizeKnots() rn = rope.ropeNode rn.setRenderMode(RopeNode.RMTube) rn.setNumSlices(3) rn.setTubeUp(Vec3(0, -1, 0)) rn.setUvMode(RopeNode.UVParametric) rn.setUvDirection(1) rn.setThickness(0.5) return rope def startShadow(self): self.shadow = self.boss.geom.attachNewNode('%s-shadow' % self.name) self.shadow.setColor(1, 1, 1, 0.3) self.shadow.setDepthWrite(0) self.shadow.setTransparency(1) self.shadow.setBin('shadow', 0) self.shadow.node().setFinal(1) self.magnetShadow = loader.loadModel( 'phase_3/models/props/drop_shadow') self.magnetShadow.reparentTo(self.shadow) self.craneShadow = loader.loadModel( 'phase_3/models/props/square_drop_shadow') self.craneShadow.setScale(0.5, 4, 1) self.craneShadow.setPos(0, -12, 0) self.craneShadow.flattenLight() self.craneShadow.reparentTo(self.shadow) taskMgr.add(self.__followShadow, self.shadowName) rope = self.makeSpline() rope.reparentTo(self.shadow) rope.setColor(1, 1, 1, 0.2) tex = self.craneShadow.findTexture('*') rope.setTexture(tex) rn = rope.ropeNode rn.setRenderMode(RopeNode.RMTape) rn.setNumSubdiv(6) rn.setThickness(0.8) rn.setTubeUp(Vec3(0, 0, 1)) rn.setMatrix( Mat4.translateMat(0, 0, self.shadowOffset) * Mat4.scaleMat(1, 1, 0.01)) def stopShadow(self): if self.shadow: self.shadow.removeNode() self.shadow = None self.magnetShadow = None self.craneShadow = None taskMgr.remove(self.shadowName) return def __followShadow(self, task): p = self.magnet.getPos(self.boss.geom) self.magnetShadow.setPos(p[0], p[1], self.shadowOffset) self.craneShadow.setPosHpr(self.crane, 0, 0, 0, 0, 0, 0) self.craneShadow.setZ(self.shadowOffset) return Task.cont def __makeLink(self, anchor, linkNum): an = ActorNode('link%s' % linkNum) anp = NodePath(an) cn = CollisionNode('cn') sphere = CollisionSphere(0, 0, 0, 1) cn.addSolid(sphere) cnp = anp.attachNewNode(cn) self.handler.addCollider(cnp, anp) self.activeLinks.append((an, anp, cnp)) self.linkSmoothers.append(SmoothMover()) anp.reparentTo(self.cable) z = float(linkNum + 1) / float(self.numLinks) * self.cableLength anp.setPos(self.crane.getPos()) anp.setZ(-z) mask = BitMask32.bit(self.firstMagnetBit + linkNum) cn.setFromCollideMask(mask) cn.setIntoCollideMask(BitMask32(0)) shellNode = CollisionNode('shell%s' % linkNum) shellNode.addSolid(self.shell) shellNP = anchor.attachNewNode(shellNode) shellNode.setIntoCollideMask(mask) self.collisions.addPath(shellNP) self.collisions.addPath(cnp) self.links.append((anp, Point3(0, 0, 0))) return anp def __rotateMagnet(self, task): self.magnet.lookAt(self.middleLink, self.p0, self.v1) return Task.cont def __enableControlInterface(self): gui = loader.loadModel('phase_3.5/models/gui/avatar_panel_gui') self.closeButton = DirectButton(image=(gui.find('**/CloseBtn_UP'), gui.find('**/CloseBtn_DN'), gui.find('**/CloseBtn_Rllvr'), gui.find('**/CloseBtn_UP')), relief=None, scale=2, text=TTLocalizer.CashbotCraneLeave, text_scale=0.04, text_pos=(0, -0.07), text_fg=VBase4(1, 1, 1, 1), pos=(1.05, 0, -0.82), command=self.__exitCrane) self.accept('escape', self.__exitCrane) self.accept('control', self.__controlPressed) self.accept('control-up', self.__controlReleased) self.accept('InputState-forward', self.__upArrow) self.accept('InputState-reverse', self.__downArrow) self.accept('InputState-turnLeft', self.__leftArrow) self.accept('InputState-turnRight', self.__rightArrow) taskMgr.add(self.__watchControls, 'watchCraneControls') taskMgr.doMethodLater(5, self.__displayCraneAdvice, self.craneAdviceName) taskMgr.doMethodLater(10, self.__displayMagnetAdvice, self.magnetAdviceName) NametagGlobals.setOnscreenChatForced(1) self.arrowVert = 0 self.arrowHorz = 0 return def __disableControlInterface(self): self.__turnOffMagnet() if self.closeButton: self.closeButton.destroy() self.closeButton = None self.__cleanupCraneAdvice() self.__cleanupMagnetAdvice() self.ignore('escape') self.ignore('control') self.ignore('control-up') self.ignore('InputState-forward') self.ignore('InputState-reverse') self.ignore('InputState-turnLeft') self.ignore('InputState-turnRight') self.arrowVert = 0 self.arrowHorz = 0 NametagGlobals.setOnscreenChatForced(0) taskMgr.remove('watchCraneControls') self.__setMoveSound(None) return def __displayCraneAdvice(self, task): if self.craneAdviceLabel == None: self.craneAdviceLabel = DirectLabel( text=TTLocalizer.CashbotCraneAdvice, text_fg=VBase4(1, 1, 1, 1), text_align=TextNode.ACenter, relief=None, pos=(0, 0, 0.69), scale=0.1) return def __cleanupCraneAdvice(self): if self.craneAdviceLabel: self.craneAdviceLabel.destroy() self.craneAdviceLabel = None taskMgr.remove(self.craneAdviceName) return def __displayMagnetAdvice(self, task): if self.magnetAdviceLabel == None: self.magnetAdviceLabel = DirectLabel( text=TTLocalizer.CashbotMagnetAdvice, text_fg=VBase4(1, 1, 1, 1), text_align=TextNode.ACenter, relief=None, pos=(0, 0, 0.55), scale=0.1) return def __cleanupMagnetAdvice(self): if self.magnetAdviceLabel: self.magnetAdviceLabel.destroy() self.magnetAdviceLabel = None taskMgr.remove(self.magnetAdviceName) return def __watchControls(self, task): if self.arrowHorz or self.arrowVert: self.__moveCraneArcHinge(self.arrowHorz, self.arrowVert) else: self.__setMoveSound(None) return Task.cont def __exitCrane(self): if self.closeButton: self.closeButton.destroy() self.closeButton = DirectLabel( relief=None, text=TTLocalizer.CashbotCraneLeaving, pos=(1.05, 0, -0.88), text_pos=(0, 0), text_scale=0.06, text_fg=VBase4(1, 1, 1, 1)) self.__cleanupCraneAdvice() self.__cleanupMagnetAdvice() self.d_requestFree() return def __incrementChangeSeq(self): self.changeSeq = self.changeSeq + 1 & 255 def __controlPressed(self): self.__cleanupMagnetAdvice() self.__turnOnMagnet() def __controlReleased(self): self.__turnOffMagnet() def __turnOnMagnet(self): if not self.magnetOn: self.__incrementChangeSeq() self.magnetOn = 1 if not self.heldObject: self.__activateSniffer() def __turnOffMagnet(self): if self.magnetOn: self.magnetOn = 0 self.__deactivateSniffer() self.releaseObject() def __upArrow(self, pressed): self.__incrementChangeSeq() self.__cleanupCraneAdvice() if pressed: self.arrowVert = 1 elif self.arrowVert > 0: self.arrowVert = 0 def __downArrow(self, pressed): self.__incrementChangeSeq() self.__cleanupCraneAdvice() if pressed: self.arrowVert = -1 elif self.arrowVert < 0: self.arrowVert = 0 def __rightArrow(self, pressed): self.__incrementChangeSeq() self.__cleanupCraneAdvice() if pressed: self.arrowHorz = 1 elif self.arrowHorz > 0: self.arrowHorz = 0 def __leftArrow(self, pressed): self.__incrementChangeSeq() self.__cleanupCraneAdvice() if pressed: self.arrowHorz = -1 elif self.arrowHorz < 0: self.arrowHorz = 0 def __moveCraneArcHinge(self, xd, yd): dt = globalClock.getDt() h = self.arm.getH() - xd * self.rotateSpeed * dt limitH = max(min(h, self.armMaxH), self.armMinH) self.arm.setH(limitH) y = self.crane.getY() + yd * self.slideSpeed * dt limitY = max(min(y, self.craneMaxY), self.craneMinY) atLimit = limitH != h or limitY != y if atLimit: now = globalClock.getFrameTime() x = math.sin(now * 79) * 0.05 z = math.sin(now * 70) * 0.02 self.crane.setPos(x, limitY, z) self.__setMoveSound(self.atLimitSfx) else: self.crane.setPos(0, limitY, 0) self.__setMoveSound(self.craneMoveSfx) def __setMoveSound(self, sfx): if sfx != self.moveSound: if self.moveSound: self.moveSound.stop() self.moveSound = sfx if self.moveSound: base.playSfx(self.moveSound, looping=1, volume=0.5) def __activateSniffer(self): if not self.snifferActivated: self.sniffer.unstash() base.cTrav.addCollider(self.sniffer, self.snifferHandler) self.accept(self.snifferEvent, self.__sniffedSomething) self.startFlicker() self.snifferActivated = 1 def __deactivateSniffer(self): if self.snifferActivated: base.cTrav.removeCollider(self.sniffer) self.sniffer.stash() self.ignore(self.snifferEvent) self.stopFlicker() self.snifferActivated = 0 def startFlicker(self): self.magnetSoundInterval.start() self.lightning = [] for i in range(4): t = float(i) / 3.0 - 0.5 l = self.boss.lightning.copyTo(self.gripper) l.setScale(random.choice([1, -1]), 1, 5) l.setZ(random.uniform(-5, -5.5)) l.flattenLight() l.setTwoSided(1) l.setBillboardAxis() l.setScale(random.uniform(0.5, 1.0)) if t < 0: l.setX(t - 0.7) else: l.setX(t + 0.7) l.setR(-20 * t) l.setP(random.uniform(-20, 20)) self.lightning.append(l) taskMgr.add(self.__flickerLightning, self.flickerName) def stopFlicker(self): self.magnetSoundInterval.finish() self.magnetLoopSfx.stop() taskMgr.remove(self.flickerName) for l in self.lightning: l.detachNode() self.lightning = None return def __flickerLightning(self, task): for l in self.lightning: if random.random() < 0.5: l.hide() else: l.show() return Task.cont def __sniffedSomething(self, entry): np = entry.getIntoNodePath() if np.hasNetTag('object'): doId = int(np.getNetTag('object')) else: self.notify.warning("%s missing 'object' tag" % np) return self.notify.debug('__sniffedSomething %d' % doId) obj = base.cr.doId2do.get(doId) if obj and obj.state != 'LocalDropped' and (obj.state != 'Dropped' or obj.craneId != self.doId): obj.d_requestGrab() obj.demand('LocalGrabbed', localAvatar.doId, self.doId) def grabObject(self, obj): if self.state == 'Off': return if self.heldObject != None: self.releaseObject() self.__deactivateSniffer() obj.wrtReparentTo(self.gripper) if obj.lerpInterval: obj.lerpInterval.finish() obj.lerpInterval = Parallel( obj.posInterval(ToontownGlobals.CashbotBossToMagnetTime, Point3(*obj.grabPos)), obj.quatInterval(ToontownGlobals.CashbotBossToMagnetTime, VBase3(obj.getH(), 0, 0)), obj.toMagnetSoundInterval) obj.lerpInterval.start() self.heldObject = obj self.handler.setDynamicFrictionCoef(obj.craneFrictionCoef) self.slideSpeed = obj.craneSlideSpeed self.rotateSpeed = obj.craneRotateSpeed if self.avId == localAvatar.doId and not self.magnetOn: self.releaseObject() return def dropObject(self, obj): if obj.lerpInterval: obj.lerpInterval.finish() obj.wrtReparentTo(render) obj.lerpInterval = Parallel( obj.quatInterval(ToontownGlobals.CashbotBossFromMagnetTime, VBase3(obj.getH(), 0, 0), blendType='easeOut')) obj.lerpInterval.start() p1 = self.bottomLink.node().getPhysicsObject() v = render.getRelativeVector(self.bottomLink, p1.getVelocity()) obj.physicsObject.setVelocity(v * 1.5) if self.heldObject == obj: self.heldObject = None self.handler.setDynamicFrictionCoef(self.emptyFrictionCoef) self.slideSpeed = self.emptySlideSpeed self.rotateSpeed = self.emptyRotateSpeed return def releaseObject(self): if self.heldObject: obj = self.heldObject obj.d_requestDrop() if obj.state == 'Grabbed': obj.demand('LocalDropped', localAvatar.doId, self.doId) def __hitTrigger(self, event): self.d_requestControl() def setBossCogId(self, bossCogId): self.bossCogId = bossCogId self.boss = base.cr.doId2do[bossCogId] def setIndex(self, index): self.index = index def setState(self, state, avId): if state == 'C': self.demand('Controlled', avId) elif state == 'F': self.demand('Free') else: self.notify.error('Invalid state from AI: %s' % state) def d_requestControl(self): self.sendUpdate('requestControl') def d_requestFree(self): self.sendUpdate('requestFree') def b_clearSmoothing(self): self.d_clearSmoothing() self.clearSmoothing() def d_clearSmoothing(self): self.sendUpdate('clearSmoothing', [0]) def clearSmoothing(self, bogus=None): self.armSmoother.clearPositions(1) for smoother in self.linkSmoothers: smoother.clearPositions(1) def reloadPosition(self): self.armSmoother.clearPositions(0) self.armSmoother.setPos(self.crane.getPos()) self.armSmoother.setHpr(self.arm.getHpr()) self.armSmoother.setPhonyTimestamp() for linkNum in range(self.numLinks): smoother = self.linkSmoothers[linkNum] an, anp, cnp = self.activeLinks[linkNum] smoother.clearPositions(0) smoother.setPos(anp.getPos()) smoother.setPhonyTimestamp() def doSmoothTask(self, task): self.armSmoother.computeAndApplySmoothPosHpr(self.crane, self.arm) for linkNum in range(self.numLinks): smoother = self.linkSmoothers[linkNum] anp = self.activeLinks[linkNum][1] smoother.computeAndApplySmoothPos(anp) return Task.cont def startSmooth(self): if not self.smoothStarted: taskName = self.smoothName taskMgr.remove(taskName) self.reloadPosition() taskMgr.add(self.doSmoothTask, taskName) self.smoothStarted = 1 def stopSmooth(self): if self.smoothStarted: taskName = self.smoothName taskMgr.remove(taskName) self.forceToTruePosition() self.smoothStarted = 0 def forceToTruePosition(self): if self.armSmoother.getLatestPosition(): self.armSmoother.applySmoothPos(self.crane) self.armSmoother.applySmoothHpr(self.arm) self.armSmoother.clearPositions(1) for linkNum in range(self.numLinks): smoother = self.linkSmoothers[linkNum] an, anp, cnp = self.activeLinks[linkNum] if smoother.getLatestPosition(): smoother.applySmoothPos(anp) smoother.clearPositions(1) def setCablePos(self, changeSeq, y, h, links, timestamp): self.changeSeq = changeSeq if self.smoothStarted: if len(links) > self.numLinks: self.notify.warning( 'Links passed in is greater than total number of links') return now = globalClock.getFrameTime() local = globalClockDelta.networkToLocalTime(timestamp, now) self.armSmoother.setY(y) self.armSmoother.setH(h) self.armSmoother.setTimestamp(local) self.armSmoother.markPosition() for linkNum in range(self.numLinks): smoother = self.linkSmoothers[linkNum] lp = links[linkNum] smoother.setPos(*lp) smoother.setTimestamp(local) smoother.markPosition() else: self.crane.setY(y) self.arm.setH(h) def d_sendCablePos(self): timestamp = globalClockDelta.getFrameNetworkTime() links = [] for linkNum in range(self.numLinks): an, anp, cnp = self.activeLinks[linkNum] p = anp.getPos() links.append((p[0], p[1], p[2])) self.sendUpdate('setCablePos', [ self.changeSeq, self.crane.getY(), self.arm.getH(), links, timestamp ]) def stopPosHprBroadcast(self): taskName = self.posHprBroadcastName taskMgr.remove(taskName) def startPosHprBroadcast(self): taskName = self.posHprBroadcastName self.b_clearSmoothing() self.d_sendCablePos() taskMgr.remove(taskName) taskMgr.doMethodLater(self.__broadcastPeriod, self.__posHprBroadcast, taskName) def __posHprBroadcast(self, task): self.d_sendCablePos() taskName = self.posHprBroadcastName taskMgr.doMethodLater(self.__broadcastPeriod, self.__posHprBroadcast, taskName) return Task.done def enterOff(self): self.clearCable() self.root.detachNode() def exitOff(self): if self.boss: self.setupCable() self.root.reparentTo(render) def enterControlled(self, avId): self.avId = avId toon = base.cr.doId2do.get(avId) if not toon: return self.grabTrack = self.makeToonGrabInterval(toon) if avId == localAvatar.doId: self.boss.toCraneMode() camera.reparentTo(self.hinge) camera.setPosHpr(0, -20, -5, 0, -20, 0) self.tube.stash() localAvatar.setPosHpr(self.controls, 0, 0, 0, 0, 0, 0) localAvatar.sendCurrentPosition() self.__activatePhysics() self.__enableControlInterface() self.startPosHprBroadcast() self.startShadow() self.accept('exitCrane', self.__exitCrane) else: self.startSmooth() toon.stopSmooth() self.grabTrack = Sequence(self.grabTrack, Func(toon.startSmooth)) self.grabTrack.start() def exitControlled(self): self.ignore('exitCrane') self.grabTrack.finish() del self.grabTrack if self.toon and not self.toon.isDisabled(): self.toon.loop('neutral') self.toon.startSmooth() self.stopWatchJoystick() self.stopPosHprBroadcast() self.stopShadow() self.stopSmooth() if self.avId == localAvatar.doId: self.__disableControlInterface() self.__deactivatePhysics() self.tube.unstash() camera.reparentTo(base.localAvatar) camera.setPos(base.localAvatar.cameraPositions[0][0]) camera.setHpr(0, 0, 0) if self.cr: place = self.cr.playGame.getPlace() if place and hasattr(place, 'fsm'): if place.fsm.getCurrentState().getName() == 'crane': place.setState('finalBattle') self.boss.toFinalBattleMode() self.__straightenCable() def enterFree(self): if self.fadeTrack: self.fadeTrack.finish() self.fadeTrack = None self.restoreScaleTrack = Sequence(Wait(6), self.getRestoreScaleInterval()) self.restoreScaleTrack.start() if self.avId == localAvatar.doId: self.controlModel.setAlphaScale(0.3) self.controlModel.setTransparency(1) taskMgr.doMethodLater(5, self.__allowDetect, self.triggerName) self.fadeTrack = Sequence( Func(self.controlModel.setTransparency, 1), self.controlModel.colorScaleInterval(0.2, VBase4(1, 1, 1, 0.3))) self.fadeTrack.start() else: self.trigger.unstash() self.accept(self.triggerEvent, self.__hitTrigger) self.avId = 0 return def __allowDetect(self, task): if self.fadeTrack: self.fadeTrack.finish() self.fadeTrack = Sequence( self.controlModel.colorScaleInterval(0.2, VBase4(1, 1, 1, 1)), Func(self.controlModel.clearColorScale), Func(self.controlModel.clearTransparency)) self.fadeTrack.start() self.trigger.unstash() self.accept(self.triggerEvent, self.__hitTrigger) def exitFree(self): if self.fadeTrack: self.fadeTrack.finish() self.fadeTrack = None self.restoreScaleTrack.pause() del self.restoreScaleTrack taskMgr.remove(self.triggerName) self.controlModel.clearColorScale() self.controlModel.clearTransparency() self.trigger.stash() self.ignore(self.triggerEvent) return def enterMovie(self): self.__activatePhysics() def exitMovie(self): self.__deactivatePhysics() self.__straightenCable()