DistributedLevel.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652
  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 announceLeaving(self):
  159. """call this just before leaving the level; this may result in
  160. the factory being destroyed on the AI"""
  161. self.doneBarrier()
  162. def placeLocalToon(self):
  163. # the entrancePoint entities register themselves with us
  164. if self.entranceId not in self.entranceId2entity:
  165. self.notify.warning('unknown entranceId %s' % self.entranceId)
  166. toonbase.localToon.setPos(0,0,0)
  167. else:
  168. epEnt = self.entranceId2entity[self.entranceId]
  169. epEnt.placeToon(toonbase.localToon,
  170. self.avIdList.index(toonbase.localToon.doId),
  171. len(self.avIdList))
  172. # kickstart the visibility
  173. firstZoneEnt = self.getEntity(epEnt.getZoneEntId())
  174. self.enterZone(firstZoneEnt.getZoneNum())
  175. def createEntityCreator(self):
  176. """Create the object that will be used to create Entities.
  177. Inheritors, override if desired."""
  178. return EntityCreator.EntityCreator(level=self)
  179. def onEntityTypePostCreate(self, entType):
  180. """listen for certain entity types to be created"""
  181. Level.Level.onEntityTypePostCreate(self, entType)
  182. # NOTE: these handlers are private in order to avoid overriding
  183. # similar handlers in base classes
  184. if entType == 'levelMgr':
  185. self.__handleLevelMgrCreated()
  186. def __handleLevelMgrCreated(self):
  187. # as soon as the levelMgr has been created, load up the model
  188. # and extract zone info. We need to do this before any entities
  189. # get parented to the level!
  190. levelMgr = self.getEntity(LevelConstants.LevelMgrEntId)
  191. self.geom = levelMgr.geom
  192. # find the zones in the model and fix them up
  193. self.zoneNum2node = LevelUtil.getZoneNum2Node(self.geom)
  194. self.zoneNums = self.zoneNum2node.keys()
  195. self.zoneNums.sort()
  196. DistributedLevel.notify.debug('zones: %s' % self.zoneNums)
  197. # fix up the floor collisions for walkable zones *before*
  198. # any entities get put under the model
  199. for zoneNum,zoneNode in self.zoneNum2node.items():
  200. # don't do this to the uberzone
  201. if zoneNum == LevelConstants.UberZoneNum:
  202. continue
  203. # if this is a walkable zone, fix up the model
  204. allColls = zoneNode.findAllMatches('**/+CollisionNode').asList()
  205. # which of them, if any, are floors?
  206. floorColls = []
  207. for coll in allColls:
  208. bitmask = coll.node().getIntoCollideMask()
  209. if not (bitmask & ToontownGlobals.FloorBitmask).isZero():
  210. floorColls.append(coll)
  211. if len(floorColls) > 0:
  212. # rename the floor collision nodes, and make sure no other
  213. # nodes under the ZoneNode have that name
  214. floorCollName = '%s%s' % (DistributedLevel.FloorCollPrefix,
  215. zoneNum)
  216. others = zoneNode.findAllMatches(
  217. '**/%s' % floorCollName).asList()
  218. for other in others:
  219. other.setName('%s_renamed' % floorCollName)
  220. for floorColl in floorColls:
  221. floorColl.setName(floorCollName)
  222. # listen for zone enter events from floor collisions
  223. def handleZoneEnter(collisionEntry,
  224. self=self, zoneNum=zoneNum):
  225. self.toonEnterZone(zoneNum)
  226. floorNode = collisionEntry.getIntoNode()
  227. if floorNode.hasTag('ouch'):
  228. ouchLevel = int(floorNode.getTag('ouch'))
  229. self.startOuch(ouchLevel*2)
  230. self.accept('enter%s' % floorCollName, handleZoneEnter)
  231. # also listen for zone exit events for the sake of the
  232. # ouch system
  233. def handleZoneExit(collisionEntry,
  234. self=self, zoneNum=zoneNum):
  235. floorNode = collisionEntry.getIntoNode()
  236. if floorNode.hasTag('ouch'):
  237. self.stopOuch()
  238. self.accept('exit%s' % floorCollName, handleZoneExit)
  239. def announceGenerate(self):
  240. DistributedLevel.notify.debug('announceGenerate')
  241. DistributedObject.DistributedObject.announceGenerate(self)
  242. def disable(self):
  243. DistributedLevel.notify.debug('disable')
  244. # geom is owned by the levelMgr
  245. if hasattr(self, 'geom'):
  246. del self.geom
  247. self.destroyLevel()
  248. DistributedObject.DistributedObject.disable(self)
  249. self.ignoreAll()
  250. # NOTE: this should be moved to FactoryInterior
  251. if self.smallTitleText:
  252. self.smallTitleText.cleanup()
  253. self.smallTitleText = None
  254. if self.titleText:
  255. self.titleText.cleanup()
  256. self.titleText = None
  257. self.zonesEnteredList = []
  258. # NOTE: this should be moved to ZoneEntity.disable
  259. toonbase.localToon.chatMgr.chatInputSpeedChat.removeFactoryMenu()
  260. def delete(self):
  261. DistributedLevel.notify.debug('delete')
  262. DistributedObject.DistributedObject.delete(self)
  263. # remove factory menu to SpeedChat
  264. toonbase.localToon.chatMgr.chatInputSpeedChat.removeFactoryMenu()
  265. # remove special camera views
  266. del self.factoryViews
  267. # make sure the ouch task is stopped
  268. self.stopOuch()
  269. def getZoneNode(self, zoneNum):
  270. return self.zoneNum2node[zoneNum]
  271. def requestReparent(self, entity, parentId):
  272. if __debug__:
  273. # some things (like cogs) are not actually entities yet;
  274. # they don't have an entId. Big deal, let it go through.
  275. if hasattr(entity, 'entId'):
  276. assert(entity.entId != parentId)
  277. parent = self.getEntity(parentId)
  278. if parent is not None:
  279. # parent has already been created
  280. entity.reparentTo(parent.getNodePath())
  281. else:
  282. # parent hasn't been created yet; schedule the reparent
  283. DistributedLevel.notify.debug(
  284. 'entity %s requesting reparent to %s, not yet created' %
  285. (entity, parentId))
  286. entity.reparentTo(hidden)
  287. # if this parent doesn't already have another child pending,
  288. # do some setup
  289. if not self.parent2pendingChildren.has_key(parentId):
  290. self.parent2pendingChildren[parentId] = []
  291. # do the reparent(s) once the parent is initialized
  292. def doReparent(parentId=parentId, self=self):
  293. assert self.parent2pendingChildren.has_key(parentId)
  294. parent=self.getEntity(parentId)
  295. for child in self.parent2pendingChildren[parentId]:
  296. DistributedLevel.notify.debug(
  297. 'performing pending reparent of %s to %s' %
  298. (child, parent))
  299. child.reparentTo(parent.getNodePath())
  300. del self.parent2pendingChildren[parentId]
  301. self.ignore(self.getEntityCreateEvent(parentId))
  302. self.accept(self.getEntityCreateEvent(parentId), doReparent)
  303. self.parent2pendingChildren[parentId].append(entity)
  304. def showZone(self, zoneNum):
  305. zone = self.zoneNum2node[zoneNum]
  306. zone.unstash()
  307. zone.clearColor()
  308. def setColorZones(self, fColorZones):
  309. self.fColorZones = fColorZones
  310. def getColorZones(self):
  311. return self.fColorZones
  312. def hideZone(self, zoneNum):
  313. zone = self.zoneNum2node[zoneNum]
  314. if self.fColorZones:
  315. zone.unstash()
  316. zone.setColor(1,0,0)
  317. else:
  318. zone.stash()
  319. def setTransparency(self, alpha, zone=None):
  320. self.geom.setTransparency(1)
  321. if zone is None:
  322. node = self.geom
  323. else:
  324. node = self.zoneNum2node[zone]
  325. node.setAlphaScale(alpha)
  326. def initVisibility(self):
  327. # start out with every zone visible, since none of the zones have
  328. # been hidden
  329. self.curVisibleZoneNums = list2dict(self.zoneNums)
  330. # the UberZone is always visible, so it's not included in the
  331. # zones' viz lists
  332. del self.curVisibleZoneNums[0]
  333. # we have not entered any zone yet
  334. self.curZoneNum = None
  335. # listen for camera-ray/floor collision events
  336. def handleCameraRayFloorCollision(collEntry, self=self):
  337. name = collEntry.getIntoNode().getName()
  338. prefixLen = len(DistributedLevel.FloorCollPrefix)
  339. if (name[:prefixLen] == DistributedLevel.FloorCollPrefix):
  340. try:
  341. zoneNum = int(name[prefixLen:])
  342. except:
  343. DistributedLevel.notify.debug(
  344. 'Invalid zone floor collision node: %s'
  345. % name)
  346. else:
  347. self.camEnterZone(zoneNum)
  348. self.accept('on-floor', handleCameraRayFloorCollision)
  349. # if no viz, listen to all the zones
  350. if not DistributedLevel.WantVisibility:
  351. zoneNums = list(self.zoneNums)
  352. zoneNums.remove(LevelConstants.UberZoneNum)
  353. self.setVisibility(zoneNums)
  354. def toonEnterZone(self, zoneNum, ouchLevel=None):
  355. DistributedLevel.notify.debug('toonEnterZone%s' % zoneNum)
  356. if zoneNum != self.lastToonZone:
  357. self.lastToonZone = zoneNum
  358. print "toon is standing in zone %s" % zoneNum
  359. messenger.send("factoryZoneChanged", [zoneNum])
  360. self.smallTitleText.hide()
  361. self.spawnTitleText()
  362. def camEnterZone(self, zoneNum):
  363. DistributedLevel.notify.debug('camEnterZone%s' % zoneNum)
  364. self.enterZone(zoneNum)
  365. def enterZone(self, zoneNum):
  366. DistributedLevel.notify.debug("entering zone %s" % zoneNum)
  367. if not DistributedLevel.WantVisibility:
  368. return
  369. if zoneNum == self.curZoneNum:
  370. return
  371. if zoneNum not in self.zoneNum2entId:
  372. DistributedLevel.notify.error(
  373. 'no ZoneEntity for this zone (%s)!!' % zoneNum)
  374. self.updateVisibility(zoneNum)
  375. def updateVisibility(self, zoneNum=None):
  376. """update the visibility assuming that we're in the specified
  377. zone; don't check to see if it's the zone we're already in"""
  378. if zoneNum is None:
  379. zoneNum = self.curZoneNum
  380. zoneEntId = self.zoneNum2entId[zoneNum]
  381. zoneEnt = self.getEntity(zoneEntId)
  382. # use dicts to efficiently ensure that there are no duplicates
  383. visibleZoneNums = list2dict([zoneNum])
  384. visibleZoneNums.update(list2dict(zoneEnt.getVisibleZoneNums()))
  385. # we should not have the uberZone in the list at this point
  386. assert not 0 in visibleZoneNums
  387. if DistributedLevel.HideZones:
  388. # figure out which zones are new and which are going invisible
  389. # use dicts because it's faster to use dict.has_key(x)
  390. # than 'x in list'
  391. addedZoneNums = []
  392. removedZoneNums = []
  393. allVZ = dict(visibleZoneNums)
  394. allVZ.update(self.curVisibleZoneNums)
  395. for vz,dummy in allVZ.items():
  396. new = vz in visibleZoneNums
  397. old = vz in self.curVisibleZoneNums
  398. if new and old:
  399. continue
  400. if new:
  401. addedZoneNums.append(vz)
  402. else:
  403. removedZoneNums.append(vz)
  404. # show the new, hide the old
  405. DistributedLevel.notify.debug('showing zones %s' % addedZoneNums)
  406. for az in addedZoneNums:
  407. self.showZone(az)
  408. DistributedLevel.notify.debug('hiding zones %s' % removedZoneNums)
  409. for rz in removedZoneNums:
  410. self.hideZone(rz)
  411. self.setVisibility(visibleZoneNums.keys())
  412. self.curZoneNum = zoneNum
  413. self.curVisibleZoneNums = visibleZoneNums
  414. def setVisibility(self, vizList):
  415. """
  416. vizList is a list of visible zone numbers.
  417. """
  418. # convert the zone numbers into their actual zoneIds
  419. # always include Toontown and factory uberZones
  420. uberZone = self.getZoneId(zoneNum=LevelConstants.UberZoneNum)
  421. # the level itself is in the 'level zone'
  422. visibleZoneIds = [ToontownGlobals.UberZone, self.levelZone, uberZone]
  423. for vz in vizList:
  424. visibleZoneIds.append(self.getZoneId(zoneNum=vz))
  425. assert(uniqueElements(visibleZoneIds))
  426. DistributedLevel.notify.debug('new viz list: %s' % visibleZoneIds)
  427. toonbase.tcr.sendSetZoneMsg(self.levelZone, visibleZoneIds)
  428. def resetVisibility(self):
  429. # start out with every zone visible, since none of the zones have
  430. # been hidden
  431. self.curVisibleZoneNums = list2dict(self.zoneNums)
  432. # the UberZone is always visible, so it's not included in the
  433. # zones' viz lists
  434. del self.curVisibleZoneNums[0]
  435. # Make sure every zone is visible
  436. for vz,dummy in self.curVisibleZoneNums.items():
  437. self.showZone(vz)
  438. # Redo visibility using current zone num
  439. self.updateVisibility()
  440. if __debug__:
  441. # level editing stuff
  442. def setAttribChange(self, entId, attribName, valueStr, username):
  443. """every time the spec is edited, we get this message
  444. from the AI"""
  445. value = eval(valueStr)
  446. self.levelSpec.setAttribChange(entId, attribName, value, username)
  447. def handleVisChange(self):
  448. """the zone visibility lists have changed"""
  449. Level.Level.handleVisChange(self)
  450. self.updateVisibility()
  451. def spawnTitleText(self):
  452. def getDescription(zoneId, self=self):
  453. entId = self.zoneNum2entId.get(zoneId)
  454. if entId:
  455. ent = self.entities.get(entId)
  456. if ent and hasattr(ent, 'description'):
  457. return ent.description
  458. return None
  459. description = getDescription(self.lastToonZone)
  460. if description and description != '':
  461. taskMgr.remove("titleText")
  462. self.smallTitleText.setText(description)
  463. self.titleText.setText(description)
  464. self.titleText.setColor(Vec4(*self.titleColor))
  465. self.titleText.setFg(self.titleColor)
  466. # Only show the big title once per session.
  467. # If we've already seen it, just show the small title
  468. titleSeq = None
  469. if not self.lastToonZone in self.zonesEnteredList:
  470. self.zonesEnteredList.append(self.lastToonZone)
  471. titleSeq = Task.sequence(
  472. Task.Task(self.hideSmallTitleTextTask),
  473. Task.Task(self.showTitleTextTask),
  474. Task.pause(0.1),
  475. Task.pause(6.0),
  476. self.titleText.lerpColor(Vec4(self.titleColor[0],
  477. self.titleColor[1],
  478. self.titleColor[2],
  479. self.titleColor[3]),
  480. Vec4(self.titleColor[0],
  481. self.titleColor[1],
  482. self.titleColor[2],
  483. 0.0),
  484. 0.5),
  485. )
  486. smallTitleSeq = Task.sequence(Task.Task(self.hideTitleTextTask),
  487. Task.Task(self.showSmallTitleTask),
  488. Task.Task(self.showSmallTitleTask))
  489. if titleSeq:
  490. seq = Task.sequence(titleSeq, smallTitleSeq)
  491. else:
  492. seq = smallTitleSeq
  493. taskMgr.add(seq, "titleText")
  494. def showTitleTextTask(self, task):
  495. assert(DistributedLevel.notify.debug("hideTitleTextTask()"))
  496. self.titleText.show()
  497. return Task.done
  498. def hideTitleTextTask(self, task):
  499. assert(DistributedLevel.notify.debug("hideTitleTextTask()"))
  500. self.titleText.hide()
  501. return Task.done
  502. def showSmallTitleTask(self, task):
  503. # make sure large title is hidden
  504. self.titleText.hide()
  505. # show the small title
  506. self.smallTitleText.show()
  507. return Task.done
  508. def hideSmallTitleTextTask(self, task):
  509. assert(DistributedLevel.notify.debug("hideTitleTextTask()"))
  510. self.smallTitleText.hide()
  511. return Task.done
  512. # Ouch!
  513. def startOuch(self, ouchLevel, period=2):
  514. print 'startOuch %s' % ouchLevel
  515. if not hasattr(self, 'doingOuch'):
  516. def doOuch(task, self=self, ouchLevel=ouchLevel, period=period):
  517. self.b_setOuch(ouchLevel)
  518. self.lastOuchTime = globalClock.getFrameTime()
  519. taskMgr.doMethodLater(period, doOuch,
  520. DistributedLevel.OuchTaskName)
  521. # check to make sure we haven't done an ouch too recently
  522. delay = 0
  523. if hasattr(self, 'lastOuchTime'):
  524. curFrameTime = globalClock.getFrameTime()
  525. timeSinceLastOuch = (curFrameTime - self.lastOuchTime)
  526. if timeSinceLastOuch < period:
  527. delay = period - timeSinceLastOuch
  528. if delay > 0:
  529. taskMgr.doMethodLater(
  530. period, doOuch,
  531. DistributedLevel.OuchTaskName)
  532. else:
  533. doOuch(None)
  534. self.doingOuch = 1
  535. def stopOuch(self):
  536. if hasattr(self, 'doingOuch'):
  537. taskMgr.remove(DistributedLevel.OuchTaskName)
  538. del self.doingOuch
  539. def b_setOuch(self, penalty, anim=None):
  540. self.notify.debug('b_setOuch %s' % penalty)
  541. av = toonbase.localToon
  542. # play the stun track (flashing toon)
  543. if not av.isStunned:
  544. self.d_setOuch(penalty)
  545. self.setOuch(penalty, anim)
  546. def d_setOuch(self, penalty):
  547. self.sendUpdate("setOuch", [penalty])
  548. def setOuch(self, penalty, anim = None):
  549. if anim == "Squish":
  550. toonbase.tcr.playGame.getPlace().fsm.request('squished')
  551. av = toonbase.localToon
  552. av.stunToon()
  553. av.playDialogueForString("!")