DistributedLevelAI.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. """DistributedLevelAI.py: contains the DistributedLevelAI class"""
  2. from AIBaseGlobal import *
  3. from ClockDelta import *
  4. import DistributedObjectAI
  5. import Level
  6. import DirectNotifyGlobal
  7. import EntityCreatorAI
  8. import WeightedChoice
  9. class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
  10. Level.Level):
  11. """DistributedLevelAI"""
  12. notify = DirectNotifyGlobal.directNotify.newCategory('DistributedLevelAI')
  13. def __init__(self, air, zoneId, entranceId, avIds):
  14. DistributedObjectAI.DistributedObjectAI.__init__(self, air)
  15. Level.Level.__init__(self)
  16. # these are required fields
  17. self.zoneId = zoneId
  18. self.entranceId = entranceId
  19. assert len(avIds) > 0 and len(avIds) <= 4
  20. assert 0 not in avIds
  21. assert None not in avIds
  22. self.avIdList = avIds
  23. self.numPlayers = len(self.avIdList)
  24. self.notify.debug("expecting avatars: %s" % str(self.avIdList))
  25. if __debug__:
  26. self.modified = 0
  27. def generate(self, levelSpec):
  28. self.notify.debug('generate')
  29. DistributedObjectAI.DistributedObjectAI.generate(self)
  30. self.initializeLevel(levelSpec)
  31. self.sendUpdate('setZoneIds', [self.zoneIds])
  32. self.sendUpdate('setStartTimestamp', [self.startTimestamp])
  33. self.sendUpdate('setScenarioIndex', [self.scenarioIndex])
  34. def getLevelZoneId(self):
  35. """no entities should be generated in the level's zone; it causes
  36. nasty race conditions on the client if there are entities in the
  37. same zone with the level"""
  38. return self.zoneId
  39. def getPlayerIds(self):
  40. return self.avIdList
  41. def getEntranceId(self):
  42. return self.entranceId
  43. def delete(self):
  44. self.notify.debug('delete')
  45. if __debug__:
  46. self.removeAutosaveTask()
  47. self.destroyLevel()
  48. DistributedObjectAI.DistributedObjectAI.delete(self)
  49. def initializeLevel(self, levelSpec):
  50. # record the level's start time so that we can sync the clients
  51. self.startTime = globalClock.getRealTime()
  52. self.startTimestamp = globalClockDelta.localToNetworkTime(
  53. self.startTime, bits=32)
  54. # choose a scenario
  55. # make list of lists: [(weight, scenarioIndex), ...]
  56. lol = zip(levelSpec.getScenarioWeights(),
  57. range(levelSpec.getNumScenarios()))
  58. wc = WeightedChoice.WeightedChoice(lol)
  59. scenarioIndex = wc.choose()[1]
  60. Level.Level.initializeLevel(self, self.doId, levelSpec, scenarioIndex)
  61. if __debug__:
  62. # listen for requests to save the spec
  63. self.accept(self.editMgrEntity.getSpecSaveEvent(), self.saveSpec)
  64. def createEntityCreator(self):
  65. """Create the object that will be used to create Entities.
  66. Inheritors, override if desired."""
  67. return EntityCreatorAI.EntityCreatorAI(level=self)
  68. def getEntityZoneId(self, entId):
  69. """figure out what network zoneId an entity is in"""
  70. # this func is called before the entity has been created; look
  71. # into the spec data, since we can't yet get a handle on the
  72. # object itself at this point
  73. spec = self.levelSpec.getEntitySpec(entId)
  74. type = spec['type']
  75. if type == 'zone':
  76. if not hasattr(self, 'zoneNum2zoneId'):
  77. # we haven't even started creating our zone entities yet;
  78. # we have no idea yet which zoneNums map to which
  79. # network zoneIds. just return None.
  80. return None
  81. # If the entity *is* the zone, it will not yet be in the
  82. # table; but since zone entities are currently not distributed,
  83. # it's fine to return None.
  84. return self.zoneNum2zoneId.get(spec['modelZoneNum'])
  85. if not spec.has_key('parentEntId'):
  86. return None
  87. return self.getEntityZoneId(spec['parentEntId'])
  88. if __debug__:
  89. # level editors should call this func to tweak attributes of level
  90. # entities
  91. def setAttribChange(self, entId, attribName, value, username='SYSTEM'):
  92. # send a copy to the client-side level obj FIRST
  93. # (it may be a message that creates an entity)
  94. self.sendUpdate('setAttribChange',
  95. [entId, attribName, repr(value), username])
  96. self.levelSpec.setAttribChange(entId, attribName, value, username)
  97. self.modified = 1
  98. self.scheduleAutosave()
  99. # backups are made every N minutes, starting from the time that
  100. # the first edit is made
  101. AutosavePeriod = simbase.config.GetFloat(
  102. 'level-autosave-period-minutes', 5)
  103. def scheduleAutosave(self):
  104. if hasattr(self, 'autosaveTask'):
  105. return
  106. self.autosaveTaskName = self.uniqueName('autosaveSpec')
  107. self.autosaveTask = taskMgr.doMethodLater(
  108. DistributedLevelAI.AutosavePeriod * 60,
  109. self.autosaveSpec,
  110. self.autosaveTaskName)
  111. def removeAutosaveTask(self):
  112. if hasattr(self, 'autosaveTask'):
  113. taskMgr.remove(self.autosaveTaskName)
  114. del self.autosaveTask
  115. def autosaveSpec(self, task=None):
  116. self.removeAutosaveTask()
  117. if self.modified:
  118. DistributedLevelAI.notify.info('autosaving spec')
  119. filename = self.levelSpec.getFilename()
  120. filename = '%s.autosave' % filename
  121. self.levelSpec.saveToDisk(filename, makeBackup=0)
  122. def saveSpec(self, task=None):
  123. DistributedLevelAI.notify.info('saving spec')
  124. self.removeAutosaveTask()
  125. if not self.modified:
  126. DistributedLevelAI.notify.info('no changes to save')
  127. return
  128. self.levelSpec.saveToDisk()
  129. self.modified = 0
  130. def requestCurrentLevelSpec(self, specHash, entTypeRegHash):
  131. senderId = self.air.msgSender
  132. # first check the typeReg hash -- if it doesn't match, the
  133. # client should not be connecting. Their entityTypeRegistry
  134. # is different from ours.
  135. srvHash = hash(self.levelSpec.entTypeReg)
  136. if srvHash != entTypeRegHash:
  137. self.sendUpdateToAvatarId(
  138. senderId, 'setSpecDeny',
  139. ['EntityTypeRegistry hashes do not match! '
  140. '(server:%s, client:%s' % (srvHash, entTypeRegHash)])
  141. return
  142. spec = None
  143. # don't need to hit disk if we're just sending 'None' over the wire
  144. useDisk = 0
  145. if hash(self.levelSpec) != specHash:
  146. spec = self.levelSpec
  147. useDisk=simbase.config.GetBool('spec-by-disk', 0)
  148. specStr = repr(spec)
  149. import DistributedLargeBlobSenderAI
  150. largeBlob = DistributedLargeBlobSenderAI.\
  151. DistributedLargeBlobSenderAI(
  152. self.air, self.zoneId, senderId, specStr,
  153. useDisk=useDisk)
  154. self.sendUpdateToAvatarId(senderId,
  155. 'setSpecSenderDoId', [largeBlob.doId])