mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 02:42:49 -04:00
208 lines
7.9 KiB
Python
Executable File
208 lines
7.9 KiB
Python
Executable File
"""DistributedLevelAI.py: contains the DistributedLevelAI class"""
|
|
|
|
from AIBaseGlobal import *
|
|
from ClockDelta import *
|
|
import DistributedObjectAI
|
|
import Level
|
|
import DirectNotifyGlobal
|
|
import EntityCreatorAI
|
|
import WeightedChoice
|
|
from PythonUtil import Functor
|
|
|
|
class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
|
|
Level.Level):
|
|
"""DistributedLevelAI"""
|
|
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedLevelAI')
|
|
|
|
def __init__(self, air, zoneId, entranceId, avIds):
|
|
DistributedObjectAI.DistributedObjectAI.__init__(self, air)
|
|
Level.Level.__init__(self)
|
|
# these are required fields
|
|
self.zoneId = zoneId
|
|
self.entranceId = entranceId
|
|
|
|
assert len(avIds) > 0 and len(avIds) <= 4
|
|
assert 0 not in avIds
|
|
assert None not in avIds
|
|
self.avIdList = avIds
|
|
self.numPlayers = len(self.avIdList)
|
|
# this is the list of avatars that are actually present
|
|
self.presentAvIds = list(self.avIdList)
|
|
self.notify.debug("expecting avatars: %s" % str(self.avIdList))
|
|
|
|
if __debug__:
|
|
self.modified = 0
|
|
|
|
def generate(self, levelSpec):
|
|
self.notify.debug('generate')
|
|
DistributedObjectAI.DistributedObjectAI.generate(self)
|
|
|
|
self.initializeLevel(levelSpec)
|
|
|
|
self.sendUpdate('setZoneIds', [self.zoneIds])
|
|
self.sendUpdate('setStartTimestamp', [self.startTimestamp])
|
|
self.sendUpdate('setScenarioIndex', [self.scenarioIndex])
|
|
|
|
def getLevelZoneId(self):
|
|
"""no entities should be generated in the level's zone; it causes
|
|
nasty race conditions on the client if there are entities in the
|
|
same zone with the level"""
|
|
return self.zoneId
|
|
|
|
def getPlayerIds(self):
|
|
return self.avIdList
|
|
|
|
def getEntranceId(self):
|
|
return self.entranceId
|
|
|
|
def delete(self):
|
|
self.notify.debug('delete')
|
|
if __debug__:
|
|
self.removeAutosaveTask()
|
|
self.destroyLevel()
|
|
self.ignoreAll()
|
|
self.air.deallocateZone(self.zoneId)
|
|
DistributedObjectAI.DistributedObjectAI.delete(self)
|
|
|
|
def initializeLevel(self, levelSpec):
|
|
# record the level's start time so that we can sync the clients
|
|
self.startTime = globalClock.getRealTime()
|
|
self.startTimestamp = globalClockDelta.localToNetworkTime(
|
|
self.startTime, bits=32)
|
|
|
|
# choose a scenario
|
|
# make list of lists: [(weight, scenarioIndex), ...]
|
|
lol = zip(levelSpec.getScenarioWeights(),
|
|
range(levelSpec.getNumScenarios()))
|
|
wc = WeightedChoice.WeightedChoice(lol)
|
|
scenarioIndex = wc.choose()[1]
|
|
|
|
Level.Level.initializeLevel(self, self.doId, levelSpec, scenarioIndex)
|
|
|
|
if __debug__:
|
|
# listen for requests to save the spec
|
|
self.accept(self.editMgrEntity.getSpecSaveEvent(), self.saveSpec)
|
|
|
|
# listen for avatar disconnects
|
|
for avId in self.avIdList:
|
|
self.acceptOnce(self.air.getAvatarExitEvent(avId),
|
|
Functor(self.handleAvatarDisconnect, avId))
|
|
|
|
# set up a barrier that will clear when all avs have left or
|
|
# disconnected
|
|
self.allToonsGoneBarrier = self.beginBarrier(
|
|
'allToonsGone', self.avIdList, 3*24*60*60, self.allToonsGone)
|
|
|
|
def handleAvatarDisconnect(self, avId):
|
|
try:
|
|
self.presentAvIds.remove(avId)
|
|
DistributedLevelAI.notify.warning('av %s has disconnected' % avId)
|
|
except:
|
|
DistributedLevelAI.notify.warning(
|
|
'got disconnect for av %s, not in list' % avId)
|
|
if not self.presentAvIds:
|
|
self.allToonsGone([])
|
|
|
|
def allToonsGone(self, toonsThatCleared):
|
|
print 'allToonsGone'
|
|
if hasattr(self, 'allToonsGoneBarrier'):
|
|
self.ignoreBarrier(self.allToonsGoneBarrier)
|
|
del self.allToonsGoneBarrier
|
|
for avId in self.avIdList:
|
|
self.ignore(self.air.getAvatarExitEvent(avId))
|
|
self.requestDelete()
|
|
|
|
def createEntityCreator(self):
|
|
"""Create the object that will be used to create Entities.
|
|
Inheritors, override if desired."""
|
|
return EntityCreatorAI.EntityCreatorAI(level=self)
|
|
|
|
def setOuch(self, penalty):
|
|
avId = self.air.msgSender
|
|
av = self.air.doId2do.get(avId)
|
|
self.notify.debug("setOuch %s" % penalty)
|
|
# make sure penalty is > 0
|
|
if av and (penalty > 0):
|
|
av.takeDamage(penalty)
|
|
|
|
if __debug__:
|
|
# level editors should call this func to tweak attributes of level
|
|
# entities
|
|
def setAttribChange(self, entId, attribName, value, username='SYSTEM'):
|
|
DistributedLevelAI.notify.info(
|
|
"setAttribChange(%s): %s, %s = %s" %
|
|
(username, entId, attribName, repr(value)))
|
|
# send a copy to the client-side level obj FIRST
|
|
# (it may be a message that creates an entity)
|
|
self.sendUpdate('setAttribChange',
|
|
[entId, attribName, repr(value), username])
|
|
self.levelSpec.setAttribChange(entId, attribName, value, username)
|
|
|
|
self.modified = 1
|
|
self.scheduleAutosave()
|
|
|
|
# backups are made every N minutes, starting from the time that
|
|
# the first edit is made
|
|
AutosavePeriod = simbase.config.GetFloat(
|
|
'level-autosave-period-minutes', 5)
|
|
|
|
def scheduleAutosave(self):
|
|
if hasattr(self, 'autosaveTask'):
|
|
return
|
|
self.autosaveTaskName = self.uniqueName('autosaveSpec')
|
|
self.autosaveTask = taskMgr.doMethodLater(
|
|
DistributedLevelAI.AutosavePeriod * 60,
|
|
self.autosaveSpec,
|
|
self.autosaveTaskName)
|
|
|
|
def removeAutosaveTask(self):
|
|
if hasattr(self, 'autosaveTask'):
|
|
taskMgr.remove(self.autosaveTaskName)
|
|
del self.autosaveTask
|
|
|
|
def autosaveSpec(self, task=None):
|
|
self.removeAutosaveTask()
|
|
if self.modified:
|
|
DistributedLevelAI.notify.info('autosaving spec')
|
|
filename = self.levelSpec.getFilename()
|
|
filename = '%s.autosave' % filename
|
|
self.levelSpec.saveToDisk(filename, makeBackup=0)
|
|
|
|
def saveSpec(self, task=None):
|
|
DistributedLevelAI.notify.info('saving spec')
|
|
self.removeAutosaveTask()
|
|
if not self.modified:
|
|
DistributedLevelAI.notify.info('no changes to save')
|
|
return
|
|
self.levelSpec.saveToDisk()
|
|
self.modified = 0
|
|
|
|
def requestCurrentLevelSpec(self, specHash, entTypeRegHash):
|
|
senderId = self.air.msgSender
|
|
|
|
# first check the typeReg hash -- if it doesn't match, the
|
|
# client should not be connecting. Their entityTypeRegistry
|
|
# is different from ours.
|
|
srvHash = hash(self.levelSpec.entTypeReg)
|
|
if srvHash != entTypeRegHash:
|
|
self.sendUpdateToAvatarId(
|
|
senderId, 'setSpecDeny',
|
|
['EntityTypeRegistry hashes do not match! '
|
|
'(server:%s, client:%s' % (srvHash, entTypeRegHash)])
|
|
return
|
|
spec = None
|
|
# don't need to hit disk if we're just sending 'None' over the wire
|
|
useDisk = 0
|
|
if hash(self.levelSpec) != specHash:
|
|
spec = self.levelSpec
|
|
useDisk=simbase.config.GetBool('spec-by-disk', 0)
|
|
specStr = repr(spec)
|
|
|
|
import DistributedLargeBlobSenderAI
|
|
largeBlob = DistributedLargeBlobSenderAI.\
|
|
DistributedLargeBlobSenderAI(
|
|
self.air, self.zoneId, senderId, specStr,
|
|
useDisk=useDisk)
|
|
self.sendUpdateToAvatarId(senderId,
|
|
'setSpecSenderDoId', [largeBlob.doId])
|