DistributedLevel.py 25 KB

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