DistributedLevel.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. """DistributedLevel.py: contains the DistributedLevel class"""
  2. from ClockDelta import *
  3. from PythonUtil import Functor, sameElements, list2dict, uniqueElements
  4. import ToontownGlobals
  5. import DistributedObject
  6. import LevelBase
  7. import DirectNotifyGlobal
  8. import EntityCreator
  9. class DistributedLevel(DistributedObject.DistributedObject,
  10. LevelBase.LevelBase):
  11. """DistributedLevel"""
  12. notify = DirectNotifyGlobal.directNotify.newCategory('DistributedLevel')
  13. WantVisibility = config.GetBool('level-visibility', 1)
  14. HideZones = config.GetBool('level-hidezones', 1)
  15. FloorCollPrefix = 'zoneFloor'
  16. def __init__(self, cr):
  17. DistributedObject.DistributedObject.__init__(self, cr)
  18. LevelBase.LevelBase.__init__(self)
  19. def generate(self):
  20. self.notify.debug('generate')
  21. DistributedObject.DistributedObject.generate(self)
  22. # this dict stores entity reparents if the parent hasn't been
  23. # created yet
  24. self.parent2ChildIds = {}
  25. # Most (if not all) of the timed entities of levels
  26. # run on looping intervals that are started once based on
  27. # the level's start time.
  28. # This sync request is *NOT* guaranteed to finish by the time
  29. # the entities get created.
  30. # We should listen for any and all time-sync events and re-sync
  31. # all our entities at that time.
  32. toonbase.tcr.timeManager.synchronize('DistributedLevel.generate')
  33. # required fields (these ought to be required fields, but
  34. # the AI level obj doesn't know the data values until it has been
  35. # generated.)
  36. def setZoneIds(self, zoneIds):
  37. self.notify.debug('setZoneIds: %s' % zoneIds)
  38. self.zoneIds = zoneIds
  39. def setStartTimestamp(self, timestamp):
  40. self.notify.debug('setStartTimestamp: %s' % timestamp)
  41. self.startTime = globalClockDelta.networkToLocalTime(timestamp,bits=32)
  42. def setScenarioIndex(self, scenarioIndex):
  43. self.scenarioIndex = scenarioIndex
  44. # ugly hack: we treat these DC fields as if they were required,
  45. # and use 'levelAnnounceGenerate()' in place of regular old
  46. # announceGenerate(). Note that we have to call
  47. # levelAnnounceGenerate() in the last 'faux-required' DC update
  48. # handler. If you add another field, move this to the last one.
  49. self.levelAnnounceGenerate()
  50. def levelAnnounceGenerate(self):
  51. pass
  52. def initializeLevel(self, spec):
  53. """subclass should call this as soon as it's located its spec data.
  54. Must be called after obj has been generated."""
  55. LevelBase.LevelBase.initializeLevel(self, self.doId,
  56. spec, self.scenarioIndex)
  57. # all of the entities have been created now.
  58. # there should not be any pending reparents left at this point
  59. assert len(self.parent2ChildIds) == 0
  60. # make sure the zoneNums from the model match the zoneNums from
  61. # the zone entities
  62. assert sameElements(self.zoneNums, self.zoneNum2entId.keys())
  63. # load stuff
  64. self.initVisibility()
  65. def createEntityCreator(self):
  66. """Create the object that will be used to create Entities.
  67. Inheritors, override if desired."""
  68. return EntityCreator.EntityCreator(level=self)
  69. def setupEntityCreationHandlers(self):
  70. LevelBase.LevelBase.setupEntityCreationHandlers(self)
  71. # load up the model ASAP so that we can get zone info out of it
  72. self.acceptOnce(self.getEntityTypeCreateEvent('levelMgr'),
  73. self.handleLevelMgrCreated)
  74. """ it would be nice to be able to do this, but this overrides the
  75. handler in LevelBase. For now, just override the LevelBase handler
  76. and call down.
  77. # fix up the model wrt zone collisions
  78. self.acceptOnce(self.getEntityTypeCreateEvent('zone'),
  79. self.handleAllZonesCreated)
  80. """
  81. def removeEntityCreationHandlers(self):
  82. LevelBase.LevelBase.removeEntityCreationHandlers(self)
  83. def handleLevelMgrCreated(self):
  84. # as soon as the levelMgr has been created, load up the model
  85. # and extract zone info
  86. self.geom = loader.loadModel(self.modelFilename)
  87. def findNumberedNodes(baseString, model=self.geom, self=self):
  88. # finds nodes whose name follows the pattern 'baseString#'
  89. # where there are no characters after #
  90. # returns dictionary that maps # to node
  91. potentialNodes = model.findAllMatches(
  92. '**/%s*' % baseString).asList()
  93. num2node = {}
  94. for potentialNode in potentialNodes:
  95. name = potentialNode.getName()
  96. self.notify.debug('potential match for %s: %s' %
  97. (baseString, name))
  98. try:
  99. num = int(name[len(baseString):])
  100. except:
  101. continue
  102. num2node[num] = potentialNode
  103. return num2node
  104. # find the zones in the model and fix them up
  105. self.zoneNum2node = findNumberedNodes('Zone')
  106. # add the UberZone to the table
  107. self.zoneNum2node[0] = self.geom
  108. self.zoneNums = self.zoneNum2node.keys()
  109. self.zoneNums.sort()
  110. self.notify.debug('zones: %s' % self.zoneNums)
  111. # hack in another doorway
  112. dw = self.geom.attachNewNode('Doorway27')
  113. dw.setPos(-49.4,86.7,19.26)
  114. dw.setH(0)
  115. # find the doorway nodes
  116. self.doorwayNum2Node = findNumberedNodes('Doorway')
  117. def handleAllZonesCreated(self):
  118. LevelBase.LevelBase.handleAllZonesCreated(self)
  119. # fix up the floor collisions for walkable zones before
  120. # any entities get put under the model
  121. for zoneNum in self.zoneNums:
  122. zoneNode = self.zoneNum2node[zoneNum]
  123. # if this is a walkable zone, fix up the model
  124. allColls = zoneNode.findAllMatches('**/+CollisionNode').asList()
  125. # which of them, if any, are floors?
  126. floorColls = []
  127. for coll in allColls:
  128. bitmask = coll.node().getIntoCollideMask()
  129. if not (bitmask & ToontownGlobals.FloorBitmask).isZero():
  130. floorColls.append(coll)
  131. if len(floorColls) > 0:
  132. # rename the floor collision nodes, and make sure no other
  133. # nodes under the ZoneNode have that name
  134. floorCollName = '%s%s' % (DistributedLevel.FloorCollPrefix,
  135. zoneNum)
  136. others = zoneNode.findAllMatches(
  137. '**/%s' % floorCollName).asList()
  138. for other in others:
  139. other.setName('%s_renamed' % floorCollName)
  140. for floorColl in floorColls:
  141. floorColl.setName(floorCollName)
  142. # listen for zone enter events from floor collisions
  143. def handleZoneEnter(collisionEntry,
  144. self=self, zoneNum=zoneNum):
  145. # eat the collisionEntry
  146. self.toonEnterZone(zoneNum)
  147. self.accept('enter%s' % floorCollName, handleZoneEnter)
  148. def announceGenerate(self):
  149. self.notify.debug('announceGenerate')
  150. DistributedObject.DistributedObject.announceGenerate(self)
  151. def disable(self):
  152. self.notify.debug('disable')
  153. self.destroyLevel()
  154. DistributedObject.DistributedObject.disable(self)
  155. self.ignoreAll()
  156. def delete(self):
  157. self.notify.debug('delete')
  158. DistributedObject.DistributedObject.delete(self)
  159. def getDoorwayNode(self, doorwayNum):
  160. # returns node that doors should parent themselves to
  161. return self.doorwayNum2Node[doorwayNum]
  162. def getZoneNode(self, zoneNum):
  163. return self.zoneNum2node[zoneNum]
  164. def requestReparent(self, entity, parentId):
  165. assert(entity.entId != parentId)
  166. if self.entities.has_key(parentId):
  167. # parent has already been created
  168. entity.reparentTo(self.entities[parentId].getNodePath())
  169. else:
  170. # parent hasn't been created yet; schedule the reparent
  171. self.notify.debug(
  172. 'entity %s requesting reparent to %s, not yet created' %
  173. (entity, parentId))
  174. entId = entity.entId
  175. entity.reparentTo(hidden)
  176. # if this parent doesn't already have another child pending,
  177. # do some setup
  178. if not self.parent2ChildIds.has_key(parentId):
  179. self.parent2ChildIds[parentId] = []
  180. # do the reparent once the parent is initialized
  181. def doReparent(parentId=parentId, self=self):
  182. assert self.parent2ChildIds.has_key(parentId)
  183. parent=self.getEntity(parentId)
  184. for entId in self.parent2ChildIds[parentId]:
  185. entity=self.getEntity(entId)
  186. self.notify.debug(
  187. 'performing pending reparent of %s to %s' %
  188. (entity, parent))
  189. entity.reparentTo(parent.getNodePath())
  190. del self.parent2ChildIds[parentId]
  191. self.accept(self.getEntityCreateEvent(parentId), doReparent)
  192. self.parent2ChildIds[parentId].append(entId)
  193. def showZone(self, zoneNum):
  194. self.zoneNum2node[zoneNum].show()
  195. def hideZone(self, zoneNum):
  196. self.zoneNum2node[zoneNum].hide()
  197. def setTransparency(self, alpha, zone=None):
  198. self.geom.setTransparency(1)
  199. if zone is None:
  200. node = self.geom
  201. else:
  202. node = self.zoneNum2node[zone]
  203. node.setAlphaScale(alpha)
  204. def initVisibility(self):
  205. # start out with every zone visible, since none of the zones have
  206. # been hidden
  207. self.curVisibleZoneNums = list2dict(self.zoneNums)
  208. # the UberZone is always visible, so it's not included in the
  209. # zones' viz lists
  210. del self.curVisibleZoneNums[0]
  211. # we have not entered any zone yet
  212. self.curZoneNum = None
  213. # listen for camera-ray/floor collision events
  214. def handleCameraRayFloorCollision(collEntry, self=self):
  215. name = collEntry.getIntoNode().getName()
  216. prefixLen = len(DistributedLevel.FloorCollPrefix)
  217. if (name[:prefixLen] == DistributedLevel.FloorCollPrefix):
  218. try:
  219. zoneNum = int(name[prefixLen:])
  220. except:
  221. self.notify.debug('Invalid zone floor collision node: %s'
  222. % name)
  223. else:
  224. self.camEnterZone(zoneNum)
  225. self.accept('on-floor', handleCameraRayFloorCollision)
  226. # if no viz, listen to all the zones
  227. if not DistributedLevel.WantVisibility:
  228. self.setVisibility(self.zoneNums)
  229. def toonEnterZone(self, zoneNum):
  230. self.notify.debug('toonEnterZone%s' % zoneNum)
  231. def camEnterZone(self, zoneNum):
  232. self.notify.debug('camEnterZone%s' % zoneNum)
  233. self.enterZone(zoneNum)
  234. def enterZone(self, zoneNum):
  235. self.notify.debug("entering zone %s" % zoneNum)
  236. if not DistributedLevel.WantVisibility:
  237. return
  238. if zoneNum == self.curZoneNum:
  239. return
  240. zoneEntId = self.zoneNum2entId[zoneNum]
  241. zoneSpec = self.entId2spec[zoneEntId]
  242. # use dicts to efficiently ensure that there are no duplicates
  243. visibleZoneNums = list2dict([zoneNum])
  244. visibleZoneNums.update(list2dict(zoneSpec['visibility']))
  245. if DistributedLevel.HideZones:
  246. # figure out which zones are new and which are going invisible
  247. # use dicts because it's faster to use dict.has_key(x)
  248. # than 'x in list'
  249. addedZoneNums = []
  250. removedZoneNums = []
  251. allVZ = dict(visibleZoneNums)
  252. allVZ.update(self.curVisibleZoneNums)
  253. for vz,None in allVZ.items():
  254. new = vz in visibleZoneNums
  255. old = vz in self.curVisibleZoneNums
  256. if new and old:
  257. continue
  258. if new:
  259. addedZoneNums.append(vz)
  260. else:
  261. removedZoneNums.append(vz)
  262. # show the new, hide the old
  263. self.notify.debug('showing zones %s' % addedZoneNums)
  264. for az in addedZoneNums:
  265. self.showZone(az)
  266. self.notify.debug('hiding zones %s' % removedZoneNums)
  267. for rz in removedZoneNums:
  268. self.hideZone(rz)
  269. self.setVisibility(visibleZoneNums.keys())
  270. self.curZoneNum = zoneNum
  271. self.curVisibleZoneNums = visibleZoneNums
  272. def setVisibility(self, vizList):
  273. # accepts list of visible zone numbers
  274. # convert the zone numbers into their actual zoneIds
  275. # always include Toontown and factory uberZones
  276. factoryUberZone = self.getZoneId(zoneNum=0)
  277. visibleZoneIds = [ToontownGlobals.UberZone, factoryUberZone]
  278. for vz in vizList:
  279. visibleZoneIds.append(self.getZoneId(zoneNum=vz))
  280. assert(uniqueElements(visibleZoneIds))
  281. self.notify.debug('new viz list: %s' % visibleZoneIds)
  282. toonbase.tcr.sendSetZoneMsg(factoryUberZone, visibleZoneIds)
  283. if __debug__:
  284. # level editing stuff
  285. def setAttribChange(self, entId, attribName, valueStr):
  286. try:
  287. value = eval(valueStr)
  288. except Exception, e:
  289. print ('Exception in %s(%s, %s, %s):\n\t%s' %
  290. (lineInfo()[2], entId, attribName, valueStr, e))
  291. raise e
  292. entity = self.getEntity(entId)
  293. entity.handleAttribChange(attribName, value)