DistributedLevel.py 25 KB

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