DistributedLevel.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  1. """DistributedLevel.py: contains the DistributedLevel class"""
  2. from ClockDelta import *
  3. from PandaModules import *
  4. from PythonUtil import Functor, sameElements, list2dict, uniqueElements
  5. from IntervalGlobal import *
  6. from ToontownMsgTypes import *
  7. import ToontownGlobals
  8. import DistributedObject
  9. import Level
  10. import LevelConstants
  11. import DirectNotifyGlobal
  12. import EntityCreator
  13. import OnscreenText
  14. import Task
  15. import LevelUtil
  16. import FactoryCameraViews
  17. class DistributedLevel(DistributedObject.DistributedObject,
  18. Level.Level):
  19. """DistributedLevel"""
  20. notify = DirectNotifyGlobal.directNotify.newCategory('DistributedLevel')
  21. WantVisibility = config.GetBool('level-visibility', 1)
  22. HideZones = config.GetBool('level-hidezones', 1)
  23. # TODO: move level-model stuff to LevelMgr or FactoryLevelMgr?
  24. FloorCollPrefix = 'zoneFloor'
  25. OuchTaskName = 'ouchTask'
  26. VisChangeTaskName = 'visChange'
  27. def __init__(self, cr):
  28. DistributedObject.DistributedObject.__init__(self, cr)
  29. Level.Level.__init__(self)
  30. self.lastToonZone = 0
  31. self.lastCamZone = 0
  32. self.titleColor = (1,1,1,1)
  33. self.titleText = OnscreenText.OnscreenText(
  34. "",
  35. fg = self.titleColor,
  36. shadow = (0,0,0,1),
  37. font = ToontownGlobals.getSuitFont(),
  38. pos = (0,-0.5),
  39. scale = 0.16,
  40. drawOrder = 0,
  41. mayChange = 1,
  42. )
  43. self.smallTitleText = OnscreenText.OnscreenText(
  44. "",
  45. fg = self.titleColor,
  46. font = ToontownGlobals.getSuitFont(),
  47. pos = (0.65,0.9),
  48. scale = 0.08,
  49. drawOrder = 0,
  50. mayChange = 1,
  51. bg = (.5,.5,.5,.5),
  52. align = TextNode.ARight,
  53. )
  54. self.zonesEnteredList = []
  55. self.fColorZones = 0
  56. # we use these to track setZone requests
  57. self.setZonesRequested = 0
  58. self.setZonesReceived = 0
  59. def generate(self):
  60. DistributedLevel.notify.debug('generate')
  61. DistributedObject.DistributedObject.generate(self)
  62. # this dict stores entity reparents if the parent hasn't been
  63. # created yet
  64. self.parent2pendingChildren = {}
  65. # if the AI sends us a full spec, it will be put here
  66. self.curSpec = None
  67. # Most (if not all) of the timed entities of levels
  68. # run on looping intervals that are started once based on
  69. # the level's start time.
  70. # This sync request is *NOT* guaranteed to finish by the time
  71. # the entities get created.
  72. # We should listen for any and all time-sync events and re-sync
  73. # all our entities at that time.
  74. toonbase.tcr.timeManager.synchronize('DistributedLevel.generate')
  75. # add factory menu to SpeedChat
  76. toonbase.localToon.chatMgr.chatInputSpeedChat.addFactoryMenu()
  77. # add special camera views
  78. self.factoryViews = FactoryCameraViews.FactoryCameraViews(self)
  79. # the real required fields
  80. def setLevelZoneId(self, zoneId):
  81. # this is the zone that the level is in; we should listen to this
  82. # zone the entire time we're in here
  83. self.levelZone = zoneId
  84. def setPlayerIds(self, avIdList):
  85. self.avIdList = avIdList
  86. assert toonbase.localToon.doId in self.avIdList
  87. def setEntranceId(self, entranceId):
  88. self.entranceId = entranceId
  89. def getEntranceId(self):
  90. return self.entranceId
  91. # "required" fields (these ought to be required fields, but
  92. # the AI level obj doesn't know the data values until it has been
  93. # generated.)
  94. def setZoneIds(self, zoneIds):
  95. DistributedLevel.notify.debug('setZoneIds: %s' % zoneIds)
  96. self.zoneIds = zoneIds
  97. def setStartTimestamp(self, timestamp):
  98. DistributedLevel.notify.debug('setStartTimestamp: %s' % timestamp)
  99. self.startTime = globalClockDelta.networkToLocalTime(timestamp,bits=32)
  100. def setScenarioIndex(self, scenarioIndex):
  101. self.scenarioIndex = scenarioIndex
  102. # ugly hack: we treat a few DC fields as if they were required,
  103. # and use 'levelAnnounceGenerate()' in place of regular old
  104. # announceGenerate(). Note that we have to call
  105. # gotAllRequired() in the last 'faux-required' DC update
  106. # handler. If you add another field, move this to the last one.
  107. self.privGotAllRequired()
  108. def privGotAllRequired(self):
  109. self.levelAnnounceGenerate()
  110. def levelAnnounceGenerate(self):
  111. pass
  112. def initializeLevel(self, levelSpec):
  113. """subclass should call this as soon as it's located its level spec.
  114. Must be called after obj has been generated."""
  115. if __dev__:
  116. # if we're in dev, give the server the opportunity to send us
  117. # a full spec
  118. self.candidateSpec = levelSpec
  119. self.sendUpdate('requestCurrentLevelSpec',
  120. [hash(levelSpec),
  121. levelSpec.entTypeReg.getHashStr()])
  122. else:
  123. self.privGotSpec(levelSpec)
  124. if __dev__:
  125. def setSpecDeny(self, reason):
  126. DistributedLevel.notify.error(reason)
  127. def setSpecSenderDoId(self, doId):
  128. DistributedLevel.notify.debug('setSpecSenderDoId: %s' % doId)
  129. blobSender = toonbase.tcr.doId2do[doId]
  130. def setSpecBlob(specBlob, blobSender=blobSender, self=self):
  131. blobSender.sendAck()
  132. from LevelSpec import LevelSpec
  133. spec = eval(specBlob)
  134. if spec is None:
  135. spec = self.candidateSpec
  136. del self.candidateSpec
  137. self.privGotSpec(spec)
  138. if blobSender.isComplete():
  139. setSpecBlob(blobSender.getBlob())
  140. else:
  141. evtName = self.uniqueName('specDone')
  142. blobSender.setDoneEvent(evtName)
  143. self.acceptOnce(evtName, setSpecBlob)
  144. def privGotSpec(self, levelSpec):
  145. Level.Level.initializeLevel(self, self.doId, levelSpec,
  146. self.scenarioIndex)
  147. # all of the local entities have been created now.
  148. # TODO: have any of the distributed entities been created at this point?
  149. # there should not be any pending reparents left at this point
  150. # TODO: is it possible for a local entity to be parented to a
  151. # distributed entity? I think so!
  152. assert len(self.parent2pendingChildren) == 0
  153. # make sure the zoneNums from the model match the zoneNums from
  154. # the zone entities
  155. modelZoneNums = self.zoneNums
  156. entityZoneNums = self.zoneNum2entId.keys()
  157. if not sameElements(modelZoneNums, entityZoneNums):
  158. DistributedLevel.notify.error(
  159. 'model zone nums (%s) do not match entity zone nums (%s)\n'
  160. 'use SpecUtil.updateSpec' %
  161. (modelZoneNums, entityZoneNums))
  162. # load stuff
  163. self.initVisibility()
  164. self.placeLocalToon()
  165. def announceLeaving(self):
  166. """call this just before leaving the level; this may result in
  167. the factory being destroyed on the AI"""
  168. self.doneBarrier()
  169. def placeLocalToon(self):
  170. # the entrancePoint entities register themselves with us
  171. if self.entranceId not in self.entranceId2entity:
  172. self.notify.warning('unknown entranceId %s' % self.entranceId)
  173. toonbase.localToon.setPos(0,0,0)
  174. else:
  175. epEnt = self.entranceId2entity[self.entranceId]
  176. epEnt.placeToon(toonbase.localToon,
  177. self.avIdList.index(toonbase.localToon.doId),
  178. len(self.avIdList))
  179. # kickstart the visibility
  180. firstZoneEnt = self.getEntity(epEnt.getZoneEntId())
  181. self.enterZone(firstZoneEnt.getZoneNum())
  182. def createEntityCreator(self):
  183. """Create the object that will be used to create Entities.
  184. Inheritors, override if desired."""
  185. return EntityCreator.EntityCreator(level=self)
  186. def onEntityTypePostCreate(self, entType):
  187. """listen for certain entity types to be created"""
  188. Level.Level.onEntityTypePostCreate(self, entType)
  189. # NOTE: these handlers are private in order to avoid overriding
  190. # similar handlers in base classes
  191. if entType == 'levelMgr':
  192. self.__handleLevelMgrCreated()
  193. def __handleLevelMgrCreated(self):
  194. # as soon as the levelMgr has been created, load up the model
  195. # and extract zone info. We need to do this before any entities
  196. # get parented to the level!
  197. levelMgr = self.getEntity(LevelConstants.LevelMgrEntId)
  198. self.geom = levelMgr.geom
  199. # find the zones in the model and fix them up
  200. self.zoneNum2node = LevelUtil.getZoneNum2Node(self.geom)
  201. self.zoneNums = self.zoneNum2node.keys()
  202. self.zoneNums.sort()
  203. DistributedLevel.notify.debug('zones: %s' % self.zoneNums)
  204. # fix up the floor collisions for walkable zones *before*
  205. # any entities get put under the model
  206. for zoneNum,zoneNode in self.zoneNum2node.items():
  207. # don't do this to the uberzone
  208. if zoneNum == LevelConstants.UberZoneNum:
  209. continue
  210. # if this is a walkable zone, fix up the model
  211. allColls = zoneNode.findAllMatches('**/+CollisionNode').asList()
  212. # which of them, if any, are floors?
  213. floorColls = []
  214. for coll in allColls:
  215. bitmask = coll.node().getIntoCollideMask()
  216. if not (bitmask & ToontownGlobals.FloorBitmask).isZero():
  217. floorColls.append(coll)
  218. if len(floorColls) > 0:
  219. # rename the floor collision nodes, and make sure no other
  220. # nodes under the ZoneNode have that name
  221. floorCollName = '%s%s' % (DistributedLevel.FloorCollPrefix,
  222. zoneNum)
  223. others = zoneNode.findAllMatches(
  224. '**/%s' % floorCollName).asList()
  225. for other in others:
  226. other.setName('%s_renamed' % floorCollName)
  227. for floorColl in floorColls:
  228. floorColl.setName(floorCollName)
  229. # listen for zone enter events from floor collisions
  230. def handleZoneEnter(collisionEntry,
  231. self=self, zoneNum=zoneNum):
  232. self.toonEnterZone(zoneNum)
  233. floorNode = collisionEntry.getIntoNode()
  234. if floorNode.hasTag('ouch'):
  235. ouchLevel = int(floorNode.getTag('ouch'))
  236. self.startOuch(ouchLevel*2)
  237. self.accept('enter%s' % floorCollName, handleZoneEnter)
  238. # also listen for zone exit events for the sake of the
  239. # ouch system
  240. def handleZoneExit(collisionEntry,
  241. self=self, zoneNum=zoneNum):
  242. floorNode = collisionEntry.getIntoNode()
  243. if floorNode.hasTag('ouch'):
  244. self.stopOuch()
  245. self.accept('exit%s' % floorCollName, handleZoneExit)
  246. def announceGenerate(self):
  247. DistributedLevel.notify.debug('announceGenerate')
  248. DistributedObject.DistributedObject.announceGenerate(self)
  249. def disable(self):
  250. DistributedLevel.notify.debug('disable')
  251. # geom is owned by the levelMgr
  252. if hasattr(self, 'geom'):
  253. del self.geom
  254. self.shutdownVisibility()
  255. self.destroyLevel()
  256. self.ignoreAll()
  257. # NOTE: this should be moved to FactoryInterior
  258. taskMgr.remove(self.uniqueName("titleText"))
  259. if self.smallTitleText:
  260. self.smallTitleText.cleanup()
  261. self.smallTitleText = None
  262. if self.titleText:
  263. self.titleText.cleanup()
  264. self.titleText = None
  265. self.zonesEnteredList = []
  266. DistributedObject.DistributedObject.disable(self)
  267. def delete(self):
  268. DistributedLevel.notify.debug('delete')
  269. DistributedObject.DistributedObject.delete(self)
  270. # remove factory menu to SpeedChat
  271. toonbase.localToon.chatMgr.chatInputSpeedChat.removeFactoryMenu()
  272. # remove special camera views
  273. del self.factoryViews
  274. # make sure the ouch task is stopped
  275. self.stopOuch()
  276. def getZoneNode(self, zoneNum):
  277. return self.zoneNum2node[zoneNum]
  278. def requestReparent(self, entity, parentId):
  279. if __debug__:
  280. # some things (like cogs) are not actually entities yet;
  281. # they don't have an entId. Big deal, let it go through.
  282. if hasattr(entity, 'entId'):
  283. assert(entity.entId != parentId)
  284. parent = self.getEntity(parentId)
  285. if parent is not None:
  286. # parent has already been created
  287. entity.reparentTo(parent.getNodePath())
  288. else:
  289. # parent hasn't been created yet; schedule the reparent
  290. DistributedLevel.notify.debug(
  291. 'entity %s requesting reparent to %s, not yet created' %
  292. (entity, parentId))
  293. entity.reparentTo(hidden)
  294. # if this parent doesn't already have another child pending,
  295. # do some setup
  296. if not self.parent2pendingChildren.has_key(parentId):
  297. self.parent2pendingChildren[parentId] = []
  298. # do the reparent(s) once the parent is initialized
  299. def doReparent(parentId=parentId, self=self):
  300. assert self.parent2pendingChildren.has_key(parentId)
  301. parent=self.getEntity(parentId)
  302. for child in self.parent2pendingChildren[parentId]:
  303. DistributedLevel.notify.debug(
  304. 'performing pending reparent of %s to %s' %
  305. (child, parent))
  306. child.reparentTo(parent.getNodePath())
  307. del self.parent2pendingChildren[parentId]
  308. self.ignore(self.getEntityCreateEvent(parentId))
  309. self.accept(self.getEntityCreateEvent(parentId), doReparent)
  310. self.parent2pendingChildren[parentId].append(entity)
  311. def showZone(self, zoneNum):
  312. zone = self.zoneNum2node[zoneNum]
  313. zone.unstash()
  314. zone.clearColor()
  315. def setColorZones(self, fColorZones):
  316. self.fColorZones = fColorZones
  317. def getColorZones(self):
  318. return self.fColorZones
  319. def hideZone(self, zoneNum):
  320. zone = self.zoneNum2node[zoneNum]
  321. if self.fColorZones:
  322. zone.unstash()
  323. zone.setColor(1,0,0)
  324. else:
  325. zone.stash()
  326. def setTransparency(self, alpha, zone=None):
  327. self.geom.setTransparency(1)
  328. if zone is None:
  329. node = self.geom
  330. else:
  331. node = self.zoneNum2node[zone]
  332. node.setAlphaScale(alpha)
  333. def initVisibility(self):
  334. # start out with every zone visible, since none of the zones have
  335. # been hidden
  336. self.curVisibleZoneNums = list2dict(self.zoneNums)
  337. # the UberZone is always visible, so it's not included in the
  338. # zones' viz lists
  339. del self.curVisibleZoneNums[0]
  340. # we have not entered any zone yet
  341. self.curZoneNum = None
  342. self.visChangedThisFrame = 0
  343. # listen for camera-ray/floor collision events
  344. def handleCameraRayFloorCollision(collEntry, self=self):
  345. name = collEntry.getIntoNode().getName()
  346. print 'camera floor ray collided with: %s' % name
  347. prefixLen = len(DistributedLevel.FloorCollPrefix)
  348. if (name[:prefixLen] == DistributedLevel.FloorCollPrefix):
  349. try:
  350. zoneNum = int(name[prefixLen:])
  351. except:
  352. DistributedLevel.notify.warning(
  353. 'Invalid zone floor collision node: %s'
  354. % name)
  355. else:
  356. self.camEnterZone(zoneNum)
  357. self.accept('on-floor', handleCameraRayFloorCollision)
  358. # register our datagram handler to listen for setZone msgs
  359. self.oldTcrHandler = toonbase.tcr.handler
  360. toonbase.tcr.handler = self.handleDatagram
  361. # if no viz, listen to all the zones
  362. if not DistributedLevel.WantVisibility:
  363. zoneNums = list(self.zoneNums)
  364. zoneNums.remove(LevelConstants.UberZoneNum)
  365. self.setVisibility(zoneNums)
  366. # send out any zone changes at the end of the frame, just before
  367. # rendering
  368. taskMgr.add(self.visChangeTask,
  369. self.uniqueName(DistributedLevel.VisChangeTaskName),
  370. priority=49)
  371. def shutdownVisibility(self):
  372. taskMgr.remove(self.uniqueName(DistributedLevel.VisChangeTaskName))
  373. if toonbase.tcr.handler == self.handleDatagram:
  374. toonbase.tcr.handler = self.oldTcrHandler
  375. del self.oldTcrHandler
  376. def getSetZoneCompleteEvent(self, num):
  377. return self.uniqueName('setZoneComplete-%s' % num)
  378. def getNextSetZoneCompleteEvent(self):
  379. return self.uniqueName('setZoneComplete-%s' % self.setZonesRequested)
  380. def handleDatagram(self, msgType, di):
  381. if msgType == CLIENT_DONE_SET_ZONE_RESP:
  382. # snoop to see what zone we're talking about
  383. di2 = DatagramIterator(di)
  384. zone = di2.getUint32()
  385. if zone != self.levelZone:
  386. self.notify.warning('got setZoneComplete for unknown zone %s' %
  387. zone)
  388. else:
  389. self.notify.info('setZone #%s complete' % self.setZonesReceived)
  390. messenger.send(self.getSetZoneCompleteEvent(
  391. self.setZonesReceived))
  392. self.setZonesReceived += 1
  393. if self.oldTcrHandler is None:
  394. toonbase.tcr.handleUnexpectedMsgType(msgType, di)
  395. else:
  396. self.oldTcrHandler(msgType, di)
  397. def toonEnterZone(self, zoneNum, ouchLevel=None):
  398. """
  399. zoneNum is an int.
  400. ouchLevel is a ??.
  401. The avatar (and not necessarily the camera) has entered
  402. a zone.
  403. See camEnterZone()
  404. """
  405. DistributedLevel.notify.info('toonEnterZone%s' % zoneNum)
  406. if zoneNum != self.lastToonZone:
  407. self.lastToonZone = zoneNum
  408. print "toon is standing in zone %s" % zoneNum
  409. messenger.send("factoryZoneChanged", [zoneNum])
  410. def camEnterZone(self, zoneNum):
  411. """
  412. zoneNum is an int.
  413. The camera (and not necessarily the avatar) has entered
  414. a zone.
  415. See toonEnterZone()
  416. """
  417. DistributedLevel.notify.info('camEnterZone%s' % zoneNum)
  418. self.enterZone(zoneNum)
  419. if zoneNum != self.lastCamZone:
  420. self.lastCamZone = zoneNum
  421. self.smallTitleText.hide()
  422. self.spawnTitleText()
  423. def lockVisibility(self, zoneNum=None, zoneId=None):
  424. """call this to lock the visibility to a particular zone
  425. pass in either network zoneId or model zoneNum
  426. this was added for battles in the HQ factories; if you engage a suit
  427. in zone A with your camera in zone B, and you don't call this func,
  428. your client will remain in zone B. If there's a door between A and B,
  429. and it closes, zone B might disappear, along with the suit and the
  430. battle objects.
  431. """
  432. assert (zoneNum is None) or (zoneId is None)
  433. assert not ((zoneNum is None) and (zoneId is None))
  434. if zoneId is not None:
  435. zoneNum = self.getZoneNumFromId(zoneId)
  436. self.notify.info('lockVisibility to zoneNum %s' % zoneNum)
  437. self.lockVizZone = zoneNum
  438. self.enterZone(self.lockVizZone)
  439. def unlockVisibility(self):
  440. """release the visibility lock"""
  441. self.notify.info('unlockVisibility')
  442. if not hasattr(self, 'lockVizZone'):
  443. self.notify.warning('visibility already unlocked')
  444. else:
  445. del self.lockVizZone
  446. self.updateVisibility()
  447. def enterZone(self, zoneNum):
  448. DistributedLevel.notify.info("entering zone %s" % zoneNum)
  449. if not DistributedLevel.WantVisibility:
  450. return
  451. if zoneNum == self.curZoneNum:
  452. return
  453. if zoneNum not in self.zoneNum2entId:
  454. DistributedLevel.notify.error(
  455. 'no ZoneEntity for this zone (%s)!!' % zoneNum)
  456. self.updateVisibility(zoneNum)
  457. def updateVisibility(self, zoneNum=None):
  458. """update the visibility assuming that we're in the specified
  459. zone; don't check to see if it's the zone we're already in"""
  460. #print 'updateVisibility %s' % globalClock.getFrameCount()
  461. if zoneNum is None:
  462. zoneNum = self.curZoneNum
  463. if hasattr(self, 'lockVizZone'):
  464. zoneNum = self.lockVizZone
  465. zoneEntId = self.zoneNum2entId[zoneNum]
  466. zoneEnt = self.getEntity(zoneEntId)
  467. # use dicts to efficiently ensure that there are no duplicates
  468. visibleZoneNums = list2dict([zoneNum])
  469. visibleZoneNums.update(list2dict(zoneEnt.getVisibleZoneNums()))
  470. if not __debug__:
  471. # HACK
  472. # make sure that the visibility list includes the zone that the toon
  473. # is standing in
  474. if self.lastToonZone not in visibleZoneNums:
  475. self.notify.warning('adding zoneNum %s to visibility list '
  476. 'because toon is standing in that zone!' %
  477. self.lastToonZone)
  478. visibleZoneNums.update(list2dict([self.lastToonZone]))
  479. # we should not have the uberZone in the list at this point
  480. assert not 0 in visibleZoneNums
  481. if DistributedLevel.HideZones:
  482. # figure out which zones are new and which are going invisible
  483. # use dicts because it's faster to use dict.has_key(x)
  484. # than 'x in list'
  485. addedZoneNums = []
  486. removedZoneNums = []
  487. allVZ = dict(visibleZoneNums)
  488. allVZ.update(self.curVisibleZoneNums)
  489. for vz,dummy in allVZ.items():
  490. new = vz in visibleZoneNums
  491. old = vz in self.curVisibleZoneNums
  492. if new and old:
  493. continue
  494. if new:
  495. addedZoneNums.append(vz)
  496. else:
  497. removedZoneNums.append(vz)
  498. # show the new, hide the old
  499. DistributedLevel.notify.info('showing zones %s' % addedZoneNums)
  500. for az in addedZoneNums:
  501. self.showZone(az)
  502. DistributedLevel.notify.info('hiding zones %s' % removedZoneNums)
  503. for rz in removedZoneNums:
  504. self.hideZone(rz)
  505. self.setVisibility(visibleZoneNums.keys())
  506. self.setZonesRequested += 1
  507. self.curZoneNum = zoneNum
  508. self.curVisibleZoneNums = visibleZoneNums
  509. def setVisibility(self, vizList):
  510. """
  511. vizList is a list of visible zone numbers.
  512. """
  513. # convert the zone numbers into their actual zoneIds
  514. # always include Toontown and factory uberZones
  515. uberZone = self.getZoneId(zoneNum=LevelConstants.UberZoneNum)
  516. # the level itself is in the 'level zone'
  517. visibleZoneIds = [ToontownGlobals.UberZone, self.levelZone, uberZone]
  518. for vz in vizList:
  519. visibleZoneIds.append(self.getZoneId(zoneNum=vz))
  520. assert(uniqueElements(visibleZoneIds))
  521. DistributedLevel.notify.info('new viz list: %s' % visibleZoneIds)
  522. toonbase.tcr.sendSetZoneMsg(self.levelZone, visibleZoneIds)
  523. def resetVisibility(self):
  524. # start out with every zone visible, since none of the zones have
  525. # been hidden
  526. self.curVisibleZoneNums = list2dict(self.zoneNums)
  527. # the UberZone is always visible, so it's not included in the
  528. # zones' viz lists
  529. del self.curVisibleZoneNums[0]
  530. # Make sure every zone is visible
  531. for vz,dummy in self.curVisibleZoneNums.items():
  532. self.showZone(vz)
  533. # Redo visibility using current zone num
  534. self.updateVisibility()
  535. def handleVisChange(self):
  536. """the zone visibility lists have changed on-the-fly"""
  537. Level.Level.handleVisChange(self)
  538. self.visChangedThisFrame = 1
  539. def visChangeTask(self, task):
  540. # this runs just before igloop; if viz lists have changed
  541. # this frame, updates the visibility and sends out a setZoneMsg
  542. if self.visChangedThisFrame:
  543. self.updateVisibility()
  544. self.visChangedThisFrame = 0
  545. return Task.cont
  546. if __dev__:
  547. # level editing stuff
  548. def setAttribChange(self, entId, attribName, valueStr, username):
  549. """every time the spec is edited, we get this message
  550. from the AI"""
  551. value = eval(valueStr)
  552. self.levelSpec.setAttribChange(entId, attribName, value, username)
  553. def spawnTitleText(self):
  554. def getDescription(zoneId, self=self):
  555. entId = self.zoneNum2entId.get(zoneId)
  556. if entId:
  557. ent = self.entities.get(entId)
  558. if ent and hasattr(ent, 'description'):
  559. return ent.description
  560. return None
  561. description = getDescription(self.lastCamZone)
  562. if description and description != '':
  563. taskMgr.remove(self.uniqueName("titleText"))
  564. self.smallTitleText.setText(description)
  565. self.titleText.setText(description)
  566. self.titleText.setColor(Vec4(*self.titleColor))
  567. self.titleText.setFg(self.titleColor)
  568. # Only show the big title once per session.
  569. # If we've already seen it, just show the small title
  570. titleSeq = None
  571. if not self.lastCamZone in self.zonesEnteredList:
  572. self.zonesEnteredList.append(self.lastCamZone)
  573. titleSeq = Task.sequence(
  574. Task.Task(self.hideSmallTitleTextTask),
  575. Task.Task(self.showTitleTextTask),
  576. Task.pause(0.1),
  577. Task.pause(6.0),
  578. self.titleText.lerpColor(Vec4(self.titleColor[0],
  579. self.titleColor[1],
  580. self.titleColor[2],
  581. self.titleColor[3]),
  582. Vec4(self.titleColor[0],
  583. self.titleColor[1],
  584. self.titleColor[2],
  585. 0.0),
  586. 0.5),
  587. )
  588. smallTitleSeq = Task.sequence(Task.Task(self.hideTitleTextTask),
  589. Task.Task(self.showSmallTitleTask))
  590. if titleSeq:
  591. seq = Task.sequence(titleSeq, smallTitleSeq)
  592. else:
  593. seq = smallTitleSeq
  594. taskMgr.add(seq, self.uniqueName("titleText"))
  595. def showTitleTextTask(self, task):
  596. assert(DistributedLevel.notify.debug("hideTitleTextTask()"))
  597. self.titleText.show()
  598. return Task.done
  599. def hideTitleTextTask(self, task):
  600. assert(DistributedLevel.notify.debug("hideTitleTextTask()"))
  601. if self.titleText:
  602. self.titleText.hide()
  603. return Task.done
  604. def showSmallTitleTask(self, task):
  605. # make sure large title is hidden
  606. if self.titleText:
  607. self.titleText.hide()
  608. # show the small title
  609. self.smallTitleText.show()
  610. return Task.done
  611. def hideSmallTitleTextTask(self, task):
  612. assert(DistributedLevel.notify.debug("hideTitleTextTask()"))
  613. if self.smallTitleText:
  614. self.smallTitleText.hide()
  615. return Task.done
  616. # Ouch!
  617. def startOuch(self, ouchLevel, period=2):
  618. print 'startOuch %s' % ouchLevel
  619. if not hasattr(self, 'doingOuch'):
  620. def doOuch(task, self=self, ouchLevel=ouchLevel, period=period):
  621. self.b_setOuch(ouchLevel)
  622. self.lastOuchTime = globalClock.getFrameTime()
  623. taskMgr.doMethodLater(period, doOuch,
  624. DistributedLevel.OuchTaskName)
  625. # check to make sure we haven't done an ouch too recently
  626. delay = 0
  627. if hasattr(self, 'lastOuchTime'):
  628. curFrameTime = globalClock.getFrameTime()
  629. timeSinceLastOuch = (curFrameTime - self.lastOuchTime)
  630. if timeSinceLastOuch < period:
  631. delay = period - timeSinceLastOuch
  632. if delay > 0:
  633. taskMgr.doMethodLater(
  634. period, doOuch,
  635. DistributedLevel.OuchTaskName)
  636. else:
  637. doOuch(None)
  638. self.doingOuch = 1
  639. def stopOuch(self):
  640. if hasattr(self, 'doingOuch'):
  641. taskMgr.remove(DistributedLevel.OuchTaskName)
  642. del self.doingOuch
  643. def b_setOuch(self, penalty, anim=None):
  644. self.notify.debug('b_setOuch %s' % penalty)
  645. av = toonbase.localToon
  646. # play the stun track (flashing toon)
  647. if not av.isStunned:
  648. self.d_setOuch(penalty)
  649. self.setOuch(penalty, anim)
  650. def d_setOuch(self, penalty):
  651. self.sendUpdate("setOuch", [penalty])
  652. def setOuch(self, penalty, anim = None):
  653. if anim == "Squish":
  654. toonbase.tcr.playGame.getPlace().fsm.request('squished')
  655. av = toonbase.localToon
  656. av.stunToon()
  657. av.playDialogueForString("!")