DistributedLevel.py 25 KB

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