493 lines
18 KiB
Python
493 lines
18 KiB
Python
from direct.distributed import DistributedObjectAI
|
|
from direct.directnotify import DirectNotifyGlobal
|
|
from toontown.toonbase import ToontownGlobals
|
|
from panda3d.core import *
|
|
import DistributedPhysicsWorldAI
|
|
from direct.fsm.FSM import FSM
|
|
from toontown.ai.ToonBarrier import *
|
|
from toontown.golf import GolfGlobals
|
|
import random
|
|
from toontown.golf import GolfHoleBase
|
|
|
|
class DistributedGolfHoleAI(DistributedPhysicsWorldAI.DistributedPhysicsWorldAI, FSM, GolfHoleBase.GolfHoleBase):
|
|
defaultTransitions = {'Off': ['Cleanup', 'WaitTee'],
|
|
'WaitTee': ['WaitSwing',
|
|
'Cleanup',
|
|
'WaitTee',
|
|
'WaitPlayback'],
|
|
'WaitSwing': ['WaitPlayback',
|
|
'Cleanup',
|
|
'WaitSwing',
|
|
'WaitTee'],
|
|
'WaitPlayback': ['WaitSwing',
|
|
'Cleanup',
|
|
'WaitTee',
|
|
'WaitPlayback'],
|
|
'Cleanup': ['Off']}
|
|
id = 0
|
|
notify = directNotify.newCategory('DistributedGolfHoleAI')
|
|
|
|
def __init__(self, zoneId, golfCourse, holeId):
|
|
FSM.__init__(self, 'Golf_%s_FSM' % self.id)
|
|
DistributedPhysicsWorldAI.DistributedPhysicsWorldAI.__init__(self, simbase.air)
|
|
GolfHoleBase.GolfHoleBase.__init__(self)
|
|
self.zoneId = zoneId
|
|
self.golfCourse = golfCourse
|
|
self.holeId = holeId
|
|
self.avIdList = golfCourse.avIdList[:]
|
|
self.watched = [0,
|
|
0,
|
|
0,
|
|
0]
|
|
self.barrierPlayback = None
|
|
self.trustedAvId = None
|
|
self.activeGolferIndex = None
|
|
self.activeGolferId = None
|
|
self.holeInfo = GolfGlobals.HoleInfo[self.holeId]
|
|
self.teeChosen = {}
|
|
for avId in self.avIdList:
|
|
self.teeChosen[avId] = -1
|
|
|
|
self.ballPos = {}
|
|
for avId in self.avIdList:
|
|
self.ballPos[avId] = Vec3(0, 0, 0)
|
|
|
|
self.playStarted = False
|
|
return
|
|
|
|
def curGolfBall(self):
|
|
return self.ball
|
|
|
|
def generate(self):
|
|
DistributedPhysicsWorldAI.DistributedPhysicsWorldAI.generate(self)
|
|
self.ball = self.createBall()
|
|
self.createRays()
|
|
if len(self.teePositions) > 1:
|
|
startPos = self.teePositions[1]
|
|
else:
|
|
startPos = self.teePositions[0]
|
|
startPos += Vec3(0, 0, GolfGlobals.GOLF_BALL_RADIUS)
|
|
self.ball.setPosition(startPos)
|
|
|
|
def delete(self):
|
|
self.notify.debug('__delete__')
|
|
DistributedPhysicsWorldAI.DistributedPhysicsWorldAI.delete(self)
|
|
self.notify.debug('calling self.terrainModel.removeNode')
|
|
self.terrainModel.removeNode()
|
|
self.notify.debug('self.barrierPlayback is %s' % self.barrierPlayback)
|
|
if self.barrierPlayback:
|
|
self.notify.debug('calling self.barrierPlayback.cleanup')
|
|
self.barrierPlayback.cleanup()
|
|
self.notify.debug('calling self.barrierPlayback = None')
|
|
self.barrierPlayback = None
|
|
self.activeGolferId = None
|
|
return
|
|
|
|
def setZoneId(self, zoneId):
|
|
self.zoneId = zoneId
|
|
|
|
def setAvatarReadyHole(self):
|
|
self.notify.debugStateCall(self)
|
|
avId = self.air.getAvatarIdFromSender()
|
|
self.golfCourse.avatarReadyHole(avId)
|
|
|
|
def startPlay(self):
|
|
self.notify.debug('startPlay')
|
|
self.playStarted = True
|
|
self.numGolfers = len(self.golfCourse.getGolferIds())
|
|
self.selectNextGolfer()
|
|
|
|
def selectNextGolfer(self):
|
|
self.notify.debug('selectNextGolfer, old golferIndex=%s old golferId=%s' % (self.activeGolferIndex, self.activeGolferId))
|
|
if self.golfCourse.isCurHoleDone():
|
|
return
|
|
if self.activeGolferIndex == None:
|
|
self.activeGolferIndex = 0
|
|
self.activeGolferId = self.golfCourse.getGolferIds()[self.activeGolferIndex]
|
|
else:
|
|
self.activeGolferIndex += 1
|
|
if self.activeGolferIndex >= len(self.golfCourse.getGolferIds()):
|
|
self.activeGolferIndex = 0
|
|
self.activeGolferId = self.golfCourse.getGolferIds()[self.activeGolferIndex]
|
|
safety = 0
|
|
while safety < 50 and not self.golfCourse.checkGolferPlaying(self.golfCourse.getGolferIds()[self.activeGolferIndex]):
|
|
self.activeGolferIndex += 1
|
|
self.notify.debug('Index %s' % self.activeGolferIndex)
|
|
if self.activeGolferIndex >= len(self.golfCourse.getGolferIds()):
|
|
self.activeGolferIndex = 0
|
|
self.activeGolferId = self.golfCourse.getGolferIds()[self.activeGolferIndex]
|
|
safety += 1
|
|
|
|
if safety != 50:
|
|
golferId = self.golfCourse.getGolferIds()[self.activeGolferIndex]
|
|
if self.teeChosen[golferId] == -1:
|
|
self.sendUpdate('golferChooseTee', [golferId])
|
|
self.request('WaitTee')
|
|
else:
|
|
self.sendUpdate('golfersTurn', [golferId])
|
|
self.request('WaitSwing')
|
|
else:
|
|
self.notify.debug('safety')
|
|
self.notify.debug('selectNextGolfer, new golferIndex=%s new golferId=%s' % (self.activeGolferIndex, self.activeGolferId))
|
|
return
|
|
|
|
def clearWatched(self):
|
|
self.watched = [1,
|
|
1,
|
|
1,
|
|
1]
|
|
for index in xrange(len(self.golfCourse.getGolferIds())):
|
|
self.watched[index] = 0
|
|
|
|
def setWatched(self, avId):
|
|
for index in xrange(len(self.golfCourse.getGolferIds())):
|
|
if self.golfCourse.getGolferIds()[index] == avId:
|
|
self.watched[index] = 1
|
|
|
|
def checkWatched(self):
|
|
if 0 not in self.watched:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def turnDone(self):
|
|
self.notify.debug('Turn Done')
|
|
avId = self.air.getAvatarIdFromSender()
|
|
if self.barrierPlayback:
|
|
self.barrierPlayback.clear(avId)
|
|
|
|
def ballInHole(self, golferId = None):
|
|
self.notify.debug('ballInHole')
|
|
if golferId:
|
|
avId = golferId
|
|
else:
|
|
avId = self.air.getAvatarIdFromSender()
|
|
self.golfCourse.setBallIn(avId)
|
|
if self.golfCourse.isCurHoleDone():
|
|
self.notify.debug('ballInHole doing nothing')
|
|
else:
|
|
self.notify.debug('ballInHole calling self.selectNextGolfer')
|
|
self.selectNextGolfer()
|
|
|
|
def getHoleId(self):
|
|
return self.holeId
|
|
|
|
def finishHole(self):
|
|
self.notify.debug('finishHole')
|
|
self.golfCourse.holeOver()
|
|
|
|
def getGolferIds(self):
|
|
return self.avIdList
|
|
|
|
def loadLevel(self):
|
|
GolfHoleBase.GolfHoleBase.loadLevel(self)
|
|
optionalObjects = self.terrainModel.findAllMatches('**/optional*')
|
|
requiredObjects = self.terrainModel.findAllMatches('**/required*')
|
|
self.parseLocators(optionalObjects, 1)
|
|
self.parseLocators(requiredObjects, 0)
|
|
self.teeNodePath = self.terrainModel.find('**/tee0')
|
|
if self.teeNodePath.isEmpty():
|
|
teePos = Vec3(0, 0, 10)
|
|
else:
|
|
teePos = self.teeNodePath.getPos()
|
|
teePos.setZ(teePos.getZ() + GolfGlobals.GOLF_BALL_RADIUS)
|
|
self.notify.debug('teeNodePath heading = %s' % self.teeNodePath.getH())
|
|
self.teePositions = [teePos]
|
|
teeIndex = 1
|
|
teeNode = self.terrainModel.find('**/tee%d' % teeIndex)
|
|
while not teeNode.isEmpty():
|
|
teePos = teeNode.getPos()
|
|
teePos.setZ(teePos.getZ() + GolfGlobals.GOLF_BALL_RADIUS)
|
|
self.teePositions.append(teePos)
|
|
self.notify.debug('teeNodeP heading = %s' % teeNode.getH())
|
|
teeIndex += 1
|
|
teeNode = self.terrainModel.find('**/tee%d' % teeIndex)
|
|
|
|
def createLocatorDict(self):
|
|
self.locDict = {}
|
|
locatorNum = 1
|
|
curNodePath = self.hardSurfaceNodePath.find('**/locator%d' % locatorNum)
|
|
while not curNodePath.isEmpty():
|
|
self.locDict[locatorNum] = curNodePath
|
|
locatorNum += 1
|
|
curNodePath = self.hardSurfaceNodePath.find('**/locator%d' % locatorNum)
|
|
|
|
def loadBlockers(self):
|
|
loadAll = simbase.config.GetBool('golf-all-blockers', 0)
|
|
self.createLocatorDict()
|
|
self.blockerNums = self.holeInfo['blockers']
|
|
for locatorNum in self.locDict:
|
|
if locatorNum in self.blockerNums or loadAll:
|
|
locator = self.locDict[locatorNum]
|
|
locatorParent = locator.getParent()
|
|
locator.getChildren().wrtReparentTo(locatorParent)
|
|
else:
|
|
self.locDict[locatorNum].removeNode()
|
|
|
|
self.hardSurfaceNodePath.flattenStrong()
|
|
|
|
def createBall(self):
|
|
golfBallGeom = self.createSphere(self.world, self.space, GolfGlobals.GOLF_BALL_DENSITY, GolfGlobals.GOLF_BALL_RADIUS, 1)[1]
|
|
return golfBallGeom
|
|
|
|
def preStep(self):
|
|
GolfHoleBase.GolfHoleBase.preStep(self)
|
|
|
|
def postStep(self):
|
|
GolfHoleBase.GolfHoleBase.postStep(self)
|
|
|
|
def postSwing(self, cycleTime, power, x, y, z, dirX, dirY):
|
|
avId = self.air.getAvatarIdFromSender()
|
|
self.storeAction = [avId,
|
|
cycleTime,
|
|
power,
|
|
x,
|
|
y,
|
|
z,
|
|
dirX,
|
|
dirY]
|
|
if self.commonHoldData:
|
|
self.doAction()
|
|
|
|
def postSwingState(self, cycleTime, power, x, y, z, dirX, dirY, curAimTime, commonObjectData):
|
|
self.notify.debug('postSwingState')
|
|
if not self.golfCourse.getStillPlayingAvIds():
|
|
return
|
|
avId = self.air.getAvatarIdFromSender()
|
|
self.storeAction = [avId,
|
|
cycleTime,
|
|
power,
|
|
x,
|
|
y,
|
|
z,
|
|
dirX,
|
|
dirY]
|
|
self.commonHoldData = commonObjectData
|
|
self.trustedAvId = self.chooseAvatarToSimulate()
|
|
self.sendUpdateToAvatarId(self.trustedAvId, 'assignRecordSwing', [avId,
|
|
cycleTime,
|
|
power,
|
|
x,
|
|
y,
|
|
z,
|
|
dirX,
|
|
dirY,
|
|
commonObjectData])
|
|
self.golfCourse.addAimTime(avId, curAimTime)
|
|
|
|
def chooseAvatarToSimulate(self):
|
|
stillPlaying = self.golfCourse.getStillPlayingAvIds()
|
|
|
|
return stillPlaying[0] if stillPlaying else 0
|
|
|
|
def ballMovie2AI(self, cycleTime, avId, movie, spinMovie, ballInFrame, ballTouchedHoleFrame, ballFirstTouchedHoleFrame, commonObjectData):
|
|
sentFromId = self.air.getAvatarIdFromSender()
|
|
if sentFromId == self.trustedAvId:
|
|
lastFrameNum = len(movie) - 2
|
|
if lastFrameNum < 0:
|
|
lastFrameNum = 0
|
|
lastFrame = movie[lastFrameNum]
|
|
lastPos = Vec3(lastFrame[1], lastFrame[2], lastFrame[3])
|
|
self.ballPos[avId] = lastPos
|
|
self.golfCourse.incrementScore(avId)
|
|
for id in self.golfCourse.getStillPlayingAvIds():
|
|
if not id == sentFromId:
|
|
self.sendUpdateToAvatarId(id, 'ballMovie2Client', [cycleTime,
|
|
avId,
|
|
movie,
|
|
spinMovie,
|
|
ballInFrame,
|
|
ballTouchedHoleFrame,
|
|
ballFirstTouchedHoleFrame,
|
|
commonObjectData])
|
|
|
|
if self.state == 'WaitPlayback' or self.state == 'WaitTee':
|
|
self.notify.warning('ballMovie2AI requesting from %s to WaitPlayback' % self.state)
|
|
self.request('WaitPlayback')
|
|
elif self.trustedAvId == None:
|
|
return
|
|
else:
|
|
self.doAction()
|
|
self.trustedAvId = None
|
|
return
|
|
|
|
def performReadyAction(self):
|
|
avId = self.storeAction[0]
|
|
if self.state == 'WaitPlayback':
|
|
self.notify.debugStateCall(self)
|
|
self.notify.debug('ignoring the postSwing for avId=%d since we are in WaitPlayback' % avId)
|
|
return
|
|
if avId == self.activeGolferId:
|
|
self.golfCourse.incrementScore(self.activeGolferId)
|
|
else:
|
|
self.notify.warning('activGolferId %d not equal to sender avId %d' % (self.activeGolferId, avId))
|
|
if avId not in self.golfCourse.drivingToons:
|
|
position = self.ballPos[avId]
|
|
else:
|
|
position = Vec3(self.storeAction[3], self.storeAction[4], self.storeAction[5])
|
|
self.useCommonObjectData(self.commonHoldData)
|
|
newPos = self.trackRecordBodyFlight(self.ball, self.storeAction[1], self.storeAction[2], position, self.storeAction[6], self.storeAction[7])
|
|
if self.state == 'WaitPlayback' or self.state == 'WaitTee':
|
|
self.notify.warning('performReadyAction requesting from %s to WaitPlayback' % self.state)
|
|
self.request('WaitPlayback')
|
|
self.sendUpdate('ballMovie2Client', [self.storeAction[1],
|
|
avId,
|
|
self.recording,
|
|
self.aVRecording,
|
|
self.ballInHoleFrame,
|
|
self.ballTouchedHoleFrame,
|
|
self.ballFirstTouchedHoleFrame,
|
|
self.commonHoldData])
|
|
self.ballPos[avId] = newPos
|
|
self.trustedAvId = None
|
|
return
|
|
|
|
def postResult(self, cycleTime, avId, recording, aVRecording, ballInHoleFrame, ballTouchedHoleFrame, ballFirstTouchedHoleFrame):
|
|
pass
|
|
|
|
def enterWaitSwing(self):
|
|
pass
|
|
|
|
def exitWaitSwing(self):
|
|
pass
|
|
|
|
def enterWaitTee(self):
|
|
pass
|
|
|
|
def exitWaitTee(self):
|
|
pass
|
|
|
|
def enterWaitPlayback(self):
|
|
self.notify.debug('enterWaitPlayback')
|
|
stillPlayingList = self.golfCourse.getStillPlayingAvIds()
|
|
self.barrierPlayback = ToonBarrier('waitClientsPlayback', self.uniqueName('waitClientsPlayback'), stillPlayingList, 120, self.handleWaitPlaybackDone, self.handlePlaybackTimeout)
|
|
|
|
def hasCurGolferReachedMaxSwing(self):
|
|
strokes = self.golfCourse.getCurHoleScore(self.activeGolferId)
|
|
maxSwing = self.holeInfo['maxSwing']
|
|
retval = strokes >= maxSwing
|
|
if retval:
|
|
av = simbase.air.doId2do.get(self.activeGolferId)
|
|
if av:
|
|
if av.getUnlimitedSwing():
|
|
retval = False
|
|
return retval
|
|
|
|
def handleWaitPlaybackDone(self):
|
|
if self.isCurBallInHole(self.activeGolferId) or self.hasCurGolferReachedMaxSwing():
|
|
if self.activeGolferId:
|
|
self.ballInHole(self.activeGolferId)
|
|
else:
|
|
self.selectNextGolfer()
|
|
|
|
def isCurBallInHole(self, golferId):
|
|
retval = False
|
|
for holePos in self.holePositions:
|
|
displacement = self.ballPos[golferId] - holePos
|
|
length = displacement.length()
|
|
self.notify.debug('hole %s length=%s' % (holePos, length))
|
|
if length <= GolfGlobals.DistanceToBeInHole:
|
|
retval = True
|
|
break
|
|
|
|
return retval
|
|
|
|
def exitWaitPlayback(self):
|
|
self.notify.debug('exitWaitPlayback')
|
|
if hasattr(self, 'barrierPlayback') and self.barrierPlayback:
|
|
self.barrierPlayback.cleanup()
|
|
self.barrierPlayback = None
|
|
return
|
|
|
|
def enterCleanup(self):
|
|
pass
|
|
|
|
def exitCleanup(self):
|
|
pass
|
|
|
|
def handlePlaybackTimeout(self, task = None):
|
|
self.notify.debug('handlePlaybackTimeout')
|
|
self.handleWaitPlaybackDone()
|
|
|
|
def getGolfCourseDoId(self):
|
|
return self.golfCourse.doId
|
|
|
|
def avatarDropped(self, avId):
|
|
self.notify.warning('avId %d dropped, self.state=%s' % (avId, self.state))
|
|
if self.barrierPlayback:
|
|
self.barrierPlayback.clear(avId)
|
|
else:
|
|
if avId == self.trustedAvId:
|
|
self.doAction()
|
|
if avId == self.activeGolferId and not self.golfCourse.haveAllGolfersExited():
|
|
self.selectNextGolfer()
|
|
|
|
def setAvatarTee(self, chosenTee):
|
|
golferId = self.air.getAvatarIdFromSender()
|
|
self.teeChosen[golferId] = chosenTee
|
|
self.ballPos[golferId] = self.teePositions[chosenTee]
|
|
self.sendUpdate('setAvatarFinalTee', [golferId, chosenTee])
|
|
self.sendUpdate('golfersTurn', [golferId])
|
|
self.request('WaitSwing')
|
|
|
|
def setBox(self, pos0, pos1, pos2, quat0, quat1, quat2, quat3, anV0, anV1, anV2, lnV0, lnV1, lnV2):
|
|
self.sendUpdate('sendBox', [pos0,
|
|
pos1,
|
|
pos2,
|
|
quat0,
|
|
quat1,
|
|
quat2,
|
|
quat3,
|
|
anV0,
|
|
anV1,
|
|
anV2,
|
|
lnV0,
|
|
lnV1,
|
|
lnV2])
|
|
|
|
def parseLocators(self, objectCollection, optional = 0):
|
|
if optional and objectCollection.getNumPaths():
|
|
if 'optionalMovers' in self.holeInfo:
|
|
for optionalMoverId in self.holeInfo['optionalMovers']:
|
|
searchStr = 'optional_mover_' + str(optionalMoverId)
|
|
for objIndex in xrange(objectCollection.getNumPaths()):
|
|
object = objectCollection.getPath(objIndex)
|
|
if searchStr in object.getName():
|
|
self.fillLocator(objectCollection, objIndex)
|
|
break
|
|
|
|
else:
|
|
for index in xrange(objectCollection.getNumPaths()):
|
|
self.fillLocator(objectCollection, index)
|
|
|
|
def fillLocator(self, objectCollection, index):
|
|
path = objectCollection[index]
|
|
pathName = path.getName()
|
|
pathArray = pathName.split('_')
|
|
sizeX = None
|
|
sizeY = None
|
|
move = None
|
|
type = None
|
|
for subString in pathArray:
|
|
if subString[:1] == 'X':
|
|
dataString = subString[1:]
|
|
dataString = dataString.replace('p', '.')
|
|
sizeX = float(dataString)
|
|
elif subString[:1] == 'Y':
|
|
dataString = subString[1:]
|
|
dataString = dataString.replace('p', '.')
|
|
sizeY = float(dataString)
|
|
elif subString[:1] == 'd':
|
|
dataString = subString[1:]
|
|
dataString = dataString.replace('p', '.')
|
|
move = float(dataString)
|
|
elif subString == 'mover':
|
|
type = 4
|
|
elif subString == 'windmillLocator':
|
|
type = 3
|
|
|
|
if type == 4 and move and sizeX and sizeY:
|
|
self.createCommonObject(4, path.getPos(), path.getHpr(), sizeX, sizeY, move)
|
|
elif type == 3:
|
|
self.createCommonObject(3, path.getPos(), path.getHpr())
|
|
return
|