mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-03 18:31:55 -04:00
AI sends edited spec to clients, level is in its own zone to prevent race conditions
This commit is contained in:
parent
c04a6237ec
commit
0f7a02e1d3
@ -51,13 +51,16 @@ class DistributedLevel(DistributedObject.DistributedObject,
|
||||
self.zonesEnteredList = []
|
||||
|
||||
def generate(self):
|
||||
self.notify.debug('generate')
|
||||
DistributedLevel.notify.debug('generate')
|
||||
DistributedObject.DistributedObject.generate(self)
|
||||
|
||||
# this dict stores entity reparents if the parent hasn't been
|
||||
# created yet
|
||||
self.parent2ChildIds = {}
|
||||
|
||||
# if the AI sends us a full spec, it will be put here
|
||||
self.curSpec = None
|
||||
|
||||
# Most (if not all) of the timed entities of levels
|
||||
# run on looping intervals that are started once based on
|
||||
# the level's start time.
|
||||
@ -70,38 +73,77 @@ class DistributedLevel(DistributedObject.DistributedObject,
|
||||
# add factory menu to SpeedChat
|
||||
toonbase.localToon.chatMgr.chatInputSpeedChat.addFactoryMenu()
|
||||
|
||||
# the real required fields
|
||||
def setLevelZoneId(self, zoneId):
|
||||
# this is the zone that the level is in; we should listen to this
|
||||
# zone the entire time we're in here
|
||||
self.levelZone = zoneId
|
||||
|
||||
# "required" fields (these ought to be required fields, but
|
||||
# the AI level obj doesn't know the data values until it has been
|
||||
# generated.)
|
||||
def setZoneIds(self, zoneIds):
|
||||
self.notify.debug('setZoneIds: %s' % zoneIds)
|
||||
DistributedLevel.notify.debug('setZoneIds: %s' % zoneIds)
|
||||
self.zoneIds = zoneIds
|
||||
|
||||
def setStartTimestamp(self, timestamp):
|
||||
self.notify.debug('setStartTimestamp: %s' % timestamp)
|
||||
DistributedLevel.notify.debug('setStartTimestamp: %s' % timestamp)
|
||||
self.startTime = globalClockDelta.networkToLocalTime(timestamp,bits=32)
|
||||
|
||||
def setScenarioIndex(self, scenarioIndex):
|
||||
self.scenarioIndex = scenarioIndex
|
||||
|
||||
# ugly hack: we treat these DC fields as if they were required,
|
||||
# ugly hack: we treat a few DC fields as if they were required,
|
||||
# and use 'levelAnnounceGenerate()' in place of regular old
|
||||
# announceGenerate(). Note that we have to call
|
||||
# levelAnnounceGenerate() in the last 'faux-required' DC update
|
||||
# gotAllRequired() in the last 'faux-required' DC update
|
||||
# handler. If you add another field, move this to the last one.
|
||||
self.levelAnnounceGenerate()
|
||||
if __debug__:
|
||||
# if we're in debug, ask the server if it wants to send us
|
||||
# a full spec
|
||||
self.sendUpdate('requestCurrentLevelSpec', [])
|
||||
else:
|
||||
self.gotAllRequired()
|
||||
|
||||
if __debug__:
|
||||
def setSpecSenderDoId(self, doId):
|
||||
DistributedLevel.notify.debug(
|
||||
'setSpecSenderDoId: %s' % doId)
|
||||
blobSender = toonbase.tcr.doId2do[doId]
|
||||
|
||||
def setSpecBlob(specBlob, blobSender=blobSender, self=self):
|
||||
blobSender.sendAck()
|
||||
from LevelSpec import LevelSpec
|
||||
self.curSpec = eval(specBlob)
|
||||
self.gotAllRequired()
|
||||
|
||||
if blobSender.isComplete():
|
||||
setSpecBlob(blobSender.getBlob())
|
||||
else:
|
||||
evtName = self.uniqueName('specDone')
|
||||
blobSender.setDoneEvent(evtName)
|
||||
self.acceptOnce(evtName, setSpecBlob)
|
||||
|
||||
def gotAllRequired(self):
|
||||
self.levelAnnounceGenerate()
|
||||
def levelAnnounceGenerate(self):
|
||||
pass
|
||||
|
||||
def initializeLevel(self, levelSpec):
|
||||
"""subclass should call this as soon as it's located its level spec.
|
||||
Must be called after obj has been generated."""
|
||||
Level.Level.initializeLevel(self, self.doId,
|
||||
levelSpec, self.scenarioIndex)
|
||||
# if the AI sent us a full spec, use it instead
|
||||
if self.curSpec is not None:
|
||||
levelSpec = self.curSpec
|
||||
Level.Level.initializeLevel(self, self.doId, levelSpec,
|
||||
self.scenarioIndex)
|
||||
|
||||
# all of the local entities have been created now.
|
||||
# TODO: have any of the distributed entities been created at this point?
|
||||
|
||||
# all of the entities have been created now.
|
||||
# there should not be any pending reparents left at this point
|
||||
# TODO: is it possible for a local entity to be parented to a
|
||||
# distributed entity? I think so!
|
||||
assert len(self.parent2ChildIds) == 0
|
||||
# make sure the zoneNums from the model match the zoneNums from
|
||||
# the zone entities
|
||||
@ -137,7 +179,7 @@ class DistributedLevel(DistributedObject.DistributedObject,
|
||||
num2node = {}
|
||||
for potentialNode in potentialNodes:
|
||||
name = potentialNode.getName()
|
||||
self.notify.debug('potential match for %s: %s' %
|
||||
DistributedLevel.notify.debug('potential match for %s: %s' %
|
||||
(baseString, name))
|
||||
try:
|
||||
num = int(name[len(baseString):])
|
||||
@ -155,7 +197,7 @@ class DistributedLevel(DistributedObject.DistributedObject,
|
||||
|
||||
self.zoneNums = self.zoneNum2node.keys()
|
||||
self.zoneNums.sort()
|
||||
self.notify.debug('zones: %s' % self.zoneNums)
|
||||
DistributedLevel.notify.debug('zones: %s' % self.zoneNums)
|
||||
|
||||
# fix up the floor collisions for walkable zones *before*
|
||||
# any entities get put under the model
|
||||
@ -196,20 +238,21 @@ class DistributedLevel(DistributedObject.DistributedObject,
|
||||
self.doorwayNum2Node = findNumberedNodes('Doorway')
|
||||
|
||||
def announceGenerate(self):
|
||||
self.notify.debug('announceGenerate')
|
||||
DistributedLevel.notify.debug('announceGenerate')
|
||||
DistributedObject.DistributedObject.announceGenerate(self)
|
||||
|
||||
def disable(self):
|
||||
self.notify.debug('disable')
|
||||
DistributedLevel.notify.debug('disable')
|
||||
|
||||
# geom is owned by the levelMgr
|
||||
del self.geom
|
||||
if hasattr(self, 'geom'):
|
||||
del self.geom
|
||||
|
||||
self.destroyLevel()
|
||||
DistributedObject.DistributedObject.disable(self)
|
||||
self.ignoreAll()
|
||||
|
||||
# NOTE: this should be moved to FactoryInterior
|
||||
# NOTE: this should be moved to FactoryInterior
|
||||
if self.smallTitleText:
|
||||
self.smallTitleText.cleanup()
|
||||
self.smallTitleText = None
|
||||
@ -218,11 +261,11 @@ class DistributedLevel(DistributedObject.DistributedObject,
|
||||
self.titleText = None
|
||||
self.zonesEnteredList = []
|
||||
|
||||
# NOTE: this should be moved to ZoneEntity.disable
|
||||
# NOTE: this should be moved to ZoneEntity.disable
|
||||
toonbase.localToon.chatMgr.chatInputSpeedChat.removeFactoryMenu()
|
||||
|
||||
def delete(self):
|
||||
self.notify.debug('delete')
|
||||
DistributedLevel.notify.debug('delete')
|
||||
DistributedObject.DistributedObject.delete(self)
|
||||
# remove factory menu to SpeedChat
|
||||
toonbase.localToon.chatMgr.chatInputSpeedChat.removeFactoryMenu()
|
||||
@ -242,7 +285,7 @@ class DistributedLevel(DistributedObject.DistributedObject,
|
||||
entity.reparentTo(parent.getNodePath())
|
||||
else:
|
||||
# parent hasn't been created yet; schedule the reparent
|
||||
self.notify.debug(
|
||||
DistributedLevel.notify.debug(
|
||||
'entity %s requesting reparent to %s, not yet created' %
|
||||
(entity, parentId))
|
||||
|
||||
@ -260,7 +303,7 @@ class DistributedLevel(DistributedObject.DistributedObject,
|
||||
parent=self.getEntity(parentId)
|
||||
for entId in self.parent2ChildIds[parentId]:
|
||||
entity=self.getEntity(entId)
|
||||
self.notify.debug(
|
||||
DistributedLevel.notify.debug(
|
||||
'performing pending reparent of %s to %s' %
|
||||
(entity, parent))
|
||||
entity.reparentTo(parent.getNodePath())
|
||||
@ -302,8 +345,9 @@ class DistributedLevel(DistributedObject.DistributedObject,
|
||||
try:
|
||||
zoneNum = int(name[prefixLen:])
|
||||
except:
|
||||
self.notify.debug('Invalid zone floor collision node: %s'
|
||||
% name)
|
||||
DistributedLevel.notify.debug(
|
||||
'Invalid zone floor collision node: %s'
|
||||
% name)
|
||||
else:
|
||||
self.camEnterZone(zoneNum)
|
||||
self.accept('on-floor', handleCameraRayFloorCollision)
|
||||
@ -315,7 +359,7 @@ class DistributedLevel(DistributedObject.DistributedObject,
|
||||
self.setVisibility(zoneNums)
|
||||
|
||||
def toonEnterZone(self, zoneNum):
|
||||
self.notify.debug('toonEnterZone%s' % zoneNum)
|
||||
DistributedLevel.notify.debug('toonEnterZone%s' % zoneNum)
|
||||
if zoneNum != self.lastToonZone:
|
||||
self.lastToonZone = zoneNum
|
||||
print "made zone transition to %s" % zoneNum
|
||||
@ -324,11 +368,11 @@ class DistributedLevel(DistributedObject.DistributedObject,
|
||||
self.spawnTitleText()
|
||||
|
||||
def camEnterZone(self, zoneNum):
|
||||
self.notify.debug('camEnterZone%s' % zoneNum)
|
||||
DistributedLevel.notify.debug('camEnterZone%s' % zoneNum)
|
||||
self.enterZone(zoneNum)
|
||||
|
||||
def enterZone(self, zoneNum):
|
||||
self.notify.debug("entering zone %s" % zoneNum)
|
||||
DistributedLevel.notify.debug("entering zone %s" % zoneNum)
|
||||
|
||||
if not DistributedLevel.WantVisibility:
|
||||
return
|
||||
@ -360,10 +404,10 @@ class DistributedLevel(DistributedObject.DistributedObject,
|
||||
else:
|
||||
removedZoneNums.append(vz)
|
||||
# show the new, hide the old
|
||||
self.notify.debug('showing zones %s' % addedZoneNums)
|
||||
DistributedLevel.notify.debug('showing zones %s' % addedZoneNums)
|
||||
for az in addedZoneNums:
|
||||
self.showZone(az)
|
||||
self.notify.debug('hiding zones %s' % removedZoneNums)
|
||||
DistributedLevel.notify.debug('hiding zones %s' % removedZoneNums)
|
||||
for rz in removedZoneNums:
|
||||
self.hideZone(rz)
|
||||
|
||||
@ -376,14 +420,15 @@ class DistributedLevel(DistributedObject.DistributedObject,
|
||||
# accepts list of visible zone numbers
|
||||
# convert the zone numbers into their actual zoneIds
|
||||
# always include Toontown and factory uberZones
|
||||
factoryUberZone = self.getZoneId(zoneNum=Level.Level.UberZoneNum)
|
||||
visibleZoneIds = [ToontownGlobals.UberZone, factoryUberZone]
|
||||
uberZone = self.getZoneId(zoneNum=Level.Level.UberZoneNum)
|
||||
# the level itself is in the 'level zone'
|
||||
visibleZoneIds = [ToontownGlobals.UberZone, self.levelZone, uberZone]
|
||||
for vz in vizList:
|
||||
visibleZoneIds.append(self.getZoneId(zoneNum=vz))
|
||||
assert(uniqueElements(visibleZoneIds))
|
||||
self.notify.debug('new viz list: %s' % visibleZoneIds)
|
||||
DistributedLevel.notify.debug('new viz list: %s' % visibleZoneIds)
|
||||
|
||||
toonbase.tcr.sendSetZoneMsg(factoryUberZone, visibleZoneIds)
|
||||
toonbase.tcr.sendSetZoneMsg(self.levelZone, visibleZoneIds)
|
||||
|
||||
if __debug__:
|
||||
# level editing stuff
|
||||
@ -455,12 +500,12 @@ class DistributedLevel(DistributedObject.DistributedObject,
|
||||
taskMgr.add(seq, "titleText")
|
||||
|
||||
def showTitleTextTask(self, task):
|
||||
assert(self.notify.debug("hideTitleTextTask()"))
|
||||
assert(DistributedLevel.notify.debug("hideTitleTextTask()"))
|
||||
self.titleText.show()
|
||||
return Task.done
|
||||
|
||||
def hideTitleTextTask(self, task):
|
||||
assert(self.notify.debug("hideTitleTextTask()"))
|
||||
assert(DistributedLevel.notify.debug("hideTitleTextTask()"))
|
||||
self.titleText.hide()
|
||||
return Task.done
|
||||
|
||||
@ -472,7 +517,7 @@ class DistributedLevel(DistributedObject.DistributedObject,
|
||||
return Task.done
|
||||
|
||||
def hideSmallTitleTextTask(self, task):
|
||||
assert(self.notify.debug("hideTitleTextTask()"))
|
||||
assert(DistributedLevel.notify.debug("hideTitleTextTask()"))
|
||||
self.smallTitleText.hide()
|
||||
return Task.done
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""DistributedLevelAI.py: contains the DistributedLevelAI class"""
|
||||
|
||||
from AIBaseGlobal import *
|
||||
from ClockDelta import *
|
||||
import DistributedObjectAI
|
||||
import Level
|
||||
@ -15,7 +16,9 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
|
||||
def __init__(self, air, zoneId):
|
||||
DistributedObjectAI.DistributedObjectAI.__init__(self, air)
|
||||
Level.Level.__init__(self)
|
||||
self.uberZoneId = zoneId
|
||||
# this is one of the required fields
|
||||
self.zoneId = zoneId
|
||||
self.hasBeenEdited = 0
|
||||
|
||||
def generate(self, levelSpec):
|
||||
self.notify.debug('generate')
|
||||
@ -27,6 +30,12 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
|
||||
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 delete(self):
|
||||
self.notify.debug('delete')
|
||||
self.destroyLevel()
|
||||
@ -45,8 +54,7 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
|
||||
wc = WeightedChoice.WeightedChoice(lol)
|
||||
scenarioIndex = wc.choose()[1]
|
||||
|
||||
Level.Level.initializeLevel(self, self.doId,
|
||||
levelSpec, scenarioIndex)
|
||||
Level.Level.initializeLevel(self, self.doId, levelSpec, scenarioIndex)
|
||||
|
||||
def createEntityCreator(self):
|
||||
"""Create the object that will be used to create Entities.
|
||||
@ -83,14 +91,17 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
|
||||
# send a copy to the client-side level obj
|
||||
self.sendUpdate('setAttribChange',
|
||||
[entId, attribName, valueStr])
|
||||
self.hasBeenEdited = 1
|
||||
|
||||
def getCurrentLevelSpec(self):
|
||||
"""returns the complete, current spec, including any edits"""
|
||||
return self.levelSpec
|
||||
def requestCurrentLevelSpec(self):
|
||||
senderId = self.air.msgSender
|
||||
spec = self.levelSpec
|
||||
specStr = repr(spec)
|
||||
|
||||
"""
|
||||
def getSpecOverride(self):
|
||||
# This is the value we'll send until someone actually edits
|
||||
# the level
|
||||
return repr(None)
|
||||
"""
|
||||
import DistributedLargeBlobSenderAI
|
||||
largeBlob = DistributedLargeBlobSenderAI.\
|
||||
DistributedLargeBlobSenderAI(
|
||||
self.air, self.zoneId, senderId, specStr,
|
||||
useDisk=simbase.config.GetBool('spec-by-disk', 0))
|
||||
self.sendUpdateToAvatarId(senderId,
|
||||
'setSpecSenderDoId', [largeBlob.doId])
|
||||
|
@ -39,6 +39,7 @@ class EntityTypeRegistry:
|
||||
|
||||
def getAttribNames(self, entityTypeName):
|
||||
""" returns ordered list of attribute names for entity type """
|
||||
assert entityTypeName in self.typeName2class
|
||||
# TODO: precompute this
|
||||
attribDescs = self.typeName2class[entityTypeName]._attribDescs
|
||||
attribNames = []
|
||||
@ -48,6 +49,7 @@ class EntityTypeRegistry:
|
||||
|
||||
def getAttribDescs(self, entityTypeName):
|
||||
""" returns dict of attribName -> attribDescriptor """
|
||||
assert entityTypeName in self.typeName2class
|
||||
# TODO: precompute this
|
||||
attribDescs = self.typeName2class[entityTypeName]._attribDescs
|
||||
attribNames = self.getAttribNames(entityTypeName)
|
||||
|
@ -9,7 +9,6 @@ class Entity:
|
||||
('comment', ''),
|
||||
)
|
||||
|
||||
|
||||
class ActiveCell(Entity):
|
||||
type = 'activeCell'
|
||||
attribs = (
|
||||
|
@ -35,6 +35,7 @@ class Level:
|
||||
|
||||
def __init__(self):
|
||||
self.levelSpec = None
|
||||
self.initialized = 0
|
||||
|
||||
def initializeLevel(self, levelId, levelSpec, scenarioIndex):
|
||||
""" subclass should call this as soon as it has located
|
||||
@ -65,7 +66,13 @@ class Level:
|
||||
self.levelSpec.setAttribChangeEventName(self.getAttribChangeEvent())
|
||||
self.accept(self.getAttribChangeEvent(), self.handleAttribChange)
|
||||
|
||||
self.initialized = 1
|
||||
|
||||
def isInitialized(self):
|
||||
return self.initialized
|
||||
|
||||
def destroyLevel(self):
|
||||
self.initialized = 0
|
||||
if hasattr(self, 'createdEntities'):
|
||||
# destroy the entities in reverse order
|
||||
while len(self.createdEntities) > 0:
|
||||
|
@ -6,14 +6,9 @@ class ZoneEntityAI(ZoneEntityBase.ZoneEntityBase):
|
||||
def __init__(self, level, entId):
|
||||
ZoneEntityBase.ZoneEntityBase.__init__(self, level, entId)
|
||||
|
||||
if self.isUberZone():
|
||||
print 'uberZone'
|
||||
# the uberzone is already allocated
|
||||
self.setZoneId(self.level.uberZoneId)
|
||||
else:
|
||||
# allocate a network zoneId for this zone
|
||||
# there is error checking in air.allocateZone
|
||||
self.setZoneId(self.level.air.allocateZone())
|
||||
# allocate a network zoneId for this zone
|
||||
# there is error checking in air.allocateZone
|
||||
self.setZoneId(self.level.air.allocateZone())
|
||||
|
||||
def destroy(self):
|
||||
if not self.isUberZone():
|
||||
|
Loading…
x
Reference in New Issue
Block a user